diff --git a/Gemfile b/Gemfile index a51c560232..ea9842c0c5 100644 --- a/Gemfile +++ b/Gemfile @@ -7,14 +7,17 @@ gem "rails", "3.0.0.beta1" gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" -if RUBY_VERSION < '1.9' - gem "ruby-debug", ">= 0.10.3" +group :mri do + if RUBY_VERSION < '1.9' + gem "system_timer" + gem "ruby-debug", ">= 0.10.3" + end end # AR gem "sqlite3-ruby", ">= 1.2.5", :require => 'sqlite3' -group :test do +group :db do gem "pg", ">= 0.9.0" gem "mysql", ">= 2.8.1" end diff --git a/Rakefile b/Rakefile index 4437b48d9a..66a65fb249 100644 --- a/Rakefile +++ b/Rakefile @@ -49,13 +49,13 @@ end desc "Install gems for all projects." task :install => :gem do - require File.expand_path("../actionpack/lib/action_pack/version", __FILE__) + version = File.read("RAILS_VERSION").strip (PROJECTS - ["railties"]).each do |project| puts "INSTALLING #{project}" - system("gem install #{project}/pkg/#{project}-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") + system("gem install #{project}/pkg/#{project}-#{version}.gem --no-ri --no-rdoc") end - system("gem install railties/pkg/railties-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") - system("gem install pkg/rails-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") + system("gem install railties/pkg/railties-#{version}.gem --no-ri --no-rdoc") + system("gem install pkg/rails-#{version}.gem --no-ri --no-rdoc") end desc "Generate documentation for the Rails framework" diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index ef3820ad67..0519783d02 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,6 +1,7 @@ require 'mail' require 'action_mailer/tmail_compat' require 'action_mailer/collector' +require 'active_support/core_ext/array/wrap' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. @@ -590,7 +591,7 @@ def collect_responses_and_parts_order(headers) #:nodoc: responses, parts_order = [], nil if block_given? - collector = ActionMailer::Collector.new(self) { render(action_name) } + collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } yield(collector) parts_order = collector.responses.map { |r| r[:content_type] } responses = collector.responses @@ -604,8 +605,10 @@ def collect_responses_and_parts_order(headers) #:nodoc: templates_name = headers.delete(:template_name) || action_name each_template(templates_path, templates_name) do |template| + self.formats = template.formats + responses << { - :body => render(:_template => template), + :body => render(:template => template), :content_type => template.mime_type.to_s } end @@ -615,7 +618,7 @@ def collect_responses_and_parts_order(headers) #:nodoc: end def each_template(paths, name, &block) #:nodoc: - Array(paths).each do |path| + Array.wrap(paths).each do |path| templates = lookup_context.find_all(name, path) templates = templates.uniq_by { |t| t.formats } diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index 5431efccfe..d03e085e83 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -11,7 +11,6 @@ def initialize(context, &block) @context = context @responses = [] @default_render = block - @default_formats = context.formats end def any(*args, &block) @@ -21,16 +20,11 @@ def any(*args, &block) end alias :all :any - def custom(mime, options={}, &block) + def custom(mime, options={}) options.reverse_merge!(:content_type => mime.to_s) - @context.formats = [mime.to_sym] - options[:body] = if block - block.call - else - @default_render.call - end + @context.freeze_formats([mime.to_sym]) + options[:body] = block_given? ? yield : @default_render.call @responses << options - @context.formats = @default_formats end end end \ No newline at end of file diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb index aeb653c5db..c7f341d46c 100644 --- a/actionmailer/lib/action_mailer/old_api.rb +++ b/actionmailer/lib/action_mailer/old_api.rb @@ -31,9 +31,6 @@ module OldApi #:nodoc: # replies to this message. adv_attr_accessor :reply_to - # Specify additional headers to be added to the message. - adv_attr_accessor :headers - # Specify the order in which parts should be sorted, based on content-type. # This defaults to the value for the +default_implicit_parts_order+. adv_attr_accessor :implicit_parts_order @@ -207,7 +204,8 @@ def create_parts @parts.unshift create_inline_part(@body) elsif @parts.empty? || @parts.all? { |p| p.content_disposition =~ /^attachment/ } lookup_context.find_all(@template, @mailer_name).each do |template| - @parts << create_inline_part(render(:_template => template), template.mime_type) + self.formats = template.formats + @parts << create_inline_part(render(:template => template), template.mime_type) end if @parts.size > 1 diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 0182e48425..2703367fdb 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -3,14 +3,14 @@ module ActionMailer class Railtie < Rails::Railtie - railtie_name :action_mailer + config.action_mailer = ActiveSupport::OrderedOptions.new initializer "action_mailer.url_for", :before => :load_environment_config do |app| ActionMailer.base_hook { include app.routes.url_helpers } end require "action_mailer/railties/log_subscriber" - log_subscriber ActionMailer::Railties::LogSubscriber.new + log_subscriber :action_mailer, ActionMailer::Railties::LogSubscriber.new initializer "action_mailer.logger" do ActionMailer.base_hook { self.logger ||= Rails.logger } diff --git a/actionmailer/lib/action_mailer/railties/log_subscriber.rb b/actionmailer/lib/action_mailer/railties/log_subscriber.rb index d1b3dd33af..af76d807d0 100644 --- a/actionmailer/lib/action_mailer/railties/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/railties/log_subscriber.rb @@ -1,8 +1,10 @@ +require 'active_support/core_ext/array/wrap' + module ActionMailer module Railties class LogSubscriber < Rails::LogSubscriber def deliver(event) - recipients = Array(event.payload[:to]).join(', ') + recipients = Array.wrap(event.payload[:to]).join(', ') info("\nSent mail to #{recipients} (%1.fms)" % event.duration) debug(event.payload[:mail]) end @@ -17,4 +19,4 @@ def logger end end end -end \ No newline at end of file +end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 16fef3a9a4..ea15709b45 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,4 +1,23 @@ -require File.expand_path('../../../load_paths', __FILE__) +# Pathname has a warning, so require it first while silencing +# warnings to shut it up. +# +# Also, in 1.9, Bundler creates warnings due to overriding +# Rubygems methods +begin + old, $VERBOSE = $VERBOSE, nil + require 'pathname' + require File.expand_path('../../../load_paths', __FILE__) +ensure + $VERBOSE = old +end + + +require 'active_support/core_ext/kernel/reporting' +silence_warnings do + # These external dependencies have warnings :/ + require 'text/format' + require 'mail' +end lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index c1cf1f0157..6f274c11df 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -2,6 +2,8 @@ require 'abstract_unit' class BaseTest < ActiveSupport::TestCase + # TODO Add some tests for implicity layout render and url helpers + # so we can get rid of old base tests altogether with old base. class BaseMailer < ActionMailer::Base self.mailer_name = "base_mailer" @@ -72,13 +74,20 @@ def explicit_multipart_with_any(hash = {}) end end - def custom_block(include_html=false) + def explicit_multipart_with_options(include_html = false) mail do |format| format.text(:content_transfer_encoding => "base64"){ render "welcome" } format.html{ render "welcome" } if include_html end end + def explicit_multipart_with_one_template(hash = {}) + mail(hash) do |format| + format.html + format.text + end + end + def implicit_different_template(template_name='') mail(:template_name => template_name) end @@ -146,6 +155,13 @@ def different_layout(layout_name='') assert_equal("Hello there", email.body.encoded) end + test "should set template content type if mail has only one part" do + mail = BaseMailer.html_only + assert_equal('text/html', mail.mime_type) + mail = BaseMailer.plain_text_only + assert_equal('text/plain', mail.mime_type) + end + # Custom headers test "custom headers" do email = BaseMailer.welcome @@ -160,7 +176,7 @@ def different_layout(layout_name='') assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded) end - test "can pass random headers in as a hash" do + test "can pass random headers in as a hash to headers" do hash = {'X-Special-Domain-Specific-Header' => "SecretValue", 'In-Reply-To' => '1234@mikel.me.com' } mail = BaseMailer.welcome_with_headers(hash) @@ -364,6 +380,11 @@ def different_layout(layout_name='') assert_equal("HTML Explicit Multipart", email.parts[1].body.encoded) end + test "explicit multipart have a boundary" do + mail = BaseMailer.explicit_multipart + assert_not_nil(mail.content_type_parameters[:boundary]) + end + test "explicit multipart does not sort order" do order = ["text/html", "text/plain"] with_default BaseMailer, :parts_order => order do @@ -397,7 +418,7 @@ def different_layout(layout_name='') assert_equal("TEXT Explicit Multipart Templates", email.parts[1].body.encoded) end - test "explicit multipart with any" do + test "explicit multipart with format.any" do email = BaseMailer.explicit_multipart_with_any assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) @@ -407,8 +428,8 @@ def different_layout(layout_name='') assert_equal("Format with any!", email.parts[1].body.encoded) end - test "explicit multipart with options" do - email = BaseMailer.custom_block(true) + test "explicit multipart with format(Hash)" do + email = BaseMailer.explicit_multipart_with_options(true) email.ready_to_send! assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) @@ -418,28 +439,23 @@ def different_layout(layout_name='') assert_equal("7bit", email.parts[1].content_transfer_encoding) end - test "explicit multipart should be multipart" do - mail = BaseMailer.explicit_multipart - assert_not_nil(mail.content_type_parameters[:boundary]) - end - - test "should set a content type if only has an html part" do - mail = BaseMailer.html_only - assert_equal('text/html', mail.mime_type) - end - - test "should set a content type if only has an plain text part" do - mail = BaseMailer.plain_text_only - assert_equal('text/plain', mail.mime_type) - end - - test "explicit multipart with one part is rendered as body" do - email = BaseMailer.custom_block + test "explicit multipart with one part is rendered as body and options are merged" do + email = BaseMailer.explicit_multipart_with_options assert_equal(0, email.parts.size) assert_equal("text/plain", email.mime_type) assert_equal("base64", email.content_transfer_encoding) end + test "explicit multipart with one template has the expected format" do + email = BaseMailer.explicit_multipart_with_one_template + assert_equal(2, email.parts.size) + assert_equal("multipart/alternative", email.mime_type) + assert_equal("text/html", email.parts[0].mime_type) + assert_equal("[:html]", email.parts[0].body.encoded) + assert_equal("text/plain", email.parts[1].mime_type) + assert_equal("[:text]", email.parts[1].body.encoded) + end + # Class level API with method missing test "should respond to action methods" do assert BaseMailer.respond_to?(:welcome) diff --git a/actionmailer/test/fixtures/base_mailer/explicit_multipart_with_one_template.erb b/actionmailer/test/fixtures/base_mailer/explicit_multipart_with_one_template.erb new file mode 100644 index 0000000000..8a69657b08 --- /dev/null +++ b/actionmailer/test/fixtures/base_mailer/explicit_multipart_with_one_template.erb @@ -0,0 +1 @@ +<%= self.formats.inspect %> \ No newline at end of file diff --git a/actionmailer/test/fixtures/test_mailer/signed_up_with_url.erb b/actionmailer/test/fixtures/url_test_mailer/signed_up_with_url.erb similarity index 100% rename from actionmailer/test/fixtures/test_mailer/signed_up_with_url.erb rename to actionmailer/test/fixtures/url_test_mailer/signed_up_with_url.erb diff --git a/actionmailer/test/old_base/mail_layout_test.rb b/actionmailer/test/old_base/mail_layout_test.rb index 5679aa5a64..2c2daa0f28 100644 --- a/actionmailer/test/old_base/mail_layout_test.rb +++ b/actionmailer/test/old_base/mail_layout_test.rb @@ -69,47 +69,25 @@ def test_should_pickup_default_layout def test_should_pickup_multipart_layout mail = AutoLayoutMailer.multipart - # CHANGED: content_type returns an object - # assert_equal "multipart/alternative", mail.content_type assert_equal "multipart/alternative", mail.mime_type assert_equal 2, mail.parts.size - # CHANGED: content_type returns an object - # assert_equal 'text/plain', mail.parts.first.content_type assert_equal 'text/plain', mail.parts.first.mime_type - - # CHANGED: body returns an object - # assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s - # CHANGED: content_type returns an object - # assert_equal 'text/html', mail.parts.last.content_type assert_equal 'text/html', mail.parts.last.mime_type - - # CHANGED: body returns an object - # assert_equal "Hello from layout text/html multipart", mail.parts.last.body assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end def test_should_pickup_multipartmixed_layout mail = AutoLayoutMailer.multipart("multipart/mixed") - # CHANGED: content_type returns an object - # assert_equal "multipart/mixed", mail.content_type assert_equal "multipart/mixed", mail.mime_type assert_equal 2, mail.parts.size - # CHANGED: content_type returns an object - # assert_equal 'text/plain', mail.parts.first.content_type assert_equal 'text/plain', mail.parts.first.mime_type - # CHANGED: body returns an object - # assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s - # CHANGED: content_type returns an object - # assert_equal 'text/html', mail.parts.last.content_type assert_equal 'text/html', mail.parts.last.mime_type - # CHANGED: body returns an object - # assert_equal "Hello from layout text/html multipart", mail.parts.last.body assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end diff --git a/actionmailer/test/old_base/mail_service_test.rb b/actionmailer/test/old_base/mail_service_test.rb index 70dafaf33c..e49307bfda 100644 --- a/actionmailer/test/old_base/mail_service_test.rb +++ b/actionmailer/test/old_base/mail_service_test.rb @@ -340,6 +340,8 @@ def setup @original_logger = TestMailer.logger @recipient = 'test@localhost' + + TestMailer.delivery_method = :test end def teardown @@ -444,9 +446,6 @@ def test_custom_templating_extension expected.from = "system@loudthinking.com" expected.date = Time.local(2004, 12, 12) - # Stub the render method so no alternative renderers need be present. - ActionView::Base.any_instance.stubs(:render).returns("Hello there, \n\nMr. #{@recipient}") - # Now that the template is registered, there should be one part. The text/plain part. created = nil assert_nothing_raised { created = TestMailer.custom_templating_extension(@recipient) } @@ -1194,6 +1193,6 @@ def test_should_still_raise_exception_with_expected_message_when_calling_an_unde RespondToMailer.not_a_method end - assert_match(/undefined method.*not_a_method/, error.message) + assert_match(/method.*not_a_method/, error.message) end -end \ No newline at end of file +end diff --git a/actionmailer/test/old_base/url_test.rb b/actionmailer/test/old_base/url_test.rb index 60740d6b0b..17b383cc2a 100644 --- a/actionmailer/test/old_base/url_test.rb +++ b/actionmailer/test/old_base/url_test.rb @@ -10,7 +10,7 @@ class ActionMailer::Base include AppRoutes.url_helpers end -class TestMailer < ActionMailer::Base +class UrlTestMailer < ActionMailer::Base default_url_options[:host] = 'www.basecamphq.com' configure do |c| @@ -26,14 +26,6 @@ def signed_up_with_url(recipient) @recipient = recipient @welcome_url = url_for :host => "example.com", :controller => "welcome", :action => "greeting" end - - class < :'self.class' self._helpers = Module.new - self._helper_serial = ::AbstractController::Helpers.next_serial end module ClassMethods @@ -95,8 +89,6 @@ def #{meth}(*args, &blk) # helper(:three, BlindHelper) { def mice() 'mice' end } # def helper(*args, &block) - self._helper_serial = AbstractController::Helpers.next_serial + 1 - modules_for_helpers(args).each do |mod| add_template_helper(mod) end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 2f9616124a..319472c937 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/module/remove_method" + module AbstractController # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in # repeated setups. The inclusion pattern has pages that look like this: @@ -182,7 +184,9 @@ module LayoutConditions # # ==== Returns # Boolean:: True if the action has a layout, false otherwise. - def _action_has_layout? + def action_has_layout? + return unless super + conditions = _layout_conditions if only = conditions[:only] @@ -237,6 +241,8 @@ def _implied_layout_name # name, return that string. Otherwise, use the superclass' # layout (which might also be implied) def _write_layout_method + remove_possible_method(:_layout) + case defined?(@_layout) ? @_layout : nil when String self.class_eval %{def _layout; #{@_layout.inspect} end} @@ -287,6 +293,17 @@ def _normalize_options(options) end end + attr_writer :action_has_layout + + def initialize(*) + @action_has_layout = true + super + end + + def action_has_layout? + @action_has_layout + end + private # This will be overwritten by _write_layout_method @@ -326,13 +343,13 @@ def _layout_for_option(name) # Template:: The template object for the default layout (or nil) def _default_layout(require_layout = false) begin - layout_name = _layout if _action_has_layout? + layout_name = _layout if action_has_layout? rescue NameError => e raise NoMethodError, "You specified #{@_layout.inspect} as the layout, but no such method was found" end - if require_layout && _action_has_layout? && !layout_name + if require_layout && action_has_layout? && !layout_name raise ArgumentError, "There was no default layout for #{self.class} in #{view_paths.inspect}" end @@ -343,9 +360,5 @@ def _default_layout(require_layout = false) def _include_layout?(options) (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) end - - def _action_has_layout? - true - end end end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 42f4939108..b251bd6405 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -31,6 +31,7 @@ def locale=(value) module Rendering extend ActiveSupport::Concern + include AbstractController::ViewPaths # Overwrite process to setup I18n proxy. @@ -41,21 +42,52 @@ def process(*) #:nodoc: I18n.config = old_config end + module ClassMethods + def view_context_class + @view_context_class ||= begin + controller = self + Class.new(ActionView::Base) do + if controller.respond_to?(:_helpers) + include controller._helpers + + if controller.respond_to?(:_router) + include controller._router.url_helpers + end + + # TODO: Fix RJS to not require this + self.helpers = controller._helpers + end + end + end + end + end + + attr_writer :view_context_class + + def view_context_class + @view_context_class || self.class.view_context_class + end + + def initialize(*) + @view_context_class = nil + super + end + # An instance of a view class. The default view class is ActionView::Base # # The view class must have the following methods: - # View.for_controller[controller] + # View.new[lookup_context, assigns, controller] # Create a new ActionView instance for a controller - # View#render_template[options] + # View#render[options] # Returns String with the rendered template # # Override this method in a module to change the default behavior. def view_context - @_view_context ||= ActionView::Base.for_controller(self) + view_context_class.new(lookup_context, view_assigns, self) end - # Mostly abstracts the fact that calling render twice is a DoubleRenderError. - # Delegates render_to_body and sticks the result in self.response_body. + # Normalize arguments, options and then delegates render_to_body and + # sticks the result in self.response_body. def render(*args, &block) options = _normalize_args(*args, &block) _normalize_options(options) @@ -74,12 +106,13 @@ def render_to_body(options = {}) # :api: plugin def render_to_string(options={}) _normalize_options(options) - AbstractController::Rendering.body_to_s(render_to_body(options)) + render_to_body(options) end # Find and renders a template based on the options given. - def _render_template(options) - view_context.render_template(options) { |template| _with_template_hook(template) } + # :api: private + def _render_template(options) #:nodoc: + view_context.render(options) end # The prefix used in render "foo" shortcuts. @@ -87,20 +120,19 @@ def _prefix controller_path end - # Return a string representation of a Rack-compatible response body. - def self.body_to_s(body) - if body.respond_to?(:to_str) - body - else - strings = [] - body.each { |part| strings << part.to_s } - body.close if body.respond_to?(:close) - strings.join - end - end - private + # This method should return a hash with assigns. + # You can overwrite this configuration per controller. + # :api: public + def view_assigns + hash = {} + variables = instance_variable_names + variables -= protected_instance_variables if respond_to?(:protected_instance_variables) + variables.each { |name| hash[name.to_s[1..-1]] = instance_variable_get(name) } + hash + end + # Normalize options by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :file => "foo/bar". def _normalize_args(action=nil, options={}) @@ -134,9 +166,5 @@ def _normalize_options(options) def _process_options(options) end - - def _with_template_hook(template) - self.formats = template.formats - end end end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb index c2a9f6336d..b331eb51b6 100644 --- a/actionpack/lib/abstract_controller/view_paths.rb +++ b/actionpack/lib/abstract_controller/view_paths.rb @@ -7,7 +7,7 @@ module ViewPaths self._view_paths = ActionView::PathSet.new end - delegate :template_exists?, :view_paths, :formats, :formats=, + delegate :find_template, :template_exists?, :view_paths, :formats, :formats=, :locale, :locale=, :to => :lookup_context # LookupContext is the object responsible to hold all information required to lookup @@ -29,10 +29,6 @@ def prepend_view_path(path) lookup_context.view_paths.unshift(*path) end - def template_exists?(*args) - lookup_context.exists?(*args) - end - module ClassMethods # Append a path to the list of view paths for this controller. # diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index fcd3cb9bd3..5797282b41 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -2,7 +2,6 @@ module ActionController class Base < Metal abstract! - include AbstractController::Callbacks include AbstractController::Layouts include AbstractController::Translation @@ -20,9 +19,11 @@ class Base < Metal include SessionManagement include ActionController::Caching include ActionController::MimeResponds + include ActionController::PolymorphicRoutes # Rails 2.x compatibility include ActionController::Compatibility + include ActionController::ImplicitRender include ActionController::Cookies include ActionController::Flash @@ -36,8 +37,12 @@ class Base < Metal # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. include ActionController::Instrumentation - include ImplicitRender + # Before callbacks should also be executed the earliest as possible, so + # also include them at the bottom. + include AbstractController::Callbacks + + # The same with rescue, append it at the end to wrap as much as possible. include ActionController::Rescue def self.inherited(klass) diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 35111a4b92..43ddf6435a 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -80,6 +80,7 @@ module ClassMethods def caches_action(*actions) return unless cache_configured? options = actions.extract_options! + options[:layout] = true unless options.key?(:layout) filter_options = options.extract!(:if, :unless).merge(:only => actions) cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options) @@ -87,14 +88,12 @@ def caches_action(*actions) end end - def _render_cache_fragment(cache, extension, layout) - render :text => cache, :layout => layout, :content_type => Mime[extension || :html] - end - - def _save_fragment(name, layout, options) + def _save_fragment(name, options) return unless caching_allowed? - content = layout ? view_context.content_for(:layout) : response_body + content = response_body + content = content.join if content.is_a?(Array) + write_fragment(name, content, options) end @@ -112,7 +111,7 @@ def expire_action(options = {}) class ActionCacheFilter #:nodoc: def initialize(options, &block) - @cache_path, @store_options, @layout = + @cache_path, @store_options, @cache_layout = options.values_at(:cache_path, :store_options, :layout) end @@ -125,12 +124,19 @@ def filter(controller) cache_path = ActionCachePath.new(controller, path_options || {}) - if cache = controller.read_fragment(cache_path.path, @store_options) - controller._render_cache_fragment(cache, cache_path.extension, @layout == false) - else + body = controller.read_fragment(cache_path.path, @store_options) + + unless body + controller.action_has_layout = false unless @cache_layout yield - controller._save_fragment(cache_path.path, @layout == false, @store_options) + controller.action_has_layout = true + body = controller._save_fragment(cache_path.path, @store_options) end + + body = controller.render_to_string(:text => cache, :layout => true) unless @cache_layout + + controller.response_body = body + controller.content_type = Mime[cache_path.extension || :html] end end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 00a7f034d3..473a2fe214 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -34,26 +34,13 @@ def fragment_cache_key(key) ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) end - def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc: - if perform_caching - if fragment_exist?(name, options) - buffer.safe_concat(read_fragment(name, options)) - else - pos = buffer.length - block.call - write_fragment(name, buffer[pos..-1], options) - end - else - block.call - end - end - # Writes content to the location signified by key (see expire_fragment for acceptable formats) def write_fragment(key, content, options = nil) return content unless cache_configured? - key = fragment_cache_key(key) + key = fragment_cache_key(key) instrument_fragment_cache :write_fragment, key do + content = content.html_safe.to_str if content.respond_to?(:html_safe) cache_store.write(key, content, options) end content @@ -62,10 +49,11 @@ def write_fragment(key, content, options = nil) # Reads a cached fragment from the location signified by key (see expire_fragment for acceptable formats) def read_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) + key = fragment_cache_key(key) instrument_fragment_cache :read_fragment, key do - cache_store.read(key, options) + result = cache_store.read(key, options) + result.respond_to?(:html_safe) ? result.html_safe : result end end diff --git a/actionpack/lib/action_controller/deprecated/base.rb b/actionpack/lib/action_controller/deprecated/base.rb index 1d05b3fbd6..bbde570ca9 100644 --- a/actionpack/lib/action_controller/deprecated/base.rb +++ b/actionpack/lib/action_controller/deprecated/base.rb @@ -63,7 +63,7 @@ def ip_spoofing_check=(value) def ip_spoofing_check ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check is deprecated. " << "Configuring ip_spoofing_check on the application configures a middleware.", caller - Rails.application.config.action_disaptch.ip_spoofing_check + Rails.application.config.action_dispatch.ip_spoofing_check end def trusted_proxies=(value) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index eebd2c943a..30aa34d956 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -34,11 +34,12 @@ def controller_name # and response object available. You might wish to control the # environment and response manually for performance reasons. - attr_internal :status, :headers, :content_type, :response, :request + attr_internal :headers, :response, :request delegate :session, :to => "@_request" def initialize(*) - @_headers = {} + @_headers = {"Content-Type" => "text/html"} + @_status = 200 super end @@ -62,10 +63,19 @@ def location=(url) headers["Location"] = url end + def status + @_status + end + def status=(status) @_status = Rack::Utils.status_code(status) end + def response_body=(val) + body = val.respond_to?(:each) ? val : [val] + super body + end + # :api: private def dispatch(name, request) @_request = request diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index ab8d87b2c4..e6cea483bb 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -40,15 +40,6 @@ def rescue_action(env) def initialize_template_class(*) end def assign_shortcuts(*) end - def template - @template ||= view_context - end - - def process_action(*) - template - super - end - def _normalize_options(options) if options[:action] && options[:action].to_s.include?(?/) ActiveSupport::Deprecation.warn "Giving a path to render :action is deprecated. " << diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index e70a20b2be..4f384d1ec5 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -261,7 +261,8 @@ def retrieve_response_from_mimes(mimes=nil, &block) block.call(collector) if block_given? if format = request.negotiate_mime(collector.order) - self.formats = [format.to_sym] + self.content_type ||= format.to_s + lookup_context.freeze_formats([format.to_sym]) collector.response_for(format) else head :not_acceptable diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 37106733cb..060117756e 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -5,10 +5,8 @@ module ActionController module RackDelegation extend ActiveSupport::Concern - included do - delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :to => "@_response" - end + delegate :headers, :status=, :location=, :content_type=, + :status, :location, :content_type, :to => "@_response" def dispatch(action, request) @_response = ActionDispatch::Response.new diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 25e4e18493..b5f1d23ef0 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -76,7 +76,7 @@ def _compute_redirect_to_location(options) # The scheme name consist of a letter followed by any combination of # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") # characters; and is terminated by a colon (":"). - when %r{^\w[\w\d+.-]*:.*} + when %r{^\w[\w+.-]*:.*} options when String request.protocol + request.host_with_port + options diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 49d3d6b466..d906e1fb5b 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -87,8 +87,9 @@ def self._write_render_options end add :update do |proc, options| + view_context = self.view_context generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc) - self.content_type = Mime::JS + self.content_type = Mime::JS self.response_body = generator.to_s end end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index f892bd9b91..86bb810947 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -5,26 +5,31 @@ module Rendering include ActionController::RackDelegation include AbstractController::Rendering - def process(*) + # Before processing, set the request formats in current controller formats. + def process_action(*) #:nodoc: self.formats = request.formats.map { |x| x.to_sym } super end - def render(*args) + # Check for double render errors and set the content_type after rendering. + def render(*args) #:nodoc: raise ::AbstractController::DoubleRenderError if response_body super + self.content_type ||= Mime[formats.first].to_s response_body end private - def _normalize_args(action=nil, options={}, &blk) + # Normalize arguments by catching blocks and setting them on :update. + def _normalize_args(action=nil, options={}, &blk) #:nodoc: options = super options[:update] = blk if block_given? options end - def _normalize_options(options) + # Normalize both text and status options. + def _normalize_options(options) #:nodoc: if options.key?(:text) && options[:text].respond_to?(:to_text) options[:text] = options[:text].to_text end @@ -36,7 +41,8 @@ def _normalize_options(options) super end - def _process_options(options) + # Process controller specific options, as status, content-type and location. + def _process_options(options) #:nodoc: status, content_type, location = options.values_at(:status, :content_type, :location) self.status = status if status @@ -46,10 +52,5 @@ def _process_options(options) super end - def _with_template_hook(template) - super - self.content_type ||= template.mime_type.to_s - end - end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 6765314df2..39a809657b 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -12,7 +12,7 @@ module RequestForgeryProtection included do # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ # sets it to :authenticity_token by default. - config.request_forgery_protection_token ||= true + config.request_forgery_protection_token ||= :authenticity_token # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. config.allow_forgery_protection ||= true diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 6178a59029..0b2cee6868 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -1,3 +1,5 @@ +require 'active_support/json' + module ActionController #:nodoc: # Responder is responsible for exposing a resource to different mime requests, # usually depending on the HTTP verb. The responder is triggered when diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 6a3afbb157..0ec89928af 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,16 +1,17 @@ require "rails" require "action_controller" +require "action_dispatch/railtie" require "action_view/railtie" require "active_support/core_ext/class/subclasses" require "active_support/deprecation/proxy_wrappers" require "active_support/deprecation" +require "action_controller/railties/log_subscriber" +require "action_controller/railties/url_helpers" + module ActionController class Railtie < Rails::Railtie - railtie_name :action_controller - - require "action_controller/railties/log_subscriber" - require "action_controller/railties/url_helpers" + config.action_controller = ActiveSupport::OrderedOptions.new ad = config.action_dispatch config.action_controller.singleton_class.send(:define_method, :session) do @@ -37,7 +38,7 @@ class Railtie < Rails::Railtie ad.session_store = val end - log_subscriber ActionController::Railties::LogSubscriber.new + log_subscriber :action_controller, ActionController::Railties::LogSubscriber.new initializer "action_controller.logger" do ActionController.base_hook { self.logger ||= Rails.logger } @@ -52,7 +53,9 @@ class Railtie < Rails::Railtie ac.stylesheets_dir = paths.public.stylesheets.to_a.first ac.secret = app.config.cookie_secret - ActionController.base_hook { self.config.replace(ac) } + ActionController.base_hook do + self.config.merge!(ac) + end end initializer "action_controller.initialize_framework_caches" do @@ -67,7 +70,7 @@ class Railtie < Rails::Railtie initializer "action_controller.url_helpers" do |app| ActionController.base_hook do - extend ::ActionController::Railtie::UrlHelpers.with(app.routes) + extend ::ActionController::Railties::UrlHelpers.with(app.routes) end message = "ActionController::Routing::Routes is deprecated. " \ diff --git a/actionpack/lib/action_controller/railties/url_helpers.rb b/actionpack/lib/action_controller/railties/url_helpers.rb index ad2a8d4ef3..5f95e1c621 100644 --- a/actionpack/lib/action_controller/railties/url_helpers.rb +++ b/actionpack/lib/action_controller/railties/url_helpers.rb @@ -1,5 +1,5 @@ module ActionController - class Railtie + module Railties module UrlHelpers def self.with(router) Module.new do diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index cdb5db32aa..120f34460e 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,7 +1,107 @@ require 'rack/session/abstract/id' -require 'action_view/test_case' module ActionController + module TemplateAssertions + extend ActiveSupport::Concern + + included do + setup :setup_subscriptions + teardown :teardown_subscriptions + end + + def setup_subscriptions + @partials = Hash.new(0) + @templates = Hash.new(0) + @layouts = Hash.new(0) + + ActiveSupport::Notifications.subscribe("action_view.render_template") do |name, start, finish, id, payload| + path = payload[:layout] + @layouts[path] += 1 + end + + ActiveSupport::Notifications.subscribe("action_view.render_template!") do |name, start, finish, id, payload| + path = payload[:virtual_path] + next unless path + partial = path =~ /^.*\/_[^\/]*$/ + if partial + @partials[path] += 1 + @partials[path.split("/").last] += 1 + @templates[path] += 1 + else + @templates[path] += 1 + end + end + end + + def teardown_subscriptions + ActiveSupport::Notifications.unsubscribe("action_view.render_template!") + end + + # Asserts that the request was rendered with the appropriate template file or partials + # + # ==== Examples + # + # # assert that the "new" view template was rendered + # assert_template "new" + # + # # assert that the "_customer" partial was rendered twice + # assert_template :partial => '_customer', :count => 2 + # + # # assert that no partials were rendered + # assert_template :partial => false + # + def assert_template(options = {}, message = nil) + validate_request! + + case options + when NilClass, String + rendered = @templates + msg = build_message(message, + "expecting but rendering with ", + options, rendered.keys.join(', ')) + assert_block(msg) do + if options.nil? + @templates.blank? + else + rendered.any? { |t,num| t.match(options) } + end + end + when Hash + if expected_partial = options[:partial] + if expected_count = options[:count] + actual_count = @partials[expected_partial] + # actual_count = found.nil? ? 0 : found[1] + msg = build_message(message, + "expecting ? to be rendered ? time(s) but rendered ? time(s)", + expected_partial, expected_count, actual_count) + assert(actual_count == expected_count.to_i, msg) + elsif options.key?(:layout) + msg = build_message(message, + "expecting layout but action rendered ", + expected_layout, @layouts.keys) + + case layout = options[:layout] + when String + assert(@layouts.include?(expected_layout), msg) + when Regexp + assert(@layouts.any? {|l| l =~ layout }, msg) + when nil + assert(@layouts.empty?, msg) + end + else + msg = build_message(message, + "expecting partial but action rendered ", + options[:partial], @partials.keys) + assert(@partials.include?(expected_partial), msg) + end + else + assert @partials.empty?, + "Expected no partials to be rendered" + end + end + end + end + class TestRequest < ActionDispatch::TestRequest #:nodoc: def initialize(env = {}) super @@ -181,6 +281,7 @@ def initialize(session = {}) # assert_redirected_to page_url(:title => 'foo') class TestCase < ActiveSupport::TestCase include ActionDispatch::TestProcess + include ActionController::TemplateAssertions # Executes a request simulating GET HTTP method and set/volley the response def get(action, parameters = nil, session = nil, flash = nil) diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 451b79b190..e42b4d09b0 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -25,7 +25,6 @@ module Http module FilterParameters extend ActiveSupport::Concern - mattr_reader :compiled_parameter_filter_for @@compiled_parameter_filter_for = {} # Return a hash of parameters with all sensitive data replaced. diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 13c0f2bad0..3f1a77295d 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -52,12 +52,6 @@ class Type cattr_reader :browser_generated_types attr_reader :symbol - @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] - def self.unverifiable_types - ActiveSupport::Deprecation.warn("unverifiable_types is deprecated and has no effect", caller) - @@unverifiable_types - end - # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: attr_accessor :order, :name, :q @@ -100,7 +94,7 @@ def register_alias(string, symbol, extension_synonyms = []) end def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) - Mime.instance_eval { const_set symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms) } + Mime.const_set(symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms)) SET << Mime.const_get(symbol.to_s.upcase) diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 0dc03a1a7e..ab7130ab08 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -84,6 +84,7 @@ def []=(key, options) options[:path] ||= "/" @set_cookies[key] = options + @delete_cookies.delete(key) value end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 3bcd004e12..43440e5f7c 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -5,20 +5,6 @@ module ActionDispatch # This middleware rescues any exception returned by the application and renders # nice exception pages if it's being rescued locally. - # - # Every time an exception is caught, a notification is published, becoming a good API - # to deal with exceptions. So, if you want send an e-mail through ActionMailer - # everytime this notification is published, you just need to do the following: - # - # ActiveSupport::Notifications.subscribe "action_dispatch.show_exception" do |name, start, end, instrumentation_id, payload| - # ExceptionNotifier.deliver_exception(start, payload) - # end - # - # The payload is a hash which has two pairs: - # - # * :env - Contains the rack env for the given request; - # * :exception - The exception raised; - # class ShowExceptions LOCALHOST = ['127.0.0.1', '::1'].freeze diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index e486bd4079..563df0f256 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -3,9 +3,8 @@ module ActionDispatch class Railtie < Rails::Railtie - railtie_name :action_dispatch - - config.action_dispatch.x_sendfile_header = "X-Sendfile" + config.action_dispatch = ActiveSupport::OrderedOptions.new + config.action_dispatch.x_sendfile_header = "" config.action_dispatch.ip_spoofing_check = true # Prepare dispatcher callbacks and run 'prepare' callbacks diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 5bc3205c51..c6e942555f 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/regexp' +require 'action_controller/polymorphic_routes' module ActionDispatch # == Routing diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 0b7b09ee7a..5a3868e1d4 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -32,6 +32,8 @@ def call(env) end class Mapping + IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor] + def initialize(set, scope, args) @set, @scope = set, scope @path, @options = extract_path_and_options(args) @@ -45,18 +47,21 @@ def to_route def extract_path_and_options(args) options = args.extract_options! - case - when using_to_shorthand?(args, options) + if using_to_shorthand?(args, options) path, to = options.find { |name, value| name.is_a?(String) } options.merge!(:to => to).delete(path) if path - when using_match_shorthand?(args, options) - path = args.first - options = { :to => path.gsub("/", "#"), :as => path.gsub("/", "_") } else path = args.first end - [ normalize_path(path), options ] + path = normalize_path(path) + + if using_match_shorthand?(path, options) + options[:to] ||= path[1..-1].sub(%r{/([^/]*)$}, '#\1') + options[:as] ||= path[1..-1].gsub("/", "_") + end + + [ path, options ] end # match "account" => "account#index" @@ -65,14 +70,13 @@ def using_to_shorthand?(args, options) end # match "account/overview" - def using_match_shorthand?(args, options) - args.present? && options.except(:via, :anchor).empty? && !args.first.include?(':') + def using_match_shorthand?(path, options) + path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$} end def normalize_path(path) - path = "#{@scope[:path]}/#{path}" - raise ArgumentError, "path is required" if path.empty? - Mapper.normalize_path(path) + raise ArgumentError, "path is required" if @scope[:path].blank? && path.blank? + Mapper.normalize_path("#{@scope[:path]}/#{path}") end def app @@ -94,7 +98,15 @@ def requirements end def defaults - @defaults ||= if to.respond_to?(:call) + @defaults ||= (@options[:defaults] || {}).tap do |defaults| + defaults.merge!(default_controller_and_action) + defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults] + @options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) } + end + end + + def default_controller_and_action + if to.respond_to?(:call) { } else defaults = case to @@ -144,8 +156,8 @@ def request_method_condition def segment_keys @segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new( - Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS) - ).names + Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS) + ).names end def to @@ -297,11 +309,14 @@ def constraints(constraints = {}) scope(:constraints => constraints) { yield } end + def defaults(defaults = {}) + scope(:defaults => defaults) { yield } + end + def match(*args) options = args.extract_options! options = (@scope[:options] || {}).merge(options) - options[:anchor] = true unless options.key?(:anchor) if @scope[:name_prefix] && !options[:as].blank? options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}" @@ -342,6 +357,10 @@ def merge_constraints_scope(parent, child) merge_options_scope(parent, child) end + def merge_defaults_scope(parent, child) + merge_options_scope(parent, child) + end + def merge_blocks_scope(parent, child) (parent || []) + [child] end @@ -362,11 +381,11 @@ def self.default_actions attr_reader :plural, :singular, :options def initialize(entities, options = {}) - entities = entities.to_s + @name = entities.to_s @options = options - @plural = entities.pluralize - @singular = entities.singularize + @plural = @name.pluralize + @singular = @name.singularize end def default_actions @@ -393,7 +412,7 @@ def action_type(action) end def name - options[:as] || plural + options[:as] || @name end def controller @@ -438,8 +457,8 @@ def action_type(action) end end - def name - options[:as] || singular + def member_name + name end end @@ -460,7 +479,7 @@ def resource(*resources, &block) scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resource, resource) do - scope(:name_prefix => resource.name.to_s) do + scope(:name_prefix => resource.name.to_s, :as => "") do yield if block_given? end @@ -468,8 +487,8 @@ def resource(*resources, &block) post :create if resource.actions.include?(:create) put :update if resource.actions.include?(:update) delete :destroy if resource.actions.include?(:destroy) - get :new, :as => resource.singular if resource.actions.include?(:new) - get :edit, :as => resource.singular if resource.actions.include?(:edit) + get :new, :as => resource.name if resource.actions.include?(:new) + get :edit, :as => resource.name if resource.actions.include?(:edit) end end @@ -563,6 +582,8 @@ def mount(app, options = nil) def match(*args) options = args.extract_options! + options[:anchor] = true unless options.key?(:anchor) + if args.length > 1 args.each { |path| match(path, options) } return self diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 722be432c7..bb689beed9 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -65,7 +65,7 @@ def split_glob_param!(params) # named routes. class NamedRouteCollection #:nodoc: include Enumerable - attr_reader :routes, :helpers + attr_reader :routes, :helpers, :module def initialize clear! @@ -179,6 +179,7 @@ def #{selector}(*args) url_for(options) end + protected :#{selector} END_EVAL helpers << selector end @@ -219,14 +220,16 @@ def draw(&block) end def finalize! + return if @finalized + @finalized = true @set.add_route(NotFound) - install_helpers @set.freeze end def clear! # Clear the controller cache so we may discover new ones @controller_constraints = nil + @finalized = false routes.clear named_routes.clear @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY) @@ -239,21 +242,30 @@ def install_helpers(destinations = [ActionController::Base, ActionView::Base], r def url_helpers @url_helpers ||= begin - router = self + routes = self - Module.new do + helpers = Module.new do extend ActiveSupport::Concern include UrlFor + @routes = routes + class << self + delegate :url_for, :to => '@routes' + end + extend routes.named_routes.module + # ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that # we can include? # Yes plz - JP included do - router.install_helpers(self) + routes.install_helpers(self) + singleton_class.send(:define_method, :_router) { routes } end - define_method(:_router) { router } + define_method(:_router) { routes } end + + helpers end end @@ -406,6 +418,7 @@ def generate(options, recall = {}, extras = false) RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash] def url_for(options) + finalize! options = default_url_options.merge(options || {}) handle_positional_args(options) @@ -437,6 +450,7 @@ def url_for(options) end def call(env) + finalize! @set.call(env) end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index ec78f53fa6..b8c02f402c 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -72,7 +72,7 @@ module Routing # you can do that by including ActionController::UrlFor in your class: # # class User < ActiveRecord::Base - # include ActionController::UrlFor + # include Rails.application.routes.url_helpers # # def base_uri # user_path(self) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 937c9f48d2..ec5e9efe44 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -73,58 +73,6 @@ def assert_redirected_to(options = {}, message=nil) end end - # Asserts that the request was rendered with the appropriate template file or partials - # - # ==== Examples - # - # # assert that the "new" view template was rendered - # assert_template "new" - # - # # assert that the "_customer" partial was rendered twice - # assert_template :partial => '_customer', :count => 2 - # - # # assert that no partials were rendered - # assert_template :partial => false - # - def assert_template(options = {}, message = nil) - validate_request! - - case options - when NilClass, String - rendered = (@controller.template.rendered[:template] || []).map { |t| t.identifier } - msg = build_message(message, - "expecting but rendering with ", - options, rendered.join(', ')) - assert_block(msg) do - if options.nil? - @controller.template.rendered[:template].blank? - else - rendered.any? { |t| t.match(options) } - end - end - when Hash - if expected_partial = options[:partial] - partials = @controller.template.rendered[:partials] - if expected_count = options[:count] - found = partials.detect { |p, _| p.identifier.match(expected_partial) } - actual_count = found.nil? ? 0 : found.second - msg = build_message(message, - "expecting ? to be rendered ? time(s) but rendered ? time(s)", - expected_partial, expected_count, actual_count) - assert(actual_count == expected_count.to_i, msg) - else - msg = build_message(message, - "expecting partial but action rendered ", - options[:partial], partials.keys) - assert(partials.keys.any? { |p| p.identifier.match(expected_partial) }, msg) - end - else - assert @controller.template.rendered[:partials].empty?, - "Expected no partials to be rendered" - end - end - end - private # Proxy to to_param if the object will respond to it. def parameterize(value) diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 1d7e8090e4..1bb81ede3b 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -145,11 +145,25 @@ def with_routing old_routes, @router = @router, ActionDispatch::Routing::RouteSet.new old_controller, @controller = @controller, @controller.clone if @controller _router = @router - @controller.singleton_class.send(:send, :include, @router.url_helpers) if @controller + + # Unfortunately, there is currently an abstraction leak between AC::Base + # and AV::Base which requires having the URL helpers in both AC and AV. + # To do this safely at runtime for tests, we need to bump up the helper serial + # to that the old AV subclass isn't cached. + # + # TODO: Make this unnecessary + if @controller + @controller.singleton_class.send(:include, _router.url_helpers) + @controller.view_context_class = Class.new(@controller.view_context_class) do + include _router.url_helpers + end + end yield @router ensure @router = old_routes - @controller = old_controller if @controller + if @controller + @controller = old_controller + end end # ROUTES TODO: These assertions should really work in an integration context diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 0aff4250c1..621d63c5e2 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -2,6 +2,7 @@ require 'uri' require 'active_support/core_ext/object/singleton_class' require 'rack/test' +require 'test/unit/assertions' module ActionDispatch module Integration #:nodoc: @@ -177,14 +178,8 @@ def initialize(app) reset! end - def url_options - opts = super.reverse_merge( - :host => host, - :protocol => https? ? "https" : "http" - ) - - opts.merge!(:port => 443) if !opts.key?(:port) && https? - opts + def default_url_options + { :host => host, :protocol => https? ? "https" : "http" } end # Resets the instance. This can be used to reset the state information @@ -293,6 +288,8 @@ def process(method, path, parameters = nil, rack_environment = nil) end module Runner + include ActionDispatch::Assertions + def app @app end @@ -300,7 +297,7 @@ def app # Reset the current session. This is useful for testing multiple sessions # in a single test case. def reset! - @integration_session = open_session + @integration_session = Integration::Session.new(app) end %w(get post put head delete cookies assigns @@ -326,30 +323,9 @@ def reset! # can use this method to open multiple sessions that ought to be tested # simultaneously. def open_session(app = nil) - session = Integration::Session.new(app || self.app) - - # delegate the fixture accessors back to the test instance - extras = Module.new { attr_accessor :delegate, :test_result } - if self.class.respond_to?(:fixture_table_names) - self.class.fixture_table_names.each do |table_name| - name = table_name.tr(".", "_") - next unless respond_to?(name) - extras.__send__(:define_method, name) { |*args| - delegate.send(name, *args) - } - end + dup.tap do |session| + yield session if block_given? end - - # delegate add_assertion to the test case - extras.__send__(:define_method, :add_assertion) { - test_result.add_assertion - } - session.extend(extras) - session.delegate = self - session.test_result = @_result - - yield session if block_given? - session end # Copy the instance variables from the current session instance into the @@ -460,6 +436,7 @@ def method_missing(sym, *args, &block) # end class IntegrationTest < ActiveSupport::TestCase include Integration::Runner + include ActionController::TemplateAssertions @@app = nil diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index afe6386105..5555217ee2 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -44,13 +44,15 @@ module ActionView autoload :Base autoload :LookupContext - autoload :MissingTemplate, 'action_view/base' autoload :Resolver, 'action_view/template/resolver' autoload :PathResolver, 'action_view/template/resolver' autoload :FileSystemResolver, 'action_view/template/resolver' autoload :PathSet, 'action_view/paths' + autoload :MissingTemplate, 'action_view/template/error' + autoload :ActionViewError, 'action_view/template/error' autoload :TemplateError, 'action_view/template/error' + autoload :TemplateHandler, 'action_view/template' autoload :TemplateHandlers, 'action_view/template' end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index ffe3060404..919b1e3470 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -1,27 +1,10 @@ require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/array/wrap' module ActionView #:nodoc: - class ActionViewError < StandardError #:nodoc: - end - - class MissingTemplate < ActionViewError #:nodoc: - attr_reader :path - - def initialize(paths, path, details, partial) - @path = path - display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") - template_type = if partial - "partial" - elsif path =~ /layouts/i - 'layout' - else - 'template' - end - - super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}") - end + class NonConcattingString < ActiveSupport::SafeBuffer end # Action View templates can be written in three ways. If the template file has a .erb (or .rhtml) extension then it uses a mixture of ERb @@ -176,14 +159,13 @@ module Subclasses include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context extend ActiveSupport::Memoizable - ActionView.run_base_hooks(self) - # Specify whether RJS responses should be wrapped in a try/catch block # that alert()s the caught exception (and then re-raises it). cattr_accessor :debug_rjs @@debug_rjs = false class_attribute :helpers + remove_method :helpers attr_reader :helpers class << self @@ -191,10 +173,12 @@ class << self delegate :logger, :to => 'ActionController::Base', :allow_nil => true end - attr_accessor :base_path, :assigns, :template_extension, :lookup_context - attr_internal :captures, :request, :layout, :controller, :template, :config + ActionView.run_base_hooks(self) - delegate :find, :exists?, :formats, :formats=, :locale, :locale=, + attr_accessor :base_path, :assigns, :template_extension, :lookup_context + attr_internal :captures, :request, :controller, :template, :config + + delegate :find_template, :template_exists?, :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :with_fallbacks, :update_details, :to => :lookup_context delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, @@ -202,42 +186,17 @@ class << self delegate :logger, :to => :controller, :allow_nil => true + # TODO: HACK FOR RJS + def view_context + self + end + def self.xss_safe? #:nodoc: true end def self.process_view_paths(value) - ActionView::PathSet.new(Array(value)) - end - - def self.for_controller(controller) - @views ||= {} - - # TODO: Decouple this so helpers are a separate concern in AV just like - # they are in AC. - if controller.class.respond_to?(:_helper_serial) - klass = @views[controller.class._helper_serial] ||= Class.new(self) do - # Try to make stack traces clearer - class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def self.name - "ActionView for #{controller.class}" - end - - def inspect - "#<#{self.class.name}>" - end - ruby_eval - - if controller.respond_to?(:_helpers) - include controller._helpers - self.helpers = controller._helpers - end - end - else - klass = self - end - - klass.new(controller.lookup_context, {}, controller) + ActionView::PathSet.new(Array.wrap(value)) end def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: @@ -246,7 +205,7 @@ def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = @helpers = self.class.helpers || Module.new @_controller = controller - @_config = ActiveSupport::InheritableOptions.new(controller.config) if controller + @_config = ActiveSupport::InheritableOptions.new(controller.config) if controller && controller.respond_to?(:config) @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } @_virtual_path = nil @@ -264,14 +223,5 @@ def punctuate_body!(part) response.body_parts << part nil end - - # Evaluates the local assigns and controller ivars, pushes them to the view. - def _evaluate_assigns_and_ivars #:nodoc: - if controller - variables = controller.instance_variable_names - variables -= controller.protected_instance_variables if controller.respond_to?(:protected_instance_variables) - variables.each { |name| instance_variable_set(name, controller.instance_variable_get(name)) } - end - end end end diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index df078a7151..61d2e702a7 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -10,8 +10,6 @@ module CompiledTemplates #:nodoc: # In order to work with ActionController, a Context # must implement: # - # Context.for_controller[controller] Create a new ActionView instance for a - # controller # Context#render_partial[options] # - responsible for setting options[:_template] # - Returns String with the rendered partial diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index e359b0bdac..a50c180f63 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -2,30 +2,32 @@ module ActionView #:nodoc: module Helpers #:nodoc: - autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper' - autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper' - autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper' - autoload :CacheHelper, 'action_view/helpers/cache_helper' - autoload :CaptureHelper, 'action_view/helpers/capture_helper' - autoload :CsrfHelper, 'action_view/helpers/csrf_helper' - autoload :DateHelper, 'action_view/helpers/date_helper' - autoload :DebugHelper, 'action_view/helpers/debug_helper' - autoload :DeprecatedBlockHelpers, 'action_view/helpers/deprecated_block_helpers' - autoload :FormHelper, 'action_view/helpers/form_helper' - autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper' - autoload :FormTagHelper, 'action_view/helpers/form_tag_helper' - autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper' - autoload :NumberHelper, 'action_view/helpers/number_helper' - autoload :PrototypeHelper, 'action_view/helpers/prototype_helper' - autoload :RawOutputHelper, 'action_view/helpers/raw_output_helper' - autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper' - autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper' - autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper' - autoload :ScriptaculousHelper, 'action_view/helpers/scriptaculous_helper' - autoload :TagHelper, 'action_view/helpers/tag_helper' - autoload :TextHelper, 'action_view/helpers/text_helper' - autoload :TranslationHelper, 'action_view/helpers/translation_helper' - autoload :UrlHelper, 'action_view/helpers/url_helper' + extend ActiveSupport::Autoload + + autoload :ActiveModelHelper + autoload :AssetTagHelper + autoload :AtomFeedHelper + autoload :CacheHelper + autoload :CaptureHelper + autoload :CsrfHelper + autoload :DateHelper + autoload :DebugHelper + autoload :DeprecatedBlockHelpers + autoload :FormHelper + autoload :FormOptionsHelper + autoload :FormTagHelper + autoload :JavaScriptHelper, "action_view/helpers/javascript_helper" + autoload :NumberHelper + autoload :PrototypeHelper + autoload :RawOutputHelper + autoload :RecordIdentificationHelper + autoload :RecordTagHelper + autoload :SanitizeHelper + autoload :ScriptaculousHelper + autoload :TagHelper + autoload :TextHelper + autoload :TranslationHelper + autoload :UrlHelper def self.included(base) base.extend(ClassMethods) diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 4e12cdab54..80b3d3a664 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/object/blank' module ActionView ActionView.base_hook do @@ -127,9 +128,9 @@ def error_message_on(object, method, *args) object = convert_to_model(object) if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && - (errors = obj.errors[method]) + (errors = obj.errors[method]).presence content_tag("div", - (options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]), + "#{options[:prepend_text]}#{ERB::Util.h(errors.first)}#{options[:append_text]}".html_safe, :class => options[:css_class] ) else @@ -295,6 +296,10 @@ def error_wrapping(html_tag) end end + def error_message + object.errors[@method_name] + end + def column_type object.send(:column_for_attribute, @method_name).type end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index de3d61ebbe..02ad41719b 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -3,6 +3,7 @@ require 'action_view/helpers/url_helper' require 'action_view/helpers/tag_helper' require 'active_support/core_ext/file' +require 'active_support/core_ext/object/blank' module ActionView module Helpers #:nodoc: @@ -11,7 +12,7 @@ module Helpers #:nodoc: # the assets exist before linking to them: # # image_tag("rails.png") - # # => Rails src= + # # => Rails # stylesheet_link_tag("application") # # => # @@ -58,7 +59,7 @@ module Helpers #:nodoc: # +asset_host+ to a proc like this: # # ActionController::Base.asset_host = Proc.new { |source| - # "http://assets#{rand(2) + 1}.example.com" + # "http://assets#{source.hash % 2 + 1}.example.com" # } # image_tag("rails.png") # # => Rails @@ -66,7 +67,7 @@ module Helpers #:nodoc: # # => # # The example above generates "http://assets1.example.com" and - # "http://assets2.example.com" randomly. This option is useful for example if + # "http://assets2.example.com". This option is useful for example if # you need fewer/more than four hosts, custom host names, etc. # # As you see the proc takes a +source+ parameter. That's a string with the @@ -242,12 +243,12 @@ def javascript_path(source) # == Caching multiple javascripts into one # # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be - # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching + # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching # is set to true (which is the case by default for the Rails production environment, but not for the development # environment). # # ==== Examples - # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false => + # javascript_include_tag :all, :cache => true # when config.perform_caching is false => # # # ... @@ -255,15 +256,15 @@ def javascript_path(source) # # # - # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true => + # javascript_include_tag :all, :cache => true # when config.perform_caching is true => # # - # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false => + # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false => # # # # - # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true => + # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true => # # # The :recursive option is also available for caching: @@ -275,11 +276,11 @@ def javascript_include_tag(*sources) cache = concat || options.delete("cache") recursive = options.delete("recursive") - if concat || (ActionController::Base.perform_caching && cache) + if concat || (config.perform_caching && cache) joined_javascript_name = (cache == true ? "all" : cache) + ".js" joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name) - unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path) + unless config.perform_caching && File.exists?(joined_javascript_path) write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) end javascript_src_tag(joined_javascript_name, options) @@ -390,25 +391,25 @@ def stylesheet_path(source) # == Caching multiple stylesheets into one # # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be - # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching + # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching # is set to true (which is the case by default for the Rails production environment, but not for the development # environment). Examples: # # ==== Examples - # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false => + # stylesheet_link_tag :all, :cache => true # when config.perform_caching is false => # # # # - # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true => + # stylesheet_link_tag :all, :cache => true # when config.perform_caching is true => # # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false => + # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false => # # # # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true => + # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true => # # # The :recursive option is also available for caching: @@ -426,11 +427,11 @@ def stylesheet_link_tag(*sources) cache = concat || options.delete("cache") recursive = options.delete("recursive") - if concat || (ActionController::Base.perform_caching && cache) + if concat || (config.perform_caching && cache) joined_stylesheet_name = (cache == true ? "all" : cache) + ".css" joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name) - unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path) + unless config.perform_caching && File.exists?(joined_stylesheet_path) write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) end stylesheet_tag(joined_stylesheet_name, options) @@ -523,7 +524,7 @@ def image_tag(source, options = {}) options.symbolize_keys! src = options[:src] = path_to_image(source) - options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize + options[:alt] ||= File.basename(src, '.*').capitalize if size = options.delete(:size) options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} @@ -623,41 +624,37 @@ def self.cache_asset_timestamps=(value) @@cache_asset_timestamps = true private + def rewrite_extension?(source, dir, ext) + source_ext = File.extname(source)[1..-1] + ext && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}")))) + end + + def rewrite_host_and_protocol(source, has_request) + host = compute_asset_host(source) + if has_request && host.present? && !is_uri?(host) + host = "#{controller.request.protocol}#{host}" + end + "#{host}#{source}" + end + # Add the the extension +ext+ if not present. Return full URLs otherwise untouched. # Prefix with /dir/ if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) + return source if is_uri?(source) + + source += ".#{ext}" if rewrite_extension?(source, dir, ext) + source = "/#{dir}/#{source}" unless source[0] == ?/ + source = rewrite_asset_path(source) + has_request = controller.respond_to?(:request) - - source_ext = File.extname(source)[1..-1] - if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}")))) - source += ".#{ext}" + if has_request && include_host && source !~ %r{^#{controller.config.relative_url_root}/} + source = "#{controller.config.relative_url_root}#{source}" end + source = rewrite_host_and_protocol(source, has_request) if include_host - unless is_uri?(source) - source = "/#{dir}/#{source}" unless source[0] == ?/ - - source = rewrite_asset_path(source) - - if has_request && include_host - unless source =~ %r{^#{controller.config.relative_url_root}/} - source = "#{controller.config.relative_url_root}#{source}" - end - end - end - - if include_host && !is_uri?(source) - host = compute_asset_host(source) - - if has_request && !host.blank? && !is_uri?(host) - host = "#{controller.request.protocol}#{host}" - end - - "#{host}#{source}" - else - source - end + source end def is_uri?(path) diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index d5cc14b29a..a904af56bb 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -32,7 +32,28 @@ module CacheHelper # Topics listed alphabetically # <% end %> def cache(name = {}, options = nil, &block) - controller.fragment_for(output_buffer, name, options, &block) + safe_concat fragment_for(name, options, &block) + nil + end + + private + # TODO: Create an object that has caching read/write on it + def fragment_for(name = {}, options = nil, &block) #:nodoc: + if controller.perform_caching + if controller.fragment_exist?(name, options) + controller.read_fragment(name, options) + else + # VIEW TODO: Make #capture usable outside of ERB + # This dance is needed because Builder can't use capture + pos = output_buffer.length + yield + fragment = output_buffer.slice!(pos..-1) + controller.write_fragment(name, fragment, options) + end + else + ret = yield + ActiveSupport::SafeBuffer.new(ret) if ret.is_a?(String) + end end end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 75fc2fddeb..f0be814700 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -2,22 +2,22 @@ module ActionView module Helpers # CaptureHelper exposes methods to let you extract generated markup which # can be used in other parts of a template or layout file. - # It provides a method to capture blocks into variables through capture and + # It provides a method to capture blocks into variables through capture and # a way to capture a block of markup for use in a layout through content_for. module CaptureHelper - # The capture method allows you to extract part of a template into a - # variable. You can then use this variable anywhere in your templates or layout. - # + # The capture method allows you to extract part of a template into a + # variable. You can then use this variable anywhere in your templates or layout. + # # ==== Examples # The capture method can be used in ERb templates... - # + # # <% @greeting = capture do %> # Welcome to my shiny new web page! The date and time is # <%= Time.now %> # <% end %> # # ...and Builder (RXML) templates. - # + # # @timestamp = capture do # "The current timestamp is #{Time.now}." # end @@ -32,16 +32,18 @@ module CaptureHelper # def capture(*args) value = nil - buffer = with_output_buffer { value = yield *args } - buffer.presence || value + buffer = with_output_buffer { value = yield(*args) } + if string = buffer.presence || value and string.is_a?(String) + NonConcattingString.new(string) + end end # Calling content_for stores a block of markup in an identifier for later use. # You can make subsequent calls to the stored content in other templates or the layout # by passing the identifier as an argument to yield. - # + # # ==== Examples - # + # # <% content_for :not_authorized do %> # alert('You are not authorized to do that!') # <% end %> @@ -75,7 +77,7 @@ def capture(*args) # # Then, in another view, you could to do something like this: # - # <%= link_to_remote 'Logout', :action => 'logout' %> + # <%= link_to 'Logout', :action => 'logout', :remote => true %> # # <% content_for :script do %> # <%= javascript_include_tag :defaults %> @@ -92,7 +94,7 @@ def capture(*args) # <% end %> # # <%# Add some other content, or use a different template: %> - # + # # <% content_for :navigation do %> #
  • <%= link_to 'Login', :action => 'login' %>
  • # <% end %> @@ -109,13 +111,13 @@ def capture(*args) # for elements that will be fragment cached. def content_for(name, content = nil, &block) content = capture(&block) if block_given? - return @_content_for[name] << content if content - @_content_for[name] + @_content_for[name] << content if content + @_content_for[name] unless content end # content_for? simply checks whether any content has been captured yet using content_for # Useful to render parts of your layout differently based on what is in your views. - # + # # ==== Examples # # Perhaps you will use different css in you layout if no content_for :right_column @@ -140,7 +142,7 @@ def content_for?(name) def with_output_buffer(buf = nil) #:nodoc: unless buf buf = ActionView::OutputBuffer.new - buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding) + buf.force_encoding(output_buffer.encoding) if output_buffer && buf.respond_to?(:force_encoding) end self.output_buffer, old_buffer = buf, output_buffer yield diff --git a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb b/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb deleted file mode 100644 index 3d0657e873..0000000000 --- a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb +++ /dev/null @@ -1,52 +0,0 @@ -module ActionView - module Helpers - module DeprecatedBlockHelpers - extend ActiveSupport::Concern - - include ActionView::Helpers::TagHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::JavaScriptHelper - include ActionView::Helpers::FormHelper - - def content_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def javascript_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def form_for(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def form_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def fields_for(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def field_set_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' - - if RUBY_VERSION < '1.9.0' - # Check whether we're called from an erb template. - # We'd return a string in any other case, but erb <%= ... %> - # can't take an <% end %> later on, so we have to use <% ... %> - # and implicitly concat. - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block) - end - else - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block.binding) - end - end - end - end -end \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 48df26efaa..2ba5339b7d 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -28,7 +28,7 @@ module Helpers # # # Note: a @person variable will have been created in the controller. # # For example: @person = Person.new - # <% form_for :person, @person, :url => { :action => "create" } do |f| %> + # <%= form_for :person, @person, :url => { :action => "create" } do |f| %> # <%= f.text_field :first_name %> # <%= f.text_field :last_name %> # <%= submit_tag 'Create' %> @@ -44,7 +44,7 @@ module Helpers # # If you are using a partial for your form fields, you can use this shortcut: # - # <% form_for :person, @person, :url => { :action => "create" } do |f| %> + # <%= form_for :person, @person, :url => { :action => "create" } do |f| %> # <%= render :partial => f %> # <%= submit_tag 'Create' %> # <% end %> @@ -102,7 +102,7 @@ module FormHelper # Rails provides succinct resource-oriented form generation with +form_for+ # like this: # - # <% form_for @offer do |f| %> + # <%= form_for @offer do |f| %> # <%= f.label :version, 'Version' %>: # <%= f.text_field :version %>
    # <%= f.label :author, 'Author' %>: @@ -119,7 +119,7 @@ module FormHelper # The generic way to call +form_for+ yields a form builder around a # model: # - # <% form_for :person, :url => { :action => "update" } do |f| %> + # <%= form_for :person, :url => { :action => "update" } do |f| %> # <%= f.error_messages %> # First name: <%= f.text_field :first_name %>
    # Last name : <%= f.text_field :last_name %>
    @@ -143,7 +143,7 @@ module FormHelper # If the instance variable is not @person you can pass the actual # record as the second argument: # - # <% form_for :person, person, :url => { :action => "update" } do |f| %> + # <%= form_for :person, person, :url => { :action => "update" } do |f| %> # ... # <% end %> # @@ -175,7 +175,7 @@ module FormHelper # possible to use both the stand-alone FormHelper methods and methods # from FormTagHelper. For example: # - # <% form_for :person, @person, :url => { :action => "update" } do |f| %> + # <%= form_for :person, @person, :url => { :action => "update" } do |f| %> # First name: <%= f.text_field :first_name %> # Last name : <%= f.text_field :last_name %> # Biography : <%= text_area :person, :biography %> @@ -195,37 +195,37 @@ module FormHelper # # For example, if @post is an existing record you want to edit # - # <% form_for @post do |f| %> + # <%= form_for @post do |f| %> # ... # <% end %> # # is equivalent to something like: # - # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %> + # <%= form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %> # ... # <% end %> # # And for new records # - # <% form_for(Post.new) do |f| %> + # <%= form_for(Post.new) do |f| %> # ... # <% end %> # # expands to # - # <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> + # <%= form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> # ... # <% end %> # # You can also overwrite the individual conventions, like this: # - # <% form_for(@post, :url => super_post_path(@post)) do |f| %> + # <%= form_for(@post, :url => super_post_path(@post)) do |f| %> # ... # <% end %> # # And for namespaced routes, like +admin_post_url+: # - # <% form_for([:admin, @post]) do |f| %> + # <%= form_for([:admin, @post]) do |f| %> # ... # <% end %> # @@ -243,7 +243,7 @@ module FormHelper # # Example: # - # <% form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %> + # <%= form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %> # ... # <% end %> # @@ -263,7 +263,7 @@ module FormHelper # custom builder. For example, let's say you made a helper to # automatically add labels to form inputs. # - # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %> + # <%= form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %> # <%= f.text_field :first_name %> # <%= f.text_field :last_name %> # <%= text_area :person, :biography %> @@ -340,11 +340,11 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # # === Generic Examples # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # First name: <%= person_form.text_field :first_name %> # Last name : <%= person_form.text_field :last_name %> # - # <% fields_for @person.permission do |permission_fields| %> + # <%= fields_for @person.permission do |permission_fields| %> # Admin? : <%= permission_fields.check_box :admin %> # <% end %> # <% end %> @@ -352,13 +352,13 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # ...or if you have an object that needs to be represented as a different # parameter, like a Client that acts as a Person: # - # <% fields_for :person, @client do |permission_fields| %> + # <%= fields_for :person, @client do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # # ...or if you don't have an object, just a name of the parameter: # - # <% fields_for :person do |permission_fields| %> + # <%= fields_for :person do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # @@ -402,9 +402,9 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # # This model can now be used with a nested fields_for, like so: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :address do |address_fields| %> + # <%= person_form.fields_for :address do |address_fields| %> # Street : <%= address_fields.text_field :street %> # Zip code: <%= address_fields.text_field :zip_code %> # <% end %> @@ -427,15 +427,15 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # accepts_nested_attributes_for :address, :allow_destroy => true # end # - # Now, when you use a form element with the _delete parameter, + # Now, when you use a form element with the _destroy parameter, # with a value that evaluates to +true+, you will destroy the associated # model (eg. 1, '1', true, or 'true'): # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :address do |address_fields| %> + # <%= person_form.fields_for :address do |address_fields| %> # ... - # Delete: <%= address_fields.check_box :_delete %> + # Delete: <%= address_fields.check_box :_destroy %> # <% end %> # <% end %> # @@ -459,9 +459,9 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # the nested fields_for call will be repeated for each instance in the # collection: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :projects do |project_fields| %> + # <%= person_form.fields_for :projects do |project_fields| %> # <% if project_fields.object.active? %> # Name: <%= project_fields.text_field :name %> # <% end %> @@ -470,11 +470,11 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # # It's also possible to specify the instance to be used: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... # <% @person.projects.each do |project| %> # <% if project.active? %> - # <% person_form.fields_for :projects, project do |project_fields| %> + # <%= person_form.fields_for :projects, project do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> @@ -483,9 +483,9 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # # Or a collection to be used: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :projects, @active_projects do |project_fields| %> + # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> @@ -508,14 +508,14 @@ def apply_form_for_options!(object_or_array, options) #:nodoc: # end # # This will allow you to specify which models to destroy in the - # attributes hash by adding a form element for the _delete + # attributes hash by adding a form element for the _destroy # parameter with a value that evaluates to +true+ # (eg. 1, '1', true, or 'true'): # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :projects do |project_fields| %> - # Delete: <%= project_fields.check_box :_delete %> + # <%= person_form.fields_for :projects do |project_fields| %> + # Delete: <%= project_fields.check_box :_destroy %> # <% end %> # <% end %> def fields_for(record_or_name_or_array, *args, &block) @@ -725,7 +725,7 @@ def text_area(object_name, method, options = {}) # Unfortunately that workaround does not work when the check box goes # within an array-like parameter, as in # - # <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %> + # <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %> # <%= form.check_box :paid %> # ... # <% end %> @@ -1014,7 +1014,7 @@ class InstanceTag class FormBuilder #:nodoc: # The methods which wrap a form helper call. class_inheritable_accessor :field_helpers - self.field_helpers = (FormHelper.instance_methods - ['form_for']) + self.field_helpers = (FormHelper.instance_method_names - ['form_for']) attr_accessor :object_name, :object, :options @@ -1040,7 +1040,7 @@ def initialize(object_name, object, template, options, proc) end (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector| - src = <<-end_src + src, file, line = <<-end_src, __FILE__, __LINE__ + 1 def #{selector}(method, options = {}) # def text_field(method, options = {}) @template.send( # @template.send( #{selector.inspect}, # "text_field", @@ -1049,7 +1049,7 @@ def #{selector}(method, options = {}) # def text_field(method, options = {}) objectify_options(options)) # objectify_options(options)) end # end end_src - class_eval src, __FILE__, __LINE__ + class_eval src, file, line end def fields_for(record_or_name_or_array, *args, &block) @@ -1115,7 +1115,7 @@ def error_messages(options = {}) # Add the submit button for the given form. When no value is given, it checks # if the object is a new resource or not to create the proper label: # - # <% form_for @post do |f| %> + # <%= form_for @post do |f| %> # <%= f.submit %> # <% end %> # diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 7039ecd233..4c523d4b20 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -151,7 +151,7 @@ def select(object, method, choices, options = {}, html_options = {}) # end # # Sample usage (selecting the associated Author for an instance of Post, @post): - # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true}) + # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true) # # If @post.author_id is already 1, this would return: # # - # <% form_tag('/posts', :remote => true) %> + # <%= form_tag('/posts', :remote => true) %> # # =>
    # def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block) @@ -430,17 +430,17 @@ def image_submit_tag(source, options = {}) # options accept the same values as tag. # # ==== Examples - # <% field_set_tag do %> + # <%= field_set_tag do %> #

    <%= text_field_tag 'name' %>

    # <% end %> # # =>

    # - # <% field_set_tag 'Your details' do %> + # <%= field_set_tag 'Your details' do %> #

    <%= text_field_tag 'name' %>

    # <% end %> # # =>
    Your details

    # - # <% field_set_tag nil, :class => 'format' do %> + # <%= field_set_tag nil, :class => 'format' do %> #

    <%= text_field_tag 'name' %>

    # <% end %> # # =>

    diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 8dab3094dd..b0a7718f22 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -1,5 +1,4 @@ require 'action_view/helpers/tag_helper' -require 'action_view/helpers/prototype_helper' module ActionView module Helpers @@ -71,7 +70,7 @@ def escape_javascript(javascript) # # Instead of passing the content as an argument, you can also use a block # in which case, you pass your +html_options+ as the first parameter. - # <% javascript_tag :defer => 'defer' do -%> + # <%= javascript_tag :defer => 'defer' do -%> # alert('All is good') # <% end -%> def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block) @@ -89,6 +88,93 @@ def javascript_tag(content_or_options_with_block = nil, html_options = {}, &bloc def javascript_cdata_section(content) #:nodoc: "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe end + + # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the + # onclick handler. + # + # The first argument +name+ is used as the button's value or display text. + # + # The next arguments are optional and may include the javascript function definition and a hash of html_options. + # + # The +function+ argument can be omitted in favor of an +update_page+ + # block, which evaluates to a string when the template is rendered + # (instead of making an Ajax request first). + # + # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" + # + # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil + # + # Examples: + # button_to_function "Greeting", "alert('Hello world!')" + # button_to_function "Delete", "if (confirm('Really?')) do_delete()" + # button_to_function "Details" do |page| + # page[:details].visual_effect :toggle_slide + # end + # button_to_function "Details", :class => "details_button" do |page| + # page[:details].visual_effect :toggle_slide + # end + def button_to_function(name, *args, &block) + html_options = args.extract_options!.symbolize_keys + + function = block_given? ? update_page(&block) : args[0] || '' + onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" + + tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) + end + + # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the + # onclick handler and return false after the fact. + # + # The first argument +name+ is used as the link text. + # + # The next arguments are optional and may include the javascript function definition and a hash of html_options. + # + # The +function+ argument can be omitted in favor of an +update_page+ + # block, which evaluates to a string when the template is rendered + # (instead of making an Ajax request first). + # + # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" + # + # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil + # + # + # Examples: + # link_to_function "Greeting", "alert('Hello world!')" + # Produces: + # Greeting + # + # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()") + # Produces: + # + # Delete + # + # + # link_to_function("Show me more", nil, :id => "more_link") do |page| + # page[:details].visual_effect :toggle_blind + # page[:more_link].replace_html "Show me less" + # end + # Produces: + # Show me more + # + def link_to_function(name, *args, &block) + html_options = args.extract_options!.symbolize_keys + + function = block_given? ? update_page(&block) : args[0] || '' + onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;" + href = html_options[:href] || '#' + + content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) + end end end end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 46e41bc406..719b64b940 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -3,10 +3,24 @@ module ActionView module Helpers #:nodoc: + # Provides methods for converting numbers into formatted strings. # Methods are provided for phone numbers, currency, percentage, - # precision, positional notation, and file size. + # precision, positional notation, file size and pretty printing. + # + # Most methods expect a +number+ argument, and will return it + # unchanged if can't be converted into a valid number. module NumberHelper + + # Raised when argument +number+ param given to the helpers is invalid and + # the option :raise is set to +true+. + class InvalidNumberError < StandardError + attr_accessor :number + def initialize(number) + @number = number + end + end + # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format # in the +options+ hash. # @@ -30,6 +44,17 @@ module NumberHelper def number_to_phone(number, options = {}) return nil if number.nil? + begin + Float(number) + is_number_html_safe = true + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + is_number_html_safe = number.to_s.html_safe? + end + end + number = number.to_s.strip options = options.symbolize_keys area_code = options[:area_code] || nil @@ -46,7 +71,7 @@ def number_to_phone(number, options = {}) number.starts_with?('-') ? number.slice!(1..-1) : number end str << " x #{extension}" unless extension.blank? - str + is_number_html_safe ? str.html_safe : str end # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format @@ -72,38 +97,42 @@ def number_to_phone(number, options = {}) # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") # # => 1234567890,50 £ def number_to_currency(number, options = {}) + return nil if number.nil? + options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {} + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(currency) - precision = options[:precision] || defaults[:precision] - unit = options[:unit] || defaults[:unit] - separator = options[:separator] || defaults[:separator] - delimiter = options[:delimiter] || defaults[:delimiter] - format = options[:format] || defaults[:format] - separator = '' if precision == 0 + options = options.reverse_merge(defaults) - value = number_with_precision(number, - :precision => precision, - :delimiter => delimiter, - :separator => separator) + unit = options.delete(:unit) + format = options.delete(:format) - if value + begin + value = number_with_precision(number, options.merge(:raise => true)) format.gsub(/%n/, value).gsub(/%u/, unit).html_safe - else - number + rescue InvalidNumberError => e + if options[:raise] + raise + else + formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit) + e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number + end end + end # Formats a +number+ as a percentage string (e.g., 65%). You can customize the # format in the +options+ hash. # # ==== Options - # * :precision - Sets the level of precision (defaults to 3). - # * :separator - Sets the separator between the units (defaults to "."). + # * :precision - Sets the precision of the number (defaults to 3). + # * :significant - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+) + # * :separator - Sets the separator between the fractional and integer digits (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults to ""). + # * :strip_insignificant_zeros - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+) # # ==== Examples # number_to_percentage(100) # => 100.000% @@ -111,21 +140,25 @@ def number_to_currency(number, options = {}) # number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000% # number_to_percentage(302.24398923423, :precision => 5) # => 302.24399% def number_to_percentage(number, options = {}) + return nil if number.nil? + options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {} + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(percentage) - precision = options[:precision] || defaults[:precision] - separator = options[:separator] || defaults[:separator] - delimiter = options[:delimiter] || defaults[:delimiter] + options = options.reverse_merge(defaults) - value = number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter) - value ? value + "%" : number + begin + "#{number_with_precision(number, options.merge(:raise => true))}%".html_safe + rescue InvalidNumberError => e + if options[:raise] + raise + else + e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%" + end + end end # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can @@ -133,7 +166,7 @@ def number_to_percentage(number, options = {}) # # ==== Options # * :delimiter - Sets the thousands delimiter (defaults to ","). - # * :separator - Sets the separator between the units (defaults to "."). + # * :separator - Sets the separator between the fractional and integer digits (defaults to "."). # # ==== Examples # number_with_delimiter(12345678) # => 12,345,678 @@ -146,148 +179,186 @@ def number_to_percentage(number, options = {}) # You can still use number_with_delimiter with the old API that accepts the # +delimiter+ as its optional second and the +separator+ as its # optional third parameter: - # number_with_delimiter(12345678, " ") # => 12 345.678 + # number_with_delimiter(12345678, " ") # => 12 345 678 # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05 def number_with_delimiter(number, *args) options = args.extract_options! options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} + begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end + end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) unless args.empty? ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' + 'instead of separate delimiter and precision arguments.', caller) - delimiter = args[0] || defaults[:delimiter] - separator = args[1] || defaults[:separator] + options[:delimiter] ||= args[0] if args[0] + options[:separator] ||= args[1] if args[1] end - delimiter ||= (options[:delimiter] || defaults[:delimiter]) - separator ||= (options[:separator] || defaults[:separator]) + options = options.reverse_merge(defaults) parts = number.to_s.split('.') - if parts[0] - parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") - parts.join(separator) - else - number - end + parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") + parts.join(options[:separator]).html_safe + end - # Formats a +number+ with the specified level of :precision (e.g., 112.32 has a precision of 2). + # Formats a +number+ with the specified level of :precision (e.g., 112.32 has a precision + # of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+). # You can customize the format in the +options+ hash. # # ==== Options - # * :precision - Sets the level of precision (defaults to 3). - # * :separator - Sets the separator between the units (defaults to "."). + # * :precision - Sets the precision of the number (defaults to 3). + # * :significant - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+) + # * :separator - Sets the separator between the fractional and integer digits (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults to ""). + # * :strip_insignificant_zeros - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+) # # ==== Examples - # number_with_precision(111.2345) # => 111.235 - # number_with_precision(111.2345, :precision => 2) # => 111.23 - # number_with_precision(13, :precision => 5) # => 13.00000 - # number_with_precision(389.32314, :precision => 0) # => 389 + # number_with_precision(111.2345) # => 111.235 + # number_with_precision(111.2345, :precision => 2) # => 111.23 + # number_with_precision(13, :precision => 5) # => 13.00000 + # number_with_precision(389.32314, :precision => 0) # => 389 + # number_with_precision(111.2345, :significant => true) # => 111 + # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100 + # number_with_precision(13, :precision => 5, :significant => true) # => 13.000 + # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true) + # # => 13 + # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3 # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.') # # => 1.111,23 # # You can still use number_with_precision with the old API that accepts the # +precision+ as its optional second parameter: - # number_with_precision(number_with_precision(111.2345, 2) # => 111.23 + # number_with_precision(111.2345, 2) # => 111.23 def number_with_precision(number, *args) + options = args.extract_options! options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], - :raise => true) rescue {} + number = begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end + end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(precision_defaults) + #Backwards compatibility unless args.empty? ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' + 'instead of a separate precision argument.', caller) - precision = args[0] || defaults[:precision] + options[:precision] ||= args[0] if args[0] end - precision ||= (options[:precision] || defaults[:precision]) - separator ||= (options[:separator] || defaults[:separator]) - delimiter ||= (options[:delimiter] || defaults[:delimiter]) + options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false + precision = options.delete :precision + significant = options.delete :significant + strip_insignificant_zeros = options.delete :strip_insignificant_zeros - begin - value = Float(number) - rescue ArgumentError, TypeError - value = nil - end - - if value - rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision - number_with_delimiter("%01.#{precision}f" % rounded_number, - :separator => separator, - :delimiter => delimiter) + if significant and precision > 0 + digits = (Math.log10(number) + 1).floor + rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision) + precision = precision - digits + precision = precision > 0 ? precision : 0 #don't let it be negative else - number + rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision end + formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options) + if strip_insignificant_zeros + escaped_separator = Regexp.escape(options[:separator]) + formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe + else + formatted_number + end + end STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze - # Formats the bytes in +size+ into a more understandable representation + # Formats the bytes in +number+ into a more understandable representation # (e.g., giving it 1500 yields 1.5 KB). This method is useful for - # reporting file sizes to users. This method returns nil if - # +size+ cannot be converted into a number. You can customize the + # reporting file sizes to users. You can customize the # format in the +options+ hash. # - # ==== Options - # * :precision - Sets the level of precision (defaults to 1). - # * :separator - Sets the separator between the units (defaults to "."). - # * :delimiter - Sets the thousands delimiter (defaults to ""). + # See number_to_human if you want to pretty-print a generic number. # + # ==== Options + # * :precision - Sets the precision of the number (defaults to 3). + # * :significant - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+) + # * :separator - Sets the separator between the fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults to ""). + # * :strip_insignificant_zeros - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+) # ==== Examples # number_to_human_size(123) # => 123 Bytes - # number_to_human_size(1234) # => 1.2 KB + # number_to_human_size(1234) # => 1.21 KB # number_to_human_size(12345) # => 12.1 KB - # number_to_human_size(1234567) # => 1.2 MB - # number_to_human_size(1234567890) # => 1.1 GB - # number_to_human_size(1234567890123) # => 1.1 TB - # number_to_human_size(1234567, :precision => 2) # => 1.18 MB - # number_to_human_size(483989, :precision => 0) # => 473 KB - # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB + # number_to_human_size(1234567) # => 1.18 MB + # number_to_human_size(1234567890) # => 1.15 GB + # number_to_human_size(1234567890123) # => 1.12 TB + # number_to_human_size(1234567, :precision => 2) # => 1.2 MB + # number_to_human_size(483989, :precision => 2) # => 470 KB + # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB # - # Zeros after the decimal point are always stripped out, regardless of the - # specified precision: - # helper.number_to_human_size(1234567890123, :precision => 5) # => "1.12283 TB" - # helper.number_to_human_size(524288000, :precision=>5) # => "500 MB" + # Unsignificant zeros after the fractional separator are stripped out by default (set + # :strip_insignificant_zeros to +false+ to change that): + # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB" + # number_to_human_size(524288000, :precision=>5) # => "500 MB" # # You can still use number_to_human_size with the old API that accepts the # +precision+ as its optional second parameter: - # number_to_human_size(1234567, 2) # => 1.18 MB - # number_to_human_size(483989, 0) # => 473 KB + # number_to_human_size(1234567, 1) # => 1 MB + # number_to_human_size(483989, 2) # => 470 KB def number_to_human_size(number, *args) - return nil if number.nil? - options = args.extract_options! options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {} + number = begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end + end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(human) unless args.empty? ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' + 'instead of a separate precision argument.', caller) - precision = args[0] || defaults[:precision] + options[:precision] ||= args[0] if args[0] end - precision ||= (options[:precision] || defaults[:precision]) - separator ||= (options[:separator] || defaults[:separator]) - delimiter ||= (options[:delimiter] || defaults[:delimiter]) + options = options.reverse_merge(defaults) + #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true) if number.to_i < 1024 unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true) - storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit) + storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe else max_exp = STORAGE_UNITS.size - 1 - number = Float(number) exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024 exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit number /= 1024 ** exponent @@ -295,15 +366,138 @@ def number_to_human_size(number, *args) unit_key = STORAGE_UNITS[exponent] unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true) - escaped_separator = Regexp.escape(separator) - formatted_number = number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter - ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') - storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) + formatted_number = number_with_precision(number, options) + storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe end end + + DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, + -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze + + # Pretty prints (formats and approximates) a number in a way it is more readable by humans + # (eg.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that + # can get very large (and too hard to read). + # + # See number_to_human_size if you want to print a file size. + # + # You can also define you own unit-quantifier names if you want to use other decimal units + # (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 mililiters", etc). You may define + # a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc). + # + # ==== Options + # * :precision - Sets the precision of the number (defaults to 3). + # * :significant - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+) + # * :separator - Sets the separator between the fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults to ""). + # * :strip_insignificant_zeros - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+) + # * :units - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys: + # * *integers*: :unit, :ten, :hundred, :thousand, :million, :billion, :trillion, :quadrillion + # * *fractionals*: :deci, :centi, :mili, :micro, :nano, :pico, :femto + # * :format - Sets the format of the output string (defaults to "%n %u"). The field types are: + # + # %u The quantifier (ex.: 'thousand') + # %n The number + # + # ==== Examples + # number_to_human(123) # => "123" + # number_to_human(1234) # => "1.23 Thousand" + # number_to_human(12345) # => "12.3 Thousand" + # number_to_human(1234567) # => "1.23 Million" + # number_to_human(1234567890) # => "1.23 Billion" + # number_to_human(1234567890123) # => "1.23 Trillion" + # number_to_human(1234567890123456) # => "1.23 Quadrillion" + # number_to_human(1234567890123456789) # => "1230 Quadrillion" + # number_to_human(489939, :precision => 2) # => "490 Thousand" + # number_to_human(489939, :precision => 4) # => "489.9 Thousand" + # number_to_human(1234567, :precision => 4, + # :significant => false) # => "1.2346 Million" + # number_to_human(1234567, :precision => 1, + # :separator => ',', + # :significant => false) # => "1,2 Million" + # + # Unsignificant zeros after the decimal separator are stripped out by default (set + # :strip_insignificant_zeros to +false+ to change that): + # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion" + # number_to_human(500000000, :precision=>5) # => "500 Million" + # + # ==== Custom Unit Quantifiers + # + # You can also use your own custom unit quantifiers: + # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt" + # + # If in your I18n locale you have: + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # billion: "gazilion-distance" + # + # Then you could do: + # + # number_to_human(543934, :units => :distance) # => "544 kilometers" + # number_to_human(54393498, :units => :distance) # => "54400 kilometers" + # number_to_human(54393498000, :units => :distance) # => "54.4 gazilion-distance" + # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters" + # number_to_human(1, :units => :distance) # => "1 meter" + # number_to_human(0.34, :units => :distance) # => "34 centimeters" + # + def number_to_human(number, options = {}) + options.symbolize_keys! + + number = begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end + end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {}) + defaults = defaults.merge(human) + + options = options.reverse_merge(defaults) + #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) + + units = options.delete :units + unit_exponents = case units + when Hash + units + when String, Symbol + I18n.translate(:"#{units}", :locale => options[:locale], :raise => true) + when nil + I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true) + else + raise ArgumentError, ":units must be a Hash or String translation scope." + end.keys.map{|e_name| DECIMAL_UNITS.invert[e_name] }.sort_by{|e| -e} + + number_exponent = Math.log10(number).floor + display_exponent = unit_exponents.find{|e| number_exponent >= e } + number /= 10 ** display_exponent + + unit = case units + when Hash + units[DECIMAL_UNITS[display_exponent]] + when String, Symbol + I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) + else + I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) + end + + decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u") + formatted_number = number_with_precision(number, options) + decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe + end + end end end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index be49b5cc28..ccdc8181db 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -35,7 +35,7 @@ module Helpers # # ...through a form... # - # <% form_remote_tag :url => '/shipping' do -%> + # <%= form_remote_tag :url => '/shipping' do -%> #
    <%= submit_tag 'Recalculate Shipping' %>
    # <% end -%> # @@ -102,39 +102,6 @@ module PrototypeHelper :form, :with, :update, :script, :type ]).merge(CALLBACKS) end - # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the - # onclick handler. - # - # The first argument +name+ is used as the button's value or display text. - # - # The next arguments are optional and may include the javascript function definition and a hash of html_options. - # - # The +function+ argument can be omitted in favor of an +update_page+ - # block, which evaluates to a string when the template is rendered - # (instead of making an Ajax request first). - # - # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" - # - # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil - # - # Examples: - # button_to_function "Greeting", "alert('Hello world!')" - # button_to_function "Delete", "if (confirm('Really?')) do_delete()" - # button_to_function "Details" do |page| - # page[:details].visual_effect :toggle_slide - # end - # button_to_function "Details", :class => "details_button" do |page| - # page[:details].visual_effect :toggle_slide - # end - def button_to_function(name, *args, &block) - html_options = args.extract_options!.symbolize_keys - - function = block_given? ? update_page(&block) : args[0] || '' - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" - - tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) - end - # Returns the JavaScript needed for a remote function. # Takes the same arguments as link_to_remote. # @@ -180,13 +147,10 @@ def remote_function(options) # #include_helpers_from_context has nothing to overwrite. class JavaScriptGenerator #:nodoc: def initialize(context, &block) #:nodoc: - context._evaluate_assigns_and_ivars @context, @lines = context, [] - @context.update_details(:formats => [:js, :html]) do - include_helpers_from_context - @context.with_output_buffer(@lines) do - @context.instance_exec(self, &block) - end + include_helpers_from_context + @context.with_output_buffer(@lines) do + @context.instance_exec(self, &block) end end @@ -616,7 +580,7 @@ def method_missing(method, *arguments) # page.hide 'spinner' # end def update_page(&block) - JavaScriptGenerator.new(@template, &block).to_s.html_safe + JavaScriptGenerator.new(view_context, &block).to_s.html_safe end # Works like update_page but wraps the generated JavaScript in a ), @@ -451,7 +453,7 @@ def test_caching_javascript_include_tag_when_caching_on def test_caching_javascript_include_tag_when_caching_on_with_proc_asset_host ENV['RAILS_ASSET_ID'] = '' @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } - ActionController::Base.perform_caching = true + config.perform_caching = true assert_equal '/javascripts/scripts.js'.length, 23 assert_dom_equal( @@ -474,7 +476,7 @@ def test_caching_javascript_include_tag_when_caching_on_with_2_argument_proc_ass "#{request.protocol}assets#{source.length}.example.com" end } - ActionController::Base.perform_caching = true + config.perform_caching = true assert_equal '/javascripts/vanilla.js'.length, 23 assert_dom_equal( @@ -514,7 +516,7 @@ def call(source, request) end end.new - ActionController::Base.perform_caching = true + config.perform_caching = true assert_equal '/javascripts/vanilla.js'.length, 23 assert_dom_equal( @@ -545,7 +547,7 @@ def ssl?() true end def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory ENV["RAILS_ASSET_ID"] = "" @controller.config.asset_host = 'http://a%d.example.com' - ActionController::Base.perform_caching = true + config.perform_caching = true hash = '/javascripts/cache/money.js'.hash % 4 assert_dom_equal( @@ -561,7 +563,7 @@ def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file ENV["RAILS_ASSET_ID"] = "" @controller.config.asset_host = 'http://a0.example.com' - ActionController::Base.perform_caching = true + config.perform_caching = true assert_dom_equal( %(), @@ -582,7 +584,7 @@ def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_ def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_the_file ENV["RAILS_ASSET_ID"] = "" @controller.config.asset_host = 'http://a0.example.com' - ActionController::Base.perform_caching = true + config.perform_caching = true assert_dom_equal( %(), @@ -603,7 +605,7 @@ def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_t def test_caching_javascript_include_tag_with_relative_url_root ENV["RAILS_ASSET_ID"] = "" @controller.config.relative_url_root = "/collaboration/hieraki" - ActionController::Base.perform_caching = true + config.perform_caching = true assert_dom_equal( %(), @@ -626,7 +628,7 @@ def test_caching_javascript_include_tag_with_relative_url_root def test_caching_javascript_include_tag_when_caching_off ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.perform_caching = false + config.perform_caching = false assert_dom_equal( %(\n\n\n\n\n\n\n), @@ -655,7 +657,7 @@ def test_caching_javascript_include_tag_when_caching_off def test_caching_javascript_include_tag_when_caching_on_and_missing_javascript_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.perform_caching = true + config.perform_caching = true assert_raise(Errno::ENOENT) { javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => true) @@ -672,7 +674,7 @@ def test_caching_javascript_include_tag_when_caching_on_and_missing_javascript_f def test_caching_javascript_include_tag_when_caching_off_and_missing_javascript_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.perform_caching = false + config.perform_caching = false assert_raise(Errno::ENOENT) { javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => true) @@ -690,7 +692,7 @@ def test_caching_javascript_include_tag_when_caching_off_and_missing_javascript_ def test_caching_stylesheet_link_tag_when_caching_on ENV["RAILS_ASSET_ID"] = "" @controller.config.asset_host = 'http://a0.example.com' - ActionController::Base.perform_caching = true + config.perform_caching = true assert_dom_equal( %(), @@ -757,7 +759,7 @@ def test_concat_stylesheet_link_tag_when_caching_off def test_caching_stylesheet_link_tag_when_caching_on_and_missing_css_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.perform_caching = true + config.perform_caching = true assert_raise(Errno::ENOENT) { stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => true) @@ -778,7 +780,7 @@ def test_caching_stylesheet_link_tag_when_caching_on_and_missing_css_file def test_caching_stylesheet_link_tag_when_caching_off_and_missing_css_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.perform_caching = false + config.perform_caching = false assert_raise(Errno::ENOENT) { stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => true) @@ -800,7 +802,7 @@ def test_caching_stylesheet_link_tag_when_caching_off_and_missing_css_file def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host ENV["RAILS_ASSET_ID"] = "" @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } - ActionController::Base.perform_caching = true + config.perform_caching = true assert_equal '/stylesheets/styles.css'.length, 23 assert_dom_equal( @@ -817,7 +819,7 @@ def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host def test_caching_stylesheet_link_tag_with_relative_url_root ENV["RAILS_ASSET_ID"] = "" @controller.config.relative_url_root = "/collaboration/hieraki" - ActionController::Base.perform_caching = true + config.perform_caching = true assert_dom_equal( %(), @@ -842,7 +844,7 @@ def test_caching_stylesheet_link_tag_with_relative_url_root def test_caching_stylesheet_include_tag_when_caching_off ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.perform_caching = false + config.perform_caching = false assert_dom_equal( %(\n\n), diff --git a/actionpack/test/template/body_parts_test.rb b/actionpack/test/template/body_parts_test.rb index defe85107e..69cf684083 100644 --- a/actionpack/test/template/body_parts_test.rb +++ b/actionpack/test/template/body_parts_test.rb @@ -8,9 +8,8 @@ def response_body() "" end def index RENDERINGS.each do |rendering| - @template.punctuate_body! rendering + view_context.punctuate_body! rendering end - @performed_render = true end end diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index 2017a18806..bf541c17d3 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -3,13 +3,119 @@ class CaptureHelperTest < ActionView::TestCase def setup super + @av = ActionView::Base.new @_content_for = Hash.new {|h,k| h[k] = "" } end + def test_capture_captures_the_temporary_output_buffer_in_its_block + assert_nil @av.output_buffer + string = @av.capture do + @av.output_buffer << 'foo' + @av.output_buffer << 'bar' + end + assert_nil @av.output_buffer + assert_equal 'foobar', string + assert_kind_of ActionView::NonConcattingString, string + end + + def test_capture_captures_the_value_returned_by_the_block_if_the_temporary_buffer_is_blank + string = @av.capture('foo', 'bar') do |a, b| + a + b + end + assert_equal 'foobar', string + assert_kind_of ActionView::NonConcattingString, string + end + + def test_capture_returns_nil_if_the_returned_value_is_not_a_string + assert_nil @av.capture { 1 } + end + def test_content_for assert ! content_for?(:title) content_for :title, 'title' assert content_for?(:title) assert ! content_for?(:something_else) end + + def test_with_output_buffer_swaps_the_output_buffer_given_no_argument + assert_nil @av.output_buffer + buffer = @av.with_output_buffer do + @av.output_buffer << '.' + end + assert_equal '.', buffer + assert_nil @av.output_buffer + end + + def test_with_output_buffer_swaps_the_output_buffer_with_an_argument + assert_nil @av.output_buffer + buffer = ActionView::OutputBuffer.new('.') + @av.with_output_buffer(buffer) do + @av.output_buffer << '.' + end + assert_equal '..', buffer + assert_nil @av.output_buffer + end + + def test_with_output_buffer_restores_the_output_buffer + buffer = ActionView::OutputBuffer.new + @av.output_buffer = buffer + @av.with_output_buffer do + @av.output_buffer << '.' + end + assert buffer.equal?(@av.output_buffer) + end + + unless RUBY_VERSION < '1.9' + def test_with_output_buffer_sets_proper_encoding + @av.output_buffer = ActionView::OutputBuffer.new + + # Ensure we set the output buffer to an encoding different than the default one. + alt_encoding = alt_encoding(@av.output_buffer) + @av.output_buffer.force_encoding(alt_encoding) + + @av.with_output_buffer do + assert alt_encoding, @av.output_buffer.encoding + end + end + end + + def test_with_output_buffer_does_not_assume_there_is_an_output_buffer + assert_nil @av.output_buffer + assert_equal "", @av.with_output_buffer {} + end + + def test_flush_output_buffer_concats_output_buffer_to_response + view = view_with_controller + assert_equal [], view.response.body_parts + + view.output_buffer << 'OMG' + view.flush_output_buffer + assert_equal ['OMG'], view.response.body_parts + assert_equal '', view.output_buffer + + view.output_buffer << 'foobar' + view.flush_output_buffer + assert_equal ['OMG', 'foobar'], view.response.body_parts + assert_equal '', view.output_buffer + end + + unless RUBY_VERSION < '1.9' + def test_flush_output_buffer_preserves_the_encoding_of_the_output_buffer + view = view_with_controller + alt_encoding = alt_encoding(view.output_buffer) + view.output_buffer.force_encoding(alt_encoding) + flush_output_buffer + assert_equal alt_encoding, view.output_buffer.encoding + end + end + + def alt_encoding(output_buffer) + output_buffer.encoding == Encoding::US_ASCII ? Encoding::UTF_8 : Encoding::US_ASCII + end + + def view_with_controller + returning(TestController.new.view_context) do |view| + view.output_buffer = ActionView::OutputBuffer.new + end + end end diff --git a/actionpack/test/template/erb/form_for_test.rb b/actionpack/test/template/erb/form_for_test.rb new file mode 100644 index 0000000000..482dbb0287 --- /dev/null +++ b/actionpack/test/template/erb/form_for_test.rb @@ -0,0 +1,11 @@ +require "abstract_unit" +require "template/erb/helper" + +module ERBTest + class TagHelperTest < BlockTestCase + test "form_for works" do + output = render_content "form_for(:staticpage, :url => {:controller => 'blah', :action => 'update'})", "" + assert_equal "
    ", output + end + end +end \ No newline at end of file diff --git a/actionpack/test/template/erb/helper.rb b/actionpack/test/template/erb/helper.rb new file mode 100644 index 0000000000..7147178849 --- /dev/null +++ b/actionpack/test/template/erb/helper.rb @@ -0,0 +1,30 @@ +module ERBTest + class ViewContext + mock_controller = Class.new do + include SharedTestRoutes.url_helpers + end + + include ActionView::Helpers::TagHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::FormHelper + + attr_accessor :output_buffer + + def protect_against_forgery?() false end + + define_method(:controller) do + mock_controller.new + end + end + + class BlockTestCase < ActiveSupport::TestCase + def render_content(start, inside) + template = block_helper(start, inside) + ActionView::Template::Handlers::Erubis.new(template).evaluate(ViewContext.new) + end + + def block_helper(str, rest) + "<%= #{str} do %>#{rest}<% end %>" + end + end +end \ No newline at end of file diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb index b91539ef0b..64a88bde1d 100644 --- a/actionpack/test/template/erb/tag_helper_test.rb +++ b/actionpack/test/template/erb/tag_helper_test.rb @@ -1,66 +1,44 @@ require "abstract_unit" +require "template/erb/helper" module ERBTest - class ViewContext - mock_controller = Class.new do - include SharedTestRoutes.url_helpers - end - - include ActionView::Helpers::TagHelper - include ActionView::Helpers::JavaScriptHelper - include ActionView::Helpers::FormHelper - - attr_accessor :output_buffer - - def protect_against_forgery?() false end - - define_method(:controller) do - mock_controller.new - end - end - - class DeprecatedViewContext < ViewContext - include ActionView::Helpers::DeprecatedBlockHelpers - end - module SharedTagHelpers extend ActiveSupport::Testing::Declarative - def render_content(start, inside) - template = block_helper(start, inside) - ActionView::Template::Handlers::Erubis.new(template).evaluate(context.new) + def maybe_deprecated + if @deprecated + assert_deprecated { yield } + else + yield + end end - test "percent equals works for content_tag" do - assert_equal "
    Hello world
    ", render_content("content_tag(:div)", "Hello world") + test "percent equals works for content_tag and does not require parenthesis on method call" do + maybe_deprecated { assert_equal "
    Hello world
    ", render_content("content_tag :div", "Hello world") } end test "percent equals works for javascript_tag" do expected_output = "" - assert_equal expected_output, render_content("javascript_tag", "alert('Hello')") + maybe_deprecated { assert_equal expected_output, render_content("javascript_tag", "alert('Hello')") } end test "percent equals works for javascript_tag with options" do expected_output = "" - assert_equal expected_output, render_content("javascript_tag(:id => 'the_js_tag')", "alert('Hello')") + maybe_deprecated { assert_equal expected_output, render_content("javascript_tag(:id => 'the_js_tag')", "alert('Hello')") } end test "percent equals works with form tags" do expected_output = "
    hello
    " - assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") + maybe_deprecated { assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") } end test "percent equals works with fieldset tags" do expected_output = "
    foohello
    " - assert_equal expected_output, render_content("field_set_tag('foo')", "<%= 'hello' %>") + maybe_deprecated { assert_equal expected_output, render_content("field_set_tag('foo')", "<%= 'hello' %>") } end end - class TagHelperTest < ActiveSupport::TestCase - def context - ViewContext - end - + class TagHelperTest < BlockTestCase def block_helper(str, rest) "<%= #{str} do %>#{rest}<% end %>" end @@ -68,15 +46,15 @@ def block_helper(str, rest) include SharedTagHelpers end - class DeprecatedTagHelperTest < ActiveSupport::TestCase - def context - DeprecatedViewContext - end - + class DeprecatedTagHelperTest < BlockTestCase def block_helper(str, rest) "<% __in_erb_template=true %><% #{str} do %>#{rest}<% end %>" end + def setup + @deprecated = true + end + include SharedTagHelpers end end \ No newline at end of file diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index f49b763881..c5c2a6b952 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -17,7 +17,7 @@ def setup ActiveSupport.escape_html_entities_in_json = true @template = self end - + def teardown ActiveSupport.escape_html_entities_in_json = false end @@ -60,6 +60,35 @@ def test_button_to_function_without_function button_to_function("Greeting") end + def test_link_to_function + assert_dom_equal %(Greeting), + link_to_function("Greeting", "alert('Hello world!')") + end + + def test_link_to_function_with_existing_onclick + assert_dom_equal %(Greeting), + link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')") + end + + def test_link_to_function_with_rjs_block + html = link_to_function( "Greet me!" ) do |page| + page.replace_html 'header', "

    Greetings

    " + end + assert_dom_equal %(Greet me!), html + end + + def test_link_to_function_with_rjs_block_and_options + html = link_to_function( "Greet me!", :class => "updater" ) do |page| + page.replace_html 'header', "

    Greetings

    " + end + assert_dom_equal %(Greet me!), html + end + + def test_link_to_function_with_href + assert_dom_equal %(Greeting), + link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') + end + def test_javascript_tag self.output_buffer = 'foo' diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb new file mode 100644 index 0000000000..df1aa2edb2 --- /dev/null +++ b/actionpack/test/template/lookup_context_test.rb @@ -0,0 +1,178 @@ +require "abstract_unit" +require "abstract_controller/rendering" + +ActionView::LookupContext::DetailsKey.class_eval do + def self.details_keys + @details_keys + end +end + +class LookupContextTest < ActiveSupport::TestCase + def setup + @lookup_context = ActionView::LookupContext.new(FIXTURE_LOAD_PATH, {}) + end + + def teardown + I18n.locale = :en + ActionView::LookupContext::DetailsKey.details_keys.clear + end + + test "process view paths on initialization" do + assert_kind_of ActionView::PathSet, @lookup_context.view_paths + end + + test "normalizes details on initialization" do + assert_equal Mime::SET, @lookup_context.formats + assert_equal :en, @lookup_context.locale + end + + test "allows me to update details" do + @lookup_context.update_details(:formats => [:html], :locale => :pt) + assert_equal [:html], @lookup_context.formats + assert_equal :pt, @lookup_context.locale + end + + test "allows me to update an specific detail" do + @lookup_context.update_details(:locale => :pt) + assert_equal :pt, I18n.locale + assert_equal :pt, @lookup_context.locale + end + + test "allows me to freeze and retrieve frozen formats" do + @lookup_context.formats.freeze + assert @lookup_context.formats.frozen? + end + + test "allows me to change some details to execute an specific block of code" do + formats = Mime::SET + @lookup_context.update_details(:locale => :pt) do + assert_equal formats, @lookup_context.formats + assert_equal :pt, @lookup_context.locale + end + assert_equal formats, @lookup_context.formats + assert_equal :en, @lookup_context.locale + end + + test "provides getters and setters for formats" do + @lookup_context.formats = :html + assert_equal [:html], @lookup_context.formats + end + + test "handles */* formats" do + @lookup_context.formats = [:"*/*"] + assert_equal Mime::SET, @lookup_context.formats + end + + test "adds :html fallback to :js formats" do + @lookup_context.formats = [:js] + assert_equal [:js, :html], @lookup_context.formats + end + + test "provides getters and setters for locale" do + @lookup_context.locale = :pt + assert_equal :pt, @lookup_context.locale + end + + test "changing lookup_context locale, changes I18n.locale" do + @lookup_context.locale = :pt + assert_equal :pt, I18n.locale + end + + test "delegates changing the locale to the I18n configuration object if it contains a lookup_context object" do + begin + I18n.config = AbstractController::I18nProxy.new(I18n.config, @lookup_context) + @lookup_context.locale = :pt + assert_equal :pt, I18n.locale + assert_equal :pt, @lookup_context.locale + ensure + I18n.config = I18n.config.i18n_config + end + + assert_equal :pt, I18n.locale + end + + test "find templates using the given view paths and configured details" do + template = @lookup_context.find("hello_world", "test") + assert_equal "Hello world!", template.source + + @lookup_context.locale = :da + template = @lookup_context.find("hello_world", "test") + assert_equal "Hey verden", template.source + end + + test "found templates respects given formats if one cannot be found from template or handler" do + ActionView::Template::Handlers::ERB.expects(:default_format).returns(nil) + @lookup_context.formats = [:text] + template = @lookup_context.find("hello_world", "test") + assert_equal [:text], template.formats + end + + test "adds fallbacks to view paths when required" do + assert_equal 1, @lookup_context.view_paths.size + + @lookup_context.with_fallbacks do + assert_equal 3, @lookup_context.view_paths.size + assert @lookup_context.view_paths.include?(ActionView::FileSystemResolver.new("")) + assert @lookup_context.view_paths.include?(ActionView::FileSystemResolver.new("/")) + end + end + + test "add fallbacks just once in nested fallbacks calls" do + @lookup_context.with_fallbacks do + @lookup_context.with_fallbacks do + assert_equal 3, @lookup_context.view_paths.size + end + end + end + + test "generates a new details key for each details hash" do + keys = [] + keys << @lookup_context.details_key + assert_equal 1, keys.uniq.size + + @lookup_context.locale = :da + keys << @lookup_context.details_key + assert_equal 2, keys.uniq.size + + @lookup_context.locale = :en + keys << @lookup_context.details_key + assert_equal 2, keys.uniq.size + + @lookup_context.formats = :html + keys << @lookup_context.details_key + assert_equal 3, keys.uniq.size + + @lookup_context.formats = nil + keys << @lookup_context.details_key + assert_equal 3, keys.uniq.size + end + + test "gives the key forward to the resolver, so it can be used as cache key" do + @lookup_context.view_paths = ActionView::FixtureResolver.new("test/_foo.erb" => "Foo") + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # Now we are going to change the template, but it won't change the returned template + # since we will hit the cache. + @lookup_context.view_paths.first.hash["test/_foo.erb"] = "Bar" + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # This time we will change the locale. The updated template should be picked since + # lookup_context generated a new key after we changed the locale. + @lookup_context.locale = :da + template = @lookup_context.find("foo", "test", true) + assert_equal "Bar", template.source + + # Now we will change back the locale and it will still pick the old template. + # This is expected because lookup_context will reuse the previous key for :en locale. + @lookup_context.locale = :en + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # Finally, we can expire the cache. And the expected template will be used. + @lookup_context.view_paths.first.clear_cache + template = @lookup_context.find("foo", "test", true) + assert_equal "Bar", template.source + end +end \ No newline at end of file diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb index bf5b81292f..f730a0d7f5 100644 --- a/actionpack/test/template/number_helper_i18n_test.rb +++ b/actionpack/test/template/number_helper_i18n_test.rb @@ -1,69 +1,95 @@ require 'abstract_unit' -class NumberHelperI18nTests < Test::Unit::TestCase - include ActionView::Helpers::NumberHelper - - attr_reader :request +class NumberHelperTest < ActionView::TestCase + tests ActionView::Helpers::NumberHelper def setup - @number_defaults = { :precision => 3, :delimiter => ',', :separator => '.' } - @currency_defaults = { :unit => '$', :format => '%u%n', :precision => 2 } - @human_defaults = { :precision => 1 } - @human_storage_units_format_default = "%n %u" - @human_storage_units_units_byte_other = "Bytes" - @human_storage_units_units_kb_other = "KB" - @percentage_defaults = { :delimiter => '' } - @precision_defaults = { :delimiter => '' } - - I18n.backend.store_translations 'en', :number => { :format => @number_defaults, - :currency => { :format => @currency_defaults }, :human => @human_defaults } + I18n.backend.store_translations 'ts', + :number => { + :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false }, + :currency => { :format => { :unit => '&$', :format => '%u - %n', :precision => 2 } }, + :human => { + :format => { + :precision => 2, + :significant => true, + :strip_insignificant_zeros => true + }, + :storage_units => { + :format => "%n %u", + :units => { + :byte => "b", + :kb => "k" + } + }, + :decimal_units => { + :format => "%n %u", + :units => { + :deci => {:one => "Tenth", :other => "Tenths"}, + :unit => "u", + :ten => {:one => "Ten", :other => "Tens"}, + :thousand => "t", + :million => "m" , + :billion =>"b" , + :trillion =>"t" , + :quadrillion =>"q" + } + } + }, + :percentage => { :format => {:delimiter => '', :precision => 2, :strip_insignificant_zeros => true} }, + :precision => { :format => {:delimiter => '', :significant => true} } + }, + :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} end - def test_number_to_currency_translates_currency_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.currency.format', :locale => 'en', - :raise => true).returns(@currency_defaults) - number_to_currency(1, :locale => 'en') + def test_number_to_currency + assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts')) end - def test_number_with_precision_translates_number_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.precision.format', :locale => 'en', - :raise => true).returns(@precision_defaults) - number_with_precision(1, :locale => 'en') + def test_number_with_precision + #Delimiter was set to "" + assert_equal("10000", number_with_precision(10000, :locale => 'ts')) + + #Precision inherited and significant was set + assert_equal("1.00", number_with_precision(1.0, :locale => 'ts')) + end - def test_number_with_delimiter_translates_number_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) - number_with_delimiter(1, :locale => 'en') + def test_number_with_delimiter + #Delimiter "," and separator "." + assert_equal("1,000,000.234", number_with_delimiter(1000000.234, :locale => 'ts')) end - def test_number_to_percentage_translates_number_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.percentage.format', :locale => 'en', - :raise => true).returns(@percentage_defaults) - number_to_percentage(1, :locale => 'en') + def test_number_to_percentage + # to see if strip_insignificant_zeros is true + assert_equal("1%", number_to_percentage(1, :locale => 'ts')) + # precision is 2, significant should be inherited + assert_equal("1.24%", number_to_percentage(1.2434, :locale => 'ts')) + # no delimiter + assert_equal("12434%", number_to_percentage(12434, :locale => 'ts')) end - def test_number_to_human_size_translates_human_formats - I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.human.format', :locale => 'en', - :raise => true).returns(@human_defaults) - I18n.expects(:translate).with(:'number.human.storage_units.format', :locale => 'en', - :raise => true).returns(@human_storage_units_format_default) - I18n.expects(:translate).with(:'number.human.storage_units.units.kb', :locale => 'en', :count => 2, - :raise => true).returns(@human_storage_units_units_kb_other) - # 2KB - number_to_human_size(2048, :locale => 'en') + def test_number_to_human_size + #b for bytes and k for kbytes + assert_equal("2 k", number_to_human_size(2048, :locale => 'ts')) + assert_equal("42 b", number_to_human_size(42, :locale => 'ts')) + end - I18n.expects(:translate).with(:'number.format', :locale => 'en', :raise => true).returns(@number_defaults) - I18n.expects(:translate).with(:'number.human.format', :locale => 'en', - :raise => true).returns(@human_defaults) - I18n.expects(:translate).with(:'number.human.storage_units.format', :locale => 'en', - :raise => true).returns(@human_storage_units_format_default) - I18n.expects(:translate).with(:'number.human.storage_units.units.byte', :locale => 'en', :count => 42, - :raise => true).returns(@human_storage_units_units_byte_other) - # 42 Bytes - number_to_human_size(42, :locale => 'en') + def test_number_to_human_with_default_translation_scope + #Using t for thousand + assert_equal "2 t", number_to_human(2000, :locale => 'ts') + #Significant was set to true with precision 2, using b for billion + assert_equal "1.2 b", number_to_human(1234567890, :locale => 'ts') + #Using pluralization (Ten/Tens and Tenth/Tenths) + assert_equal "1 Tenth", number_to_human(0.1, :locale => 'ts') + assert_equal "1.3 Tenth", number_to_human(0.134, :locale => 'ts') + assert_equal "2 Tenths", number_to_human(0.2, :locale => 'ts') + assert_equal "1 Ten", number_to_human(10, :locale => 'ts') + assert_equal "1.2 Ten", number_to_human(12, :locale => 'ts') + assert_equal "2 Tens", number_to_human(20, :locale => 'ts') + end + + def test_number_to_human_with_custom_translation_scope + #Significant was set to true with precision 2, with custom translated units + assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human) end end diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 0a2b82bd89..50c57a5588 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -19,6 +19,15 @@ def terabytes(number) gigabytes(number) * 1024 end + def silence_deprecation_warnings + @old_deprecatios_silenced = ActiveSupport::Deprecation.silenced + ActiveSupport::Deprecation.silenced = true + end + + def restore_deprecation_warnings + ActiveSupport::Deprecation.silenced = @old_deprecatios_silenced + end + def test_number_to_phone assert_equal("555-1234", number_to_phone(5551234)) assert_equal("800-555-1212", number_to_phone(8005551212)) @@ -31,8 +40,6 @@ def test_number_to_phone assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => '')) assert_equal("22-555-1212", number_to_phone(225551212)) assert_equal("+45-22-555-1212", number_to_phone(225551212, :country_code => 45)) - assert_equal("x", number_to_phone("x")) - assert_nil number_to_phone(nil) end def test_number_to_currency @@ -43,9 +50,6 @@ def test_number_to_currency assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50")) assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) - #assert_equal("$x.", number_to_currency("x")) # fails due to API consolidation - assert_equal("$x", number_to_currency("x")) - assert_nil number_to_currency(nil) end def test_number_to_percentage @@ -54,9 +58,8 @@ def test_number_to_percentage assert_equal("302.06%", number_to_percentage(302.0574, {:precision => 2})) assert_equal("100.000%", number_to_percentage("100")) assert_equal("1000.000%", number_to_percentage("1000")) - assert_equal("x%", number_to_percentage("x")) + assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true)) assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ',')) - assert_nil number_to_percentage(nil) end def test_number_with_delimiter @@ -70,8 +73,6 @@ def test_number_with_delimiter assert_equal("123,456,789.78901", number_with_delimiter(123456789.78901)) assert_equal("0.78901", number_with_delimiter(0.78901)) assert_equal("123,456.78", number_with_delimiter("123456.78")) - assert_equal("x", number_with_delimiter("x")) - assert_nil number_with_delimiter(nil) end def test_number_with_delimiter_with_options_hash @@ -81,6 +82,16 @@ def test_number_with_delimiter_with_options_hash assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',') end + def test_number_with_delimiter_old_api + silence_deprecation_warnings + assert_equal '12 345 678', number_with_delimiter(12345678, " ") + assert_equal '12-345-678.05', number_with_delimiter(12345678.05, '-') + assert_equal '12.345.678,05', number_with_delimiter(12345678.05, '.', ',') + assert_equal '12,345,678.05', number_with_delimiter(12345678.05, ',', '.') + assert_equal '12 345 678-05', number_with_delimiter(12345678.05, ',', '.', :delimiter => ' ', :separator => '-') + restore_deprecation_warnings + end + def test_number_with_precision assert_equal("111.235", number_with_precision(111.2346)) assert_equal("31.83", number_with_precision(31.825, :precision => 2)) @@ -91,10 +102,6 @@ def test_number_with_precision assert_equal("3268", number_with_precision((32.6751 * 100.00), :precision => 0)) assert_equal("112", number_with_precision(111.50, :precision => 0)) assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0)) - - # Return non-numeric params unchanged. - assert_equal("x", number_with_precision("x")) - assert_nil number_with_precision(nil) end def test_number_with_precision_with_custom_delimiter_and_separator @@ -102,48 +109,272 @@ def test_number_with_precision_with_custom_delimiter_and_separator assert_equal '1.231,83', number_with_precision(1231.825, :precision => 2, :separator => ',', :delimiter => '.') end + def test_number_with_precision_with_significant_digits + assert_equal "124000", number_with_precision(123987, :precision => 3, :significant => true) + assert_equal "120000000", number_with_precision(123987876, :precision => 2, :significant => true ) + assert_equal "40000", number_with_precision("43523", :precision => 1, :significant => true ) + assert_equal "9775", number_with_precision(9775, :precision => 4, :significant => true ) + assert_equal "5.4", number_with_precision(5.3923, :precision => 2, :significant => true ) + assert_equal "5", number_with_precision(5.3923, :precision => 1, :significant => true ) + assert_equal "1", number_with_precision(1.232, :precision => 1, :significant => true ) + assert_equal "7", number_with_precision(7, :precision => 1, :significant => true ) + assert_equal "1", number_with_precision(1, :precision => 1, :significant => true ) + assert_equal "53", number_with_precision(52.7923, :precision => 2, :significant => true ) + assert_equal "9775.00", number_with_precision(9775, :precision => 6, :significant => true ) + assert_equal "5.392900", number_with_precision(5.3929, :precision => 7, :significant => true ) + end + + def test_number_with_precision_with_strip_insignificant_zeros + assert_equal "9775.43", number_with_precision(9775.43, :precision => 4, :strip_insignificant_zeros => true ) + assert_equal "9775.2", number_with_precision(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) + end + + def test_number_with_precision_with_significant_true_and_zero_precision + # Zero precision with significant is a mistake (would always return zero), + # so we treat it as if significant was false (increases backwards compatibily for number_to_human_size) + assert_equal "124", number_with_precision(123.987, :precision => 0, :significant => true) + assert_equal "12", number_with_precision(12, :precision => 0, :significant => true ) + assert_equal "12", number_with_precision("12.3", :precision => 0, :significant => true ) + end + + def test_number_with_precision_old_api + silence_deprecation_warnings + assert_equal("31.8250", number_with_precision(31.825, 4)) + assert_equal("111.235", number_with_precision(111.2346, 3)) + assert_equal("111.00", number_with_precision(111, 2)) + assert_equal("111.000", number_with_precision(111, 2, :precision =>3)) + restore_deprecation_warnings + end + def test_number_to_human_size assert_equal '0 Bytes', number_to_human_size(0) assert_equal '1 Byte', number_to_human_size(1) assert_equal '3 Bytes', number_to_human_size(3.14159265) assert_equal '123 Bytes', number_to_human_size(123.0) assert_equal '123 Bytes', number_to_human_size(123) - assert_equal '1.2 KB', number_to_human_size(1234) + assert_equal '1.21 KB', number_to_human_size(1234) assert_equal '12.1 KB', number_to_human_size(12345) - assert_equal '1.2 MB', number_to_human_size(1234567) - assert_equal '1.1 GB', number_to_human_size(1234567890) - assert_equal '1.1 TB', number_to_human_size(1234567890123) - assert_equal '1025 TB', number_to_human_size(terabytes(1025)) + assert_equal '1.18 MB', number_to_human_size(1234567) + assert_equal '1.15 GB', number_to_human_size(1234567890) + assert_equal '1.12 TB', number_to_human_size(1234567890123) + assert_equal '1030 TB', number_to_human_size(terabytes(1026)) assert_equal '444 KB', number_to_human_size(kilobytes(444)) - assert_equal '1023 MB', number_to_human_size(megabytes(1023)) + assert_equal '1020 MB', number_to_human_size(megabytes(1023)) assert_equal '3 TB', number_to_human_size(terabytes(3)) - assert_equal '1.18 MB', number_to_human_size(1234567, :precision => 2) + assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) - assert_equal("123 Bytes", number_to_human_size("123")) - assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) + assert_equal '123 Bytes', number_to_human_size('123') + assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4) assert_equal '10 KB', number_to_human_size(kilobytes(10.000), :precision => 4) assert_equal '1 Byte', number_to_human_size(1.1) assert_equal '10 Bytes', number_to_human_size(10) - #assert_nil number_to_human_size('x') # fails due to API consolidation - assert_nil number_to_human_size(nil) end def test_number_to_human_size_with_options_hash - assert_equal '1.18 MB', number_to_human_size(1234567, :precision => 2) + assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) - assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) + assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4) assert_equal '10 KB', number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 TB', number_to_human_size(1234567890123, :precision => 0) - assert_equal '500 MB', number_to_human_size(524288000, :precision=>0) - assert_equal '40 KB', number_to_human_size(41010, :precision => 0) - assert_equal '40 KB', number_to_human_size(41100, :precision => 0) + assert_equal '1 TB', number_to_human_size(1234567890123, :precision => 1) + assert_equal '500 MB', number_to_human_size(524288000, :precision=>3) + assert_equal '40 KB', number_to_human_size(41010, :precision => 1) + assert_equal '40 KB', number_to_human_size(41100, :precision => 2) + assert_equal '1.0 KB', number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false) + assert_equal '1.012 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false) + assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0 end def test_number_to_human_size_with_custom_delimiter_and_separator - assert_equal '1,01 KB', number_to_human_size(kilobytes(1.0123), :precision => 2, :separator => ',') + assert_equal '1,01 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',') assert_equal '1,01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',') - assert_equal '1.000,1 TB', number_to_human_size(terabytes(1000.1), :delimiter => '.', :separator => ',') + assert_equal '1.000,1 TB', number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',') end + + def test_number_to_human_size_old_api + silence_deprecation_warnings + assert_equal '1.3143 KB', number_to_human_size(kilobytes(1.3143), 4, :significant => false) + assert_equal '10.45 KB', number_to_human_size(kilobytes(10.453), 4) + assert_equal '10 KB', number_to_human_size(kilobytes(10.453), 4, :precision => 2) + restore_deprecation_warnings + end + + def test_number_to_human + assert_equal '123', number_to_human(123) + assert_equal '1.23 Thousand', number_to_human(1234) + assert_equal '12.3 Thousand', number_to_human(12345) + assert_equal '1.23 Million', number_to_human(1234567) + assert_equal '1.23 Billion', number_to_human(1234567890) + assert_equal '1.23 Trillion', number_to_human(1234567890123) + assert_equal '1.23 Quadrillion', number_to_human(1234567890123456) + assert_equal '1230 Quadrillion', number_to_human(1234567890123456789) + assert_equal '490 Thousand', number_to_human(489939, :precision => 2) + assert_equal '489.9 Thousand', number_to_human(489939, :precision => 4) + assert_equal '489 Thousand', number_to_human(489000, :precision => 4) + assert_equal '489.0 Thousand', number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false) + assert_equal '1.2346 Million', number_to_human(1234567, :precision => 4, :significant => false) + assert_equal '1,2 Million', number_to_human(1234567, :precision => 1, :significant => false, :separator => ',') + assert_equal '1 Million', number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false + end + + def test_number_to_human_with_custom_units + #Only integers + volume = {:unit => "ml", :thousand => "lt", :million => "m3"} + assert_equal '123 lt', number_to_human(123456, :units => volume) + assert_equal '12 ml', number_to_human(12, :units => volume) + assert_equal '1.23 m3', number_to_human(1234567, :units => volume) + + #Including fractionals + distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} + assert_equal '1.23 mm', number_to_human(0.00123, :units => distance) + assert_equal '1.23 cm', number_to_human(0.0123, :units => distance) + assert_equal '1.23 dm', number_to_human(0.123, :units => distance) + assert_equal '1.23 m', number_to_human(1.23, :units => distance) + assert_equal '1.23 dam', number_to_human(12.3, :units => distance) + assert_equal '1.23 hm', number_to_human(123, :units => distance) + assert_equal '1.23 km', number_to_human(1230, :units => distance) + assert_equal '1.23 km', number_to_human(1230, :units => distance) + assert_equal '1.23 km', number_to_human(1230, :units => distance) + assert_equal '12.3 km', number_to_human(12300, :units => distance) + + #The quantifiers don't need to be a continuous sequence + gangster = {:hundred => "hundred bucks", :million => "thousand quids"} + assert_equal '1 hundred bucks', number_to_human(100, :units => gangster) + assert_equal '25 hundred bucks', number_to_human(2500, :units => gangster) + assert_equal '25 thousand quids', number_to_human(25000000, :units => gangster) + assert_equal '12300 thousand quids', number_to_human(12345000000, :units => gangster) + + #Spaces are stripped from the resulting string + assert_equal '4', number_to_human(4, :units => {:unit => "", :ten => 'tens '}) + assert_equal '4.5 tens', number_to_human(45, :units => {:unit => "", :ten => ' tens '}) + end + + def test_number_to_human_with_custom_format + assert_equal '123 times Thousand', number_to_human(123456, :format => "%n times %u") + volume = {:unit => "ml", :thousand => "lt", :million => "m3"} + assert_equal '123.lt', number_to_human(123456, :units => volume, :format => "%n.%u") + end + + def test_number_helpers_should_return_nil_when_given_nil + assert_nil number_to_phone(nil) + assert_nil number_to_currency(nil) + assert_nil number_to_percentage(nil) + assert_nil number_with_delimiter(nil) + assert_nil number_with_precision(nil) + assert_nil number_to_human_size(nil) + assert_nil number_to_human(nil) + end + + def test_number_helpers_should_return_non_numeric_param_unchanged + assert_equal("+1-x x 123", number_to_phone("x", :country_code => 1, :extension => 123)) + assert_equal("x", number_to_phone("x")) + assert_equal("$x.", number_to_currency("x.")) + assert_equal("$x", number_to_currency("x")) + assert_equal("x%", number_to_percentage("x")) + assert_equal("x", number_with_delimiter("x")) + assert_equal("x.", number_with_precision("x.")) + assert_equal("x", number_with_precision("x")) + assert_equal "x", number_to_human_size('x') + assert_equal "x", number_to_human('x') + end + + def test_number_helpers_outputs_are_html_safe + assert number_to_human(1).html_safe? + assert !number_to_human("").html_safe? + assert number_to_human("asdf".html_safe).html_safe? + + assert number_to_human_size(1).html_safe? + assert number_to_human_size(1000000).html_safe? + assert !number_to_human_size("").html_safe? + assert number_to_human_size("asdf".html_safe).html_safe? + + assert number_with_precision(1, :strip_insignificant_zeros => false).html_safe? + assert number_with_precision(1, :strip_insignificant_zeros => true).html_safe? + assert !number_with_precision("").html_safe? + assert number_with_precision("asdf".html_safe).html_safe? + + assert number_to_currency(1).html_safe? + assert !number_to_currency("").html_safe? + assert number_to_currency("asdf".html_safe).html_safe? + + assert number_to_percentage(1).html_safe? + assert !number_to_percentage("").html_safe? + assert number_to_percentage("asdf".html_safe).html_safe? + + assert number_to_phone(1).html_safe? + assert !number_to_phone("").html_safe? + assert number_to_phone("asdf".html_safe).html_safe? + + assert number_with_delimiter(1).html_safe? + assert !number_with_delimiter("").html_safe? + assert number_with_delimiter("asdf".html_safe).html_safe? + end + + def test_number_helpers_should_raise_error_if_invalid_when_specified + assert_raise InvalidNumberError do + number_to_human("x", :raise => true) + end + begin + number_to_human("x", :raise => true) + rescue InvalidNumberError => e + assert_equal "x", e.number + end + + assert_raise InvalidNumberError do + number_to_human_size("x", :raise => true) + end + begin + number_to_human_size("x", :raise => true) + rescue InvalidNumberError => e + assert_equal "x", e.number + end + + assert_raise InvalidNumberError do + number_with_precision("x", :raise => true) + end + begin + number_with_precision("x", :raise => true) + rescue InvalidNumberError => e + assert_equal "x", e.number + end + + assert_raise InvalidNumberError do + number_to_currency("x", :raise => true) + end + begin + number_with_precision("x", :raise => true) + rescue InvalidNumberError => e + assert_equal "x", e.number + end + + assert_raise InvalidNumberError do + number_to_percentage("x", :raise => true) + end + begin + number_to_percentage("x", :raise => true) + rescue InvalidNumberError => e + assert_equal "x", e.number + end + + assert_raise InvalidNumberError do + number_with_delimiter("x", :raise => true) + end + begin + number_with_delimiter("x", :raise => true) + rescue InvalidNumberError => e + assert_equal "x", e.number + end + + assert_raise InvalidNumberError do + number_to_phone("x", :raise => true) + end + begin + number_to_phone("x", :raise => true) + rescue InvalidNumberError => e + assert_equal "x", e.number + end + + end + end diff --git a/actionpack/test/template/output_buffer_test.rb b/actionpack/test/template/output_buffer_test.rb index 36bbaf9099..bd49a11af1 100644 --- a/actionpack/test/template/output_buffer_test.rb +++ b/actionpack/test/template/output_buffer_test.rb @@ -10,6 +10,7 @@ def index tests TestController def setup + @vc = @controller.view_context get :index assert_equal ['foo'], body_parts end @@ -19,21 +20,21 @@ def setup end test 'flushing ignores nil output buffer' do - @controller.template.flush_output_buffer + @controller.view_context.flush_output_buffer assert_nil output_buffer assert_equal ['foo'], body_parts end test 'flushing ignores empty output buffer' do - @controller.template.output_buffer = '' - @controller.template.flush_output_buffer + @vc.output_buffer = '' + @vc.flush_output_buffer assert_equal '', output_buffer assert_equal ['foo'], body_parts end test 'flushing appends the output buffer to the body parts' do - @controller.template.output_buffer = 'bar' - @controller.template.flush_output_buffer + @vc.output_buffer = 'bar' + @vc.flush_output_buffer assert_equal '', output_buffer assert_equal ['foo', 'bar'], body_parts end @@ -41,8 +42,8 @@ def setup if '1.9'.respond_to?(:force_encoding) test 'flushing preserves output buffer encoding' do original_buffer = ' '.force_encoding(Encoding::EUC_JP) - @controller.template.output_buffer = original_buffer - @controller.template.flush_output_buffer + @vc.output_buffer = original_buffer + @vc.flush_output_buffer assert_equal ['foo', original_buffer], body_parts assert_not_equal original_buffer, output_buffer assert_equal Encoding::EUC_JP, output_buffer.encoding @@ -51,10 +52,10 @@ def setup protected def output_buffer - @controller.template.output_buffer + @vc.output_buffer end def body_parts - @controller.template.response.body_parts + @controller.response.body_parts end end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index cea8ab1bce..e54ebfbf8d 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -9,7 +9,7 @@ module RenderTestCases def setup_view(paths) @assigns = { :secret => 'in the sauce' } @view = ActionView::Base.new(paths, @assigns) - @controller_view = ActionView::Base.for_controller(TestController.new) + @controller_view = TestController.new.view_context # Reload and register danish language for testing I18n.reload! @@ -228,6 +228,14 @@ def test_render_with_layout @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield") end + # TODO: Move to deprecated_tests.rb + def test_render_with_nested_layout_deprecated + assert_deprecated do + assert_equal %(title\n\n\n
    column
    \n
    content
    \n), + @view.render(:file => "test/deprecated_nested_layout.erb", :layout => "layouts/yield") + end + end + def test_render_with_nested_layout assert_equal %(title\n\n\n
    column
    \n
    content
    \n), @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 699fb2f5bc..6782bf06d4 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -20,7 +20,14 @@ def test_returns_missing_translation_message_wrapped_into_span def test_translation_of_an_array I18n.expects(:translate).with(["foo", "bar"], :raise => true).returns(["foo", "bar"]) - assert_equal ["foo", "bar"], translate(["foo", "bar"]) + assert_equal "foobar", translate(["foo", "bar"]) + end + + def test_translation_of_an_array_with_html + expected = 'foobar' + I18n.expects(:translate).with(["foo", "bar"], :raise => true).returns(['foo', 'bar']) + @view = ActionView::Base.new(ActionController::Base.view_paths, {}) + assert_equal expected, @view.render(:file => "test/array_translation") end def test_delegates_localize_to_i18n @@ -34,4 +41,10 @@ def test_scoping_by_partial @view = ActionView::Base.new(ActionController::Base.view_paths, {}) assert_equal "helper", @view.render(:file => "test/translation") end + + def test_scoping_by_partial_of_an_array + I18n.expects(:translate).with("test.scoped_array_translation.foo.bar", :raise => true).returns(["foo", "bar"]) + @view = ActionView::Base.new(ActionController::Base.view_paths, {}) + assert_equal "foobar", @view.render(:file => "test/scoped_array_translation") + end end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 165cb655da..87b2e59255 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -238,10 +238,7 @@ def test_link_tag_using_delete_javascript_and_href_and_confirm end def test_link_tag_using_block_in_erb - __in_erb_template = '' - - link_to("http://example.com") { concat("Example site") } - + output_buffer = link_to("http://example.com") { concat("Example site") } assert_equal 'Example site', output_buffer end diff --git a/activemodel/README b/activemodel/README index 0d6fd1f21c..3945a6da06 100644 --- a/activemodel/README +++ b/activemodel/README @@ -11,9 +11,9 @@ Active Model is a solution for this problem. Active Model provides a known set of interfaces that your objects can implement to then present a common interface to the Action Pack helpers. You can include functionality from the following modules: - + * Adding attribute magic to your objects - + Add prefixes and suffixes to defined attribute methods... class Person @@ -34,7 +34,7 @@ functionality from the following modules: {Learn more}[link:classes/ActiveModel/AttributeMethods.html] * Adding callbacks to your objects - + class Person extend ActiveModel::Callbacks define_model_callbacks :create @@ -50,19 +50,19 @@ functionality from the following modules: wrap your create method. {Learn more}[link:classes/ActiveModel/CallBacks.html] - + * For classes that already look like an Active Record object - + class Person include ActiveModel::Conversion end ...returns the class itself when sent :to_model - + {Learn more}[link:classes/ActiveModel/Conversion.html] - + * Tracking changes in your object - + Provides all the value tracking features implemented by ActiveRecord... person = Person.new @@ -77,29 +77,29 @@ functionality from the following modules: person.previous_changes # => {'name' => ['bob, 'robert']} {Learn more}[link:classes/ActiveModel/Dirty.html] - + * Adding +errors+ support to your object - + Provides the error messages to allow your object to interact with Action Pack helpers seamlessly... class Person - + def initialize @errors = ActiveModel::Errors.new(self) end - + attr_accessor :name attr_reader :errors - + def validate! errors.add(:name, "can not be nil") if name == nil end - + def ErrorsPerson.human_attribute_name(attr, options = {}) "Name" end - + end ... gives you... @@ -108,18 +108,18 @@ functionality from the following modules: # => ["Name Can not be nil"] person.errors.full_messages # => ["Name Can not be nil"] - + {Learn more}[link:classes/ActiveModel/Errors.html] - + * Testing the compliance of your object - + Use ActiveModel::Lint to test the compliance of your object to the basic ActiveModel API... {Learn more}[link:classes/ActiveModel/Lint/Tests.html] - + * Providing a human face to your object - + ActiveModel::Naming provides your model with the model_name convention and a human_name attribute... @@ -131,19 +131,19 @@ functionality from the following modules: NamedPerson.model_name #=> "NamedPerson" NamedPerson.model_name.human #=> "Named person" - + {Learn more}[link:classes/ActiveModel/Naming.html] - + * Adding observer support to your objects - + ActiveModel::Observers allows your object to implement the Observer pattern in a Rails App and take advantage of all the standard observer functions. {Learn more}[link:classes/ActiveModel/Observer.html] - + * Making your object serializable - + ActiveModel::Serialization provides a standard interface for your object to provide to_json or to_xml serialization... @@ -153,48 +153,37 @@ functionality from the following modules: s.to_xml # => "\n :red - light.change_color! #=> true - light.current_state #=> :green - - {Learn more}[link:classes/ActiveModel/StateMachine.html] - + * Integrating with Rail's internationalization (i18n) handling through ActiveModel::Translations... - + class Person extend ActiveModel::Translation end {Learn more}[link:classes/ActiveModel/Translation.html] - + * Providing a full Validation stack for your objects... - + class Person include ActiveModel::Validations - + attr_accessor :first_name, :last_name - + + validates_each :first_name, :last_name do |record, attr, value| record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z end end - + + person = Person.new(:first_name => 'zoolander') person.valid? #=> false - + {Learn more}[link:classes/ActiveModel/Validations.html] * Make custom validators - + class Person include ActiveModel::Validations validates_with HasNameValidator @@ -212,5 +201,5 @@ functionality from the following modules: p.errors.full_messages #=> ["Name must exist"] p.name = "Bob" p.valid? #=> true - + {Learn more}[link:classes/ActiveModel/Validator.html] diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 588976f1d4..f04829ef09 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -53,7 +53,6 @@ class MissingAttributeError < NoMethodError module AttributeMethods extend ActiveSupport::Concern - # Declare and check for suffixed attribute methods. module ClassMethods # Defines an "attribute" method (like +inheritance_column+ or # +table_name+). A new (class) method will be created with the @@ -90,13 +89,20 @@ module ClassMethods # # => 'address_id' def define_attr_method(name, value=nil, &block) sing = singleton_class - sing.send :alias_method, "original_#{name}", name + sing.class_eval <<-eorb, __FILE__, __LINE__ + 1 + if method_defined?(:original_#{name}) + undef :original_#{name} + end + alias_method :original_#{name}, :#{name} + eorb if block_given? sing.send :define_method, name, &block else # use eval instead of a block to work around a memory leak in dev # mode in fcgi - sing.class_eval "def #{name}; #{value.to_s.inspect}; end" + sing.class_eval <<-eorb, __FILE__, __LINE__ + 1 + def #{name}; #{value.to_s.inspect}; end + eorb end end @@ -257,8 +263,13 @@ def define_attribute_methods(attr_names) if respond_to?(generate_method) send(generate_method, attr_name) else + method_name = matcher.method_name(attr_name) + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__+1 - def #{matcher.method_name(attr_name)}(*args) + if method_defined?(:#{method_name}) + undef :#{method_name} + end + def #{method_name}(*args) send(:#{matcher.method_missing_target}, '#{attr_name}', *args) end STR @@ -286,6 +297,7 @@ def generated_attribute_methods #:nodoc: end end + # Returns true if the attribute methods defined have been generated. def attribute_methods_generated? @attribute_methods_generated ||= nil end diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index a7e0cf90c1..d4e98de57b 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/wrap' require 'active_support/callbacks' module ActiveModel @@ -91,7 +92,7 @@ def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options) - types = Array(options.delete(:only)) + types = Array.wrap(options.delete(:only)) types = [:before, :around, :after] if types.empty? callbacks.each do |callback| @@ -124,10 +125,10 @@ def _define_after_model_callback(klass, callback) #:nodoc: def self.after_#{callback}(*args, &block) options = args.extract_options! options[:prepend] = true - options[:if] = Array(options[:if]) << "!halted && value != false" + options[:if] = Array.wrap(options[:if]) << "!halted && value != false" set_callback(:#{callback}, :after, *(args << options), &block) end CALLBACK end end -end \ No newline at end of file +end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 5f02929a9d..cb67ef7270 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -1,3 +1,5 @@ +require 'active_support/hash_with_indifferent_access' + module ActiveModel # ActiveModel::Dirty provides a way to track changes in your # object in the same way as ActiveRecord does. @@ -86,12 +88,17 @@ module Dirty attribute_method_affix :prefix => 'reset_', :suffix => '!' end + def initialize(*) + @changed_attributes = {} + super + end + # Do any attributes have unsaved changes? # person.changed? # => false # person.name = 'bob' # person.changed? # => true def changed? - !changed_attributes.empty? + !@changed_attributes.empty? end # List of attributes with unsaved changes. @@ -99,7 +106,7 @@ def changed? # person.name = 'bob' # person.changed # => ['name'] def changed - changed_attributes.keys + @changed_attributes.keys end # Map of changed attrs => [original value, new value]. @@ -107,7 +114,7 @@ def changed # person.name = 'bob' # person.changes # => { 'name' => ['bill', 'bob'] } def changes - changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h } + changed.inject(HashWithIndifferentAccess.new){ |h, attr| h[attr] = attribute_change(attr); h } end # Map of attributes that were changed when the model was saved. @@ -116,33 +123,23 @@ def changes # person.save # person.previous_changes # => {'name' => ['bob, 'robert']} def previous_changes - previously_changed_attributes + @previously_changed end private - # Map of change attr => original value. - def changed_attributes - @changed_attributes ||= {} - end - - # Map of fields that were changed when the model was saved - def previously_changed_attributes - @previously_changed || {} - end - # Handle *_changed? for +method_missing+. def attribute_changed?(attr) - changed_attributes.include?(attr) + @changed_attributes.include?(attr) end # Handle *_change for +method_missing+. def attribute_change(attr) - [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) + [@changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) end # Handle *_was for +method_missing+. def attribute_was(attr) - attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + attribute_changed?(attr) ? @changed_attributes[attr] : __send__(attr) end # Handle *_will_change! for +method_missing+. @@ -153,12 +150,12 @@ def attribute_will_change!(attr) rescue TypeError, NoMethodError end - changed_attributes[attr] = value + @changed_attributes[attr] = value end # Handle reset_*! for +method_missing+. def reset_attribute!(attr) - __send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr) + __send__("#{attr}=", @changed_attributes[attr]) if attribute_changed?(attr) end end end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index d8320275df..a9a54a90e0 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/inflections' require 'active_support/ordered_hash' @@ -206,7 +207,7 @@ def full_messages full_messages = [] each do |attribute, messages| - messages = Array(messages) + messages = Array.wrap(messages) next if messages.empty? if attribute == :base diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 39512a427b..8cdd3d2fe8 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -1,6 +1,7 @@ require 'active_support/inflector' module ActiveModel + class Name < String attr_reader :singular, :plural, :element, :collection, :partial_path alias_method :cache_key, :collection @@ -57,4 +58,5 @@ def model_name @_model_name ||= ActiveModel::Name.new(self) end end + end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 86149f1e5f..c226359ea7 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/hash/conversions' @@ -85,8 +86,8 @@ def initialize(serializable, options = nil) @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s } end - # To replicate the behavior in ActiveRecord#attributes, - # :except takes precedence over :only. If :only is not set + # To replicate the behavior in ActiveRecord#attributes, :except + # takes precedence over :only. If :only is not set # for a N level model but is set for the N+1 level models, # then because :except is set to a default value, the second # level model can have both :except and :only set. So if @@ -108,7 +109,7 @@ def serializable_attributes end def serializable_method_attributes - Array(options[:methods]).inject([]) do |methods, name| + Array.wrap(options[:methods]).inject([]) do |methods, name| methods << MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s) methods end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index ba8648f8c9..708557f4ae 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/keys' require 'active_model/errors' @@ -112,11 +113,10 @@ def validates_each(*attr_names, &block) # end # end # - # This usage applies to +validate_on_create+ and +validate_on_update as well+. def validate(*args, &block) options = args.last if options.is_a?(Hash) && options.key?(:on) - options[:if] = Array(options[:if]) + options[:if] = Array.wrap(options[:if]) options[:if] << "@_on_validate == :#{options[:on]}" end set_callback(:validate, *args, &block) diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index b61f0cb266..b7c52be3f0 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/array/wrap' require "active_support/core_ext/module/anonymous" module ActiveModel #:nodoc: @@ -130,7 +131,7 @@ class EachValidator < Validator # +options+ reader, however the :attributes option will be removed # and instead be made available through the +attributes+ reader. def initialize(options) - @attributes = Array(options.delete(:attributes)) + @attributes = Array.wrap(options.delete(:attributes)) raise ":attributes cannot be blank" if @attributes.empty? super check_validity! diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb new file mode 100644 index 0000000000..0883363baf --- /dev/null +++ b/activemodel/test/cases/dirty_test.rb @@ -0,0 +1,30 @@ +require "cases/helper" + +class DirtyTest < ActiveModel::TestCase + class DirtyModel + include ActiveModel::Dirty + define_attribute_methods [:name] + + def initialize + super + @name = nil + end + + def name + @name + end + + def name=(val) + name_will_change! + @name = val + end + end + + test "changes accessible through both strings and symbols" do + model = DirtyModel.new + model.name = "David" + assert !model.changes[:name].nil? + assert !model.changes['name'].nil? + end + +end diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 8b1ea8596a..8121530ab3 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -22,5 +22,5 @@ s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('arel', '~> 0.3.1') + s.add_dependency('arel', '~> 0.3.3') end diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 9ecf231a66..08389907ef 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -213,6 +213,11 @@ def reader_method(name, class_name, mapping, allow_nil, constructor) module_eval do define_method(name) do |*args| force_reload = args.first || false + + unless instance_variable_defined?("@#{name}") + instance_variable_set("@#{name}", nil) + end + if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? }) attrs = mapping.collect {|pair| read_attribute(pair.first)} object = case constructor diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index b69577f8dd..d623ddb915 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -111,8 +111,11 @@ def clear_association_cache #:nodoc: private # Gets the specified association instance if it responds to :loaded?, nil otherwise. def association_instance_get(name) - association = instance_variable_get("@#{name}") - association if association.respond_to?(:loaded?) + ivar = "@#{name}" + if instance_variable_defined?(ivar) + association = instance_variable_get(ivar) + association if association.respond_to?(:loaded?) + end end # Set the specified association instance. @@ -1207,7 +1210,7 @@ def belongs_to(association_id, options = {}) # * Developer#projects.empty? # * Developer#projects.size # * Developer#projects.find(id) - # * Developer#clients.exists?(...) + # * Developer#projects.exists?(...) # * Developer#projects.build (similar to Project.new("project_id" => id)) # * Developer#projects.create (similar to c = Project.new("project_id" => id); c.save; c) # The declaration may include an options hash to specialize the behavior of the association. @@ -1542,15 +1545,19 @@ def configure_dependency_for_has_one(reflection) case name when :destroy, :delete - define_method(method_name) do - association = send(reflection.name) - association.send(name) if association - end + class_eval <<-eoruby, __FILE__, __LINE__ + 1 + def #{method_name} + association = #{reflection.name} + association.#{name} if association + end + eoruby when :nullify - define_method(method_name) do - association = send(reflection.name) - association.update_attribute(reflection.primary_key_name, nil) if association - end + class_eval <<-eoruby, __FILE__, __LINE__ + 1 + def #{method_name} + association = #{reflection.name} + association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association + end + eoruby else raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})" end @@ -1568,10 +1575,12 @@ def configure_dependency_for_belongs_to(reflection) end method_name = :"belongs_to_dependent_#{name}_for_#{reflection.name}" - define_method(method_name) do - association = send(reflection.name) - association.send(name) if association - end + class_eval <<-eoruby, __FILE__, __LINE__ + 1 + def #{method_name} + association = #{reflection.name} + association.#{name} if association + end + eoruby after_destroy method_name end end @@ -1670,15 +1679,6 @@ def create_has_and_belongs_to_many_reflection(association_id, options, &extensio reflection end - def using_limitable_reflections?(reflections) - reflections.collect(&:collection?).length.zero? - end - - def column_aliases(join_dependency) - join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name| - "#{connection.quote_table_name join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ") - end - def add_association_callbacks(association_name, options) callbacks = %w(before_add after_add before_remove after_remove) callbacks.each do |callback_name| diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 9487d16123..6eda70d0ce 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -101,6 +101,7 @@ def to_ary Array(@target) end end + alias_method :to_a, :to_ary def reset reset_target! diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 022dd2ae9b..4fb1df3ab9 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -49,10 +49,11 @@ class AssociationProxy #:nodoc: alias_method :proxy_respond_to?, :respond_to? alias_method :proxy_extend, :extend delegate :to_param, :to => :proxy_target - instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ } + instance_methods.each { |m| undef_method m unless m =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ } def initialize(owner, reflection) @owner, @reflection = owner, reflection + @updated = false reflection.check_validity! Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) } reset diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 146a6ca55f..0464e8ceea 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -5,6 +5,10 @@ module Associations # If the association has a :through option further specialization # is provided by its child HasManyThroughAssociation. class HasManyAssociation < AssociationCollection #:nodoc: + def initialize(owner, reflection) + @finder_sql = nil + super + end protected def owner_quoted_id if @reflection.options[:primary_key] diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 4a3ab9ea82..a8698a2f5a 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -18,7 +18,7 @@ module Dirty def save_with_dirty(*args) #:nodoc: if status = save_without_dirty(*args) @previously_changed = changes - changed_attributes.clear + @changed_attributes.clear end status end @@ -26,16 +26,16 @@ def save_with_dirty(*args) #:nodoc: # Attempts to save! the record and clears changed attributes if successful. def save_with_dirty!(*args) #:nodoc: save_without_dirty!(*args).tap do - @previously_changed = changes - changed_attributes.clear + @previously_changed = changes + @changed_attributes.clear end end # reload the record and clears changed attributes. def reload_with_dirty(*args) #:nodoc: reload_without_dirty(*args).tap do - previously_changed_attributes.clear - changed_attributes.clear + @previously_changed.clear + @changed_attributes.clear end end @@ -45,12 +45,12 @@ def write_attribute(attr, value) attr = attr.to_s # The attribute already has an unsaved change. - if changed_attributes.include?(attr) - old = changed_attributes[attr] - changed_attributes.delete(attr) unless field_changed?(attr, old, value) + if attribute_changed?(attr) + old = @changed_attributes[attr] + @changed_attributes.delete(attr) unless field_changed?(attr, old, value) else old = clone_attribute_value(:read_attribute, attr) - changed_attributes[attr] = old if field_changed?(attr, old, value) + @changed_attributes[attr] = old if field_changed?(attr, old, value) end # Carry on. diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 52587ef251..367b4ce217 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1041,6 +1041,12 @@ def instantiate(record) object.instance_variable_set(:'@attributes', record) object.instance_variable_set(:'@attributes_cache', {}) + object.instance_variable_set(:@new_record, false) + object.instance_variable_set(:@readonly, false) + object.instance_variable_set(:@destroyed, false) + object.instance_variable_set(:@marked_for_destruction, false) + object.instance_variable_set(:@previously_changed, {}) + object.instance_variable_set(:@changed_attributes, {}) object.send(:_run_find_callbacks) object.send(:_run_initialize_callbacks) @@ -1506,6 +1512,12 @@ def initialize(attributes = nil) @attributes = attributes_from_column_definition @attributes_cache = {} @new_record = true + @readonly = false + @destroyed = false + @marked_for_destruction = false + @previously_changed = {} + @changed_attributes = {} + ensure_proper_type if scope = self.class.send(:current_scoped_methods) @@ -1570,7 +1582,7 @@ def initialize_copy(other) # user_path(user) # => "/users/Phusion" def to_param # We can't use alias_method here, because method 'id' optimizes itself on the fly. - (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes + id && id.to_s # Be sure to stringify the id for routes end # Returns a cache key that can be used to identify this record. @@ -1597,12 +1609,12 @@ def quoted_id #:nodoc: # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false. def new_record? - @new_record || false + @new_record end # Returns true if this object has been destroyed, otherwise returns false. def destroyed? - @destroyed || false + @destroyed end # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed. @@ -1695,7 +1707,7 @@ def becomes(klass) # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method # in Base is replaced with this when the validations module is mixed in, which it is by default. def update_attribute(name, value) - send(name.to_s + '=', value) + send("#{name}=", value) save(:validate => false) end @@ -1912,14 +1924,14 @@ def frozen? # Returns duplicated record with unfreezed attributes. def dup obj = super - obj.instance_variable_set('@attributes', instance_variable_get('@attributes').dup) + obj.instance_variable_set('@attributes', @attributes.dup) obj end # Returns +true+ if the record is read only. Records loaded through joins with piggy-back # attributes will be marked as read only since they cannot be saved. def readonly? - defined?(@readonly) && @readonly == true + @readonly end # Marks this record as read only. @@ -1939,10 +1951,10 @@ def inspect protected def clone_attributes(reader_method = :read_attribute, attributes = {}) - self.attribute_names.inject(attributes) do |attrs, name| - attrs[name] = clone_attribute_value(reader_method, name) - attrs + attribute_names.each do |name| + attributes[name] = clone_attribute_value(reader_method, name) end + attributes end def clone_attribute_value(reader_method, attribute_name) @@ -2041,26 +2053,6 @@ def attributes_protected_by_default default end - # Returns a copy of the attributes hash where all the values have been safely quoted for use in - # an SQL statement. - def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) - quoted = {} - connection = self.class.connection - attribute_names.each do |name| - if (column = column_for_attribute(name)) && (include_primary_key || !column.primary) - value = read_attribute(name) - - # We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML. - if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time)) - value = value.to_yaml - end - - quoted[name] = connection.quote(value, column) - end - end - include_readonly_attributes ? quoted : remove_readonly_attributes(quoted) - end - # Returns a copy of the attributes hash where all the values have been safely quoted for use in # an Arel insert/update method. def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) @@ -2245,4 +2237,4 @@ def object_from_yaml(string) # TODO: Remove this and make it work with LAZY flag require 'active_record/connection_adapters/abstract_adapter' -ActiveRecord.run_base_hooks(ActiveRecord::Base) \ No newline at end of file +ActiveRecord.run_base_hooks(ActiveRecord::Base) diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 1128286f2b..add5d99ca6 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -205,6 +205,16 @@ module ActiveRecord # including after_* hooks. Note, however, that in that case the client # needs to be aware of it because an ordinary +save+ will raise such exception # instead of quietly returning +false+. + # + # == Debugging callbacks + # + # To list the methods and procs registered with a particular callback, append _callback_chain to the callback name that you wish to list and send that to your class from the Rails console: + # + # >> Topic.after_save_callback_chain + # => [#, kind:after_save, identifiernil, + # options{}] + # module Callbacks extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index abb695264e..0c87e052c4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -181,6 +181,29 @@ def commit_db_transaction() end # done if the transaction block raises an exception or returns false. def rollback_db_transaction() end + # Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL + # fragment that has the same semantics as LIMIT and OFFSET. + # + # +options+ must be a Hash which contains a +:limit+ option + # and an +:offset+ option. + # + # This method *modifies* the +sql+ parameter. + # + # ===== Examples + # add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50}) + # generates + # SELECT * FROM suppliers LIMIT 10 OFFSET 50 + + def add_limit_offset!(sql, options) + if limit = options[:limit] + sql << " LIMIT #{sanitize_limit(limit)}" + end + if offset = options[:offset] + sql << " OFFSET #{offset.to_i}" + end + sql + end + def default_sequence_name(table, column) nil end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1f1df7e8c3..521bd810d0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -61,7 +61,7 @@ def self.mysql_connection(config) # :nodoc: begin require_library_or_gem('mysql') rescue LoadError - $stderr.puts '!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.' + $stderr.puts '!!! Please install the mysql gem and try again: gem install mysql.' raise end end @@ -375,6 +375,18 @@ def release_savepoint execute("RELEASE SAVEPOINT #{current_savepoint_name}") end + def add_limit_offset!(sql, options) #:nodoc: + limit, offset = options[:limit], options[:offset] + if limit && offset + sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}" + elsif limit + sql << " LIMIT #{sanitize_limit(limit)}" + elsif offset + sql << " OFFSET #{offset.to_i}" + end + sql + end + # SCHEMA STATEMENTS ======================================== def structure_dump #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b3ce8c79dd..c9a5f00dfd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -651,14 +651,12 @@ def drop_database(name) #:nodoc: end end - # Returns the list of all tables in the schema search path or a specified schema. def tables(name = nil) - schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') query(<<-SQL, name).map { |row| row[0] } SELECT tablename FROM pg_tables - WHERE schemaname IN (#{schemas}) + WHERE schemaname = ANY (current_schemas(false)) SQL end diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb index 8f9f05ce36..fa7a19487c 100644 --- a/activerecord/lib/active_record/dynamic_finder_match.rb +++ b/activerecord/lib/active_record/dynamic_finder_match.rb @@ -7,6 +7,9 @@ def self.match(method) def initialize(method) @finder = :first + @bang = false + @instantiator = nil + case method.to_s when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/ @finder = :last if $1 == 'last_by' diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index fd5ffc6d77..d4cda927ad 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -315,7 +315,9 @@ def write(text="") end def announce(message) - text = "#{@version} #{name}: #{message}" + version = defined?(@version) ? @version : nil + + text = "#{version} #{name}: #{message}" length = [0, 75 - text.length].max write "== %s %s" % [text, "=" * length] end @@ -372,7 +374,7 @@ def migration end def load_migration - load(filename) + require(File.expand_path(filename)) name.constantize end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index d8fae495e7..76ec7eb681 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -243,11 +243,14 @@ def accepts_nested_attributes_for(*attr_names) # def pirate_attributes=(attributes) # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) # end - class_eval %{ + class_eval <<-eoruby, __FILE__, __LINE__ + 1 + if method_defined?(:#{association_name}_attributes=) + remove_method(:#{association_name}_attributes=) + end def #{association_name}_attributes=(attributes) assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) end - }, __FILE__, __LINE__ + eoruby else raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 60addd46c6..de47461c73 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -10,7 +10,7 @@ module ActiveRecord class Railtie < Rails::Railtie - railtie_name :active_record + config.active_record = ActiveSupport::OrderedOptions.new config.generators.orm :active_record, :migration => true, :timestamps => true @@ -19,9 +19,8 @@ class Railtie < Rails::Railtie load "active_record/railties/databases.rake" end - # TODO If we require the wrong file, the error never comes up. require "active_record/railties/log_subscriber" - log_subscriber ActiveRecord::Railties::LogSubscriber.new + log_subscriber :active_record, ActiveRecord::Railties::LogSubscriber.new initializer "active_record.initialize_timezone" do ActiveRecord.base_hook do diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index fa6caa4910..0229793a9a 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -435,7 +435,7 @@ namespace :db do task :create => :environment do raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? require 'rails/generators' - require 'generators/rails/session_migration/session_migration_generator' + require 'rails/generators/rails/session_migration/session_migration_generator' Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] end diff --git a/activerecord/lib/active_record/railties/log_subscriber.rb b/activerecord/lib/active_record/railties/log_subscriber.rb index 48b25032ce..31b98bb6ed 100644 --- a/activerecord/lib/active_record/railties/log_subscriber.rb +++ b/activerecord/lib/active_record/railties/log_subscriber.rb @@ -1,6 +1,11 @@ module ActiveRecord module Railties class LogSubscriber < Rails::LogSubscriber + def initialize + super + @odd_or_even = false + end + def sql(event) name = '%s (%.1fms)' % [event.payload[:name], event.duration] sql = event.payload[:sql].squeeze(' ') @@ -24,4 +29,4 @@ def logger end end end -end \ No newline at end of file +end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 32b9a2aa87..5e8fc104cb 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -154,6 +154,11 @@ def klass @klass ||= active_record.send(:compute_type, class_name) end + def initialize(macro, name, options, active_record) + super + @collection = [:has_many, :has_and_belongs_to_many].include?(macro) + end + # Returns a new, unsaved instance of the associated class. +options+ will # be passed to the class's constructor. def build_association(*options) @@ -256,9 +261,6 @@ def polymorphic_inverse_of(associated_class) # association. Returns +true+ if the +macro+ is one of +has_many+ or # +has_and_belongs_to_many+, +false+ otherwise. def collection? - if @collection.nil? - @collection = [:has_many, :has_and_belongs_to_many].include?(macro) - end @collection end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index aca4629dd8..a20c152eeb 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -14,6 +14,11 @@ class Relation def initialize(klass, table) @klass, @table = klass, table + + @implicit_readonly = nil + @loaded = nil + + SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)} (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])} end @@ -54,7 +59,7 @@ def to_a preload = @preload_values preload += @includes_values unless eager_loading? - preload.each {|associations| @klass.send(:preload_associations, @records, associations) } + preload.each {|associations| @klass.send(:preload_associations, @records, associations) } # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT. readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index d6d3d66642..c1cce679b6 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -175,7 +175,7 @@ def construct_relation_for_association_calculations end def construct_relation_for_association_find(join_dependency) - relation = except(:includes, :eager_load, :preload, :select).select(@klass.send(:column_aliases, join_dependency)) + relation = except(:includes, :eager_load, :preload, :select).select(column_aliases(join_dependency)) apply_join_dependency(relation, join_dependency) end @@ -184,7 +184,7 @@ def apply_join_dependency(relation, join_dependency) relation = association.join_relation(relation) end - limitable_reflections = @klass.send(:using_limitable_reflections?, join_dependency.reflections) + limitable_reflections = using_limitable_reflections?(join_dependency.reflections) if !limitable_reflections && relation.limit_value limited_id_condition = construct_limited_ids_condition(relation.except(:select)) @@ -311,5 +311,14 @@ def find_last end end + def column_aliases(join_dependency) + join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name| + "#{connection.quote_table_name join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ") + end + + def using_limitable_reflections?(reflections) + reflections.collect(&:collection?).length.zero? + end + end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e00d9cdf27..0250e739b8 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -6,6 +6,7 @@ module QueryMethods (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method| attr_accessor :"#{query_method}_values" + next if [:where, :having].include?(query_method) class_eval <<-CEVAL def #{query_method}(*args) new_relation = clone @@ -133,13 +134,8 @@ def build_arel arel = h.is_a?(String) ? arel.having(h) : arel.having(*h) end - if defined?(@limit_value) && @limit_value.present? - arel = arel.take(@limit_value) - end - - if defined?(@offset_value) && @offset_value.present? - arel = arel.skip(@offset_value) - end + arel = arel.take(@limit_value) if @limit_value.present? + arel = arel.skip(@offset_value) if @offset_value.present? @group_values.uniq.each do |g| arel = arel.group(g) if g.present? @@ -162,19 +158,14 @@ def build_arel arel = arel.project(quoted_table_name + '.*') end - arel = - if defined?(@from_value) && @from_value.present? - arel.from(@from_value) - else - arel.from(quoted_table_name) - end + arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name) case @lock_value when TrueClass arel = arel.lock when String arel = arel.lock(@lock_value) - end if defined?(@lock_value) + end if @lock_value.present? arel end diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index a18380f01c..2841ff1239 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -4,30 +4,14 @@ def merge(r) merged_relation = clone return merged_relation unless r - merged_relation = merged_relation.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) - - merged_relation.readonly_value = r.readonly_value unless r.readonly_value.nil? - merged_relation.limit_value = r.limit_value if r.limit_value.present? - merged_relation.lock_value = r.lock_value unless merged_relation.lock_value - merged_relation.offset_value = r.offset_value if r.offset_value.present? - - merged_relation = merged_relation. - joins(r.joins_values). - group(r.group_values). - select(r.select_values). - from(r.from_value). - having(r.having_values) - - merged_relation.order_values = r.order_values if r.order_values.present? - - merged_relation.create_with_value = @create_with_value - - if @create_with_value && r.create_with_value - merged_relation.create_with_value = @create_with_value.merge(r.create_with_value) - else - merged_relation.create_with_value = r.create_with_value || @create_with_value + (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method| + unless (value = r.send(:"#{method}_values")).blank? + merged_relation.send(:"#{method}_values=", value) + end end + merged_relation = merged_relation.joins(r.joins_values) + merged_wheres = @where_values r.where_values.each do |w| @@ -40,6 +24,14 @@ def merge(r) merged_relation.where_values = merged_wheres + ActiveRecord::Relation::SINGLE_VALUE_METHODS.reject {|m| m == :lock}.each do |method| + unless (value = r.send(:"#{method}_value")).nil? + merged_relation.send(:"#{method}_value=", value) + end + end + + merged_relation.lock_value = r.lock_value unless merged_relation.lock_value + merged_relation end diff --git a/activerecord/lib/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb similarity index 100% rename from activerecord/lib/generators/active_record.rb rename to activerecord/lib/rails/generators/active_record.rb diff --git a/activerecord/lib/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb similarity index 93% rename from activerecord/lib/generators/active_record/migration/migration_generator.rb rename to activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 7939977f72..f6159deeeb 100644 --- a/activerecord/lib/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -1,4 +1,4 @@ -require 'generators/active_record' +require 'rails/generators/active_record' module ActiveRecord module Generators diff --git a/activerecord/lib/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb similarity index 100% rename from activerecord/lib/generators/active_record/migration/templates/migration.rb rename to activerecord/lib/rails/generators/active_record/migration/templates/migration.rb diff --git a/activerecord/lib/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb similarity index 95% rename from activerecord/lib/generators/active_record/model/model_generator.rb rename to activerecord/lib/rails/generators/active_record/model/model_generator.rb index 2641083e0d..3e72fbeca8 100644 --- a/activerecord/lib/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -1,4 +1,4 @@ -require 'generators/active_record' +require 'rails/generators/active_record' module ActiveRecord module Generators diff --git a/activerecord/lib/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb similarity index 100% rename from activerecord/lib/generators/active_record/model/templates/migration.rb rename to activerecord/lib/rails/generators/active_record/model/templates/migration.rb diff --git a/activerecord/lib/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb similarity index 100% rename from activerecord/lib/generators/active_record/model/templates/model.rb rename to activerecord/lib/rails/generators/active_record/model/templates/model.rb diff --git a/activerecord/lib/generators/active_record/observer/observer_generator.rb b/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb similarity index 88% rename from activerecord/lib/generators/active_record/observer/observer_generator.rb rename to activerecord/lib/rails/generators/active_record/observer/observer_generator.rb index a6b57423b8..c1c0e3f25b 100644 --- a/activerecord/lib/generators/active_record/observer/observer_generator.rb +++ b/activerecord/lib/rails/generators/active_record/observer/observer_generator.rb @@ -1,4 +1,4 @@ -require 'generators/active_record' +require 'rails/generators/active_record' module ActiveRecord module Generators diff --git a/activerecord/lib/generators/active_record/observer/templates/observer.rb b/activerecord/lib/rails/generators/active_record/observer/templates/observer.rb similarity index 100% rename from activerecord/lib/generators/active_record/observer/templates/observer.rb rename to activerecord/lib/rails/generators/active_record/observer/templates/observer.rb diff --git a/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb similarity index 94% rename from activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb rename to activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb index 59c4792066..afcda2a98a 100644 --- a/activerecord/lib/generators/active_record/session_migration/session_migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/session_migration/session_migration_generator.rb @@ -1,4 +1,4 @@ -require 'generators/active_record' +require 'rails/generators/active_record' module ActiveRecord module Generators diff --git a/activerecord/lib/generators/active_record/session_migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb similarity index 100% rename from activerecord/lib/generators/active_record/session_migration/templates/migration.rb rename to activerecord/lib/rails/generators/active_record/session_migration/templates/migration.rb diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index c59be264a4..9b28766405 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -142,4 +142,25 @@ def test_foreign_key_violations_are_translated_to_specific_exception end end end + + def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas + sql_inject = "1 select * from schema" + assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit => sql_inject) + if current_adapter?(:MysqlAdapter) + assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit => sql_inject, :offset => 7) + else + assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit => sql_inject, :offset => 7) + end + end + + def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas + sql_inject = "1, 7 procedure help()" + if current_adapter?(:MysqlAdapter) + assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit => sql_inject) + assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit => '1 ; DROP TABLE USERS', :offset => 7) + else + assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit => sql_inject) + assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit => sql_inject, :offset => 7) + end + end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index ffa6d45948..42a891bc3b 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -805,7 +805,7 @@ def test_preload_has_many_using_primary_key end def test_include_has_many_using_primary_key - expected = Firm.find(1).clients_using_primary_key.sort_by &:name + expected = Firm.find(1).clients_using_primary_key.sort_by(&:name) # Oracle adapter truncates alias to 30 characters if current_adapter?(:OracleAdapter) firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name' diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 608d5a3608..ff799191c2 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -18,10 +18,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies - def test_associate_existing - assert_queries(2) { posts(:thinking);people(:david) } + # Dummies to force column loads so query counts are clean. + def setup + Person.create :first_name => 'gummy' + Reader.create :person_id => 0, :post_id => 0 + end - posts(:thinking).people + def test_associate_existing + assert_queries(2) { posts(:thinking); people(:david) } assert_queries(1) do posts(:thinking).people << people(:david) @@ -287,7 +291,7 @@ def test_association_callback_ordering ], log.last(2) post.people_with_callbacks = [people(:michael),people(:david), Person.new(:first_name => "Julian"), Person.create!(:first_name => "Roger")] - assert_equal (%w(Ted Bob Sam Lary) * 2).sort, log[-12..-5].collect(&:last).sort + assert_equal((%w(Ted Bob Sam Lary) * 2).sort, log[-12..-5].collect(&:last).sort) assert_equal [ [:added, :before, "Julian"], [:added, :after, "Julian"], @@ -296,7 +300,7 @@ def test_association_callback_ordering ], log.last(4) post.people_with_callbacks.clear - assert_equal (%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort + assert_equal((%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort) end def test_dynamic_find_should_respect_association_include diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index d5dbb88886..7372f2da1b 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -186,15 +186,6 @@ def test_succesful_build_association assert_equal account, firm.account end - def test_failing_build_association - firm = Firm.new("name" => "GlobalMegaCorp") - firm.save - - account = firm.build_account - assert !account.save - assert_equal ["can't be empty"], account.errors["credit_limit"] - end - def test_build_association_twice_without_saving_affects_nothing count_of_account = Account.count firm = Firm.find(:first) diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 43abcae75e..4ba867dc7c 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -15,12 +15,12 @@ def test_construct_finder_sql_applies_aliases_tables_on_association_conditions def test_construct_finder_sql_ignores_empty_joins_hash sql = Author.joins({}).to_sql - assert_no_match /JOIN/i, sql + assert_no_match(/JOIN/i, sql) end def test_construct_finder_sql_ignores_empty_joins_array sql = Author.joins([]).to_sql - assert_no_match /JOIN/i, sql + assert_no_match(/JOIN/i, sql) end def test_find_with_implicit_inner_joins_honors_readonly_without_select diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index e9af5a60d8..8bdf8bcd55 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -52,18 +52,6 @@ def test_has_many_uniq_through_dynamic_find assert_equal 1, authors(:mary).unique_categorized_posts.find_all_by_title("So I was thinking").size end - def test_polymorphic_has_many - assert posts(:welcome).taggings.include?(taggings(:welcome_general)) - end - - def test_polymorphic_has_one - assert_equal taggings(:welcome_general), posts(:welcome).tagging - end - - def test_polymorphic_belongs_to - assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable - end - def test_polymorphic_has_many_going_through_join_model assert_equal tags(:general), tag = posts(:welcome).tags.first assert_no_queries do diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 9bc34bd750..d99fb44f01 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -180,6 +180,14 @@ def test_reload_returns_assocition end end + if RUBY_VERSION < '1.9' + def test_splat_does_not_invoke_to_a_on_singular_targets + author = posts(:welcome).author + author.reload.target.expects(:to_a).never + [*author] + end + end + def setup_dangling_association josh = Author.create(:name => "Josh") p = Post.create(:title => "New on Edge", :body => "More cool stuff!", :author => josh) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e3047fe873..8774ed58aa 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -674,10 +674,10 @@ def test_increment_counter def test_decrement_counter Topic.decrement_counter("replies_count", 2) - assert_equal -1, Topic.find(2).replies_count + assert_equal(-1, Topic.find(2).replies_count) Topic.decrement_counter("replies_count", 2) - assert_equal -2, Topic.find(2).replies_count + assert_equal(-2, Topic.find(2).replies_count) end def test_reset_counters @@ -1533,7 +1533,7 @@ def test_numeric_fields def test_auto_id auto = AutoId.new auto.save - assert (auto.id > 0) + assert(auto.id > 0) end def quote_column_name(name) @@ -2181,7 +2181,7 @@ def test_inspect_instance end def test_inspect_new_instance - assert_match /Topic id: nil/, Topic.new.inspect + assert_match(/Topic id: nil/, Topic.new.inspect) end def test_inspect_limited_select_instance @@ -2240,9 +2240,9 @@ def test_benchmark_with_log_level ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count } ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count } ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count } - assert_no_match /Debug Topic Count/, log.string - assert_match /Warn Topic Count/, log.string - assert_match /Error Topic Count/, log.string + assert_no_match(/Debug Topic Count/, log.string) + assert_match(/Warn Topic Count/, log.string) + assert_match(/Error Topic Count/, log.string) ensure ActiveRecord::Base.logger = original_logger end @@ -2253,8 +2253,8 @@ def test_benchmark_with_use_silence ActiveRecord::Base.logger = Logger.new(log) ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" } ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } - assert_no_match /Loud/, log.string - assert_match /Quiet/, log.string + assert_no_match(/Loud/, log.string) + assert_match(/Quiet/, log.string) ensure ActiveRecord::Base.logger = original_logger end diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index ff2322ac15..dc7f82b001 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -43,6 +43,11 @@ def history end end +class CallbackDeveloperWithFalseValidation < CallbackDeveloper + before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false } + before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } +end + class ParentDeveloper < ActiveRecord::Base set_table_name 'developers' attr_accessor :after_save_called @@ -139,7 +144,7 @@ class CallbackCancellationDeveloper < ActiveRecord::Base attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy - before_save { !@cancel_before_save } + before_save {defined?(@cancel_before_save) ? !@cancel_before_save : false} before_create { !@cancel_before_create } before_update { !@cancel_before_update } before_destroy { !@cancel_before_destroy } @@ -437,10 +442,8 @@ def assert_save_callbacks_not_called(someone) end private :assert_save_callbacks_not_called - def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper - david = CallbackDeveloper.find(1) - CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false } - CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } + def test_callback_returning_false + david = CallbackDeveloperWithFalseValidation.find(1) david.save assert_equal [ [ :after_find, :method ], diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index b4032c23e6..bba216ae19 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -24,7 +24,7 @@ def test_default_integers assert_instance_of Fixnum, default.positive_integer assert_equal 1, default.positive_integer assert_instance_of Fixnum, default.negative_integer - assert_equal -1, default.negative_integer + assert_equal(-1, default.negative_integer) assert_instance_of BigDecimal, default.decimal_number assert_equal BigDecimal.new("2.78"), default.decimal_number end @@ -33,7 +33,7 @@ def test_default_integers if current_adapter?(:PostgreSQLAdapter) def test_multiline_default_text # older postgres versions represent the default with escapes ("\\012" for a newline) - assert ( "--- []\n\n" == Default.columns_hash['multiline_default'].default || + assert( "--- []\n\n" == Default.columns_hash['multiline_default'].default || "--- []\\012\\012" == Default.columns_hash['multiline_default'].default) end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 4961d12a44..a64ccb2120 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -26,6 +26,11 @@ class NumericData < ActiveRecord::Base end class DirtyTest < ActiveRecord::TestCase + # Dummy to force column loads so query counts are clean. + def setup + Person.create :first_name => 'foo' + end + def test_attribute_changes # New record - no changes. pirate = Pirate.new diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index d2451f24c1..9e88ec8016 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -502,6 +502,18 @@ def test_named_bind_variables assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on end + class SimpleEnumerable + include Enumerable + + def initialize(ary) + @ary = ary + end + + def each(&b) + @ary.each(&b) + end + end + def test_bind_enumerable quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) @@ -511,12 +523,11 @@ def test_bind_enumerable assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' - require 'set' - assert_equal '1,2,3', bind('?', Set.new([1, 2, 3])) - assert_equal quoted_abc, bind('?', Set.new(%w(a b c))) + assert_equal '1,2,3', bind('?', SimpleEnumerable.new([1, 2, 3])) + assert_equal quoted_abc, bind('?', SimpleEnumerable.new(%w(a b c))) - assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3])) - assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # ' + assert_equal '1,2,3', bind(':a', :a => SimpleEnumerable.new([1, 2, 3])) + assert_equal quoted_abc, bind(':a', :a => SimpleEnumerable.new(%w(a b c))) # ' end def test_bind_empty_enumerable diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index f965652a9a..e78b522b65 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -244,14 +244,14 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase fixtures :topics, :developers, :accounts def test_without_complete_instantiation - assert_nil @first - assert_nil @topics - assert_nil @developers - assert_nil @accounts + assert !defined?(@first) + assert !defined?(@topics) + assert !defined?(@developers) + assert !defined?(@accounts) end def test_fixtures_from_root_yml_without_instantiation - assert_nil @unknown + assert !defined?(@unknown), "@unknown is not defined" end def test_accessor_methods @@ -279,7 +279,7 @@ class FixturesWithoutInstanceInstantiationTest < ActiveRecord::TestCase fixtures :topics, :developers, :accounts def test_without_instance_instantiation - assert_nil @first + assert !defined?(@first), "@first is not defined" assert_not_nil @topics assert_not_nil @developers assert_not_nil @accounts diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index e831ebf36c..1fb59d3589 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -7,6 +7,7 @@ require 'test/unit' require 'stringio' +require 'mocha' require 'active_record' require 'active_support/dependencies' diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index 54bc8e2343..a3145d2c04 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -27,6 +27,8 @@ def test_should_demodulize_root_in_json @contact = NamespacedContact.new :name => 'whatever' json = @contact.to_json assert_match %r{^\{"namespaced_contact":\{}, json + ensure + NamespacedContact.include_root_in_json = false end def test_should_include_root_in_json diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index aa7ce2ecb6..fcad3e90d3 100644 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -6,25 +6,6 @@ class SpecialDeveloper < Developer; end -class TopicManualObserver - include Singleton - - attr_reader :action, :object, :callbacks - - def initialize - Topic.add_observer(self) - @callbacks = [] - end - - def update(callback_method, object) - @callbacks << { "callback_method" => callback_method, "object" => object } - end - - def has_been_notified? - !@callbacks.empty? - end -end - class TopicaAuditor < ActiveRecord::Observer observe :topic @@ -85,27 +66,6 @@ def test_before_destroy assert_equal original_count - (1 + topic_to_be_destroyed.replies.size), Topic.count end - def test_after_save - ActiveRecord::Base.observers = :topic_manual_observer - ActiveRecord::Base.instantiate_observers - - topic = Topic.find(1) - topic.title = "hello" - topic.save - - assert TopicManualObserver.instance.has_been_notified? - assert_equal :after_save, TopicManualObserver.instance.callbacks.last["callback_method"] - end - - def test_observer_update_on_save - ActiveRecord::Base.observers = TopicManualObserver - ActiveRecord::Base.instantiate_observers - - topic = Topic.find(1) - assert TopicManualObserver.instance.has_been_notified? - assert_equal :after_find, TopicManualObserver.instance.callbacks.first["callback_method"] - end - def test_auto_observer topic_observer = TopicaAuditor.instance assert_nil TopicaAuditor.observed_class diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index f3b94eb829..6ba84fa57b 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -26,8 +26,8 @@ def test_basic_query_logging Developer.all wait assert_equal 1, @logger.logged(:debug).size - assert_match /Developer Load/, @logger.logged(:debug).last - assert_match /SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last + assert_match(/Developer Load/, @logger.logged(:debug).last) + assert_match(/SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last) end def test_cached_queries @@ -37,7 +37,7 @@ def test_cached_queries end wait assert_equal 2, @logger.logged(:debug).size - assert_match /CACHE/, @logger.logged(:debug).last - assert_match /SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last + assert_match(/CACHE/, @logger.logged(:debug).last) + assert_match(/SELECT .*?FROM .?developers.?/, @logger.logged(:debug).last) end -end \ No newline at end of file +end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 3151457440..a3b496a0e6 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -382,7 +382,7 @@ def test_merged_scoped_find Developer.send(:with_scope, :find => { :conditions => "salary < 100000" }) do Developer.send(:with_scope, :find => { :offset => 1, :order => 'id asc' }) do # Oracle adapter does not generated space after asc therefore trailing space removed from regex - assert_sql /ORDER BY id asc/ do + assert_sql(/ORDER BY id asc/) do assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) end end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index dd32eeeff2..e213986ede 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -477,7 +477,7 @@ def test_native_types assert_not_equal "Z", bob.moment_of_truth.zone # US/Eastern is -5 hours from GMT assert_equal Rational(-5, 24), bob.moment_of_truth.offset - assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM + assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM assert_equal DateTime::ITALY, bob.moment_of_truth.start end end @@ -493,7 +493,7 @@ def test_unabstracted_database_dependent_types ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint Person.reset_column_information - assert_match /tinyint/, Person.columns_hash['intelligence_quotient'].sql_type + assert_match(/tinyint/, Person.columns_hash['intelligence_quotient'].sql_type) ensure ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil end @@ -1104,13 +1104,25 @@ def test_finds_pending_migrations assert_equal migrations[0].name, 'InnocentJointable' end + def test_relative_migrations + $".delete_if do |fname| + fname == (MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb") + end + Object.send(:remove_const, :PeopleHaveLastNames) + + Dir.chdir(MIGRATIONS_ROOT) do + ActiveRecord::Migrator.up("valid/", 1) + end + + assert defined?(PeopleHaveLastNames) + end + def test_only_loads_pending_migrations # migrate up to 1 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) # now unload the migrations that have been defined - PeopleHaveLastNames.unloadable - ActiveSupport::Dependencies.remove_unloadable_constants! + Object.send(:remove_const, :PeopleHaveLastNames) ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 894d96346e..6c2b4fa3a7 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -83,8 +83,8 @@ def test_scopes_can_be_specified_with_deep_hash_conditions end def test_scopes_are_composable - assert_equal (approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved - assert_equal (replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied + assert_equal((approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved) + assert_equal((replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied) assert !(approved == replied) assert !(approved & replied).empty? diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 3710f8e40b..91349689bc 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -10,6 +10,10 @@ class QueryCacheTest < ActiveRecord::TestCase fixtures :tasks, :topics, :categories, :posts, :categories_posts + def setup + Task.connection.clear_query_cache + end + def test_find_queries assert_queries(2) { Task.find(1); Task.find(1) } end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 2c9158aa7b..67818622d7 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -22,12 +22,6 @@ def test_human_name assert_equal "Subscriber", Subscriber.model_name.human end - def test_column_null_not_null - subscriber = Subscriber.find(:first) - assert subscriber.column_for_attribute("name").null - assert !subscriber.column_for_attribute("nick").null - end - def test_read_attribute_names assert_equal( %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id parent_title type ).sort, diff --git a/activerecord/test/cases/schema_authorization_test_postgresql.rb b/activerecord/test/cases/schema_authorization_test_postgresql.rb index ba7754513d..2860f1ad48 100644 --- a/activerecord/test/cases/schema_authorization_test_postgresql.rb +++ b/activerecord/test/cases/schema_authorization_test_postgresql.rb @@ -66,6 +66,15 @@ def test_sequence_schema_caching end end end + + def test_tables_in_current_schemas + assert !@connection.tables.include?(TABLE_NAME) + USERS.each do |u| + set_session_auth u + assert @connection.tables.include?(TABLE_NAME) + set_session_auth + end + end private def set_session_auth auth = nil diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index aca70b4238..c550030329 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -51,6 +51,7 @@ def commit_db_transaction assert !Topic.find(2).approved?, "Second should have been unapproved" ensure class << Topic.connection + remove_method :commit_db_transaction alias :commit_db_transaction :real_commit_db_transaction rescue nil end end @@ -382,28 +383,53 @@ def test_sqlite_add_column_in_transaction private def add_exception_raising_after_save_callback_to_topic - Topic.class_eval "def after_save_for_transaction; raise 'Make the transaction rollback' end" + Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 + remove_method(:after_save_for_transaction) + def after_save_for_transaction + raise 'Make the transaction rollback' + end + eoruby end def remove_exception_raising_after_save_callback_to_topic - Topic.class_eval "def after_save_for_transaction; end" + Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 + remove_method :after_save_for_transaction + def after_save_for_transaction; end + eoruby end def add_exception_raising_after_create_callback_to_topic - Topic.class_eval "def after_create_for_transaction; raise 'Make the transaction rollback' end" + Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 + remove_method(:after_create_for_transaction) + def after_create_for_transaction + raise 'Make the transaction rollback' + end + eoruby end def remove_exception_raising_after_create_callback_to_topic - Topic.class_eval "def after_create_for_transaction; end" + Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 + remove_method :after_create_for_transaction + def after_create_for_transaction; end + eoruby end %w(validation save destroy).each do |filter| define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do - Topic.class_eval "def before_#{filter}_for_transaction() Book.create; false end" + Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 + remove_method :before_#{filter}_for_transaction + def before_#{filter}_for_transaction + Book.create + false + end + eoruby end define_method("remove_cancelling_before_#{filter}_with_db_side_effect_to_topic") do - Topic.class_eval "def before_#{filter}_for_transaction; end" + Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1 + remove_method :before_#{filter}_for_transaction + def before_#{filter}_for_transaction; end + eoruby end end end diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index a0ff35f948..38fa2b821d 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -47,25 +47,6 @@ def test_validates_uniqueness_of_generates_message_with_custom_default_message @topic.valid? end - # validates_uniqueness_of w/o mocha - - def test_validates_associated_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:taken => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:taken => 'global message'}}} - - Topic.validates_uniqueness_of :title - unique_topic.valid? - assert_equal ['custom message'], unique_topic.errors[:replies] - end - - def test_validates_associated_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:taken => 'global message'}}} - - Topic.validates_uniqueness_of :title - unique_topic.valid? - assert_equal ['global message'], unique_topic.errors[:replies] - end - # validates_associated w/ mocha def test_validates_associated_generates_message diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index b841108bd1..67296a171a 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -625,6 +625,22 @@ def element_path(id, prefix_options = {}, query_options = nil) "#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}" end + # Gets the new element path for REST resources. + # + # ==== Options + # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., :account_id => 19 + # would yield a URL like /accounts/19/purchases/new.xml). + # + # ==== Examples + # Post.new_element_path + # # => /posts/new.xml + # + # Comment.collection_path(:post_id => 5) + # # => /posts/5/comments/new.xml + def new_element_path(prefix_options = {}) + "#{prefix(prefix_options)}#{collection_name}/new.#{format.extension}" + end + # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails # will split from the +prefix_options+. # @@ -653,6 +669,19 @@ def collection_path(prefix_options = {}, query_options = nil) alias_method :set_primary_key, :primary_key= #:nodoc: + # Builds a new, unsaved record using the default values from the remote server so + # that it can be used with RESTful forms. + # + # ==== Options + # * +attributes+ - A hash that overrides the default values from the server. + # + # Returns the new resource instance. + # + def build(attributes = {}) + attrs = connection.get("#{new_element_path}").merge(attributes) + self.new(attrs) + end + # Creates a new resource instance and makes a request to the remote service # that it be saved, making it equivalent to the following simultaneous calls: # @@ -989,6 +1018,22 @@ def new? end alias :new_record? :new? + # Returns +true+ if this object has been saved, otherwise returns +false+. + # + # ==== Examples + # persisted = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall') + # persisted.persisted? # => true + # + # not_persisted = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM') + # not_persisted.persisted? # => false + # + # not_persisted.save + # not_persisted.persisted? # => true + # + def persisted? + !new? + end + # Gets the \id attribute of the resource. def id attributes[self.class.primary_key] @@ -1346,6 +1391,10 @@ def element_path(options = nil) self.class.element_path(to_param, options || prefix_options) end + def new_element_path + self.class.new_element_path(prefix_options) + end + def collection_path(options = nil) self.class.collection_path(options || prefix_options) end diff --git a/activeresource/lib/active_resource/railtie.rb b/activeresource/lib/active_resource/railtie.rb index 27c88415f6..aa878c7212 100644 --- a/activeresource/lib/active_resource/railtie.rb +++ b/activeresource/lib/active_resource/railtie.rb @@ -3,10 +3,10 @@ module ActiveResource class Railtie < Rails::Railtie - railtie_name :active_resource + config.active_resource = ActiveSupport::OrderedOptions.new require "active_resource/railties/log_subscriber" - log_subscriber ActiveResource::Railties::LogSubscriber.new + log_subscriber :active_resource, ActiveResource::Railties::LogSubscriber.new initializer "active_resource.set_configs" do |app| app.config.active_resource.each do |k,v| diff --git a/activeresource/test/setter_trap.rb b/activeresource/test/setter_trap.rb index 7cfd9ca111..437fbdad32 100644 --- a/activeresource/test/setter_trap.rb +++ b/activeresource/test/setter_trap.rb @@ -1,3 +1,5 @@ +require 'abstract_unit' + class SetterTrap < ActiveSupport::BasicObject class << self def rollback_sets(obj) diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 78fb48924e..bfb1e83002 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -18,8 +18,8 @@ s.has_rdoc = true - s.add_dependency('i18n', '~> 0.3.6.pre') + s.add_dependency('i18n', '~> 0.3.6') s.add_dependency('tzinfo', '~> 0.3.16') s.add_dependency('builder', '~> 2.1.2') - s.add_dependency('memcache-client', '~> 1.7.5') + s.add_dependency('memcache-client', '>= 1.7.5') end diff --git a/activesupport/lib/active_support/core_ext/array/extract_options.rb b/activesupport/lib/active_support/core_ext/array/extract_options.rb index 9ca32dc7aa..40ceb3eb9e 100644 --- a/activesupport/lib/active_support/core_ext/array/extract_options.rb +++ b/activesupport/lib/active_support/core_ext/array/extract_options.rb @@ -1,3 +1,14 @@ +class Hash + # By default, only instances of Hash itself are extractable. + # Subclasses of Hash may implement this method and return + # true to declare themselves as extractable. If a Hash + # is extractable, Array#extract_options! pops it from + # the Array when it is the last element of the Array. + def extractable_options? + instance_of?(Hash) + end +end + class Array # Extracts options from a set of arguments. Removes and returns the last # element in the array if it's a hash, otherwise returns a blank hash. @@ -9,6 +20,10 @@ class Array # options(1, 2) # => {} # options(1, 2, :a => :b) # => {:a=>:b} def extract_options! - last.is_a?(::Hash) ? pop : {} + if last.is_a?(Hash) && last.extractable_options? + pop + else + {} + end end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index c18905b369..9631a7d242 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/singleton_class' require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/module/remove_method' class Class # Declare a class-level attribute whose value is inheritable and @@ -45,12 +46,14 @@ def class_attribute(*attrs) s.send(:define_method, attr) { } s.send(:define_method, :"#{attr}?") { !!send(attr) } s.send(:define_method, :"#{attr}=") do |value| + singleton_class.remove_possible_method(attr) singleton_class.send(:define_method, attr) { value } end define_method(attr) { self.class.send(attr) } define_method(:"#{attr}?") { !!send(attr) } define_method(:"#{attr}=") do |value| + singleton_class.remove_possible_method(attr) singleton_class.send(:define_method, attr) { value } end if instance_writer end diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index 1602a609eb..feef5d2d57 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -10,42 +10,48 @@ # Person.hair_colors = [:brown, :black, :blonde, :red] class Class def cattr_reader(*syms) - syms.flatten.each do |sym| - next if sym.is_a?(Hash) + options = syms.extract_options! + syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} # unless defined? @@hair_colors - @@#{sym} = nil # @@hair_colors = nil - end # end - # - def self.#{sym} # def self.hair_colors - @@#{sym} # @@hair_colors - end # end - # - def #{sym} # def hair_colors - @@#{sym} # @@hair_colors - end # end + unless defined? @@#{sym} + @@#{sym} = nil + end + + def self.#{sym} + @@#{sym} + end EOS + + unless options[:instance_reader] == false + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + @@#{sym} + end + EOS + end end end def cattr_writer(*syms) options = syms.extract_options! - syms.flatten.each do |sym| + syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} # unless defined? @@hair_colors - @@#{sym} = nil # @@hair_colors = nil - end # end - # - def self.#{sym}=(obj) # def self.hair_colors=(obj) - @@#{sym} = obj # @@hair_colors = obj - end # end - # - #{" # - def #{sym}=(obj) # def hair_colors=(obj) - @@#{sym} = obj # @@hair_colors = obj - end # end - " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false + unless defined? @@#{sym} + @@#{sym} = nil + end + + def self.#{sym}=(obj) + @@#{sym} = obj + end EOS + + unless options[:instance_writer] == false + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym}=(obj) + @@#{sym} = obj + end + EOS + end self.send("#{sym}=", yield) if block_given? end end diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index b5785bdcd3..12caa76c98 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/object/singleton_class' +require 'active_support/core_ext/module/remove_method' class Class def superclass_delegating_accessor(name, options = {}) @@ -27,7 +28,9 @@ def superclass_delegating_accessor(name, options = {}) # inheritance behavior, without having to store the object in an instance # variable and look up the superclass chain manually. def _stash_object_in_method(object, method, instance_reader = true) + singleton_class.remove_possible_method(method) singleton_class.send(:define_method, method) { object } + remove_possible_method(method) define_method(method) { object } if instance_reader end @@ -35,7 +38,7 @@ def _superclass_delegating_accessor(name, options = {}) singleton_class.send(:define_method, "#{name}=") do |value| _stash_object_in_method(value, name, options[:instance_reader] != false) end - self.send("#{name}=", nil) + send("#{name}=", nil) end end diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 47a31839a6..a9f821b01e 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -1,4 +1,5 @@ require 'active_support/inflector' +require 'active_support/core_ext/time/conversions' class DateTime # Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 48b185d05e..c882434f78 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -54,6 +54,15 @@ def content_type "string" => Proc.new { |string| string.to_s }, "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, "base64Binary" => Proc.new { |bin| ActiveSupport::Base64.decode64(bin) }, + "binary" => Proc.new do |bin, entity| + case entity['encoding'] + when 'base64' + ActiveSupport::Base64.decode64(bin) + # TODO: Add support for other encodings + else + bin + end + end, "file" => Proc.new do |file, entity| f = StringIO.new(ActiveSupport::Base64.decode64(file)) f.extend(FileLike) diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index d9b84e6543..ac35db6ab6 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -37,7 +37,7 @@ def silence_stderr #:nodoc: # puts 'But this will' def silence_stream(stream) old_stream = stream.dup - stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null') + stream.reopen(RUBY_PLATFORM =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') stream.sync = true yield ensure diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index bf272e9e73..f59fcd123c 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -7,4 +7,6 @@ require 'active_support/core_ext/module/attr_accessor_with_default' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/synchronization' -require 'active_support/core_ext/module/deprecation' \ No newline at end of file +require 'active_support/core_ext/module/deprecation' +require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/module/method_names' \ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 131b512944..9c4d5fae26 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -2,21 +2,25 @@ class Module def mattr_reader(*syms) - syms.extract_options! + options = syms.extract_options! syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} # unless defined? @@pagination_options - @@#{sym} = nil # @@pagination_options = nil - end # end + unless defined? @@#{sym} + @@#{sym} = nil + end - def self.#{sym} # def self.pagination_options - @@#{sym} # @@pagination_options - end # end - - def #{sym} # def pagination_options - @@#{sym} # @@pagination_options - end # end + def self.#{sym} + @@#{sym} + end EOS + + unless options[:instance_reader] == false + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + @@#{sym} + end + EOS + end end end @@ -24,20 +28,20 @@ def mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| class_eval(<<-EOS, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} # unless defined? @@pagination_options - @@#{sym} = nil # @@pagination_options = nil - end # end + unless defined? @@#{sym} + @@#{sym} = nil + end - def self.#{sym}=(obj) # def self.pagination_options=(obj) - @@#{sym} = obj # @@pagination_options = obj - end # end + def self.#{sym}=(obj) + @@#{sym} = obj + end EOS unless options[:instance_writer] == false - class_eval(<<-EOS, __FILE__, __LINE__) - def #{sym}=(obj) # def pagination_options=(obj) - @@#{sym} = obj # @@pagination_options = obj - end # end + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym}=(obj) + @@#{sym} = obj + end EOS end end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index df8aefea5a..b73f4c2b59 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,14 +1,21 @@ +require "active_support/core_ext/module/remove_method" + class Module # Provides a delegate class method to easily expose contained objects' methods # as your own. Pass one or more methods (specified as symbols or strings) - # and the name of the target object as the final :to option (also a symbol - # or string). At least one method and the :to option are required. + # and the name of the target object via the :to option (also a symbol + # or string). At least one method and the :to option are required. # # Delegation is particularly useful with Active Record associations: # # class Greeter < ActiveRecord::Base - # def hello() "hello" end - # def goodbye() "goodbye" end + # def hello + # "hello" + # end + # + # def goodbye + # "goodbye" + # end # end # # class Foo < ActiveRecord::Base @@ -34,7 +41,7 @@ class Module # class Foo # CONSTANT_ARRAY = [0,1,2,3] # @@class_array = [4,5,6,7] - # + # # def initialize # @instance_array = [8,9,10,11] # end @@ -72,9 +79,9 @@ class Module # invoice.customer_name # => "John Doe" # invoice.customer_address # => "Vimmersvej 13" # - # If the object to which you delegate can be nil, you may want to use the - # :allow_nil option. In that case, it returns nil instead of raising a - # NoMethodError exception: + # If the delegate object is +nil+ an exception is raised, and that happens + # no matter whether +nil+ responds to the delegated method. You can get a + # +nil+ instead with the +:allow_nil+ option. # # class Foo # attr_accessor :bar @@ -120,11 +127,15 @@ def delegate(*methods) end module_eval(<<-EOS, file, line) + if instance_methods(false).map(&:to_s).include?("#{prefix}#{method}") + remove_possible_method("#{prefix}#{method}") + end + def #{prefix}#{method}(*args, &block) # def customer_name(*args, &block) #{to}.__send__(#{method.inspect}, *args, &block) # client.__send__(:name, *args, &block) rescue NoMethodError # rescue NoMethodError if #{to}.nil? # if client.nil? - #{on_nil} + #{on_nil} # return # depends on :allow_nil else # else raise # raise end # end diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 23a1063901..c08ad251dd 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -3,7 +3,7 @@ class Module # Returns the name of the module containing this one. # - # p M::N.parent_name # => "M" + # M::N.parent_name # => "M" def parent_name unless defined? @parent_name @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil @@ -19,13 +19,13 @@ def parent_name # end # X = M::N # - # p M::N.parent # => M - # p X.parent # => M + # M::N.parent # => M + # X.parent # => M # # The parent of top-level and anonymous modules is Object. # - # p M.parent # => Object - # p Module.new.parent # => Object + # M.parent # => Object + # Module.new.parent # => Object # def parent parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object @@ -40,9 +40,9 @@ def parent # end # X = M::N # - # p M.parents # => [Object] - # p M::N.parents # => [M, Object] - # p X.parents # => [M, Object] + # M.parents # => [Object] + # M::N.parents # => [M, Object] + # X.parents # => [M, Object] # def parents parents = [] diff --git a/activesupport/lib/active_support/core_ext/module/method_names.rb b/activesupport/lib/active_support/core_ext/module/method_names.rb new file mode 100644 index 0000000000..2eb40a83ab --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/method_names.rb @@ -0,0 +1,14 @@ +class Module + if instance_methods[0].is_a?(Symbol) + def instance_method_names(*args) + instance_methods(*args).map(&:to_s) + end + + def method_names(*args) + methods(*args).map(&:to_s) + end + else + alias_method :instance_method_names, :instance_methods + alias_method :method_names, :methods + end +end \ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb new file mode 100644 index 0000000000..2714a46b28 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -0,0 +1,6 @@ +class Module + def remove_possible_method(method) + remove_method(method) + rescue NameError + end +end \ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index fbb7b79fc6..48b028bb64 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,3 @@ -require 'active_support/inflector' - # String inflections define new methods on the String class to transform names for different purposes. # For instance, you can figure out the name of a database from the name of a class. # 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 567ba00b0d..3ee5bcaab4 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,4 +1,5 @@ -require "erb" +require 'erb' +require 'active_support/core_ext/object/singleton_class' class ERB module Util @@ -23,12 +24,14 @@ def html_escape(s) end end - undef :h + remove_method(:h) alias h html_escape - module_function :html_escape module_function :h + singleton_class.send(:remove_method, :html_escape) + module_function :html_escape + # A utility method for escaping HTML entities in JSON strings. # This method is also aliased as j. # @@ -88,6 +91,10 @@ def html_safe def to_s self end + + def to_yaml(*args) + to_str.to_yaml(*args) + end end end diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 6d9c080442..86103ebce2 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -1,4 +1,5 @@ require 'active_support/inflector' +require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/values/time_zone' class Time diff --git a/activesupport/lib/active_support/core_ext/time/marshal_with_utc_flag.rb b/activesupport/lib/active_support/core_ext/time/marshal_with_utc_flag.rb index 9de8157eb0..8d46d80251 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal_with_utc_flag.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal_with_utc_flag.rb @@ -7,14 +7,18 @@ class << self alias_method :_original_load, :_load def _load(marshaled_time) time = _original_load(marshaled_time) - utc = time.instance_variable_get('@marshal_with_utc_coercion') - utc ? time.utc : time + time.instance_eval do + if defined?(@marshal_with_utc_coercion) + val = remove_instance_variable("@marshal_with_utc_coercion") + end + val ? utc : self + end end end alias_method :_original_dump, :_dump def _dump(*args) - obj = frozen? ? dup : self + obj = dup obj.instance_variable_set('@marshal_with_utc_coercion', utc?) obj._original_dump(*args) end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 543dab4a75..8241b69c8b 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -4,6 +4,10 @@ module ActiveSupport class HashWithIndifferentAccess < Hash + def extractable_options? + true + end + def initialize(constructor = {}) if constructor.is_a?(Hash) super() diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 236f2eb628..2ce27cf406 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -14,8 +14,8 @@ def transliterate(string) if RUBY_VERSION >= '1.9' undef_method :transliterate def transliterate(string) - warn "Ruby 1.9 doesn't support Unicode normalization yet" - string.dup + proxy = ActiveSupport::Multibyte.proxy_class.new(string) + proxy.normalize(:kd).gsub(/[^\x00-\x7F]+/, '') end # The iconv transliteration code doesn't function correctly diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb index 7e6f7d754b..428c48a484 100644 --- a/activesupport/lib/active_support/multibyte.rb +++ b/activesupport/lib/active_support/multibyte.rb @@ -53,8 +53,8 @@ def self.proxy_class \xf4 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf])\z /xn, # Quick check for valid Shift-JIS characters, disregards the odd-even pairing 'Shift_JIS' => /\A(?: - [\x00-\x7e \xa1-\xdf] | - [\x81-\x9f \xe0-\xef] [\x40-\x7e \x80-\x9e \x9f-\xfc])\z /xn + [\x00-\x7e\xa1-\xdf] | + [\x81-\x9f\xe0-\xef] [\x40-\x7e\x80-\x9e\x9f-\xfc])\z /xn } end end diff --git a/activesupport/lib/active_support/multibyte/utils.rb b/activesupport/lib/active_support/multibyte/utils.rb index b243df46d8..94b393cee2 100644 --- a/activesupport/lib/active_support/multibyte/utils.rb +++ b/activesupport/lib/active_support/multibyte/utils.rb @@ -27,7 +27,7 @@ def self.verify(string) def self.verify(string) if expression = valid_character # Splits the string on character boundaries, which are determined based on $KCODE. - string.split(//).all? { |c| expression.match(c) } + string.split(//).all? { |c| expression =~ c } else true end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index cd60054862..a3ddc7705a 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -44,7 +44,7 @@ def initialize(queue, pattern) when Regexp, NilClass pattern else - /^#{Regexp.escape(pattern.to_s)}/ + /^#{Regexp.escape(pattern.to_s)}$/ end end diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index d2c13e030d..e45d16ee96 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -3,7 +3,7 @@ module ActiveSupport class Railtie < Rails::Railtie - railtie_name :active_support + config.active_support = ActiveSupport::OrderedOptions.new # Loads support for "whiny nil" (noisy warnings when methods are invoked # on +nil+ values) if Configuration#whiny_nils is true. @@ -30,9 +30,7 @@ class Railtie < Rails::Railtie module I18n class Railtie < Rails::Railtie - railtie_name :i18n - - # Initialize I18n load paths to an array + config.i18n = ActiveSupport::OrderedOptions.new config.i18n.railties_load_path = [] config.i18n.load_path = [] diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb index f0db5b3021..4a9ac920e8 100644 --- a/activesupport/lib/active_support/ruby/shim.rb +++ b/activesupport/lib/active_support/ruby/shim.rb @@ -18,3 +18,4 @@ require 'active_support/core_ext/rexml' require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/file/path' +require 'active_support/core_ext/module/method_names' \ No newline at end of file diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index ab30984d62..ed8c02ba3e 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -5,9 +5,10 @@ require 'active_support/testing/declarative' require 'active_support/testing/pending' require 'active_support/testing/isolation' +require 'active_support/core_ext/kernel/reporting' begin - require 'mocha' + silence_warnings { require 'mocha' } rescue LoadError # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh. Object.const_set :Mocha, Module.new diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 453f4fcc0f..9507dbf473 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -78,8 +78,8 @@ def run(runner) @@ran_class_setup = true end - serialized = run_in_isolation do |runner| - super(runner) + serialized = run_in_isolation do |isolated_runner| + super(isolated_runner) end retval, proxy = Marshal.load(serialized) diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 4db3dd1705..3cb4d89e02 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -305,8 +305,8 @@ def period_for_local(time, dst=true) # TODO: Preload instead of lazy load for thread safety def tzinfo - require 'tzinfo' unless defined?(TZInfo) - @tzinfo ||= TZInfo::Timezone.get(MAPPING[name]) + require 'tzinfo' unless defined?(::TZInfo) + @tzinfo ||= ::TZInfo::Timezone.get(MAPPING[name]) end unless const_defined?(:ZONES) diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index da338a2a26..67f652325e 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -1,10 +1,17 @@ -require File.expand_path('../../../load_paths', __FILE__) +begin + old, $VERBOSE = $VERBOSE, nil + require File.expand_path('../../../load_paths', __FILE__) +ensure + $VERBOSE = old +end lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) require 'test/unit' -require 'mocha' +require 'active_support/core_ext/kernel/reporting' + +silence_warnings { require 'mocha' } ENV['NO_RELOAD'] = '1' require 'active_support' diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb index e74c64ba8d..8caf000c5d 100644 --- a/activesupport/test/callback_inheritance_test.rb +++ b/activesupport/test/callback_inheritance_test.rb @@ -1,3 +1,4 @@ +require 'abstract_unit' require 'test/unit' require 'active_support' diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 3fb940ad3c..49d9de63b0 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -1,4 +1,4 @@ -# require 'abstract_unit' +require 'abstract_unit' require 'test/unit' require 'active_support' diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index b374eca370..aecc644549 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/object/conversions' require 'active_support/core_ext' # FIXME: pulling in all to_xml extensions +require 'active_support/hash_with_indifferent_access' class ArrayExtAccessTests < Test::Unit::TestCase def test_from @@ -294,12 +295,45 @@ def test_to_xml_dups_options end class ArrayExtractOptionsTests < Test::Unit::TestCase + class HashSubclass < Hash + end + + class ExtractableHashSubclass < Hash + def extractable_options? + true + end + end + def test_extract_options assert_equal({}, [].extract_options!) assert_equal({}, [1].extract_options!) assert_equal({:a=>:b}, [{:a=>:b}].extract_options!) assert_equal({:a=>:b}, [1, {:a=>:b}].extract_options!) end + + def test_extract_options_doesnt_extract_hash_subclasses + hash = HashSubclass.new + hash[:foo] = 1 + array = [hash] + options = array.extract_options! + assert_equal({}, options) + assert_equal [hash], array + end + + def test_extract_options_extracts_extractable_subclass + hash = ExtractableHashSubclass.new + hash[:foo] = 1 + array = [hash] + options = array.extract_options! + assert_equal({:foo => 1}, options) + assert_equal [], array + end + + def test_extract_options_extracts_hwia + hash = [{:foo => 1}.with_indifferent_access] + options = hash.extract_options! + assert_equal 1, options[:foo] + end end class ArrayUniqByTests < Test::Unit::TestCase diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 2214ba9894..0f579d12e5 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -5,7 +5,8 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase def setup @class = Class.new do cattr_accessor :foo - cattr_accessor :bar, :instance_writer => false + cattr_accessor :bar, :instance_writer => false + cattr_reader :shaq, :instance_reader => false end @object = @class.new end @@ -29,4 +30,9 @@ def test_should_not_create_instance_writer assert @object.respond_to?(:bar) assert !@object.respond_to?(:bar=) end + + def test_should_not_create_instance_reader + assert @class.respond_to?(:shaq) + assert !@object.respond_to?(:shaq) + end end diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index 636edb8d4b..6d6cb61571 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -11,6 +11,13 @@ class Child < Parent class Mokopuna < Child end + + class PercysMom + superclass_delegating_accessor :superpower + end + + class Percy < PercysMom + end end class DelegatingAttributesTest < Test::Unit::TestCase @@ -70,18 +77,17 @@ def test_child_class_delegates_to_parent_but_can_be_overridden end def test_delegation_stops_at_the_right_level - assert_nil Mokopuna.some_attribute - assert_nil Child.some_attribute - Child.some_attribute="1" - assert_equal "1", Mokopuna.some_attribute - ensure - Child.some_attribute=nil + assert_nil Percy.superpower + assert_nil PercysMom.superpower + + PercysMom.superpower = :heatvision + assert_equal :heatvision, Percy.superpower end - + def test_delegation_stops_for_nil Mokopuna.some_attribute = nil Child.some_attribute="1" - + assert_equal "1", Child.some_attribute assert_nil Mokopuna.some_attribute ensure diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 5b1d53ac7b..86272a28c1 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -757,6 +757,7 @@ def test_xsd_like_types_from_xml 2007-12-25T12:34:56+0000 YmFiZS5wbmc= + VGhhdCdsbCBkbywgcGlnLg== EOT @@ -766,7 +767,8 @@ def test_xsd_like_types_from_xml :price => BigDecimal("12.50"), :expires_at => Time.utc(2007,12,25,12,34,56), :notes => "", - :illustration => "babe.png" + :illustration => "babe.png", + :caption => "That'll do, pig." }.stringify_keys assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"] diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index bd9461e62c..263e78feaa 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -6,6 +6,7 @@ def setup m = @module = Module.new do mattr_accessor :foo mattr_accessor :bar, :instance_writer => false + mattr_reader :shaq, :instance_reader => false end @class = Class.new @class.instance_eval { include m } @@ -31,4 +32,9 @@ def test_should_not_create_instance_writer assert @object.respond_to?(:bar) assert !@object.respond_to?(:bar=) end + + def test_should_not_create_instance_reader + assert @module.respond_to?(:shaq) + assert !@object.respond_to?(:shaq) + end end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 9edd7cc7c0..1712b0649b 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -141,6 +141,20 @@ def test_delegation_to_method_that_exists_on_nil_when_allowing_nil assert_equal 0.0, nil_project.to_f end + def test_delegation_does_not_raise_error_when_removing_singleton_instance_methods + parent = Class.new do + def self.parent_method; end + end + + assert_nothing_raised do + child = Class.new(parent) do + class << self + delegate :parent_method, :to => :superclass + end + end + end + end + def test_parent assert_equal Yz::Zy, Yz::Zy::Cd.parent assert_equal Yz, Yz::Zy.parent diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index d8145d467b..234e41c772 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -285,7 +285,7 @@ def test_sprintf def test_percent assert_equal("% 1", "%% %d" % {:num => 1.0}) - assert_equal("%{num} %d", "%%{num} %%d" % {:num => 1}) + assert_equal("%{num} %d 1", "%%{num} %%d %d" % {:num => 1}) end def test_sprintf_percent_in_replacement @@ -444,6 +444,10 @@ def to_s assert_equal "hello".concat(13), string assert string.html_safe? end + + test 'emits normal string yaml' do + assert_equal 'foo'.to_yaml, 'foo'.html_safe.to_yaml(:foo => 1) + end end class StringExcludeTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 08c079e113..159b7d8366 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -761,7 +761,7 @@ def test_marshaling_with_utc_instance marshaled = Marshal.dump t unmarshaled = Marshal.load marshaled assert_equal t, unmarshaled - assert_equal t.zone, unmarshaled.zone + assert_equal "UTC", unmarshaled.zone end def test_marshaling_with_local_instance @@ -777,7 +777,7 @@ def test_marshaling_with_frozen_utc_instance marshaled = Marshal.dump t unmarshaled = Marshal.load marshaled assert_equal t, unmarshaled - assert_equal t.zone, unmarshaled.zone + assert_equal "UTC", unmarshaled.zone end def test_marshaling_with_frozen_local_instance diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 6ff6dfb607..2cbf9e5042 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -88,7 +88,7 @@ def test_warnings_should_be_enabled_on_first_load old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true filename = "check_warnings" - expanded = File.expand_path("test/dependencies/#{filename}") + expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}") $check_warnings_load_count = 0 assert !ActiveSupport::Dependencies.loaded.include?(expanded) diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb index 1cd313ec81..91b856ed7c 100644 --- a/activesupport/test/flush_cache_on_private_memoization_test.rb +++ b/activesupport/test/flush_cache_on_private_memoization_test.rb @@ -1,3 +1,4 @@ +require 'abstract_unit' require 'active_support' require 'test/unit' diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index ebd26d3fc6..56372903f3 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -180,18 +180,10 @@ module InflectorTestCases "Test with + sign" => "test_with_sign" } - # Ruby 1.9 doesn't do Unicode normalization yet. - if RUBY_VERSION >= '1.9' - StringToParameterizedAndNormalized = { - "Malmö" => "malm", - "Garçons" => "gar-ons" - } - else - StringToParameterizedAndNormalized = { - "Malmö" => "malmo", - "Garçons" => "garcons" - } - end + StringToParameterizedAndNormalized = { + "Malmö" => "malmo", + "Garçons" => "garcons" + } UnderscoreToHuman = { "employee_salary" => "Employee salary", diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 67c3527e23..92fbe5b92f 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -65,7 +65,7 @@ def test_publishing_after_a_new_subscribe_works assert_equal [[:foo]] * 4, @events end - def test_log_subscriber_with_pattern + def test_log_subscriber_with_string events = [] @notifier.subscribe('1') { |*args| events << args } @@ -74,10 +74,10 @@ def test_log_subscriber_with_pattern @notifier.publish 'a.1' @notifier.wait - assert_equal [['1'], ['1.a']], events + assert_equal [['1']], events end - def test_log_subscriber_with_pattern_as_regexp + def test_log_subscriber_with_pattern events = [] @notifier.subscribe(/\d/) { |*args| events << args } diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb index cbab61a523..58710e0165 100644 --- a/activesupport/test/ts_isolated.rb +++ b/activesupport/test/ts_isolated.rb @@ -1,3 +1,5 @@ +$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib') + require 'test/unit' require 'rbconfig' require 'active_support/core_ext/kernel/reporting' diff --git a/ci/ci_build.rb b/ci/ci_build.rb index 2a7bbc376e..1602c5106d 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -27,7 +27,7 @@ def rake(*tasks) puts puts "[CruiseControl] Bundling RubyGems" puts - build_results[:bundle] = system 'rm -rf ~/.bundle; env CI=1 bundle install' + build_results[:bundle] = system 'sudo rm -rf ~/.bundle; env CI=1 bundle install' end cd "#{root_dir}/activesupport" do @@ -35,7 +35,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActiveSupport" puts build_results[:activesupport] = rake 'test' - build_results[:activesupport_isolated] = rake 'test:isolated' + # build_results[:activesupport_isolated] = rake 'test:isolated' end cd "#{root_dir}/railties" do @@ -50,7 +50,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActionPack" puts build_results[:actionpack] = rake 'test' - build_results[:actionpack_isolated] = rake 'test:isolated' + # build_results[:actionpack_isolated] = rake 'test:isolated' end cd "#{root_dir}/actionmailer" do @@ -58,7 +58,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActionMailer" puts build_results[:actionmailer] = rake 'test' - build_results[:actionmailer_isolated] = rake 'test:isolated' + # build_results[:actionmailer_isolated] = rake 'test:isolated' end cd "#{root_dir}/activemodel" do @@ -66,7 +66,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActiveModel" puts build_results[:activemodel] = rake 'test' - build_results[:activemodel_isolated] = rake 'test:isolated' + # build_results[:activemodel_isolated] = rake 'test:isolated' end rm_f "#{root_dir}/activeresource/debug.log" @@ -75,7 +75,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActiveResource" puts build_results[:activeresource] = rake 'test' - build_results[:activeresource_isolated] = rake 'test:isolated' + # build_results[:activeresource_isolated] = rake 'test:isolated' end rm_f "#{root_dir}/activerecord/debug.log" @@ -84,7 +84,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActiveRecord with MySQL" puts build_results[:activerecord_mysql] = rake 'mysql:rebuild_databases', 'mysql:test' - build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test' + # build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -92,7 +92,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActiveRecord with PostgreSQL" puts build_results[:activerecord_postgresql8] = rake 'postgresql:rebuild_databases', 'postgresql:test' - build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test' + # build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -100,7 +100,7 @@ def rake(*tasks) puts "[CruiseControl] Building ActiveRecord with SQLite 3" puts build_results[:activerecord_sqlite3] = rake 'sqlite3:test' - build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test' + # build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test' end diff --git a/load_paths.rb b/load_paths.rb index b87e0d7235..873315f978 100644 --- a/load_paths.rb +++ b/load_paths.rb @@ -2,7 +2,13 @@ require File.expand_path('../.bundle/environment', __FILE__) rescue LoadError begin - require 'rubygems' + # bust gem prelude + if defined? Gem + Gem.cache + gem 'bundler' + else + require 'rubygems' + end require 'bundler' Bundler.setup rescue LoadError diff --git a/railties/Rakefile b/railties/Rakefile index fe049d565f..6368456366 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -36,7 +36,7 @@ end desc "Updates application README to the latest version Railties README" task :update_readme do - readme = "lib/generators/rails/app/templates/README" + readme = "lib/rails/generators/rails/app/templates/README" rm readme cp "./README", readme end @@ -48,7 +48,7 @@ task :generate_guides do end task :update_prototype_ujs do - system "curl http://github.com/rails/prototype-ujs/raw/master/src/rails.js > lib/generators/rails/app/templates/public/javascripts/rails.js" + system "curl http://github.com/rails/prototype-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/public/javascripts/rails.js" end # Generate documentation ------------------------------------------------------------------ @@ -62,8 +62,8 @@ Rake::RDocTask.new { |rdoc| rdoc.rdoc_files.include('README', 'CHANGELOG') rdoc.rdoc_files.include('lib/*.rb') rdoc.rdoc_files.include('lib/rails/*.rb') - rdoc.rdoc_files.include('lib/generators/*.rb') - rdoc.rdoc_files.include('lib/commands/**/*.rb') + rdoc.rdoc_files.include('lib/rails/generators/*.rb') + rdoc.rdoc_files.include('lib/rails/commands/**/*.rb') } # Generate GEM ---------------------------------------------------------------------------- diff --git a/railties/builtin/rails_info/rails/info_helper.rb b/railties/builtin/rails_info/rails/info_helper.rb deleted file mode 100644 index e5605a8d9b..0000000000 --- a/railties/builtin/rails_info/rails/info_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Rails::InfoHelper -end diff --git a/railties/guides/images/challenge.png b/railties/guides/images/challenge.png new file mode 100644 index 0000000000..d163748640 Binary files /dev/null and b/railties/guides/images/challenge.png differ diff --git a/railties/guides/images/edge_badge.png b/railties/guides/images/edge_badge.png new file mode 100644 index 0000000000..cddd46c4b8 Binary files /dev/null and b/railties/guides/images/edge_badge.png differ diff --git a/railties/guides/images/posts_index.png b/railties/guides/images/posts_index.png index 50e956e8be..f6cd2f9b80 100644 Binary files a/railties/guides/images/posts_index.png and b/railties/guides/images/posts_index.png differ diff --git a/railties/guides/images/rails_welcome.png b/railties/guides/images/rails_welcome.png index 7e02ce5014..0e02cf5a8c 100644 Binary files a/railties/guides/images/rails_welcome.png and b/railties/guides/images/rails_welcome.png differ diff --git a/railties/guides/rails_guides.rb b/railties/guides/rails_guides.rb index 0d9458bf0f..e6f0b694a6 100644 --- a/railties/guides/rails_guides.rb +++ b/railties/guides/rails_guides.rb @@ -5,40 +5,27 @@ require 'rubygems' begin + # Guides generation in the Rails repo. as_lib = File.join(pwd, "../../activesupport/lib") ap_lib = File.join(pwd, "../../actionpack/lib") - $: << as_lib if File.directory?(as_lib) - $: << ap_lib if File.directory?(ap_lib) - - require "action_controller" - require "action_view" + $:.unshift as_lib if File.directory?(as_lib) + $:.unshift ap_lib if File.directory?(ap_lib) rescue LoadError - gem "actionpack", '>= 2.3' - - require "action_controller" - require "action_view" + # Guides generation from gems. + gem "actionpack", '>= 3.0' end begin gem 'RedCloth', '>= 4.1.1' + require 'redcloth' rescue Gem::LoadError $stderr.puts %(Generating Guides requires RedCloth 4.1.1+) exit 1 end -require 'redcloth' - -module RailsGuides - autoload :Generator, "rails_guides/generator" - autoload :Indexer, "rails_guides/indexer" - autoload :Helpers, "rails_guides/helpers" - autoload :TextileExtensions, "rails_guides/textile_extensions" - autoload :Levenshtein, "rails_guides/levenshtein" -end - +require "rails_guides/textile_extensions" RedCloth.send(:include, RailsGuides::TextileExtensions) -if $0 == __FILE__ - RailsGuides::Generator.new.generate -end +require "rails_guides/generator" +RailsGuides::Generator.new.generate diff --git a/railties/guides/rails_guides/generator.rb b/railties/guides/rails_guides/generator.rb index bd25111405..670d2bc4db 100644 --- a/railties/guides/rails_guides/generator.rb +++ b/railties/guides/rails_guides/generator.rb @@ -1,70 +1,95 @@ require 'set' +require 'fileutils' -class String - def html_safe! - self - end unless "post 9415935902f120a9bac0bfce7129725a0db38ed3".respond_to?(:html_safe!) -end +require 'active_support/core_ext/string/output_safety' +require 'action_controller' +require 'action_view' + +require 'rails_guides/indexer' +require 'rails_guides/helpers' +require 'rails_guides/levenshtein' module RailsGuides class Generator - attr_reader :output, :view_path, :view, :guides_dir + attr_reader :guides_dir, :source_dir, :output_dir - def initialize(output = nil) - @guides_dir = File.join(File.dirname(__FILE__), '..') - - @output = output || File.join(@guides_dir, "output") - - unless ENV["ONLY"] - FileUtils.rm_r(@output) if File.directory?(@output) - FileUtils.mkdir(@output) - end - - @view_path = File.join(@guides_dir, "source") + def initialize(output=nil) + initialize_dirs(output) + create_output_dir_if_needed end def generate - guides = Dir.entries(view_path).find_all {|g| g =~ /\.textile(?:\.erb)?$/ } - - if ENV["ONLY"] - only = ENV["ONLY"].split(",").map{|x| x.strip }.map {|o| "#{o}.textile" } - guides = guides.find_all {|g| only.include?(g) } - puts "GENERATING ONLY #{guides.inspect}" - end - - guides.each do |guide| - generate_guide(guide) - end - - # Copy images and css files to html directory - FileUtils.cp_r File.join(guides_dir, 'images'), File.join(output, 'images') - FileUtils.cp_r File.join(guides_dir, 'files'), File.join(output, 'files') + generate_guides + copy_assets end - def generate_guide(guide) - guide =~ /(.*?)\.textile(?:\.erb)?$/ - name = $1 + 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") + end - puts "Generating #{name}" + def create_output_dir_if_needed + FileUtils.mkdir_p(output_dir) + end - file = File.join(output, "#{name}.html") - File.open(file, 'w') do |f| - @view = ActionView::Base.new(view_path) - @view.extend(Helpers) + def generate_guides + guides_to_generate.each do |guide| + output_file = output_file_for(guide) + generate_guide(guide, output_file) if generate?(guide, output_file) + end + end + def guides_to_generate + guides = Dir.entries(source_dir).grep(/\.textile(?:\.erb)?$/) + ENV.key?("ONLY") ? select_only(guides) : guides + end + + def select_only(guides) + prefixes = ENV["ONLY"].split(",").map(&:strip) + guides.select do |guide| + prefixes.any? {|p| guide.start_with?(p)} + end + end + + def copy_assets + FileUtils.cp_r(File.join(guides_dir, 'images'), File.join(output_dir, 'images')) + FileUtils.cp_r(File.join(guides_dir, 'files'), File.join(output_dir, 'files')) + end + + def output_file_for(guide) + guide.sub(/\.textile(?:\.erb)?$/, '.html') + end + + def generate?(source_file, output_file) + fin = File.join(source_dir, source_file) + fout = File.join(output_dir, output_file) + ENV['ALL'] == '1' || !File.exists?(fout) || File.mtime(fout) < File.mtime(fin) + end + + def generate_guide(guide, output_file) + puts "Generating #{output_file}" + File.open(File.join(output_dir, output_file), 'w') do |f| + view = ActionView::Base.new(source_dir) + view.extend(Helpers) + if guide =~ /\.textile\.erb$/ # Generate the erb pages with textile formatting - e.g. index/authors result = view.render(:layout => 'layout', :file => guide) - f.write textile(result) + result = textile(result) else - body = File.read(File.join(view_path, guide)) - body = set_header_section(body, @view) - body = set_index(body, @view) + body = File.read(File.join(source_dir, guide)) + body = set_header_section(body, view) + body = set_index(body, view) + + result = view.render(:layout => 'layout', :text => textile(body)) - result = view.render(:layout => 'layout', :text => textile(body).html_safe!) - f.write result warn_about_broken_links(result) if ENV.key?("WARN_BROKEN_LINKS") end + + result = insert_edge_badge(result) if ENV.key?('INSERT_EDGE_BADGE') + f.write result end end @@ -77,8 +102,8 @@ def set_header_section(body, view) header = textile(header) - view.content_for(:page_title) { page_title.html_safe! } - view.content_for(:header_section) { header.html_safe! } + view.content_for(:page_title) { page_title.html_safe } + view.content_for(:header_section) { header.html_safe } new_body end @@ -94,22 +119,22 @@ def set_index(body, view) # Set index for 2 levels i.level_hash.each do |key, value| - link = view.content_tag(:a, :href => key[:id]) { textile(key[:title]) } + link = view.content_tag(:a, :href => key[:id]) { textile(key[:title]).html_safe } children = value.keys.map do |k| - l = view.content_tag(:a, :href => k[:id]) { textile(k[:title]) } - view.content_tag(:li, l) + l = view.content_tag(:a, :href => k[:id]) { textile(k[:title]).html_safe } + view.content_tag(:li, l.html_safe) end - children_ul = view.content_tag(:ul, children.join(" ")) + children_ul = view.content_tag(:ul, children.join(" ").html_safe) - index << view.content_tag(:li, link + children_ul) + index << view.content_tag(:li, link.html_safe + children_ul.html_safe) end index << '' index << '' - view.content_for(:index_section) { index.html_safe! } + view.content_for(:index_section) { index.html_safe } i.result end @@ -174,5 +199,9 @@ def check_fragment_identifiers(html, anchors) end end end + + def insert_edge_badge(html) + html.sub(/]*>/, '\&') + end end end diff --git a/railties/guides/rails_guides/textile_extensions.rb b/railties/guides/rails_guides/textile_extensions.rb index b22be5752d..affef1ccb0 100644 --- a/railties/guides/rails_guides/textile_extensions.rb +++ b/railties/guides/rails_guides/textile_extensions.rb @@ -1,7 +1,7 @@ module RailsGuides module TextileExtensions def notestuff(body) - body.gsub!(/^(IMPORTANT|CAUTION|WARNING|NOTE|INFO)(?:\.|\:)(.*)$/) do |m| + body.gsub!(/^(IMPORTANT|CAUTION|WARNING|NOTE|INFO)[.:](.*)$/) do |m| css_class = $1.downcase css_class = 'warning' if ['caution', 'important'].include?(css_class) @@ -13,9 +13,9 @@ def notestuff(body) end def tip(body) - body.gsub!(/^(TIP)\:(.*)$/) do |m| + body.gsub!(/^TIP[.:](.*)$/) do |m| result = "

    " - result << $2.strip + result << $1.strip result << '

    ' result end diff --git a/railties/guides/source/2_2_release_notes.textile b/railties/guides/source/2_2_release_notes.textile index 78f28d1d76..117635bcee 100644 --- a/railties/guides/source/2_2_release_notes.textile +++ b/railties/guides/source/2_2_release_notes.textile @@ -53,7 +53,7 @@ rake doc:guides This will put the guides inside +Rails.root/doc/guides+ and you may start surfing straight away by opening +Rails.root/doc/guides/index.html+ in your favourite browser. -* Lead Contributors: "Rails Documentation Team":http://guides.rails.info/credits.html +* Lead Contributors: "Rails Documentation Team":credits.html * Major contributions from "Xavier Noria":http://advogato.org/person/fxn/diary.html and "Hongli Lai":http://izumi.plan99.net/blog/. * More information: ** "Rails Guides hackfest":http://hackfest.rubyonrails.org/guide diff --git a/railties/guides/source/2_3_release_notes.textile b/railties/guides/source/2_3_release_notes.textile index b864c5dc79..f3530c4ddd 100644 --- a/railties/guides/source/2_3_release_notes.textile +++ b/railties/guides/source/2_3_release_notes.textile @@ -47,7 +47,7 @@ After some versions without an upgrade, Rails 2.3 offers some new features for R h3. Documentation -The "Ruby on Rails guides":http://guides.rubyonrails.org/ project has published several additional guides for Rails 2.3. In addition, a "separate site":http://guides.rails.info/ maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the "Rails wiki":http://newwiki.rubyonrails.org/ and early planning for a Rails Book. +The "Ruby on Rails guides":http://guides.rubyonrails.org/ project has published several additional guides for Rails 2.3. In addition, a "separate site":http://edgeguides.rubyonrails.org/ maintains updated copies of the Guides for Edge Rails. Other documentation efforts include a relaunch of the "Rails wiki":http://newwiki.rubyonrails.org/ and early planning for a Rails Book. * More Information: "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects. diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile index 4b0dc72908..cc84129b35 100644 --- a/railties/guides/source/3_0_release_notes.textile +++ b/railties/guides/source/3_0_release_notes.textile @@ -1,13 +1,13 @@ -h2. Rails 3.0: Release Notes +h2. Ruby on Rails 3.0 Release Notes -Rails 3.0 is ponies and rainbows! It's going to cook you dinner and fold your laundry. You're going to wonder how life was ever possible before you it arrived. It's the Best Version of Rails We've Ever Done! +Rails 3.0 is ponies and rainbows! It's going to cook you dinner and fold your laundry. You're going to wonder how life was ever possible before it arrived. It's the Best Version of Rails We've Ever Done! But seriously now, it's really good stuff. There are all the good ideas brought over from when the Merb team joined the party and brought a focus on framework agnosticism, slimmer and faster internals, and a handful of tasty APIs. If you're coming to Rails 3.0 from Merb 1.x, you should recognize lots. If you're coming from Rails 2.x, you're going to love it too. Even if you don't give a hoot about any of our internal cleanups, Rails 3.0 is going to delight. We have a bunch of new features and improved APIs. It's never been a better time to be a Rails developer. Some of the highlights are: * Brand new router with an emphasis on RESTful declarations -* New Action Mailer API modelled after Action Controller (now without the agonizing pain of sending multipart messages!) +* New Action Mailer API modeled after Action Controller (now without the agonizing pain of sending multipart messages!) * New Active Record chainable query language built on top of relational algebra * Unobtrusive JavaScript helpers with drivers for Prototype, jQuery, and more coming (end of inline JS) * Explicit dependency management with Bundler @@ -20,6 +20,18 @@ endprologue. WARNING: Rails 3.0 is currently in beta. This means that there are probably bugs and that you should "report them":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview if you see them. You also may not want to run the NORAD nuclear launch application off a beta version. But if you're starting development on a new application and you don't mind getting wind in your hair, please do jump on board! +TIP: To install the Rails 3 prerelease beta using rubygems you have to install all the Rails dependencies first as these will not be installed for you by rubygems: + + +# Use sudo if your setup requires it +gem install tzinfo builder i18n memcache-client rack \ + rake rack-test erubis mail text-format \ + thor bundler +gem install rack-mount -v=0.4 +gem install rails --pre + + + h3. Upgrading to Rails 3 If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 2.3.5 and make sure your application still runs as expected before attempting to update to Rails 3. Then take heed of the following changes: @@ -43,7 +55,7 @@ rails console # instead of script/console rails g scaffold post title:string # instead of script/generate scaffold post title:string -Run rails --help for a list of all the options. +Run rails --help for a list of all the options. h4. Dependencies and config.gem @@ -65,7 +77,6 @@ Aside from Rails Upgrade tool, if you need more help, there are people on IRC an More information - "The Path to Rails 3: Approaching the upgrade":http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade - h3. Creating a Rails 3.0 application The new installing rails sequence (for the beta) is: @@ -80,34 +91,32 @@ h4. Vendoring Gems Rails now uses a +Gemfile+ in the application root to determine the gems you require for your application to start. This +Gemfile+ is processed by the "Bundler":http://github.com/carlhuda/bundler, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems. -More information: - "Using bundler":http://yehudakatz.com/2009/11/03/using-the-new-gem-bundler-today/ - +More information: - "bundler README on Github":http://github.com/carlhuda/bundler h4. Living on the Edge +Bundler+ and +Gemfile+ makes freezing your Rails application easy as pie with the new dedicated bundle command, so rake freeze is no longer relevant and has been dropped. -If you want to bundle straight from the Git repository, you can pass the edge flag: +If you want to bundle straight from the Git repository, you can pass the +--edge+ flag: $ rails myapp --edge -More information: -* "Spinning up a new Rails app":http://yehudakatz.com/2009/12/31/spinning-up-a-new-rails-app/ -* "Rails 3 and Passenger":http://cakebaker.42dh.com/2010/01/17/rails-3-and-passenger/ +If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag: + +$ ruby /path/to/rails/railties/bin/rails myapp --dev + h3. Rails Architectural Changes There are six major changes in the architecture of Rails. - h4. Railties Restrung Railties was updated to provide a consistent plugin API for the entire Rails framework as well as a total rewrite of generators and the Rails bindings, the result is that developers can now hook into any significant stage of the generators and application framework in a consistent, defined manner. - h4. All Rails core components are decoupled With the merge of Merb and Rails, one of the big jobs was to remove the tight coupling between Rails core components. This has now been achieved, and all Rails core components are now using the same API that you can use for developing plugins. This means any plugin you make, or any core component replacement (like DataMapper or Sequel) can access all the functionality that the Rails core components have access to and extend and enhance at will. @@ -131,7 +140,7 @@ More Information: - "Rails Edge Architecture":http://yehudakatz.com/2009/06/11/r h4. Arel Integration -"Arel":http://github.com/rails/arel (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record. +"Arel":http://github.com/brynary/arel (or Active Relation) has been taken on as the underpinnings of Active Record and is now required for Rails. Arel provides an SQL abstraction that simplifies out Active Record and provides the underpinnings for the relation functionality in Active Record. More information: - "Why I wrote Arel":http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/. @@ -145,7 +154,7 @@ More information: - "New Action Mailer API in Rails 3":http://lindsaar.net/2010/ h3. Documentation -The documentation in the Rails tree is being updated with all the API changes, additionally, the "Rails Edge guides":http://guides.rails.info/ are being updated one by one to reflect the changes in Rails 3.0. The guides at "guides.rubyonrails.org":http://guides.rubyonrails.org/ however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released). +The documentation in the Rails tree is being updated with all the API changes, additionally, the "Rails Edge Guides":http://edgeguides.rubyonrails.org/ are being updated one by one to reflect the changes in Rails 3.0. The guides at "guides.rubyonrails.org":http://guides.rubyonrails.org/ however will continue to contain only the stable version of Rails (at this point, version 2.3.5, until 3.0 is released). More Information: - "Rails Documentation Projects":http://weblog.rubyonrails.org/2009/1/15/rails-documentation-projects. @@ -166,7 +175,7 @@ h3. Railties With the decoupling of the main Rails frameworks, Railties got a huge overhaul so as to make linking up frameworks, engines or plugins as painless and extensible as possible: -* Each application now has it's own name space, application is started with YourAppName.boot for example, makes interacting with other applications a lot easier. +* Each application now has its own name space, application is started with YourAppName.boot for example, makes interacting with other applications a lot easier. * Anything under Rails.root/app is now added to the load path, so you can make app/observers/user_observer.rb and Rails will load it without any modifications. * Rails 3.0 now provides a Rails.config object, which provides a central repository of all sorts of Rails wide configuration options. @@ -203,7 +212,7 @@ Railties now deprecates: More information: * "Discovering Rails 3 generators":http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators * "Making Generators for Rails 3 with Thor":http://caffeinedd.com/guides/331-making-generators-for-rails-3-with-thor - +* "The Rails Module (in Rails 3)":http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/ h3. Action Pack @@ -224,8 +233,8 @@ More Information: - "Rails Edge Architecture":http://yehudakatz.com/2009/06/11/r h4. Action Controller * application_controller.rb now has protect_from_forgery on by default. -* The cookie_verifier_secret has been moved to initializers/cookie_verification_secret.rb. -* The session_store configuration has moved to initializers/session_store.rb. +* The cookie_verifier_secret has been deprecated and now instead it is assigned through Rails.application.config.cookie_secret and moved into its own file: config/initializers/cookie_verification_secret.rb. +* The session_store was configured in ActionController::Base.session, and that is now moved to Rails.application.config.session_store. Defaults are set up in config/initializers/session_store.rb. * cookies.secure allowing you to set encrypted values in cookies with cookie.secure[:key] => value. * cookies.permanent allowing you to set permanent values in the cookie hash cookie.permanent[:key] => value that raise exceptions on signed values if verification failures. * You can now pass :notice => 'This is a flash message' or :alert => 'Something went wrong' to the format call inside a +respond_to+ block. The flash[] hash still works as previously. @@ -251,7 +260,7 @@ Action Dispatch is new in Rails 3.0 and provides a new, cleaner implementation f # Instead of: -ActionController::Routing::Routes.draw do +ActionController::Routing::Routes.draw do |map| map.resources :posts end @@ -313,7 +322,6 @@ Produces: * I18n select label on should now be :en.helpers.select instead of :en.support.select. * You no longer need to place a minus sign at the end of a ruby interpolation inside an ERb template to remove the trailing carriage return in the HTML output. * Added +grouped_collection_select+ helper to Action View. -* Action View now will raise exceptions if CSS stylesheets and javascript files listed in the +javascript_include_tag+ and +stylesheet_include_tag+ helpers are missing. * +content_for?+ has been added allowing you to check for the existence of content in a view before rendering. @@ -386,7 +394,7 @@ Active Record received a lot of attention in Rails 3.0, including abstraction in h4. Query Interface -Active Record, through the use of Arel, now returns relations on it's core methods. The existing API in Rails 2.3.x is still supported and will not be deprecated until Rails 3.1 and not removed until Rails 3.2, however, the new API provides the following new methods that all return relations allowing them to be chained together: +Active Record, through the use of Arel, now returns relations on its core methods. The existing API in Rails 2.3.x is still supported and will not be deprecated until Rails 3.1 and not removed until Rails 3.2, however, the new API provides the following new methods that all return relations allowing them to be chained together: * where - provides conditions on the relation, what gets returned. * select - choose what attributes of the models you wish to have returned from the database. @@ -421,7 +429,7 @@ Additionally, many fixes in the Active Record branch: * SQLite 2 support has been dropped in favour of SQLite 3. * MySQL support for column order. -* PostgreSQL adapter has had it's +TIME ZONE+ support fixed so it no longer inserts incorrect values. +* PostgreSQL adapter has had its +TIME ZONE+ support fixed so it no longer inserts incorrect values. * Support multiple schemas in table names for PostgreSQL. * PostgreSQL support for the XML data type column. * +table_name+ is now cached. @@ -461,7 +469,7 @@ Active Resource was also extracted out to Active Model allowing you to use Activ * Fix ActiveResource::ConnectionError#to_s when +@response+ does not respond to #code or #message, handles Ruby 1.9 compat. * Add support for errors in JSON format. * Ensure load works with numeric arrays. -* Recognises a 410 response from remote resource as the resource has been deleted. +* Recognizes a 410 response from remote resource as the resource has been deleted. * Add ability to set SSL options on Active Resource connections. * Setting connection timeout also affects +Net::HTTP+ open_timeout. @@ -478,7 +486,7 @@ A large effort was made in Active Support to make it cherry pickable, that is, y These are the main changes in Active Support: * Large clean up of the library removing unused methods throughout. -* Active Support no longer provides vendored versions of "TZInfo":http://tzinfo.rubyforge.org/, "Memcache Client":http://deveiate.org/projects/RMemCache/ and "Builder":http://builder.rubyforge.org/, these are all included as dependencies and installed via the gem bundle command. +* Active Support no longer provides vendored versions of "TZInfo":http://tzinfo.rubyforge.org/, "Memcache Client":http://deveiate.org/projects/RMemCache/ and "Builder":http://builder.rubyforge.org/, these are all included as dependencies and installed via the bundle install command. * Safe buffers are implemented in ActiveSupport::SafeBuffer. * Added Array.uniq_by and Array.uniq_by!. * Fixed bug on +TimeZone.seconds_to_utc_offset+ returning wrong value. @@ -501,7 +509,7 @@ These are the main changes in Active Support: * String#to_time and String#to_datetime handle fractional seconds. * Added support to new callbacks for around filter object that respond to :before and :after used in before and after callbacks. * The ActiveSupport::OrderedHash#to_a method returns an ordered set of arrays. Matches Ruby 1.9's Hash#to_a. -* MissingSourceFile exists as a constant but it is now just equals to LoadError +* MissingSourceFile exists as a constant but it is now just equals to LoadError. * Added Class#class_attribute, to be able to declare a class-level attribute whose value is inheritable and overwritable by subclasses. * Finally removed +DeprecatedCallbacks+ in ActiveRecord::Associations. @@ -520,10 +528,10 @@ The security patch for REXML remains in Active Support because early patchlevels The following methods have been removed because they are no longer used in the framework: -* Object#remove_subclasses_of, Object#subclasses_of, Object#extend_with_included_modules_from, Object#extended_by -* Class#subclasses, Class#reachable?, Class#remove_class -* Regexp#number_of_captures -* Regexp.unoptionalize, Regexp.optionalize, Regexp#number_of_captures +* +Kernel#daemonize+ +* Object#remove_subclasses_of Object#extend_with_included_modules_from, Object#extended_by +* Class#remove_class +* Regexp#number_of_captures, Regexp.unoptionalize, Regexp.optionalize, Regexp#number_of_captures h3. Action Mailer @@ -532,11 +540,11 @@ Action Mailer has been given a new API with TMail being replaced out with the ne * All mailers are now in app/mailers by default. * Can now send email using new API with three methods: +attachments+, +headers+ and +mail+. -* ActionMailer emailing methods now return Mail::Message objects, which can then be sent the +deliver+ message to send itself. +* Action Mailer emailing methods now return Mail::Message objects, which can then be sent the +deliver+ message to send itself. * All delivery methods are now abstracted out to the Mail gem. * The mail delivery method can accept a hash of all valid mail header fields with their value pair. -* The mail delivery method acts in a similar way to Action Controller's respond_to block, and you can explicitly or implicitly render templates. Action Mailer will turn the email into a multipart email as needed. -* You can pass a proc to the format.mime_type calls within the mail block and explicitly render specific types of text, or add layouts or different templates. The +render+ call inside the proc is from Abstract Controller, so all the same options are available as they are in Action Controller. +* The +mail+ delivery method acts in a similar way to Action Controller's +respond_to+, and you can explicitly or implicitly render templates. Action Mailer will turn the email into a multipart email as needed. +* You can pass a proc to the format.mime_type calls within the mail block and explicitly render specific types of text, or add layouts or different templates. The +render+ call inside the proc is from Abstract Controller and supports the same options. * What were mailer unit tests have been moved to functional tests. Deprecations: @@ -545,7 +553,7 @@ Deprecations: * Mailer dynamic create_method_name and deliver_method_name are deprecated, just call method_name which now returns a Mail::Message object. * ActionMailer.deliver(message) is deprecated, just call message.deliver. * template_root is deprecated, pass options to a render call inside a proc from the format.mime_type method inside the mail generation block -* The body method to define instance variables is deprecated (body {:ivar => value}), just declare instance variables in the method directly and they will be available in the view. +* The +body+ method to define instance variables is deprecated (body {:ivar => value}), just declare instance variables in the method directly and they will be available in the view. * Mailers being in app/models is deprecated, use app/mailers instead. More Information: diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 46a28da8c4..bedca59c12 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -625,9 +625,9 @@ class ClientsController < ApplicationController # returns it. The user will get the PDF as a file download. def download_pdf client = Client.find(params[:id]) - send_data(generate_pdf, + send_data generate_pdf(client), :filename => "#{client.name}.pdf", - :type => "application/pdf") + :type => "application/pdf" end private diff --git a/railties/guides/source/action_mailer_basics.textile b/railties/guides/source/action_mailer_basics.textile index 941c2e9771..e5ba8380e8 100644 --- a/railties/guides/source/action_mailer_basics.textile +++ b/railties/guides/source/action_mailer_basics.textile @@ -4,7 +4,7 @@ This guide should provide you with all you need to get started in sending and re endprologue. -WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in other versions of Rails. +WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails. h3. Introduction diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index 4aa43a8f3c..a517193cea 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -33,13 +33,13 @@ gem install actionpack gem install rack -Now we'll create a simple "Hello World" application that uses the +titleize+ method provided by Action View. +Now we'll create a simple "Hello World" application that uses the +titleize+ method provided by Active Support. *hello_world.rb:* require 'rubygems' -require 'action_view' +require 'active_support/core_ext/string/inflections' require 'rack' def hello_world(env) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 302dad4f1a..3a62ee567d 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -11,6 +11,8 @@ This guide covers different ways to retrieve data from the database using Active endprologue. +WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in other versions of Rails. + If you're used to using raw SQL to find database records then, generally, you will find that there are better ways to carry out the same operations in Rails. Active Record insulates you from the need to use SQL in most cases. Code examples throughout this guide will refer to one or more of the following models: @@ -49,7 +51,22 @@ Active Record will perform queries on the database for you and is compatible wit h3. Retrieving Objects from the Database -To retrieve objects from the database, Active Record provides a class method called +Model.find+. This method allows you to pass arguments into it to perform certain queries on your database without the need of writing raw SQL. +To retrieve objects from the database, Active Record provides several finder methods. These methods allows you to pass arguments into it to perform certain queries on your database without the need of writing raw SQL. + +The methods are: +* +where+ +* +select+ +* +group+ +* +order+ +* +limit+ +* +offset+ +* +joins+ +* +includes+ +* +lock+ +* +readonly+ +* +from+ + +All of these methods return a Relation Primary operation of Model.find(options) can be summarized as: @@ -64,7 +81,7 @@ Active Record lets you retrieve a single object using three different ways. h5. Using a Primary Key -Using Model.find(primary_key, options = nil), you can retrieve the object corresponding to the supplied _primary key_ and matching the supplied options (if any). For example: +Using Model.find(primary_key), you can retrieve the object corresponding to the supplied _primary key_ and matching the supplied options (if any). For example: # Find the client with primary key (id) 10. @@ -82,7 +99,7 @@ SELECT * FROM clients WHERE (clients.id = 10) h5. +first+ -Model.first(options = nil) finds the first record matched by the supplied options. If no +options+ are supplied, the first matching record is returned. For example: +Model.first finds the first record matched by the supplied options. For example: client = Client.first @@ -97,11 +114,9 @@ SELECT * FROM clients LIMIT 1 Model.first returns +nil+ if no matching record is found. No exception will be raised. -NOTE: +Model.find(:first, options)+ is equivalent to +Model.first(options)+ - h5. +last+ -Model.last(options = nil) finds the last record matched by the supplied options. If no +options+ are supplied, the last matching record is returned. For example: +Model.last finds the last record matched by the supplied options. For example: client = Client.last @@ -116,13 +131,11 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1 Model.last returns +nil+ if no matching record is found. No exception will be raised. -NOTE: +Model.find(:last, options)+ is equivalent to +Model.last(options)+ - h4. Retrieving Multiple Objects h5. Using Multiple Primary Keys -Model.find(array_of_primary_key, options = nil) also accepts an array of _primary keys_. An array of all the matching records for the supplied _primary keys_ is returned. For example: +Model.find(array_of_primary_key) also accepts an array of _primary keys_. An array of all the matching records for the supplied _primary keys_ is returned. For example: # Find the clients with primary keys 1 and 10. @@ -138,26 +151,6 @@ SELECT * FROM clients WHERE (clients.id IN (1,10)) Model.find(array_of_primary_key) will raise an +ActiveRecord::RecordNotFound+ exception unless a matching record is found for all of the supplied primary keys. -h5. Find all - -Model.all(options = nil) finds all the records matching the supplied +options+. If no +options+ are supplied, all rows from the database are returned. - - -# Find all the clients. -clients = Client.all -=> [# "Lifo">, # "Ryan">, # "Russel">] - - -And the equivalent SQL is: - - -SELECT * FROM clients - - -Model.all returns an empty array +[]+ if no matching record is found. No exception will be raised. - -NOTE: +Model.find(:all, options)+ is equivalent to +Model.all(options)+ - h4. Retrieving Multiple Objects in Batches Sometimes you need to iterate over a large set of records. For example to send a newsletter to all users, to export some data, etc. @@ -166,14 +159,14 @@ The following may seem very straight forward at first: # Very inefficient when users table has thousands of rows. -User.all.each do |user| +User.each do |user| NewsLetter.weekly_deliver(user) end But if the total number of rows in the table is very large, the above approach may vary from being under performant to just plain impossible. -This is because +User.all+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory. +This is because +User.each+ makes Active Record fetch _the entire table_, build a model object per row, and keep the entire array in the memory. Sometimes that is just too many objects and demands too much memory. h5. +find_each+ @@ -232,16 +225,16 @@ The +find+ method allows you to specify conditions to limit the records returned h4. Pure String Conditions -If you'd like to add conditions to your find, you could just specify them in there, just like +Client.first(:conditions => "orders_count = '2'")+. This will find all clients where the +orders_count+ field's value is 2. +If you'd like to add conditions to your find, you could just specify them in there, just like +Client.where("orders_count = '2'")+. This will find all clients where the +orders_count+ field's value is 2. -WARNING: Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, +Client.first(:conditions => "name LIKE '%#{params[:name]}%'")+ is not safe. See the next section for the preferred way to handle conditions using an array. +WARNING: Building your own conditions as pure strings can leave you vulnerable to SQL injection exploits. For example, +Client.where("name LIKE '%#{params[:name]}%'")+ is not safe. See the next section for the preferred way to handle conditions using an array. h4. Array Conditions Now what if that number could vary, say as an argument from somewhere, or perhaps from the user's level status somewhere? The find then becomes something like: -Client.first(:conditions => ["orders_count = ?", params[:orders]]) +Client.where(["orders_count = ?", params[:orders]]) Active Record will go through the first element in the conditions value and any additional elements will replace the question marks +(?)+ in the first element. @@ -249,7 +242,7 @@ Active Record will go through the first element in the conditions value and any Or if you want to specify two conditions, you can do it like: -Client.first(:conditions => ["orders_count = ? AND locked = ?", params[:orders], false]) +Client.where(["orders_count = ? AND locked = ?", params[:orders], false]) In this example, the first question mark will be replaced with the value in +params[:orders]+ and the second will be replaced with the SQL representation of +false+, which depends on the adapter. @@ -257,13 +250,13 @@ In this example, the first question mark will be replaced with the value in +par The reason for doing code like: -Client.first(:conditions => ["orders_count = ?", params[:orders]]) +Client.where(["orders_count = ?", params[:orders]]) instead of: -Client.first(:conditions => "orders_count = #{params[:orders]}") +Client.where("orders_count = #{params[:orders]}") is because of argument safety. Putting the variable directly into the conditions string will pass the variable to the database *as-is*. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string. @@ -275,7 +268,7 @@ h5. Placeholder Conditions Similar to the +(?)+ replacement style of params, you can also specify keys/values hash in your array conditions: -Client.all(:conditions => +Client.where( ["created_at >= :start_date AND created_at <= :end_date", { :start_date => params[:start_date], :end_date => params[:end_date] }]) @@ -286,7 +279,7 @@ h5. Range Conditions If you're looking for a range inside of a table (for example, users created in a certain timeframe) you can use the conditions option coupled with the +IN+ SQL statement for this. If you had two dates coming in from a controller you could do something like this to look for a range: -Client.all(:conditions => ["created_at IN (?)", +Client.where(["created_at IN (?)", (params[:start_date].to_date)..(params[:end_date].to_date)]) @@ -308,7 +301,7 @@ h5. Time and Date Conditions Things can get *really* messy if you pass in Time objects as it will attempt to compare your field to *every second* in that range: -Client.all(:conditions => ["created_at IN (?)", +Client.where(["created_at IN (?)", (params[:start_date].to_date.to_time)..(params[:end_date].to_date.to_time)]) @@ -329,14 +322,14 @@ Where _query_ is the actual query used to get that error. In this example it would be better to use greater-than and less-than operators in SQL, like so: -Client.all(:conditions => +Client.where( ["created_at > ? AND created_at < ?", params[:start_date], params[:end_date]]) You can also use the greater-than-or-equal-to and less-than-or-equal-to like this: -Client.all(:conditions => +Client.where( ["created_at >= ? AND created_at <= ?", params[:start_date], params[:end_date]]) @@ -351,13 +344,13 @@ NOTE: Only equality, range and subset checking are possible with Hash conditions h5. Equality Conditions -Client.all(:conditions => { :locked => true }) +Client.where({ :locked => true }) The field name does not have to be a symbol it can also be a string: -Client.all(:conditions => { 'locked' => true }) +Client.where({ 'locked' => true }) h5. Range Conditions @@ -365,7 +358,7 @@ h5. Range Conditions The good thing about this is that we can pass in a range for our fields without it generating a large query as shown in the preamble of this section. -Client.all(:conditions => { :created_at => (Time.now.midnight - 1.day)..Time.now.midnight}) +Client.where({ :created_at => (Time.now.midnight - 1.day)..Time.now.midnight}) This will find all clients created yesterday by using a +BETWEEN+ SQL statement: @@ -381,7 +374,7 @@ h5. Subset Conditions If you want to find records using the +IN+ expression you can pass an array to the conditions hash: -Client.all(:conditions => { :orders_count => [1,3,5] }) +Client.where({ :orders_count => [1,3,5] }) This code will generate SQL like this: @@ -390,22 +383,6 @@ This code will generate SQL like this: SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) -h3. Find Options - -Apart from +:conditions+, +Model.find+ takes a variety of other options via the options hash for customizing the resulting record set. - - -Model.find(id_or_array_of_ids, options_hash) -Model.find(:last, options_hash) -Model.find(:first, options_hash) - -Model.first(options_hash) -Model.last(options_hash) -Model.all(options_hash) - - -The following sections give a top level overview of all the possible keys for the +options_hash+. - h4. Ordering To retrieve records from the database in a specific order, you can specify the +:order+ option to the +find+ call. @@ -413,37 +390,37 @@ To retrieve records from the database in a specific order, you can specify the + For example, if you're getting a set of records and want to order them in ascending order by the +created_at+ field in your table: -Client.all(:order => "created_at") +Client.order("created_at") You could specify +ASC+ or +DESC+ as well: -Client.all(:order => "created_at DESC") +Client.order("created_at DESC") # OR -Client.all(:order => "created_at ASC") +Client.order("created_at ASC") Or ordering by multiple fields: -Client.all(:order => "orders_count ASC, created_at DESC") +Client.order("orders_count ASC, created_at DESC") h4. Selecting Specific Fields By default, Model.find selects all the fields from the result set using +select *+. -To select only a subset of fields from the result set, you can specify the subset via +:select+ option on the +find+. +To select only a subset of fields from the result set, you can specify the subset via the +select+ method. -NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonly-objects. +NOTE: If the +select+ method is used, all the returning objects will be "read only":#readonly-objects.
    For example, to select only +viewable_by+ and +locked+ columns: -Client.all(:select => "viewable_by, locked") +Client.select("viewable_by, locked") The SQL query used by this find call will be somewhat like: @@ -463,17 +440,17 @@ Where +<attribute>+ is the attribute you asked for. The +id+ method will n You can also call SQL functions within the select option. For example, if you would like to only grab a single record per unique value in a certain field by using the +DISTINCT+ function you can do it like this: -Client.all(:select => "DISTINCT(name)") +Client.select("DISTINCT(name)") h4. Limit and Offset -To apply +LIMIT+ to the SQL fired by the +Model.find+, you can specify the +LIMIT+ using +:limit+ and +:offset+ options on the find. +To apply +LIMIT+ to the SQL fired by the +Model.find+, you can specify the +LIMIT+ using +limit+ and +offset+ methods on the relation. -If you want to limit the amount of records to a certain subset of all the records retrieved you usually use +:limit+ for this, sometimes coupled with +:offset+. Limit is the maximum number of records that will be retrieved from a query, and offset is the number of records it will start reading from from the first record of the set. For example: +If you want to limit the amount of records to a certain subset of all the records retrieved you usually use +limit+ for this, sometimes coupled with +offset+. Limit is the maximum number of records that will be retrieved from a query, and offset is the number of records it will start reading from from the first record of the set. For example: -Client.all(:limit => 5) +Client.limit(5) This code will return a maximum of 5 clients and because it specifies no offset it will return the first 5 clients in the table. The SQL it executes will look like this: @@ -482,10 +459,10 @@ This code will return a maximum of 5 clients and because it specifies no offset SELECT * FROM clients LIMIT 5 -Or specifying both +:limit+ and +:offset+: +Or chaining both +limit+ and +offset+: -Client.all(:limit => 5, :offset => 5) +Client.limit(5).offset(5) This code will return a maximum of 5 clients and because it specifies an offset this time, it will return these records starting from the 5th client in the clients table. The SQL looks like: @@ -496,12 +473,12 @@ SELECT * FROM clients LIMIT 5, 5 h4. Group -To apply +GROUP BY+ clause to the SQL fired by the +Model.find+, you can specify the +:group+ option on the find. +To apply +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find. For example, if you want to find a collection of the dates orders were created on: -Order.all(:group => "date(created_at)", :order => "created_at") +Order.group("date(created_at)").order("created_at") And this will give you a single +Order+ object for each date where there are orders in the database. @@ -519,7 +496,7 @@ SQL uses +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can For example: -Order.all(:group => "date(created_at)", :having => ["created_at > ?", 1.month.ago]) +Order.group("date(created_at)".having(["created_at > ?", 1.month.ago]) The SQL that would be executed would be something like this: @@ -532,18 +509,18 @@ This will return single order objects for each day, but only for the last month. h4. Readonly Objects -To explicitly disallow modification/destruction of the matching records returned by +Model.find+, you could specify the +:readonly+ option as +true+ to the find call. +To explicitly disallow modification/destruction of the matching records returned in a Relation object, you could chain the +readonly+ method as +true+ to the find call. Any attempt to alter or destroy the readonly records will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. To set this option, specify it like this: -Client.first(:readonly => true) +Client.first.readonly(true) If you assign this record to a variable client, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception: -client = Client.first(:readonly => true) +client = Client.first.readonly(true) client.locked = false client.save @@ -676,7 +653,7 @@ Now all of the following will produce the expected join queries using +INNER JOI h5. Joining a Single Association -Category.all :joins => :posts +Category.joins(:posts) This produces: @@ -689,7 +666,7 @@ SELECT categories.* FROM categories h5. Joining Multiple Associations -Post.all :joins => [:category, :comments] +Post.joins(:category, :comments) This produces: @@ -703,13 +680,13 @@ SELECT posts.* FROM posts h5. Joining Nested Associations (Single Level) -Post.all :joins => {:comments => :guest} +Post.joins(:comments => :guest) h5. Joining Nested Associations (Multiple Level) -Category.all :joins => {:posts => [{:comments => :guest}, :tags]} +Category.joins(:posts => [{:comments => :guest}, :tags]) h4. Specifying Conditions on the Joined Tables @@ -718,14 +695,14 @@ You can specify conditions on the joined tables using the regular "Array":#array time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.all :joins => :orders, :conditions => {'orders.created_at' => time_range} +Client.joins(:orders).where('orders.created_at' => time_range) An alternative and cleaner syntax to this is to nest the hash conditions: time_range = (Time.now.midnight - 1.day)..Time.now.midnight -Client.all :joins => :orders, :conditions => {:orders => {:created_at => time_range}} +Client.joins(:orders).where(:orders => {:created_at => time_range}) This will find all clients who have orders that were created yesterday, again using a +BETWEEN+ SQL expression. @@ -750,12 +727,12 @@ This code looks fine at the first sight. But the problem lies within the total n Solution to N 1 queries problem -Active Record lets you specify all the associations in advanced that are going to be loaded. This is possible by specifying the +:include+ option of the +Model.find+ call. By +:include+, Active Record ensures that all the specified associations are loaded using minimum possible number of queries. +Active Record lets you specify all the associations in advanced that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all the specified associations are loaded using minimum possible number of queries. Revisiting the above case, we could rewrite +Client.all+ to use eager load addresses: -clients = Client.all(:include => :address, :limit => 10) +clients = Client.includes(:address).limit(10) clients.each do |client| puts client.address.postcode @@ -772,12 +749,12 @@ SELECT addresses.* FROM addresses h4. Eager Loading Multiple Associations -Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +:include+ option. +Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method. h5. Array of Multiple Associations -Post.all :include => [:category, :comments] +Post.includes(:category, :comments) This loads all the posts and the associated category and comments for each post. @@ -785,14 +762,14 @@ This loads all the posts and the associated category and comments for each post. h5. Nested Associations Hash -Category.find 1, :include => {:posts => [{:comments => :guest}, :tags]} +Category.find(1).includes(:posts => [{:comments => :guest}, :tags]) The above code finds the category with id 1 and eager loads all the posts associated with the found category. Additionally, it will also eager load every posts' tags and comments. Every comment's guest association will get eager loaded as well. h4. Specifying Conditions on Eager Loaded Associations -Even though Active Record lets you specify conditions on the eager loaded associations just like +:joins+, the recommended way is to use ":joins":#joining-tables instead. +Even though Active Record lets you specify conditions on the eager loaded associations just like +joins+, the recommended way is to use "joins":#joining-tables instead. h3. Dynamic Finders @@ -889,10 +866,10 @@ Which will execute: SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan') -You can also use +:include+ or +:joins+ for this to do something a little more complex: +You can also use the +includes+ or +joins+ methods for this to do something a little more complex: -Client.count(:conditions => "clients.first_name = 'Ryan' AND orders.status = 'received'", :include => "orders") +Client.count.where("clients.first_name = 'Ryan' AND orders.status = 'received'").includes("orders") Which will execute: @@ -957,5 +934,6 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/16 +* February 3, 2010: Update to Rails 3 by "James Miller":credits.html#bensie * February 7, 2009: Second version by "Pratik":credits.html#lifo * December 29 2008: Initial version by "Ryan Bigg":credits.html#radar diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index bf39ed4c9f..0a9374e028 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -6,6 +6,68 @@ By referring to this guide you will learn the extensions to the Ruby core classe endprologue. +h3. How to Load Core Extensions + +h4. Stand-Alone Active Support + +In order to have a near zero default footprint, Active Support does not load anything by default. It is broken in small pieces so that you may load just what you need, and also has some convenience entry points to load related extensions in one shot, even everything. + +Thus, after a simple require like: + + +require 'active_support' + + +objects do not even respond to +blank?+, let's see how to load its definition. + +h5. Cherry-picking a Definition + +The most lightweight way to get +blank?+ is to cherry-pick the file that defines it. + +For every single method defined as a core extension this guide has a note that says where is such a method defined. In the case of +blank?+ the note reads: + +NOTE: Defined in +active_support/core_ext/object/blank.rb+. + +That means that this single call is enough: + + +require 'active_support/core_ext/object/blank' + + +Active Support has been carefully revised so that cherry-picking a file loads only strictly needed dependencies, if any. + +h5. Loading Grouped Core Extensions + +The next level is to simply load all extensions to +Object+. As a rule of thumb, extensions to +SomeClass+ are available in one shot by loading +active_support/core_ext/some_class+. + +Thus, if that would do, to have +blank?+ available we could just load all extensions to +Object+: + + +require 'active_support/core_ext/object' + + +h5. Loading All Core Extensions + +You may prefer just to load all core extensions, there is a file for that: + + +require 'active_support/core_ext' + + +h5. Loading All Active Support + +And finally, if you want to have all Active Support available just issue: + + +require 'active_support/all' + + +That does not even put the entire Active Support in memory upfront indeed, some stuff is configured via +autoload+, so it is only loaded if used. + +h4. Active Support Within a Ruby on Rails Application + +A Ruby on Rails application loads all Active Support unless +config.active_support.bare+ is true. In that case, the application will only load what the framework itself cherry-picks for its own needs, and can still cherry-pick itself at any granularity level, as explained in the previous section. + h3. Extensions to All Objects h4. +blank?+ and +present?+ @@ -138,16 +200,19 @@ end NOTE: Defined in +active_support/core_ext/object/try.rb+. -h4. +metaclass+ +h4. +singleton_class+ -The method +metaclass+ returns the singleton class on any object: +The method +singleton_class+ returns the singleton class of the receiver: -String.metaclass # => # -String.new.metaclass # => #> +String.singleton_class # => # +String.new.singleton_class # => #> -NOTE: Defined in +active_support/core_ext/object/metaclass.rb+. +WARNING: Fixnums and symbols have no singleton classes, +singleton_class+ +raises +TypeError+ on them. + +NOTE: Defined in +active_support/core_ext/object/singleton_class.rb+. h4. +class_eval(*args, &block)+ @@ -168,7 +233,7 @@ class Proc end -NOTE: Defined in +active_support/core_ext/object/metaclass.rb+. +NOTE: Defined in +active_support/core_ext/object/singleton_class.rb+. h4. +acts_like?(duck)+ @@ -323,6 +388,40 @@ TIP: Since +with_options+ forwards calls to its receiver they can be nested. Eac NOTE: Defined in +active_support/core_ext/object/with_options.rb+. +h5. +subclasses_of+ + +The method +subclasses_of+ receives an arbitrary number of class objects and returns all their anonymous or reachable descendants as a single array: + + +class C; end +subclasses_of(C) # => [] + +subclasses_of(Integer) # => [Bignum, Fixnum] + +module M + class A; end + class B1 < A; end + class B2 < A; end +end + +module N + class C < M::B1; end +end + +subclasses_of(M::A) # => [N::C, M::B2, M::B1] + + +The order in which these classes are returned is unspecified. The returned collection may have duplicates: + + +subclasses_of(Numeric, Integer) +# => [Bignum, Float, Fixnum, Integer, Date::Infinity, Rational, BigDecimal, Bignum, Fixnum] + + +See also +Class#subclasses+ in "Extensions to +Class+ FIX THIS LINK":FIXME. + +NOTE: Defined in +active_support/core_ext/object/extending.rb+. + h4. Instance Variables Active Support provides several methods to ease access to instance variables. @@ -341,7 +440,7 @@ end C.new(0, 1).instance_variable_names # => ["@y", "@x"]
    -WARNING: The order in which the names are returned is unespecified, and it indeed depends on the version of the interpreter. +WARNING: The order in which the names are returned is unspecified, and it indeed depends on the version of the interpreter. NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. @@ -422,11 +521,23 @@ end NOTE: Defined in +active_support/core_ext/kernel/reporting.rb+. +h4. +require_library_or_gem+ + +The convenience method +require_library_or_gem+ tries to load its argument with a regular +require+ first. If it fails loads +rubygems+ and tries again. + +If the first attempt is a failure and +rubygems+ can't be loaded the method raises +LoadError+. On the other hand, if +rubygems+ is available but the argument is not loadable as a gem, the method gives up and +LoadError+ is also raised. + +For example, that's the way the MySQL adapter loads the MySQL library: + + +require_library_or_gem('mysql') + + +NOTE: Defined in +active_support/core_ext/kernel/requires.rb+. + h3. Extensions to +Module+ -h4. Aliasing - -h5. +alias_method_chain+ +h4. +alias_method_chain+ Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_. @@ -476,6 +587,8 @@ Rails uses +alias_method_chain+ all over the code base. For example validations NOTE: Defined in +active_support/core_ext/module/aliasing.rb+. +h4. Attributes + h5. +alias_attribute+ Model attributes have a reader, a writer, and a predicate. You can aliase a model attribute having the corresponding three methods defined for you in one shot. As in other aliasing methods, the new name is the first argument, and the old name is the second (my mnemonic is they go in the same order as if you did an assignment): @@ -490,13 +603,453 @@ end NOTE: Defined in +active_support/core_ext/module/aliasing.rb+. -h4. Delegation +h5. +attr_accessor_with_default+ -The class method +delegate+ +The method +attr_accessor_with_default+ serves the same purpose as the Ruby macro +attr_accessor+ but allows you to set a default value for the attribute: + + +class Url + attr_accessor_with_default :port, 80 +end + +Url.new.port # => 80 + + +The default value can be also specified with a block, which is called in the context of the corresponding object: + + +class User + attr_accessor :name, :surname + attr_accessor_with_default(:full_name) { + [name, surname].compact.join(" ") + } +end + +u = User.new +u.name = 'Xavier' +u.surname = 'Noria' +u.full_name # => "Xavier Noria" + + +The result is not cached, the block is invoked in each call to the reader. + +You can overwrite the default with the writer: + + +url = Url.new +url.host # => 80 +url.host = 8080 +url.host # => 8080 + + +The default value is returned as long as the attribute is unset. The reader does not rely on the value of the attribute to know whether it has to return the default. It rather monitors the writer: if there's any assignment the value is no longer considered to be unset. + +Active Resource uses this macro to set a default value for the +:primary_key+ attribute: + + +attr_accessor_with_default :primary_key, 'id' + + +NOTE: Defined in +active_support/core_ext/module/attr_accessor_with_default.rb+. + +h5. Internal Attributes + +When you are defining an attribute in a class that is meant to be subclassed name collisions are a risk. That's remarkably important for libraries. + +Active Support defines the macros +attr_internal_reader+, +attr_internal_writer+, and +attr_internal_accessor+. They behave like their Ruby builtin +attr_*+ counterparts, except they name the unerlying instace variable in a way that makes collisions less likely. + +The macro +attr_internal+ is a synonim for +attr_internal_accessor+: + + +# library +class ThirdPartyLibrary::Crawler + attr_internal :log_level +end + +# client code +class MyCrawler < ThirdPartyLibrary::Crawler + attr_accessor :log_level +end + + +In the previous example it could be the case that +:log_level+ does not belong to the public interface of the library and it is only used for development. The client code, unaware of the potential conflict, subclasses and defines its own +:log_level+. Thanks to +attr_internal+ there's no collision. + +By default the internal instance variable is named with a leading underscore, +@_log_level+ in the example above. That's configurable via +Module.attr_internal_naming_format+ though, you can pass any +sprintf+-like format string with a leading +@+ and a +%s+ somewhere, which is where the name will be placed. The default is +"@_%s"+. + +Rails uses internal attributes in a few spots, for examples for views: + + +module ActionView + class Base + attr_internal :captures + attr_internal :request, :layout + attr_internal :controller, :template + end +end + + +NOTE: Defined in +active_support/core_ext/module/attr_internal.rb+. + +h5. Module Attributes + +The macros +mattr_reader+, +mattr_writer+, and +mattr_accessor+ are analogous to the +cattr_*+ macros defined for class. Check "Class Attributes":#class-attributes. + +For example, the dependencies mechanism uses them: + + +module ActiveSupport + module Dependencies + mattr_accessor :warnings_on_first_load + mattr_accessor :history + mattr_accessor :loaded + mattr_accessor :mechanism + mattr_accessor :load_paths + mattr_accessor :load_once_paths + mattr_accessor :autoloaded_constants + mattr_accessor :explicitly_unloadable_constants + mattr_accessor :logger + mattr_accessor :log_activity + mattr_accessor :constant_watch_stack + mattr_accessor :constant_watch_stack_mutex + end +end + + +NOTE: Defined in +active_support/core_ext/module/attribute_accessors.rb+. + +h4. Method Delegation + +The class method +delegate+ offers an easy way to forward methods. + +For example, if +User+ has some details like the age factored out to +Profile+, it could be handy to still be able to acces such attribute directly, user.age, instead of having to explicit the chain user.profile.age. + +That can be accomplished by hand: + + +class User + has_one :profile + + def age + profile.age + end +end + + +But with +delegate+ you can make that shorter and the intention even more obvious: + + +class User + has_one :profile + + delegate :age, to => :profile +end + + +The macro accepts more than one method: + + +class User + has_one :profile + + delegate :age, :avatar, :twitter_username, to => :profile +end + + +Methods can be delegated to objects returned by methods, as in the examples above, but also to instance variables, class variables, and constants. Just pass their names as symbols or strings, including the at signs in the last cases. + +For example, +ActionView::Base+ delegates +erb_trim_mode=+: + + +module ActionView + class Base + delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' + end +end + + +In fact, you can delegate to any expression passed as a string. It will be evaluated in the context of the receiver. Controllers for example delegate alerts and notices to the current flash: + + +delegate :alert, :notice, :to => "request.flash" + + +If the target is +nil+ calling any delegated method will raise an exception even if +nil+ responds to such method. You can override this behavior setting the option +:allow_nil+ to true, in which case the forwarded call will simply return +nil+. + +If the target is a method, the name of delegated methods can also be prefixed. If the +:prefix+ option is set to (exactly) the +true+ object, the value of the +:to+ option is prefixed: + + +class Invoice + belongs_to :customer + + # defines a method called customer_name + delegate :name, :to => :customer, :prefix => true +end + + +And a custom prefix can be set as well, in that case it does not matter wheter the target is a method or not: + + +class Account + belongs_to :user + + # defines a method called admin_email + delegate :email, :to => :user, :prefix => 'admin' +end + + +NOTE: Defined in +active_support/core_ext/module/delegation.rb+. + +h4. Method Removal + +h5. +remove_possible_method+ + +The method +remove_possible_method+ is like the standard +remove_method+, except it silently returns on failure: + + +class A; end + +A.class_eval do + remove_method(:nonexistent) # raises NameError + remove_possible_method(:nonexistent) # no problem, continue +end + + +This may come in handy if you need to define a method that may already exist, since redefining a method issues a warning "method redefined; discarding old redefined_method_name". + +NOTE: Defined in +active_support/core_ext/module/remove_method.rb+. + +h4. Parents + +h5. +parent+ + +The +parent+ method on a nested named module returns the module that contains its corresponding constant: + + +module X + module Y + module Z + end + end +end +M = X::Y::Z + +X::Y::Z.parent # => X::Y +M.parent # => X::Y + + +If the module is anonymous or belongs to the top-level, +parent+ returns +Object+. + +WARNING: Note that in that case +parent_name+ returns +nil+. + +NOTE: Defined in +active_support/core_ext/module/introspection.rb+. + +h5. +parent_name+ + +The +parent_name+ method on a nested named module returns the fully-qualified name of the module that contains its corresponding constant: + + +module X + module Y + module Z + end + end +end +M = X::Y::Z + +X::Y::Z.parent_name # => "X::Y" +M.parent_name # => "X::Y" + + +For top-level or anonymous modules +parent_name+ returns +nil+. + +WARNING: Note that in that case +parent+ returns +Object+. + +NOTE: Defined in +active_support/core_ext/module/introspection.rb+. + +h5. +parents+ + +The method +parents+ calls +parent+ on the receiver and upwards until +Object+ is reached. The chain is returned in an array, from bottom to top: + + +module X + module Y + module Z + end + end +end +M = X::Y::Z + +X::Y::Z.parents # => [X::Y, X, Object] +M.parents # => [X::Y, X, Object] + + +NOTE: Defined in +active_support/core_ext/module/introspection.rb+. + +h4. Constants + +The method +local_constants+ returns the names of the constants that have been defined in the receiver module: + + +module X + X1 = 1 + X2 = 2 + module Y + Y1 = :y1 + X1 = :overrides_X1_above + end +end + +X.local_constants # => ["X2", "X1", "Y"], assumes Ruby 1.8 +X::Y.local_constants # => ["X1", "Y1"], assumes Ruby 1.8 + + +The names are returned as strings in Ruby 1.8, and as symbols in Ruby 1.9. The method +local_constant_names+ returns always strings. + +WARNING: This method is exact if running under Ruby 1.9. In previous versions it may miss some constants if their value in some ancestor stores the exact same object than in the receiver. + +NOTE: Defined in +active_support/core_ext/module/introspection.rb+. + +h4. Synchronization + +The +synchronize+ macro declares a method to be synchronized: + + +class Counter + @@mutex = Mutex.new + attr_reader :value + + def initialize + @value = 0 + end + + def incr + @value += 1 # non-atomic + end + synchronize :incr, :with => '@@mutex' +end + + +The method receives the name of an action, and a +:with+ option with code. The code is evaluated in the context of the receiver each time the method is invoked, and it should evaluate to a +Mutex+ instance or any other object that responds to +synchronize+ and accepts a block. + +NOTE: Defined in +active_support/core_ext/module/synchronization.rb+. + +h4. Reachable + +A named module is reachable if it is stored in its correspoding constant. It means you can reach the module object via the constant. + +That is what ordinarily happens, if a module is called "M", the +M+ constant exists and holds it: + + +module M +end + +M.reachable? # => true + + +But since constants and modules are indeed kind of decoupled, module objects can become unreachable: + + +module M +end + +orphan = Object.send(:remove_const, :M) + +# The module object is orphan now but it still has a name. +orphan.name # => "M" + +# You cannot reach it via the constant M because it does not even exist. +orphan.reachable? # => false + +# Let's define a module called "M" again. +module M +end + +# The constant M exists now again, and it stores a module +# object called "M", but it is a new instance. +orphan.reachable? # => false + + +NOTE: Defined in +active_support/core_ext/module/reachable.rb+. + +h4. Anonymous + +A module may or may not have a name: + + +module M +end +M.name # => "M" + +N = Module.new +N.name # => "N" + +Module.new.name # => "" in 1.8, nil in 1.9 + + +You can check whether a module has a name with the predicate +anonymous?+: + + +module M +end +M.anonymous? # => false + +Module.new.anonymous? # => true + + +Note that being unreachable does not imply being anonymous: + + +module M +end + +m = Object.send(:remove_const, :M) + +m.reachable? # => false +m.anonymous? # => false + + +though an anonymous module is unreachable by definition. + +NOTE: Defined in +active_support/core_ext/module/anonymous.rb+. h3. Extensions to +Class+ -h4. Class Attribute Accessors +h4. Class Attributes + +The method +Class#class_attribute+ declares one or more inheritable class attributes that can be overriden at any level down the hierarchy: + + +class A + class_attribute :x +end + +class B < A; end + +class C < B; end + +A.x = :a +B.x # => :a +C.x # => :a + +B.x = :b +A.x # => :a +C.x # => :b + +C.x = :c +A.x # => :a +B.x # => :b + + +For example that's the way the +allow_forgery_protection+ flag is implemented for controllers: + + +class_attribute :allow_forgery_protection +self.allow_forgery_protection = true + + +For convenience +class_attribute+ defines also a predicate, so that declaration also generates +allow_forgery_protection?+. Such predicate returns the double boolean negation of the value. + +NOTE: Defined in +active_support/core_ext/class/attribute.rb+ The macros +cattr_reader+, +cattr_writer+, and +cattr_accessor+ are analogous to their +attr_*+ counterparts but for classes. They initialize a class variable to +nil+ unless it already exists, and generate the corresponding class methods to access it: @@ -529,6 +1082,8 @@ module ActiveRecord end
    +A model may find that option useful as a way to prevent mass-assignment from setting the attribute. + NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+. h4. Class Inheritable Attributes @@ -587,9 +1142,11 @@ If for whatever reason an application loads the definition of a mailer class and NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+. -h4. Subclasses +h4. Descendants -The +subclasses+ method returns the names of all subclasses of a given class as an array of strings. That comprises not only direct subclasses, but all descendants down the hierarchy: +h5. +subclasses+ + +The +subclasses+ method returns the names of all the anonymous or reachable descendants of its receiver as an array of strings: class C; end @@ -614,57 +1171,77 @@ The order in which these class names are returned is unspecified. See also +Object#subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. -NOTE: Defined in +active_support/core_ext/class/removal.rb+. +WARNING: This method is redefined in some Rails core classes. -h4. Class Removal - -Roughly speaking, the +remove_class+ method removes the class objects passed as arguments: - - -Class.remove_class(Hash, Dir) # => [Hash, Dir] -Hash # => NameError: uninitialized constant Hash -Dir # => NameError: uninitialized constant Dir - - -More specifically, +remove_class+ attempts to remove constants with the same name as the passed class objects from their parent modules. So technically this method does not guarantee the class objects themselves are not still valid and alive somewhere after the method call: - - -module M - class A; end - class B < A; end -end - -A2 = M::A - -M::A.object_id # => 13053950 -Class.remove_class(M::A) - -M::B.superclass.object_id # => 13053950 (same object as before) -A2.name # => "M::A" (name is hard-coded in object) - - -WARNING: Removing fundamental classes like +String+ can result in really funky behaviour. - -The method +remove_subclasses+ provides a shortcut for removing all descendants of a given class, where "removing" has the meaning explained above: - - -class A; end -class B1 < A; end -class B2 < A; end -class C < A; end - -A.subclasses # => ["C", "B2", "B1"] -A.remove_subclasses -A.subclasses # => [] -C # => NameError: uninitialized constant C - - -See also +Object#remove_subclasses_of+ in "Extensions to All Objects FIX THIS LINK":FIXME. - -NOTE: Defined in +active_support/core_ext/class/removal.rb+. +NOTE: Defined in +active_support/core_ext/class/subclasses.rb+. h3. Extensions to +String+ +h4. Output Safety + +Inserting data into HTML templates needs extra care. For example you can't just interpolate +@review.title+ verbatim into an HTML page. On one hand if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;". On the other hand, depending on the application that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks. + +Active Support has the concept of (html) safe strings since Rails 3. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. + +Strings are considered to be unsafe by default: + + +"".html_safe? # => false + + +You can obtain a safe string from a given one with the +html_safe+ method: + + +s = "".html_safe +s.html_safe? # => true + + +It is important to understand that +html_safe+ performs no escaping whatsoever, it is just an assertion: + + +s = "".html_safe +s.html_safe? # => true +s # => "" + + +It is your responsability to ensure calling +html_safe+ on a particular string is fine. + +NOTE: For performance reasons safe strings are implemented in a way that cannot offer an in-place +html_safe!+ variant. + +If you append onto a safe string, either in-place with +concat+/<<, or with +, the result is a safe string. Unsafe arguments are escaped: + + +"".html_safe + "<" # => "<" + + +Safe arguments are directly appended: + + +"".html_safe + "<".html_safe # => "<" + + +These methods should not be used in ordinary views. In Rails 3 unsafe values are automatically escaped: + + +<%= @review.title %> <%# fine in Rails 3, escaped if needed %> + + +To insert something verbatim use the +raw+ helper rather than calling +html_safe+: + + +<%= raw @cms.current_template %> <%# inserts @cms.current_template as is %> + + +The +raw+ helper calls +html_safe+ for you: + + +def raw(stringish) + stringish.to_s.html_safe +end + + +NOTE: Defined in +active_support/core_ext/string/output_safety.rb+. + h4. +squish+ The method +String#squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: @@ -755,6 +1332,338 @@ The call +str.last(n)+ is equivalent to +str.from(-n)+ if +n+ > 0, and returns a NOTE: Defined in +active_support/core_ext/string/access.rb+. +h4. Inflections + +h5. +pluralize+ + +The method +pluralize+ returns the plural of its receiver: + + +"table".pluralize # => "tables" +"ruby".pluralize # => "rubies" +"equipment".pluralize # => "equipment" + + +As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Builtin rules can be extended in +config/initializers/inflections.rb+. That file is generated by the +rails+ command and has instructions in comments. + +Active Record uses this method to compute the default table name that corresponds to a model: + + +# active_record/base.rb +def undecorated_table_name(class_name = base_class.name) + table_name = class_name.to_s.demodulize.underscore + table_name = table_name.pluralize if pluralize_table_names + table_name +end + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +singularize+ + +The inverse of +pluralize+: + + +"tables".singularize # => "table" +"rubies".singularize # => "ruby" +"equipment".singularize # => "equipment" + + +Associations compute the name of the corresponding default associated class using this method: + + +# active_record/reflection.rb +def derive_class_name + class_name = name.to_s.camelize + class_name = class_name.singularize if collection? + class_name +end + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +camelize+ + +The method +camelize+ returns its receiver in camel case: + + +"product".camelize # => "Product" +"admin_user".camelize # => "AdminUser" + + +As a rule of thumb you can think of this method as the one that transforms paths into Ruby class or module names, where slashes separate namespaces: + + +"backoffice/session".camelize # => "Backoffice::Session" + + +For example, Action Pack uses this method to load the class that provides a certain session store: + + +# action_controller/metal/session_management.rb +def session_store=(store) + if store == :active_record_store + self.session_store = ActiveRecord::SessionStore + else + @@session_store = store.is_a?(Symbol) ? + ActionDispatch::Session.const_get(store.to_s.camelize) : + store + end +end + + ++camelize+ accepts an optional argument, it can be +:upper+ (default), or +:lower+. With the latter the first letter becomes lowercase: + + +"visual_effect".camelize(:lower) # => "visualEffect" + + +That may be handy to compute method names in a language that follows that convention, for example JavaScript. + ++camelize+ is aliased to +camelcase+. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +underscore+ + +The method +underscore+ is the inverse of +camelize+, explained above: + + +"Product".underscore # => "product" +"AdminUser".underscore # => "admin_user" + + +Also converts "::" back to "/": + + +"Backoffice::Session".underscore # => "backoffice/session" + + +and understands strings that start with lowercase: + + +"visualEffect".underscore # => "visual_effect" + + ++underscore+ accepts no argument though. + +Rails class and module autoloading uses +underscore+ to infer the relative path without extension of a file that would define a given missing constant: + + +# active_support/dependencies.rb +def load_missing_constant(from_mod, const_name) + ... + qualified_name = qualified_name_for from_mod, const_name + path_suffix = qualified_name.underscore + ... +end + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +titleize+ + +The method +titleize+ capitalizes the words in the receiver: + + +"alice in wonderland".titleize # => "Alice In Wonderland" +"fermat's enigma".titleize # => "Fermat's Enigma" + + ++titleize+ is aliased to +titlecase+. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +dasherize+ + +The method +dasherize+ replaces the underscores in the receiver with dashes: + + +"name".dasherize # => "name" +"contact_data".dasherize # => "contact-data" + + +The XML serializer of models uses this method to dasherize node names: + + +# active_model/serializers/xml.rb +def reformat_name(name) + name = name.camelize if camelize? + dasherize? ? name.dasherize : name +end + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +demodulize+ + +Given a string with a qualified constant reference expression, +demodulize+ returns the very constant name, that is, the rightmost part of it: + + +"Product".demodulize # => "Product" +"Backoffice::UsersController".demodulize # => "UsersController" +"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils" + + +Active Record for example uses this method to compute the name of a counter cache column: + + +# active_record/reflection.rb +def counter_cache_column + if options[:counter_cache] == true + "#{active_record.name.demodulize.underscore.pluralize}_count" + elsif options[:counter_cache] + options[:counter_cache] + end +end + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +parameterize+ + +The method +parameterize+ normalizes its receiver in a way that can be used in pretty URLs. + + +"John Smith".parameterize # => "john-smith" +"Kurt Gödel".parameterize # => "kurt-godel" + + +In fact, the result string is wrapped in an instance of +ActiveSupport::Multibyte::Chars+. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +tableize+ + +The method +tableize+ is +underscore+ followed by +pluralize+. + + +"Person".tableize # => "people" +"Invoice".tableize # => "invoices" +"InvoiceLine".tableize # => "invoice_lines" + + +As a rule of thumb, +tableize+ returns the table name that corresponds to a given model for simple cases. The actual implementation in Active Record is not straight +tableize+ indeed, because it also demodulizes de class name and checks a few options that may affect the returned string. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +classify+ + +The method +classify+ is the inverse of +tableize+. It gives you the class name corresponding to a table name: + + +"people".classify # => "Person" +"invoices".classify # => "Invoice" +"invoice_lines".classify # => "InvoiceLine" + + +The method understands qualified table names: + + +"highrise_production.companies".classify # => "Company" + + +Note that +classify+ returns a class name as a string. You can get the actual class object invoking +constantize+ on it, explained next. + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +constantize+ + +The method +constantize+ resolves the constant reference expression in its receiver: + + +"Fixnum".constantize # => Fixnum + +module M + X = 1 +end +"M::X".constantize # => 1 + + +If the string evaluates to no known constant, or its content is not even a valid constant name, +constantize+ raises +NameError+. + +Constant name resolution by +constantize+ starts always at the top-level +Object+ even if there is no leading "::". + + +X = :in_Object +module M + X = :in_M + + X # => :in_M + "::X".constantize # => :in_Object + "X".constantize # => :in_Object (!) +end + + +So, it is in general not equivalent to what Ruby would do in the same spot, had a real constant be evaluated. + +Mailer test cases obtain the mailer being tested from the name of the test class using +constantize+: + + +# action_mailer/test_case.rb +def determine_default_mailer(name) + name.sub(/Test$/, '').constantize +rescue NameError => e + raise NonInferrableMailerError.new(name) +end + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +humanize+ + +The method +humanize+ gives you a sensible name for display out of an attribute name. To do so it replaces underscores with spaces, removes any "_id" suffix, and capitalizes the first word: + + +"name".humanize # => "Name" +"author_id".humanize # => "Author" +"comments_count".humanize # => "Comments count" + + +The helper method +full_messages+ uses +humanize+ as a fallback to include attribute names: + + +def full_messages + full_messages = [] + + each do |attribute, messages| + ... + attr_name = attribute.to_s.gsub('.', '_').humanize + attr_name = @base.class.human_attribute_name(attribute, :default => attr_name) + ... + end + + full_messages +end + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + +h5. +foreign_key+ + +The method +foreign_key+ gives a foreign key column name from a class name. To do so it demodulizes, underscores, and adds "_id": + + +"User".foreign_key # => "user_id" +"InvoiceLine".foreign_key # => "invoice_line_id" +"Admin::Session".foreign_key # => "session_id" + + +Pass a false argument if you do not want the underscore in "_id": + + +"User".foreign_key(false) # => "userid" + + +Associations use this method to infer foreign keys, for example +has_one+ and +has_many+ do this: + + +# active_record/associations.rb +foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key + + +NOTE: Defined in +active_support/core_ext/string/inflections.rb+. + h3. Extensions to +Numeric+ h4. Bytes @@ -816,7 +1725,15 @@ NOTE: Defined in +active_support/core_ext/integer/inflections.rb+. h3. Extensions to +Float+ -... +h4. +round+ + +The builtin method +Float#round+ rounds a float to the nearest integer. Active Support adds an optional parameter to let you specify a precision: + + +Math::E.round(4) # => 2.7183 + + +NOTE: Defined in +active_support/core_ext/float/rounding.rb+. h3. Extensions to +BigDecimal+ @@ -1765,7 +2682,7 @@ File.atomic_write(joined_asset_path) do |cache| end -To accomplish this +atomic_write+ creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed. If the target file exists +atomic_write+ overwrites it and keeps owners and permissions. +To accomplish this +atomic_write+ creates a temporary file. That's the file the code in the block actually writes to. On completion, the temporary file is renamed, which is an atomic operation on POSIX systems. If the target file exists +atomic_write+ overwrites it and keeps owners and permissions. WARNING. Note you can't append with +atomic_write+. @@ -1799,20 +2716,11 @@ NOTE: Defined in +active_support/core_ext/name_error.rb+. h3. Extensions to +LoadError+ -Rails hijacks +LoadError.new+ to return a +MissingSourceFile+ exception: +Active Support adds +is_missing?+ to +LoadError+, and also assigns that class to the constant +MissingSourceFile+ for backwards compatibility. - -$ ruby -e 'require "nonexistent"' -...: no such file to load -- nonexistent (LoadError) -... -$ rails runner 'require "nonexistent"' -...: no such file to load -- nonexistent (MissingSourceFile) -... - +Given a path name +is_missing?+ tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). -The class +MissingSourceFile+ is a subclass of +LoadError+, so any code that rescues +LoadError+ as usual still works as expected. Point is these exception objects respond to +is_missing?+, which given a path name tests whether the exception was raised due to that particular file (except perhaps for the ".rb" extension). - -For example, when an action of +PostsController+ is called Rails tries to load +posts_helper.rb+, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist, but it in turn requires another library that is missing. In that case Rails must reraise the exception. The method +is_missing?+ provides a way to distinguish both cases: +For example, when an action of +PostsController+ is called Rails tries to load +posts_helper.rb+, but that file may not exist. That's fine, the helper module is not mandatory so Rails silences a load error. But it could be the case that the helper module does exist and in turn requires another library that is missing. In that case Rails must reraise the exception. The method +is_missing?+ provides a way to distinguish both cases: def default_helper_module! @@ -1820,7 +2728,7 @@ def default_helper_module! module_path = module_name.underscore helper module_path rescue MissingSourceFile => e - raise e unless e.is_missing? "#{module_path}_helper" + raise e unless e.is_missing? "helpers/#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/activerecord_validations_callbacks.textile index dc61021f76..6575612bee 100644 --- a/railties/guides/source/activerecord_validations_callbacks.textile +++ b/railties/guides/source/activerecord_validations_callbacks.textile @@ -240,11 +240,11 @@ end The +validates_exclusion_of+ helper has an option +:in+ that receives the set of values that will not be accepted for the validated attributes. The +:in+ option has an alias called +:within+ that you can use for the same purpose, if you'd like to. This example uses the +:message+ option to show how you can include the attribute's value. -The default error message for +validates_exclusion_of+ is "_is not included in the list_". +The default error message for +validates_exclusion_of+ is "_is reserved_". h4. +validates_format_of+ -This helper validates the attributes' values by testing whether they match a given regular expresion, which is specified using the +:with+ option. +This helper validates the attributes' values by testing whether they match a given regular expression, which is specified using the +:with+ option. class Product < ActiveRecord::Base diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index ac6f944457..e27c2a6dc6 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -152,7 +152,7 @@ expire_fragment('all_available_products') h4. Sweepers -Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into a +ActionController::Caching::Sweeper+ class. This class is an Observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter. +Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into an +ActionController::Caching::Sweeper+ subclass. This class is an observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter. Continuing with our Product controller example, we could rewrite it with a sweeper like this: diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index a84e928731..c888212604 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -12,15 +12,18 @@ NOTE: This tutorial assumes you have basic Rails knowledge from reading the "Get endprologue. +WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails. + h3. Command Line Basics There are a few commands that are absolutely critical to your everyday usage of Rails. In the order of how much you'll probably use them are: -* console -* server +* rails console +* rails server * rake -* generate -* rails +* rails generate +* rails dbconsole +* rails app_name Let's create a simple Rails application to step through each of these commands in context. @@ -28,33 +31,35 @@ h4. +rails+ The first thing we'll want to do is create a new Rails application by running the +rails+ command after installing Rails. -WARNING: You know you need the rails gem installed by typing +gem install rails+ first, right? Okay, okay, just making sure. +WARNING: You know you need the rails gem installed by typing +gem install rails+ first, if you don't have this installed, follow the instructions in the "Rails 3 Release Notes":/3_0_release_notes.textile $ rails commandsapp - - create - create app/controllers - create app/helpers - create app/models + create + create README + create .gitignore + create Rakefile + create config.ru + create Gemfile + create app ... - ... - create log/production.log - create log/development.log - create log/test.log + create tmp/cache + create tmp/pids + create vendor/plugins + create vendor/plugins/.gitkeep Rails will set you up with what seems like a huge amount of stuff for such a tiny command! You've got the entire Rails directory structure now with all the code you need to run our simple application right out of the box. INFO: This output will seem very familiar when we get to the +generate+ command. Creepy foreshadowing! -h4. +server+ +h4. +rails server+ -Let's try it! The +server+ command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser. +Let's try it! The +rails server+ command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser. INFO: WEBrick isn't your only option for serving Rails. We'll get to that in a later section. -Without any prodding of any kind, +server+ will run our new shiny Rails app: +Without any prodding of any kind, +rails server+ will run our new shiny Rails app: $ cd commandsapp @@ -67,13 +72,11 @@ $ rails server [2008-11-04 10:11:38] INFO WEBrick::HTTPServer#start: pid=18994 port=3000 -WHOA. With just three commands we whipped up a Rails server listening on port 3000. Go! Go right now to your browser and go to http://localhost:3000. I'll wait. +With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open "http://localhost:3000":http://localhost:3000, you will see a basic rails app running. -See? Cool! It doesn't do much yet, but we'll change that. +h4. +rails generate+ -h4. +generate+ - -The +generate+ command uses templates to create a whole lot of things. You can always find out what's available by running +generate+ by itself. Let's do that: +The +rails generate+ command uses templates to create a whole lot of things. You can always find out what's available by running +rails generate+ by itself. Let's do that: $ rails generate @@ -82,16 +85,18 @@ Usage: rails generate generator [options] [args] ... ... -Installed Generators - Built-in: controller, integration_test, mailer, migration, model, observer, performance_test, plugin, resource, scaffold, session_migration +Please choose a generator below. -... -... +Rails: + controller + generator + ... + ... NOTE: You can install more generators through generator gems, portions of plugins you'll undoubtedly install, and you can even create your own! -Using generators will save you a large amount of time by writing *boilerplate code* for you -- necessary for the darn thing to work, but not necessary for you to spend time writing. That's what we have computers for, right? +Using generators will save you a large amount of time by writing *boilerplate code*, code that is necessary for the app to work, but not necessary for you to spend time writing. That's what we have computers for. Let's make our own controller with the controller generator. But what command should we use? Let's ask the generator: @@ -123,34 +128,34 @@ Modules Example: Test: test/functional/admin/credit_card_controller_test.rb -Ah, the controller generator is expecting parameters in the form of +generate controller ControllerName action1 action2+. Let's make a +Greetings+ controller with an action of *hello*, which will say something nice to us. +The controller generator is expecting parameters in the form of +generate controller ControllerName action1 action2+. Let's make a +Greetings+ controller with an action of *hello*, which will say something nice to us. $ rails generate controller Greetings hello - exists app/controllers/ - exists app/helpers/ - create app/views/greetings - exists test/functional/ create app/controllers/greetings_controller.rb - create test/functional/greetings_controller_test.rb - create app/helpers/greetings_helper.rb - create app/views/greetings/hello.html.erb + invoke erb + create app/views/greetings + create app/views/greetings/hello.html.erb + error rspec [not found] + invoke helper + create app/helpers/greetings_helper.rb + error rspec [not found] -Look there! Now what all did this generate? It looks like it made sure a bunch of directories were in our application, and created a controller file, a functional test file, a helper for the view, and a view file. +What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a functional test file, a helper for the view, and a view file. -Let's check out the controller and modify it a little (in +app/controllers/greetings_controller.rb+): +Check out the controller and modify it a little (in +app/controllers/greetings_controller.rb+):ma class GreetingsController < ApplicationController def hello - @message = "Hello, how are you today? I am exuberant!" + @message = "Hello, how are you today?" end end -Then the view, to display our nice message (in +app/views/greetings/hello.html.erb+): +Then the view, to display our message (in +app/views/greetings/hello.html.erb+):

    A Greeting for You!

    @@ -166,11 +171,11 @@ $ rails server WARNING: Make sure that you do not have any "tilde backup" files in +app/views/(controller)+, or else WEBrick will _not_ show the expected output. This seems to be a *bug* in Rails 2.3.0. -The URL will be +http://localhost:3000/greetings/hello+. I'll wait for you to be suitably impressed. +The URL will be "http://localhost:3000/greetings/hello":http://localhost:3000/greetings/hello. INFO: With a normal, plain-old Rails application, your URLs will generally follow the pattern of http://(host)/(controller)/(action), and a URL like http://(host)/(controller) will hit the *index* action of that controller. -"What about data, though?", you ask over a cup of coffee. Rails comes with a generator for data models too. Can you guess its generator name? +Rails comes with a generator for data models too: $ rails generate model @@ -181,7 +186,6 @@ Usage: rails generate model ModelName [field:type, field:type] Examples: rails generate model account - creates an Account model, test, fixture, and migration: Model: app/models/account.rb Test: test/unit/account_test.rb Fixtures: test/fixtures/accounts.yml @@ -189,12 +193,12 @@ Examples: rails generate model post title:string body:text published:boolean - creates a Post model with a string title, text body, and published flag. + Creates a Post model with a string title, text body, and published flag. But instead of generating a model directly (which we'll be doing later), let's set up a scaffold. A *scaffold* in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above. -Let's set up a simple resource called "HighScore" that will keep track of our highest score on video games we play. +We will set up a simple resource called "HighScore" that will keep track of our highest score on video games we play. $ rails generate scaffold HighScore game:string score:integer @@ -227,19 +231,17 @@ dependency model create db/migrate/20081217071914_create_high_scores.rb -Taking it from the top - the generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the +high_scores+ table and fields), takes care of the route for the *resource*, and new tests for everything. +The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the +high_scores+ table and fields), takes care of the route for the *resource*, and new tests for everything. -The migration requires that we *migrate*, that is, run some Ruby code (living in that +20081217071914_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while. - -CAUTION: Hey. Install the sqlite3-ruby gem while you're at it. +gem install sqlite3-ruby+ +The migration requires that we *migrate*, that is, run some Ruby code (living in that +20100209025147_create_high_scores.rb+) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the +rake db:migrate+ command. We'll talk more about Rake in-depth in a little while. $ rake db:migrate -(in /home/commandsapp) - CreateHighScores: migrating - create_table(:high_scores) - -> 0.0070s - CreateHighScores: migrated (0.0077s) +(in /Users/mikel/rails_programs/commandsapp) +== CreateHighScores: migrating =============================================== +-- create_table(:high_scores) + -> 0.0026s +== CreateHighScores: migrated (0.0028s) ====================================== INFO: Let's talk about unit tests. Unit tests are code that tests and makes assertions about code. In unit testing, we take a little part of code, say a method of a model, and test its inputs and outputs. Unit tests are your friend. The sooner you make peace with the fact that your quality of life will drastically increase when you unit test your code, the better. Seriously. We'll make one in a moment. @@ -248,22 +250,22 @@ Let's see the interface Rails created for us. rails server; http://localhost:300 We can create new high scores (55,160 on Space Invaders!) -h4. +console+ +h4. +rails console+ The +console+ command lets you interact with your Rails application from the command line. On the underside, +rails console+ uses IRB, so if you've ever used it, you'll be right at home. This is useful for testing out quick ideas with code and changing data server-side without touching the website. -h4. +dbconsole+ +h4. +rails dbconsole+ -+dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. ++rails dbconsole+ figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. -h4. +plugin+ +h4. +rails plugin+ -The +plugin+ command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command *discover*, which sifts through repositories looking for plugins, or call *source* to add a specific repository of plugins, or you can specify the plugin location directly. +The +rails plugin+ command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command *discover*, which sifts through repositories looking for plugins, or call *source* to add a specific repository of plugins, or you can specify the plugin location directly. Let's say you're creating a website for a client who wants a small accounting system. Every event having to do with money must be logged, and must never be deleted. Wouldn't it be great if we could override the behavior of a model to never actually take its record out of the database, but *instead*, just set a field? There is such a thing! The plugin we're installing is called "acts_as_paranoid", and it lets models implement a "deleted_at" column that gets set when you call destroy. Later, when calling find, the plugin will tack on a database check to filter out "deleted" things. - +================================================================================== $ rails plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_paranoid + ./CHANGELOG diff --git a/railties/guides/source/contribute.textile b/railties/guides/source/contribute.textile index 5087c2f385..1203e38a4e 100644 --- a/railties/guides/source/contribute.textile +++ b/railties/guides/source/contribute.textile @@ -11,8 +11,8 @@ h3. How to Contribute? * Guides are written in Textile, and reside at railties/guides/source in the docrails project. * All images are in the railties/guides/images directory. * Sample format : "Active Record Associations":http://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile -* Sample output : "Active Record Associations":http://guides.rails.info/association_basics.html -* You can build the Guides during testing by running +rake guides+ in the +railties+ directory. +* Sample output : "Active Record Associations":association_basics.html +* You can build the Guides during testing by running +rake generate_guides+ in the +railties+ directory. h3. What to Contribute? diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile index 805fcf1a32..1b6823a39a 100644 --- a/railties/guides/source/contributing_to_rails.textile +++ b/railties/guides/source/contributing_to_rails.textile @@ -76,21 +76,28 @@ TIP: You may want to "put your git branch name in your shell prompt":http://gith h4. Set up and Run the Tests All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. Rails needs the +mocha+ gem for running some tests, so install it with: + gem install mocha -For the tests that touch the database, this means creating the databases. If you're using MySQL: +For the tests that touch the database, this means creating test databases. If you're using MySQL, create a user named +rails+ with privileges on the test databases. -mysql> create database activerecord_unittest; -mysql> create database activerecord_unittest2; -mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* - to 'rails'@'localhost'; -mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* - to 'rails'@'localhost'; +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* + to 'rails'@'localhost'; +mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* + to 'rails'@'localhost'; +Enter this from the +activerecord+ directory to create the test databases: + + +rake mysql:build_databases + + +NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation. + If you’re using another database, check the files under +activerecord/test/connections+ in the Rails source code for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails. Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory: diff --git a/railties/guides/source/credits.textile.erb b/railties/guides/source/credits.textile.erb index 49e390908f..28c70f2b39 100644 --- a/railties/guides/source/credits.textile.erb +++ b/railties/guides/source/credits.textile.erb @@ -43,6 +43,10 @@ p. We'd like to thank the following people for their tireless contributions to t Cássio Marques is a Brazilian software developer working with different programming languages such as Ruby, JavaScript, CPP and Java, as an independent consultant. He blogs at "/* CODIFICANDO */":http://cassiomarques.wordpress.com, which is mainly written in Portuguese, but will soon get a new section for posts with English translation. <% end %> +<% author('James Miller', 'bensie') do %> + James Miller is a software developer for "JK Tech":http://www.jk-tech.com in San Diego, CA. Find me on GitHub, Gmail, Twitter, and Freenode as bensie. +<% end %> + <% author('Emilio Tagua', 'miloops') do %> Emilio Tagua -- a.k.a. miloops -- is an Argentinian entrepreneur, developer, open source contributor and Rails evangelist. Cofounder of "Eventioz":http://eventioz.com. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as miloops. <% end %> @@ -50,3 +54,7 @@ p. We'd like to thank the following people for their tireless contributions to t <% author('Heiko Webers', 'hawe') do %> Heiko Webers is the founder of "bauland42":http://www.bauland42.de, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the "Ruby on Rails Security Project":http://www.rorsecurity.info. After 10 years of desktop application development, Heiko has rarely looked back. <% end %> + +<% author('Mikel Lindsaar', 'raasdnil') do %> + Mikel Lindsaar has been working with Rails since 2006 and is the author of the Ruby Mail gem and core contributor (he helped re-write ActionMailer's API). Mikel has a "blog":http://lindsaar.net/ and "tweets":http://twitter.com/raasdnil. +<% end %> diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 0a76a0f06f..d33bb4b4ff 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -293,7 +293,7 @@ form_for(@article) ## Editing an existing article # long-style: -form_for(:article, @article, :url => article_path(@article), :method => "put") +form_for(:article, @article, :url => article_path(@article), :html => { :method => "put" }) # short-style: form_for(@article)
    diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index a6ac7f0f5b..c479c2fb20 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -9,17 +9,17 @@ This guide covers getting up and running with Ruby on Rails. After reading it, y endprologue. -WARNING. This Guide is based on Rails 2.3.3. Some of the code shown here will not work in other versions of Rails. +WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails. -h3. This Guide Assumes +h3. Guide Assumptions This guide is designed for beginners who want to get started with a Rails application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The "Ruby":http://www.ruby-lang.org/en/downloads language +* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher * The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system -* A working installation of "SQLite":http://www.sqlite.org (preferred), "MySQL":http://www.mysql.com, or "PostgreSQL":http://www.postgresql.org +* A working installation of the "SQLite3 Database":http://www.sqlite.org -It is highly recommended that you *familiarize yourself with Ruby before diving into Rails*. You will find it much easier to follow what's going on with a Rails application if you understand basic Ruby syntax. Rails isn't going to magically revolutionize the way you write web applications if you have no experience with the language it uses. There are some good free resources on the internet for learning Ruby, including: +Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning curve diving straight into Rails. There are some good free resources on the internet for learning Ruby, including: * "Mr. Neighborly's Humble Little Ruby Book":http://www.humblelittlerubybook.com * "Programming Ruby":http://www.ruby-doc.org/docs/ProgrammingRuby/ @@ -27,19 +27,19 @@ It is highly recommended that you *familiarize yourself with Ruby before diving h3. What is Rails? -Rails is a web development framework written in the Ruby language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. Longtime Rails developers also report that it makes web application development more fun. +Rails is a web application development framework written in the Ruby language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. Experienced Rails developers also report that it makes web application development more fun. -Rails is opinionated software. That is, it assumes that there is a best way to do things, and it's designed to encourage that best way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience. +Rails is opinionated software. It makes the assumption that there is a "best" way to do things, and it's designed to encourage that way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience. The Rails philosophy includes several guiding principles: * DRY - "Don't Repeat Yourself" - suggests that writing the same code over and over again is a bad thing. -* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to do it, rather than letting you tweak every little thing through endless configuration files. +* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to do it, rather than requiring you to specify every little thing through endless configuration files. * REST is the best pattern for web applications - organizing your application around resources and standard HTTP verbs is the fastest way to go. h4. The MVC Architecture -Rails is organized around the Model, View, Controller architecture, usually just called MVC. MVC benefits include: +At the core of Rails is the Model, View, Controller architecture, usually just called MVC. MVC benefits include: * Isolation of business logic from the user interface * Ease of keeping code DRY @@ -59,15 +59,23 @@ Controllers provide the "glue" between models and views. In Rails, controllers a h4. The Components of Rails -Rails provides a full stack of components for creating web applications, including: +Rails ships as many individual components. -* Action Controller -* Action View -* Active Record +* Action Pack + ** Action Controller + ** Action Dispatch + ** Action View * Action Mailer +* Active Model +* Active Record * Active Resource -* Railties * Active Support +* Railties + + +h5. Action Pack + +Action Pack is a single gem that contains Action Controller, Action View and Action Dispatch. The "VC" part of "MVC". h5. Action Controller @@ -77,38 +85,46 @@ h5. Action View Action View manages the views of your Rails application. It can create both HTML and XML output by default. Action View manages rendering templates, including nested and partial templates, and includes built-in AJAX support. -h5. Active Record +h5. Action Dispatch -Active Record is the base for the models in a Rails application. It provides database independence, basic CRUD functionality, advanced finding capabilities, and the ability to relate models to one another, among other services. +Action Dispatch handles routing of web requests and dispatches them as you want, either to your application, any other Rack application. h5. Action Mailer Action Mailer is a framework for building e-mail services. You can use Action Mailer to send emails based on flexible templates, or to receive and process incoming email. +h5. Active Model + +Active Model provides a defined interface between the Action Pack gem services and Object Relationship Mapping gems such as Active Record. Active Model allows Rails to utilize other ORM frameworks in place of Active Record if your application needs this. + +h5. Active Record + +Active Record is the base for the models in a Rails application. It provides database independence, basic CRUD functionality, advanced finding capabilities, and the ability to relate models to one another, among other services. + h5. Active Resource Active Resource provides a framework for managing the connection between business objects and RESTful web services. It implements a way to map web-based resources to local objects with CRUD semantics. -h5. Railties - -Railties is the core Rails code that builds new Rails applications and glues the various frameworks together in any Rails application. - h5. Active Support Active Support is an extensive collection of utility classes and standard Ruby library extensions that are used in the Rails, both by the core code and by your applications. +h5. Railties + +Railties is the core Rails code that builds new Rails applications and glues the various frameworks and plugins together in any Rails application. + h4. REST -The foundation of the RESTful architecture is generally considered to be Roy Fielding's doctoral thesis, "Architectural Styles and the Design of Network-based Software Architectures":http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. Fortunately, you need not read this entire document to understand how REST works in Rails. REST, an acronym for Representational State Transfer, boils down to two main principles for our purposes: +Rest stands for Representational State Transfer and is the foundation of the RESTful architecture. This is generally considered to be Roy Fielding's doctoral thesis, "Architectural Styles and the Design of Network-based Software Architectures":http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. While you can read through the thesis, REST in terms of Rails boils down to two main principles: -* Using resource identifiers (which, for the purposes of discussion, you can think of as URLs) to represent resources +* Using resource identifiers such as URLs to represent resources. * Transferring representations of the state of that resource between system components. For example, to a Rails application a request such as this: DELETE /photos/17 -would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities and browser quirks. +would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action - deleting that resource. REST is a natural style for the architecture of web applications, and Rails hooks into this shielding you from many of the RESTful complexities and browser quirks. If you'd like more details on REST as an architectural style, these resources are more approachable than Fielding's thesis: @@ -126,35 +142,25 @@ h4. Installing Rails In most cases, the easiest way to install Rails is to take advantage of RubyGems: -$ gem install rails +Usually run this as the root user: +# gem install rails -NOTE. There are some special circumstances in which you might want to use an alternate installation strategy: +NOTE. In the Rails 3.0.0-beta, you will need to manually install the dependencies for Rails itself as a bug in rubygems will cause these to not be installed, see the "3.0 Release Notes":3_0_release_notes.html for the commands to run. -* If you're working on Windows, you may find it easier to install Instant Rails. Be aware, though, that "Instant Rails":http://instantrails.rubyforge.org/wiki/wiki.pl releases tend to lag seriously behind the actual Rails version. Also, you will find that Rails development on Windows is overall less pleasant than on other operating systems. If at all possible, we suggest that you install a Linux virtual machine and use that for Rails development, instead of using Windows. -* If you want to keep up with cutting-edge changes to Rails, you'll want to clone the "Rails source code":http://github.com/rails/rails/tree/master from github. This is not recommended as an option for beginners, though. - -WARNING. As of mid-2009, cloning the master branch will get you preliminary Rails 3.0 code. To follow along with this guide, you should clone the 2-3-stable branch instead. +TIP. If you're working on Windows, you may find it easier to install "Instant Rails":http://instantrails.rubyforge.org/wiki/wiki.pl. Be aware, though, that Instant Rails releases tend to lag seriously behind the actual Rails version. Also, you will find that Rails development on Windows is overall less pleasant than on other operating systems. If at all possible, we suggest that you install a Linux virtual machine and use that for Rails development, instead of using Windows. h4. Creating the Blog Application -Open a terminal, navigate to a folder where you have rights to create files, and type: +The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. If you need to see the completed code, you can download it from "Getting Started Code":http://github.com/mikel/getting-started-code. + +To begin, open a terminal, navigate to a folder where you have rights to create files, and type: $ rails blog -This will create a Rails application that uses a SQLite database for data storage. If you prefer to use MySQL, run this command instead: - - -$ rails blog -d mysql - - -And if you're using PostgreSQL for data storage, run this command: - - -$ rails blog -d postgresql - +This will create a Rails application called Blog in a directory called blog. TIP: You can see all of the switches that the Rails application builder accepts by running rails -h. @@ -167,52 +173,62 @@ $ cd blog In any case, Rails will create a folder in your working directory called blog. Open up that folder and explore its contents. Most of the work in this tutorial will happen in the app/ folder, but here's a basic rundown on the function of each folder that Rails creates in a new application by default: |_.File/Folder|_.Purpose| -|README|This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on.| +|Gemfile|This file allows you to specify what gem dependencies are needed for your Rails application.| +|README.rdoc|This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on.| |Rakefile|This file contains batch jobs that can be run from the terminal.| |app/|Contains the controllers, models, and views for your application. You'll focus on this folder for the remainder of this guide.| |config/|Configure your application's runtime rules, routes, database, and more.| +|config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Shows your current database schema, as well as the database migrations. You'll learn about migrations shortly.| |doc/|In-depth documentation for your application.| |lib/|Extended modules for your application (not covered in this guide).| |log/|Application log files.| |public/|The only folder seen to the world as-is. This is where your images, javascript, stylesheets (CSS), and other static files go.| -|script/|Scripts provided by Rails to do recurring tasks, such as benchmarking, plugin installation, and starting the console or the web server.| +|script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html| |tmp/|Temporary files| -|vendor/|A place for third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.| +|vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.| + +h4. Installing the Required Gems + +Rails uses the "Bundler":http://www.github.com/carlhuda/bundler gem to populate the +vendor+ directory with all the gems your application depends on. As we don't need any special gems beyond the default, we just need to do the following: + + +As the root user: +# gem install bundler +# bundle install + + +This will copy down the versions of all the gems you need to start a rails application. h4. Configuring a Database Just about every Rails application will interact with a database. The database to use is specified in a configuration file, +config/database.yml+. -If you open this file in a new Rails application, you'll see a default database configuration using SQLite. The file contains sections for three different environments in which Rails can run by default: +If you open this file in a new Rails application, you'll see a default database configuration using SQLite3. The file contains sections for three different environments in which Rails can run by default: * The +development+ environment is used on your development computer as you interact manually with the application * The +test+ environment is used to run automated tests * The +production+ environment is used when you deploy your application for the world to use. -h5. Configuring a SQLite Database +h5. Configuring a SQLite3 Database -Rails comes with built-in support for "SQLite":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using a SQLite database when creating a new project, but you can always change it later. +Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using a SQLite database when creating a new project, but you can always change it later. -Here's the section of the default configuration file with connection information for the development environment: +Here's the section of the default configuration file (config/database.yml) with connection information for the development environment: development: -adapter: sqlite3 -database: db/development.sqlite3 -pool: 5 -timeout: 5000 + adapter: sqlite3 + database: db/development.sqlite3 + pool: 5 + timeout: 5000 -If you don't have any database set up, SQLite is the easiest to get installed. If you're on OS X 10.5 or greater on a Mac, you already have it. Otherwise, you can install it using RubyGems: - - -$ gem install sqlite3-ruby - +NOTE: In this guide we are using an SQLite3 database for data storage, this is because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems, if you are using a database in a production environment, Rails most likely has an adapter for it. h5. Configuring a MySQL Database -If you choose to use MySQL, your +config/database.yml+ will look a little different. Here's the development section: +If you choose to use MySQL instead of the shipped Sqlite3 database, your +config/database.yml+ will look a little different. Here's the development section: development: @@ -229,7 +245,7 @@ If your development computer's MySQL installation includes a root user with an e h5. Configuring a PostgreSQL Database -If you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases: +Finally if you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases: development: @@ -251,17 +267,39 @@ Now that you have your database configured, it's time to have Rails create an em $ rake db:create -NOTE. Rake is a general-purpose command-runner that Rails uses for many things. You can see the list of available rake commands in your application by running +rake -T+. +This will create your development and test SQLite3 databases inside the db/ folder. + +TIP: Rake is a general-purpose command-runner that Rails uses for many things. You can see the list of available rake commands in your application by running +rake -T+. h3. Hello, Rails! -One of the traditional places to start with a new language is by getting some text up on screen quickly. To do that in Rails, you need to create at minimum a controller and a view. Fortunately, you can do that in a single command. Enter this command in your terminal: +One of the traditional places to start with a new language is by getting some text up on screen quickly, to do this, you need to get your Rails application server running. + +h4. Starting up the Web Server + +You actually have a functional Rails application already. To see it, you need to start a web server on your development machine. You can do this by running: + + +$ rails server + + +This will fire up an instance of the Mongrel web server by default (Rails can also use several other web servers). To see your application in action, open a browser window and navigate to "http://localhost:3000":http://localhost:3000. You should see Rails' default information page: + +!images/rails_welcome.png(Welcome Aboard screenshot)! + +TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server. + +The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your Application's environment. + +h4. Say "Hello", Rails + +To get Rails saying "Hello", you need to create at minimum a controller and a view. Fortunately, you can do that in a single command. Enter this command in your terminal: $ rails generate controller home index -TIP: If you're on Windows, or your Ruby is set up in some non-standard fashion, you may need to explicitly pass Rails +script+ commands to Ruby: +rails generate controller home index+. +TIP: If you're on Windows, or your Ruby is set up in some non-standard fashion, you may need to explicitly pass Rails +rails+ commands to Ruby: +ruby \path\to\rails controller home index+. Rails will create several files for you, including +app/views/home/index.html.erb+. This is the template that will be used to display the results of the +index+ action (method) in the +home+ controller. Open this file in your text editor and edit it to contain a single line of code: @@ -269,48 +307,33 @@ Rails will create several files for you, including +app/views/home/index.html.er

    Hello, Rails!

    -h4. Starting up the Web Server - -You actually have a functional Rails application already - after running only two commands! To see it, you need to start a web server on your development machine. You can do this by running another command: - - -$ rails server - - -This will fire up an instance of the Mongrel web server by default (Rails can also use several other web servers). To see your application in action, open a browser window and navigate to +http://localhost:3000+. You should see Rails' default information page: - -!images/rails_welcome.png(Welcome Aboard screenshot)! - -TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server. - -The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. To view the page you just created, navigate to +http://localhost:3000/home/index+. - h4. Setting the Application Home Page -You'd probably like to replace the "Welcome Aboard" page with your own application's home page. The first step to doing this is to delete the default page from your application: +Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails" to show up. In our case, we want it to show up when we navigate to the root URL of our site, "http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard" smoke test. + +The first step to doing this is to delete the default page from your application: $ rm public/index.html -Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's, _routing file_, which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. At the bottom of the file you'll see the _default routes_: +We need to do this as Rails will deliver any static file in the +public+ directory in preference to any dynamic contact we generate from the controllers. + +Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. There are only comments in this file, so we need to add at the top the following: -map.connect ':controller/:action/:id' -map.connect ':controller/:action/:id.:format' +Blog::Application.routes.draw do |map| + + root :to => "home#index" + + # The priority is based upon order of creation: + # first created -> highest priority. + #... -The default routes handle simple requests such as +/home/index+: Rails translates that into a call to the +index+ action in the +home+ controller. As another example, +/posts/edit/1+ would run the +edit+ action in the +posts+ controller with an +id+ of 1. +The +root :to => "home#index"+ tells Rails to map the root action to the home controller's index action. -To hook up your home page, you need to add another line to the routing file, above the default routes: - - -map.root :controller => "home" - - -This line illustrates one tiny bit of the "convention over configuration" approach: if you don't specify an action, Rails assumes the +index+ action. - -Now if you navigate to +http://localhost:3000+ in your browser, you'll see the +home/index+ view. +Now if you navigate to "http://localhost:3000":http://localhost:3000 in your browser, you'll see +Hello, Rails!+. NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html. @@ -328,30 +351,31 @@ $ rails generate scaffold Post name:string title:string content:text NOTE. While scaffolding will get you up and running quickly, the "one size fits all" code that it generates is unlikely to be a perfect fit for your application. In most cases, you'll need to customize the generated code. Many experienced Rails developers avoid scaffolding entirely, preferring to write all or most of their source code from scratch. -The scaffold generator will build 14 files in your application, along with some folders, and edit one more. Here's a quick overview of what it creates: +The scaffold generator will build 15 files in your application, along with some folders, and edit one more. Here's a quick overview of what it creates: -|_.File |_.Purpose| -|app/models/post.rb |The Post model| -|db/migrate/20090113124235_create_posts.rb |Migration to create the posts table in your database (your name will include a different timestamp)| -|app/views/posts/index.html.erb |A view to display an index of all posts | -|app/views/posts/show.html.erb |A view to display a single post| -|app/views/posts/new.html.erb |A view to create a new post| -|app/views/posts/edit.html.erb |A view to edit an existing post| -|app/views/layouts/posts.html.erb |A view to control the overall look and feel of the other posts views| -|public/stylesheets/scaffold.css |Cascading style sheet to make the scaffolded views look better| -|app/controllers/posts_controller.rb |The Posts controller| -|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller| -|app/helpers/posts_helper.rb |Helper functions to be used from the posts views| -|config/routes.rb |Edited to include routing information for posts| -|test/fixtures/posts.yml |Dummy posts for use in testing| -|test/unit/post_test.rb |Unit testing harness for the posts model| -|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper| +|_.File |_.Purpose| +|db/migrate/20100207214725_create_posts.rb.rb |Migration to create the posts table in your database (your name will include a different timestamp)| +|app/models/post.rb |The Post model| +|test/unit/post_test.rb |Unit testing harness for the posts model| +|test/fixtures/posts.yml |Dummy posts for use in testing| +|app/controllers/posts_controller.rb |The Posts controller| +|app/views/posts/index.html.erb |A view to display an index of all posts | +|app/views/posts/edit.html.erb |A view to edit an existing post| +|app/views/posts/show.html.erb |A view to display a single post| +|app/views/posts/new.html.erb |A view to create a new post| +|app/views/posts/_form.html.erb |A view to control the overall look and feel of the other posts views| +|app/views/layouts/posts.html.erb |A view to control the overall look and feel of the other posts views| +|test/functional/posts_controller_test.rb |Functional testing harness for the posts controller| +|app/helpers/posts_helper.rb |Helper functions to be used from the posts views| +|config/routes.rb |Edited to include routing information for posts| +|test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper| +|public/stylesheets/scaffold.css |Cascading style sheet to make the scaffolded views look better| h4. Running a Migration One of the products of the +rails generate scaffold+ command is a _database migration_. Migrations are Ruby classes that are designed to make it simple to create and modify database tables. Rails uses rake commands to run migrations, and it's possible to undo a migration after it's been applied to your database. Migration filenames include a timestamp to ensure that they're processed in the order that they were created. -If you look in the +db/migrate/20090113124235_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find: +If you look in the +db/migrate/20100207214725_create_posts.rb+ file (remember, yours will have a slightly different name), here's what you'll find: class CreatePosts < ActiveRecord::Migration @@ -371,7 +395,7 @@ class CreatePosts < ActiveRecord::Migration end -If you were to translate that into words, it says something like: when this migration is run, create a table named +posts+ with two string columns (+name+ and +title+) and a text column (+content+), and generate timestamp fields to track record creation and updating. You can learn the detailed syntax for migrations in the "Rails Database Migrations":migrations.html guide. +The above migration creates two methods, +up+, called when you run this migration into the database, and +down+ in case you need to reverse the changes made by this migration at a later date. The +up+ command in this case creates a +posts+ table with two string columns and a text column. It also is creating two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide. At this point, you can use a rake command to run the migration: @@ -379,7 +403,14 @@ At this point, you can use a rake command to run the migration: $ rake db:migrate -Remember, you can't run migrations before running +rake db:create+ to create your database, as we covered earlier. +Rails will execute this migration command and tell you it created the Posts table. + + +== CreatePosts: migrating ==================================================== +-- create_table(:posts) + -> 0.0019s +== CreatePosts: migrated (0.0020s) =========================================== + NOTE. Because you're working in the development environment by default, this command will apply to the database defined in the +development+ section of your +config/database.yml+ file. @@ -396,7 +427,7 @@ The +link_to+ method is one of Rails' built-in view helpers. It creates a hyperl h4. Working with Posts in the Browser -Now you're ready to start working with posts. To do that, navigate to +http://localhost:3000+ and then click the "My Blog" link: +Now you're ready to start working with posts. To do that, navigate to "http://localhost:3000":http://localhost:3000/ and then click the "My Blog" link: !images/posts_index.png(Posts Index screenshot)! @@ -423,8 +454,9 @@ Rails includes methods to help you validate the data that you send to models. Op class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } end @@ -442,15 +474,15 @@ After the console loads, you can use it to work with your application's models: >> p = Post.create(:content => "A new post") -=> # +=> # >> p.save => false >> p.errors -=> #, -@errors={"name"=>["can't be blank"], "title"=>["can't be blank", -"is too short (minimum is 5 characters)"]}> +=> #["can't be blank", + "is too short (minimum is 5 characters)"], + :name=>["can't be blank"] }> This code shows creating a new +Post+ instance, attempting to save it and getting +false+ for a return value (indicating that the save failed), and inspecting the +errors+ of the post. @@ -472,11 +504,11 @@ def index end
    -This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.find(:all)+ or +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions. +This code sets the +@posts+ instance variable to an array of all posts in the database. +Post.all+ calls the +Post+ model to return all of the posts that are currently in the database, with no limiting conditions. TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html. -The +respond_to+ block handles both HTML and XML calls to this action. If you browse to +http://localhost:3000/posts.xml+, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/views/posts/index.html.erb+: +The +respond_to+ block handles both HTML and XML calls to this action. If you browse to "http://localhost:3000/posts.xml":http://localhost:3000/posts.xml, you'll see all of the posts in XML format. The HTML format looks for a view in +app/views/posts/+ with a name that corresponds to the action name. Rails makes all of the instance variables from the action available to the view. Here's +app/views/posts/index.html.erb+:

    Listing posts

    @@ -486,17 +518,19 @@ The +respond_to+ block handles both HTML and XML calls to this action. If you br Name Title Content + + + <% @posts.each do |post| %> - <%=h post.name %> - <%=h post.title %> - <%=h post.content %> + <%= post.name %> + <%= post.title %> + <%= post.content %> <%= link_to 'Show', post %> <%= link_to 'Edit', edit_post_path(post) %> - <%= link_to 'Destroy', post, :confirm => 'Are you sure?', - :method => :delete %> + <%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %> <% end %> @@ -508,10 +542,11 @@ The +respond_to+ block handles both HTML and XML calls to this action. If you br This view iterates over the contents of the +@posts+ array to display content and links. A few things to note in the view: -* +h+ is a Rails helper method to sanitize displayed data, preventing cross-site scripting attacks * +link_to+ builds a hyperlink to a particular destination * +edit_post_path+ is a helper that Rails provides as part of RESTful routing. You'll see a variety of these helpers for the different actions that the controller includes. +NOTE. In previous versions of Rails, you had to use +<%=h post.name %>+ so that any HTML would be escaped before being inserted into the page. In Rails 3.0, this is now the default. To get unescaped HTML, you now use +<%= raw post.name %>+. + TIP: For more details on the rendering process, see "Layouts and Rendering in Rails":layouts_and_rendering.html. h4. Customizing the Layout @@ -519,21 +554,17 @@ h4. Customizing the Layout The view is only part of the story of how HTML is displayed in your web browser. Rails also has the concept of +layouts+, which are containers for views. When Rails renders a view to the browser, it does so by putting the view's HTML into a layout's HTML. The +rails generate scaffold+ command automatically created a default layout, +app/views/layouts/posts.html.erb+, for the posts. Open this layout in your editor and modify the +body+ tag: - - - + + - Posts: <%= controller.action_name %> <%= stylesheet_link_tag 'scaffold' %> -

    <%= flash[:notice] %>

    +

    <%= notice %>

    -<%= yield %> +<%= yield %> @@ -561,34 +592,48 @@ The +new.html.erb+ view displays this empty Post to the user:

    New post

    -<% form_for(@post) do |f| %> - <%= f.error_messages %> - -

    - <%= f.label :name %>
    - <%= f.text_field :name %> -

    -

    - <%= f.label :title %>
    - <%= f.text_field :title %> -

    -

    - <%= f.label :content %>
    - <%= f.text_area :content %> -

    -

    - <%= f.submit "Create" %> -

    -<% end %> +<%= render 'form' %> <%= link_to 'Back', posts_path %>
    +The +<%= render 'form' %>+ line is our first introduction to _partials_ in Rails. A partial is a snippet of HTML and Ruby code that can be reused in multiple locations. In this case, the form used to make a new post, is basically identical to a form used to edit a post, both have text fields for the name and title and a text area for the content with a button to make a new post or update the existing post. + +If you take a look at +views/posts/_form.html.erb+ file, you will see the following: + + +<% form_for(@post) do |f| %> + <%= f.error_messages %> + +
    + <%= f.label :name %>
    + <%= f.text_field :name %> +
    +
    + <%= f.label :title %>
    + <%= f.text_field :title %> +
    +
    + <%= f.label :content %>
    + <%= f.text_area :content %> +
    +
    + <%= f.submit %> +
    +<% end %> +
    + +This partial receives all the instance variables defined in the calling view file, so in this case, the controller assigned the new Post object to +@post+ and so, this is available in both the view and partial as +@post+. + +For more information on partials, refer to the "Layouts and Rendering in Rails":layouts_and_rendering.html#using-partials guide. + The +form_for+ block is used to create an HTML form. Within this block, you have access to methods to build various controls on the form. For example, +f.text_field :name+ tells Rails to create a text input on the form, and to hook it up to the +name+ attribute of the instance being displayed. You can only use these methods with attributes of the model that the form is based on (in this case +name+, +title+, and +content+). Rails uses +form_for+ in preference to having you write raw HTML because the code is more succinct, and because it explicitly ties the form to a particular model instance. +The +form_for+ block is also smart enough to work out if you are doing a _New Post_ or an _Edit Post_ action, and will set the form +action+ tags and submit button names appropriately in the HTML output. + TIP: If you need to create an HTML form that displays arbitrary fields, not tied to a model, you should use the +form_tag+ method, which provides shortcuts for building forms that are not necessarily tied to a model instance. -When the user clicks the +Create+ button on this form, the browser will send information back to the +create+ method of the controller (Rails knows to call the +create+ method because the form is sent with an HTTP POST request; that's one of the conventions that I mentioned earlier): +When the user clicks the +Create Post+ button on this form, the browser will send information back to the +create+ method of the controller (Rails knows to call the +create+ method because the form is sent with an HTTP POST request; that's one of the conventions that I mentioned earlier): def create @@ -596,22 +641,24 @@ def create respond_to do |format| if @post.save - flash[:notice] = 'Post was successfully created.' - format.html { redirect_to(@post) } - format.xml { render :xml => @post, :status => :created, - :location => @post } + format.html { redirect_to(@post, + :notice => 'Post was successfully created.') } + format.xml { render :xml => @post, + :status => :created, :location => @post } else format.html { render :action => "new" } format.xml { render :xml => @post.errors, - :status => :unprocessable_entity } + :status => :unprocessable_entity } end end end -The +create+ action instantiates a new Post object from the data supplied by the user on the form, which Rails makes available in the +params+ hash. After saving the new post, it uses +flash[:notice]+ to create an informational message for the user, and redirects to the show action for the post. If there's any problem, the +create+ action just shows the +new+ view a second time, with any error messages. +The +create+ action instantiates a new Post object from the data supplied by the user on the form, which Rails makes available in the +params+ hash. After successfully saving the new post, returns the appropriate format that the user has requested (HTML in our case). It then redirects the user to the resulting post +show+ action and sets a notice to the user that the Post was successfully created. -Rails provides the +flash+ hash (usually just called the Flash) so that messages can be carried over to another action, providing the user with useful information on the status of their request. In the case of +create+, the user never actually sees any page rendered during the Post creation process, because it immediately redirects to the new Post as soon Rails saves the record. The Flash carries over a message to the next action, so that when the user is redirected back to the +show+ action, they are presented with a message saying "Post was successfully created." +If the post was not successfully saved, due to a validation error, then the controller returns the user back to the +new+ action with any error messages so that the user has the chance to fix the error and try again. + +The "Post was successfully created" message is stored inside of the Rails +flash+ hash, (usually just called the Flash) so that messages can be carried over to another action, providing the user with useful information on the status of their request. In the case of +create+, the user never actually sees any page rendered during the Post creation process, because it immediately redirects to the new Post as soon Rails saves the record. The Flash carries over a message to the next action, so that when the user is redirected back to the +show+ action, they are presented with a message saying "Post was successfully created." h4. Showing an Individual Post @@ -633,17 +680,17 @@ The +show+ action uses +Post.find+ to search for a single record in the database

    Name: - <%=h @post.name %> + <%= @post.name %>

    Title: - <%=h @post.title %> + <%= @post.title %>

    Content: - <%=h @post.content %> + <%= @post.content %>

    @@ -666,30 +713,15 @@ After finding the requested post, Rails uses the +edit.html.erb+ view to display

    Editing post

    -<% form_for(@post) do |f| %> - <%= f.error_messages %> - -

    - <%= f.label :name %>
    - <%= f.text_field :name %> -

    -

    - <%= f.label :title %>
    - <%= f.text_field :title %> -

    -

    - <%= f.label :content %>
    - <%= f.text_area :content %> -

    -

    - <%= f.submit "Update" %> -

    -<% end %> +<%= render 'form' %> <%= link_to 'Show', @post %> | <%= link_to 'Back', posts_path %> +<% end %>
    +Again, as with the +new+ action, the +edit+ action is using the +form+ partial, this time however, the form will do a PUT action to the PostsController and the submit button will display "Update Post" + Submitting the form created by this view will invoke the +update+ action within the controller: @@ -698,13 +730,13 @@ def update respond_to do |format| if @post.update_attributes(params[:post]) - flash[:notice] = 'Post was successfully updated.' - format.html { redirect_to(@post) } + format.html { redirect_to(@post, + :notice => 'Post was successfully updated.') } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @post.errors, - :status => :unprocessable_entity } + :status => :unprocessable_entity } end end end @@ -712,8 +744,6 @@ end In the +update+ action, Rails first uses the +:id+ parameter passed back from the edit view to locate the database record that's being edited. The +update_attributes+ call then takes the rest of the parameters from the request and applies them to this record. If all goes well, the user is redirected to the post's +show+ view. If there are any problems, it's back to +edit+ to correct them. -NOTE. Sharp-eyed readers will have noticed that the +form_for+ declaration is identical for the +new+ and +edit+ views. Rails generates different code for the two forms because it's smart enough to notice that in the one case it's being passed a new record that has never been saved, and in the other case an existing record that has already been saved to the database. In a production Rails application, you would ordinarily eliminate this duplication by moving identical code to a _partial template_, which you could then include in both parent templates. But the scaffold generator tries not to make too many assumptions, and generates code that's easy to modify if you want different forms for +create+ and +edit+. - h4. Destroying a Post Finally, clicking one of the +destroy+ links sends the associated id to the +destroy+ action: @@ -732,124 +762,6 @@ end The +destroy+ method of an Active Record model instance removes the corresponding record from the database. After that's done, there isn't any record to display, so Rails redirects the user's browser to the index view for the model. -h3. DRYing up the Code - -At this point, it's worth looking at some of the tools that Rails provides to eliminate duplication in your code. In particular, you can use _partials_ to clean up duplication in views and _filters_ to help with duplication in controllers. - -h4. Using Partials to Eliminate View Duplication - -As you saw earlier, the scaffold-generated views for the +new+ and +edit+ actions are largely identical. You can pull the shared code out into a partial template. This requires editing the new and edit views, and adding a new template. The new +_form.html.erb+ template should be saved in the same +app/views/posts+ folder as the files from which it is being extracted. Note that the name of this file begins with an underscore; that's the Rails naming convention for partial templates. - -new.html.erb: - - -

    New post

    - -<%= render :partial => "form" %> - -<%= link_to 'Back', posts_path %> -
    - -edit.html.erb: - - -

    Editing post

    - -<%= render :partial => "form" %> - -<%= link_to 'Show', @post %> | -<%= link_to 'Back', posts_path %> -
    - -_form.html.erb: - - -<% form_for(@post) do |f| %> - <%= f.error_messages %> - -

    - <%= f.label :name %>
    - <%= f.text_field :name %> -

    -

    - <%= f.label :title, "title" %>
    - <%= f.text_field :title %> -

    -

    - <%= f.label :content %>
    - <%= f.text_area :content %> -

    -

    - <%= f.submit "Save" %> -

    -<% end %> -
    - -Now, when Rails renders the +new+ or +edit+ view, it will insert the +_form+ partial at the indicated point. Note the naming convention for partials: if you refer to a partial named +form+ inside of a view, the corresponding file is +_form.html.erb+, with a leading underscore. - -For more information on partials, refer to the "Layouts and Rendering in Rails":layouts_and_rendering.html#using-partials guide. - -h4. Using Filters to Eliminate Controller Duplication - -At this point, if you look at the controller for posts, you'll see some duplication: - - -class PostsController < ApplicationController - # ... - def show - @post = Post.find(params[:id]) - # ... - end - - def edit - @post = Post.find(params[:id]) - end - - def update - @post = Post.find(params[:id]) - # ... - end - - def destroy - @post = Post.find(params[:id]) - # ... - end -end - - -Four instances of the exact same line of code doesn't seem very DRY. Rails provides _filters_ as a way to address this sort of repeated code. In this case, you can DRY things up by using a +before_filter+: - - -class PostsController < ApplicationController - before_filter :find_post, - :only => [:show, :edit, :update, :destroy] - # ... - def show - # ... - end - - def edit - end - - def update - # ... - end - - def destroy - # ... - end - -private - def find_post - @post = Post.find(params[:id]) - end -end - - -Rails runs _before filters_ before any action in the controller. You can use the +:only+ clause to limit a before filter to only certain actions, or an +:except+ clause to specifically skip a before filter for certain actions. Rails also allows you to define _after filters_ that run after processing an action, as well as _around filters_ that surround the processing of actions. Filters can also be defined in external classes to make it easy to share them between controllers. - -For more information on filters, see the "Action Controller Overview":action_controller_overview.html guide. - h3. Adding a Second Model Now that you've seen what's in a model built with scaffolding, it's time to add a second model to the application. The second model will handle comments on blog posts. @@ -859,14 +771,13 @@ h4. Generating a Model Models in Rails use a singular name, and their corresponding database tables use a plural name. For the model to hold comments, the convention is to use the name Comment. Even if you don't want to use the entire apparatus set up by scaffolding, most Rails developers still use generators to make things like models and controllers. To create the new model, run this command in your terminal: -$ rails generate model Comment commenter:string body:text - post:references +$ rails generate model Comment commenter:string body:text post:references This command will generate four files: * +app/models/comment.rb+ - The model -* +db/migrate/20091013214407_create_comments.rb+ - The migration +* +db/migrate/20100207235629_create_comments.rb+ - The migration * +test/unit/comment_test.rb+ and +test/fixtures/comments.yml+ - The test harness. First, take a look at +comment.rb+: @@ -905,7 +816,14 @@ The +t.references+ line sets up a foreign key column for the association between $ rake db:migrate -Rails is smart enough to only execute the migrations that have not already been run against the current database. +Rails is smart enough to only execute the migrations that have not already been run against the current database, so in this case you will just see: + + +== CreateComments: migrating ================================================= +-- create_table(:comments) + -> 0.0017s +== CreateComments: migrated (0.0018s) ======================================== + h4. Associating Models @@ -926,8 +844,10 @@ You'll need to edit the +post.rb+ file to add the other side of the association: class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } + has_many :comments end @@ -936,12 +856,14 @@ These two declarations enable a good bit of automatic behavior. For example, if TIP: For more information on Active Record associations, see the "Active Record Associations":association_basics.html guide. -h4. Adding a Route +h4. Adding a Route for Comments -_Routes_ are entries in the +config/routes.rb+ file that tell Rails how to match incoming HTTP requests to controller actions. Open up that file and find the existing line referring to +posts+ (it will be right at the top of the file). Then edit it as follows: +As with the +home+ controller, we will need to add a route so that Rails knows where we would like to navigate to see +comments+. Open up the +config/routes.rb+ file again, you will see an entry that was added automatically for +posts+ near the top by the scaffold generator, +resources :posts+, edit it as follows: -map.resources :posts, :has_many => :comments +resources :posts do + resources :comments +end This creates +comments+ as a _nested resource_ within +posts+. This is another part of capturing the hierarchical relationship that exists between posts and comments. @@ -953,249 +875,300 @@ h4. Generating a Controller With the model in hand, you can turn your attention to creating a matching controller. Again, there's a generator for this: -$ rails generate controller Comments index show new edit +$ rails generate controller Comments -This creates eight files: +This creates four files: * +app/controllers/comments_controller.rb+ - The controller * +app/helpers/comments_helper.rb+ - A view helper file -* +app/views/comments/index.html.erb+ - The view for the index action -* +app/views/comments/show.html.erb+ - The view for the show action -* +app/views/comments/new.html.erb+ - The view for the new action -* +app/views/comments/edit.html.erb+ - The view for the edit action * +test/functional/comments_controller_test.rb+ - The functional tests for the controller * +test/unit/helpers/comments_helper_test.rb+ - The unit tests for the helper -The controller will be generated with empty methods and views for each action that you specified in the call to +rails generate controller+: +Like with any blog, our readers will create their comments directly after reading the post, and once they have added their comment, will be sent back to the post show page to see their comment now listed. Due to this, our +CommentsController+ is there to provide a method to create comments and delete SPAM comments when they arrive. - -class CommentsController < ApplicationController - def index - end - - def show - end - - def new - end - - def edit - end - -end - - -You'll need to flesh this out with code to actually process requests appropriately in each method. Here's a version that (for simplicity's sake) only responds to requests that require HTML: - - -class CommentsController < ApplicationController - before_filter :find_post - - def index - @comments = @post.comments - end - - def show - @comment = @post.comments.find(params[:id]) - end - - def new - @comment = @post.comments.build - end - - def create - @comment = @post.comments.build(params[:comment]) - if @comment.save - redirect_to post_comment_url(@post, @comment) - else - render :action => "new" - end - end - - def edit - @comment = @post.comments.find(params[:id]) - end - - def update - @comment = Comment.find(params[:id]) - if @comment.update_attributes(params[:comment]) - redirect_to post_comment_url(@post, @comment) - else - render :action => "edit" - end - end - - def destroy - @comment = Comment.find(params[:id]) - @comment.destroy - redirect_to post_comments_path(@post) - end - -private - def find_post - @post = Post.find(params[:post_id]) - end - -end - - -You'll see a bit more complexity here than you did in the controller for posts. That's a side-effect of the nesting that you've set up; each request for a comment has to keep track of the post to which the comment is attached. - -In addition, the code takes advantage of some of the methods available for an association. For example, in the +new+ method, it calls - - -@comment = @post.comments.build - - -This creates a new +Comment+ object _and_ sets up the +post_id+ field to have the +id+ from the specified +Post+ object in a single operation. - -h4. Building Views - -Because you skipped scaffolding, you'll need to build views for comments "by hand". Invoking +rails generate controller+ will give you skeleton views, but they'll be devoid of actual content. Here's a first pass at fleshing out the comment views. - -The +views/comments/index.html.erb+ view: - - -

    Comments for <%= @post.title %>

    - - - - - - - -<% for comment in @comments %> - - - - - - - -<% end %> -
    CommenterBody
    <%=h comment.commenter %><%=h comment.body %><%= link_to 'Show', post_comment_path(@post, comment) %> - <%= link_to 'Edit', edit_post_comment_path(@post, comment) %> - - <%= link_to 'Destroy', post_comment_path(@post, comment), - :confirm => 'Are you sure?', :method => :delete %> -
    - -
    - -<%= link_to 'New comment', new_post_comment_path(@post) %> -<%= link_to 'Back to Post', @post %> -
    - -The +views/comments/new.html.erb+ view: - - -

    New comment

    - -<% form_for([@post, @comment]) do |f| %> - <%= f.error_messages %> - -

    - <%= f.label :commenter %>
    - <%= f.text_field :commenter %> -

    -

    - <%= f.label :body %>
    - <%= f.text_area :body %> -

    -

    - <%= f.submit "Create" %> -

    -<% end %> - -<%= link_to 'Back', post_comments_path(@post) %> -
    - -The +views/comments/show.html.erb+ view: - - -

    Comment on <%= @post.title %>

    - -

    - Commenter: - <%=h @comment.commenter %> -

    - -

    - Comment: - <%=h @comment.body %> -

    - -<%= link_to 'Edit', edit_post_comment_path(@post, @comment) %> | -<%= link_to 'Back', post_comments_path(@post) %> -
    - -The +views/comments/edit.html.erb+ view: - - -

    Editing comment

    - -<% form_for([@post, @comment]) do |f| %> - <%= f.error_messages %> - -

    - <%= f.label :commenter %>
    - <%= f.text_field :commenter %> -

    -

    - <%= f.label :body %>
    - <%= f.text_area :body %> -

    -

    - <%= f.submit "Update" %> -

    -<% end %> - -<%= link_to 'Show', post_comment_path(@post, @comment) %> | -<%= link_to 'Back', post_comments_path(@post) %> -
    - -Again, the added complexity here (compared to the views you saw for managing posts) comes from the necessity of juggling a post and its comments at the same time. - -h4. Hooking Comments to Posts - -As a next step, I'll modify the +views/posts/show.html.erb+ view to show the comments on that post, and to allow managing those comments: +So first, we'll wire up the Post show template (+/app/views/posts/show.html.erb+) to let us make a new comment:

    Name: - <%=h @post.name %> + <%= @post.name %>

    Title: - <%=h @post.title %> + <%= @post.title %>

    Content: - <%=h @post.content %> + <%= @post.content %>

    -

    Comments

    -<% @post.comments.each do |c| %> -

    - Commenter: - <%=h c.commenter %> -

    +

    Add a comment:

    +<% form_for([@post, @post.comments.build]) do |f| %> + <%= f.error_messages %> -

    - Comment: - <%=h c.body %> -

    +
    + <%= f.label :commenter %>
    + <%= f.text_field :commenter %> +
    +
    + <%= f.label :body %>
    + <%= f.text_area :body %> +
    +
    + <%= f.submit %> +
    <% end %> <%= link_to 'Edit Post', edit_post_path(@post) %> | <%= link_to 'Back to Posts', posts_path %> | -<%= link_to 'Manage Comments', post_comments_path(@post) %>
    -Note that each post has its own individual comments collection, accessible as +@post.comments+. That's a consequence of the declarative associations in the models. Path helpers such as +post_comments_path+ come from the nested route declaration in +config/routes.rb+. +This adds a form on the Post show page that creates a new comment, which will call the +CommentsController+ +create+ action, so let's wire that up: + + +class CommentsController < ApplicationController + def create + @post = Post.find(params[:post_id]) + @comment = @post.comments.create(params[:comment]) + redirect_to post_path(@post) + end +end + + +You'll see a bit more complexity here than you did in the controller for posts. That's a side-effect of the nesting that you've set up; each request for a comment has to keep track of the post to which the comment is attached, thus the initial find action to the Post model to get the post in question. + +In addition, the code takes advantage of some of the methods available for an association. For example, in the +new+ method, it calls + +Once we have made the new comment, we send the user back to the +post_path(@post)+ URL. This runs the +show+ action of the +PostsController+ which then renders the +show.html.erb+ template where we want the comment to show, so then, we'll add that to the +app/view/posts/show.html.erb+. + + +

    + Name: + <%= @post.name %> +

    + +

    + Title: + <%= @post.title %> +

    + +

    + Content: + <%= @post.content %> +

    + +

    Comments

    +<% @post.comments.each do |comment| %> +

    + Commenter: + <%= comment.commenter %> +

    + +

    + Comment: + <%= comment.body %> +

    +<% end %> + +

    Add a comment:

    +<% form_for([@post, @post.comments.build]) do |f| %> + <%= f.error_messages %> + +
    + <%= f.label :commenter %>
    + <%= f.text_field :commenter %> +
    +
    + <%= f.label :body %>
    + <%= f.text_area :body %> +
    +
    + <%= f.submit %> +
    +<% end %> + +
    + +<%= link_to 'Edit Post', edit_post_path(@post) %> | +<%= link_to 'Back to Posts', posts_path %> | +
    + +Now you can add posts and comments to your blog and have them show up in the right places. + +h3. Refactorization + +Now that we have Posts and Comments working, we can take a look at the +app/views/posts/show.html.erb+ template, it is getting long and awkward, we can use partials to clean this up. + +h4. Rendering Partial Collections + +First will make a comment partial to extract showing all the comments for the post, so make a file +app/views/comments/_comment.html.erb+ and put into it: + + +

    + Commenter: + <%= comment.commenter %> +

    + +

    + Comment: + <%= comment.body %> +

    +
    + +Then in the +app/views/posts/show.html.erb+ you can change it to look like the following: + + +

    + Name: + <%= @post.name %> +

    + +

    + Title: + <%= @post.title %> +

    + +

    + Content: + <%= @post.content %> +

    + +

    Comments

    +<%= render :partial => "comments/comment", + :collection => @post.comments %> + +

    Add a comment:

    +<% form_for([@post, @post.comments.build]) do |f| %> + <%= f.error_messages %> + +
    + <%= f.label :commenter %>
    + <%= f.text_field :commenter %> +
    +
    + <%= f.label :body %>
    + <%= f.text_area :body %> +
    +
    + <%= f.submit %> +
    +<% end %> + +
    + +<%= link_to 'Edit Post', edit_post_path(@post) %> | +<%= link_to 'Back to Posts', posts_path %> | +
    + +This will now render the partial in +app/views/comments/_comment.html.erb+ once for each comment that is in the +@post.comments+ collection. As the +render+ method iterates over the @post.comments collection, it assigns each comment to a local variable named the same as the partial, in this case +comment+ which is then available in the partial for us to show. + +h4. Rendering a Partial Form + +Lets also move that new comment section out to it's own partial, again, you create a file +app/views/comments/_form.html.erb+ and in it you put: + + +<% form_for([@post, @post.comments.build]) do |f| %> + <%= f.error_messages %> + +
    + <%= f.label :commenter %>
    + <%= f.text_field :commenter %> +
    +
    + <%= f.label :body %>
    + <%= f.text_area :body %> +
    +
    + <%= f.submit %> +
    +<% end %> +
    + +Then you make the +app/views/posts/show.html.erb+ look like the following: + + +

    + Name: + <%= @post.name %> +

    + +

    + Title: + <%= @post.title %> +

    + +

    + Content: + <%= @post.content %> +

    + +

    Comments

    +<%= render :partial => "comments/comment", + :collection => @post.comments %> + +

    Add a comment:

    +<%= render "comments/form" %> + +
    + +<%= link_to 'Edit Post', edit_post_path(@post) %> | +<%= link_to 'Back to Posts', posts_path %> | +
    + +The second render just defines the partial template we want to render, comments/form, Rails is smart enough to spot the forward slash in that string and realise that you want to render the _form.html.erb file in the app/views/comments directory. + +The +@post+ object is available any partials rendered in the view because we defined it as an instance variable. + +h3. Deleting Comments + +Another important feature on a blog is being able to delete SPAM comments. To do this, we need to implement a link of some sort in the view and a +DELETE+ action in the +CommentsController+. + +So first, let's add the delete link in the +app/views/comments/_comment.html.erb+ partial: + + +

    + Commenter: + <%= comment.commenter %> +

    + +

    + Comment: + <%= comment.body %> +

    + +

    + <%= link_to 'Destroy Comment', [comment.post, comment], + :confirm => 'Are you sure?', + :method => :delete %> +

    +
    + +Clicking this new "Destroy Comment" link will fire off a DELETE /posts/:id/comments/:id to our +CommentsController+, which can then use this to find the comment we want to delete, so let's add a destroy action to our controller: + + +class CommentsController < ApplicationController + + def create + @post = Post.find(params[:post_id]) + @comment = @post.comments.create(params[:comment]) + redirect_to post_path(@post) + end + + def destroy + @post = Post.find(params[:post_id]) + @comment = @post.comments.find(params[:id]) + @comment.destroy + redirect_to post_path(@post) + end + +end + + +The +destroy+ action will find the post we are looking at, locate the comment within the @post.comments collection, and then remove it from the database and send us back to the show action for the post. + h4. Deleting Associated Objects @@ -1203,21 +1176,81 @@ If you decide at some point to delete a post, you likely want to delete the comm class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } has_many :comments, :dependent => :destroy end - +
    + +h3. Security + +Before you publish your blog online, you will most likely want to prevent just anyone from being able to add, edit and delete posts or delete comments. + +Rails provides a very simple http authentication system that will work nicely in this situation. First, we enable simple HTTP based authentication in our app/controllers/application_controller.rb: + + +class ApplicationController < ActionController::Base + protect_from_forgery + + private + + def authenticate + authenticate_or_request_with_http_basic do |user_name, password| + user_name == 'admin' && password == 'password' + end + end + +end + + +You can obviously change the username and password to whatever you want. We put this method inside of +ApplicationController+ so that it is available to all of our controllers. + +Then in the +PostsController+ we need to have a way to block access to the various actions if the person is not authenticated, here we can use the Rails before_filter method, which allows us to specify that Rails must run a method and only then allow access to the requested action if that method allows it. + +To use the before filter, we specify it at the top of our +PostsController+, in this case, we want the user to be authenticated on every action, except for +index+ and +show+, so we write that: + + +class PostsController < ApplicationController + + before_filter :authenticate, :except => [:index, :show] + + # GET /posts + # GET /posts.xml + def index + @posts = Post.all + respond_to do |format| +# snipped for brevity + + +We also only want to allow authenticated users to delete comments, so in the +CommentsController+ we write: + + +class CommentsController < ApplicationController + + before_filter :authenticate, :only => :destroy + + def create + @post = Post.find(params[:post_id]) +# snipped for brevity + + +Now if you try to create a new post, you will be greeted with a basic HTTP Authentication challenge + +!images/challenge.png(Basic HTTP Authentication Challenge)! + h3. Building a Multi-Model Form -Comments and posts are edited on two separate forms - which makes sense, given the flow of this mini-application. But what if you want to edit more than one thing on a single form? Rails 2.3 offers new support for nested forms. Let's add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags: +Another piece of your average blog is the ability to tag posts. This requires that your application edits more than one thing on a single form. Rails offers support for nested forms. + +To demonstrate this, we will add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags: $ rails generate model tag name:string post:references -Run the migration to create the database table: +Again, run the migration to create the database table: $ rake db:migrate @@ -1227,9 +1260,11 @@ Next, edit the +post.rb+ file to create the other side of the association, and t class Post < ActiveRecord::Base - validates_presence_of :name, :title - validates_length_of :title, :minimum => 5 - has_many :comments + validates :name, :presence => true + validates :title, :presence => true, + :length => { :minimum => 5 } + + has_many :comments, :dependent => :destroy has_many :tags accepts_nested_attributes_for :tags, :allow_destroy => :true, @@ -1239,54 +1274,150 @@ end The +:allow_destroy+ option on the nested attribute declaration tells Rails to display a "remove" checkbox on the view that you'll build shortly. The +:reject_if+ option prevents saving new tags that do not have any attributes filled in. -You'll also need to modify +views/posts/_form.html.erb+ to include the tags: +We will modify +views/posts/_form.html.erb+ to render a partial to make a tag: -<% @post.tags.build if @post.tags.empty? %> +<% @post.tags.build %> <% form_for(@post) do |post_form| %> <%= post_form.error_messages %> -

    +

    <%= post_form.label :name %>
    <%= post_form.text_field :name %> -

    -

    - <%= post_form.label :title, "Title" %>
    +

    +
    + <%= post_form.label :title %>
    <%= post_form.text_field :title %> -

    -

    +

    +
    <%= post_form.label :content %>
    <%= post_form.text_area :content %> -

    +

    Tags

    - <% post_form.fields_for :tags do |tag_form| %> -

    - <%= tag_form.label :name, 'Tag:' %> - <%= tag_form.text_field :name %> -

    - <% unless tag_form.object.nil? || tag_form.object.new_record? %> -

    - <%= tag_form.label :_delete, 'Remove:' %> - <%= tag_form.check_box :_delete %> -

    - <% end %> - <% end %> - -

    - <%= post_form.submit "Save" %> -

    + <%= render :partial => 'tags/form', + :locals => {:form => post_form} %> +
    + <%= post_form.submit %> +
    <% end %>
    +This example shows another option of the render helper, being able to pass in local variables, in this case, we want the local variable +form+ in the partial to refer to the +post_form+ object. + +You will also note that we also have changed the +f+ in form_for(@post) do |f| to post_form to clarify what is going on somewhat. + +We also add a @post.tags.build at the top of this form, this is to make sure there is a new tag ready to have it's name filled in by the user. If you do not build the new tag, then the form will not appear as there is no new Tag object ready to create. + +Now create the folder app/views/tags and make a file in there called _form.html.erb which contains the form for the tag: + + +<% form.fields_for :tags do |tag_form| %> +
    + <%= tag_form.label :name, 'Tag:' %> + <%= tag_form.text_field :name %> +
    + <% unless tag_form.object.nil? || tag_form.object.new_record? %> +
    + <%= tag_form.label :_destroy, 'Remove:' %> + <%= tag_form.check_box :_destroy %> +
    + <% end %> +<% end %> +
    + +Finally, we will edit the app/views/posts/show.html.erb template to show our tags. + + +

    + Name: + <%= @post.name %> +

    + +

    + Title: + <%= @post.title %> +

    + +

    + Content: + <%= @post.content %> +

    + +

    + Tags: + <%= @post.tags.map { |t| t.name }.join(", ") %> +

    + +

    Comments

    +<%= render :partial => "comments/comment", + :collection => @post.comments %> + +

    Add a comment:

    +<%= render "comments/form" %> + + +<%= link_to 'Edit Post', edit_post_path(@post) %> | +<%= link_to 'Back to Posts', posts_path %> | +
    + With these changes in place, you'll find that you can edit a post and its tags directly on the same view. -NOTE. You may want to use JavaScript to dynamically add additional tags on a single form. For an example of this and other advanced techniques, see the "complex form examples application":http://github.com/alloy/complex-form-examples/tree/master. +However, that method call @post.tags.map { |t| t.name }.join(", ") is awkward, we could handle this by making a helper method. + +h3. View Helpers + +View Helpers live in app/helpers and provide small snippets of reusable code for views. In our case, we want a method that strings a bunch of objects together using their name attribute and joining them with a comma. As this is for the Post show template, we put it in the PostsHelper. + +Open up app/helpers/posts_helper.rb and add the following: + + +module PostsHelper + def join_tags(post) + post.tags.map { |t| t.name }.join(", ") + end +end + + +Now you can edit the view in app/views/posts/show.html.erb to look like this: + + +

    + Name: + <%= @post.name %> +

    + +

    + Title: + <%= @post.title %> +

    + +

    + Content: + <%= @post.content %> +

    + +

    + Tags: + <%= join_tags(@post) %> +

    + +

    Comments

    +<%= render :partial => "comments/comment", + :collection => @post.comments %> + +

    Add a comment:

    +<%= render "comments/form" %> + + +<%= link_to 'Edit Post', edit_post_path(@post) %> | +<%= link_to 'Back to Posts', posts_path %> | +
    h3. What's Next? Now that you've seen your first Rails application, you should feel free to update it and experiment on your own. But you don't have to do everything without help. As you need assistance getting up and running with Rails, feel free to consult these support resources: -* The "Ruby On Rails guides":http://guides.rubyonrails.org +* The "Ruby On Rails guides":index.html * The "Ruby on Rails mailing list":http://groups.google.com/group/rubyonrails-talk * The "#rubyonrails":irc://irc.freenode.net/#rubyonrails channel on irc.freenode.net * The "Rails Wiki":http://wiki.rubyonrails.org/ @@ -1296,10 +1427,13 @@ Rails also comes with built-in help that you can generate using the rake command * Running +rake doc:guides+ will put a full copy of the Rails Guides in the +doc/guides+ folder of your application. Open +doc/guides/index.html+ in your web browser to explore the Guides. * Running +rake doc:rails+ will put a full copy of the API documentation for Rails in the +doc/api+ folder of your application. Open +doc/api/index.html+ in your web browser to explore the API documentation. + h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/2 +* February 8, 2010: Full re-write for Rails 3.0-beta, added helpers and before_filters, refactored code by "Mikel Lindsaar":credits:html#raasdnil +* January 24, 2010: Re-write for Rails 3.0 by "Mikel Lindsaar":credits:html#raasdnil * July 18, 2009: Minor cleanup in anticipation of Rails 2.3.3 by "Mike Gunderloy":credits.html#mgunderloy * February 1, 2009: Updated for Rails 2.3 by "Mike Gunderloy":credits.html#mgunderloy * November 3, 2008: Formatting patch from Dave Rothlisberger @@ -1307,4 +1441,4 @@ h3. Changelog * October 16, 2008: Revised based on feedback from Pratik Naik by "Mike Gunderloy":credits.html#mgunderloy (not yet approved for publication) * October 13, 2008: First complete draft by "Mike Gunderloy":credits.html#mgunderloy (not yet approved for publication) * October 12, 2008: More detail, rearrangement, editing by "Mike Gunderloy":credits.html#mgunderloy (not yet approved for publication) -* September 8, 2008: initial version by James Miller (not yet approved for publication) +* September 8, 2008: initial version by "James Miller":credits.html#bensie (not yet approved for publication) diff --git a/railties/guides/source/index.textile.erb b/railties/guides/source/index.textile.erb index 851d91da2f..d7db81309f 100644 --- a/railties/guides/source/index.textile.erb +++ b/railties/guides/source/index.textile.erb @@ -4,7 +4,7 @@ h2. Ruby on Rails Guides These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. There are two different versions of the Guides site, and you should be sure to use the one that applies to your situation: * "Current Release version":http://guides.rubyonrails.org - based on Rails 2.3 -* "Edge version":http://guides.rails.info - based on the current Rails "master branch":http://github.com/rails/rails/tree/master +* "Edge version":http://edgeguides.rubyonrails.org - based on the current Rails "master branch":http://github.com/rails/rails/tree/master <% end %> @@ -120,5 +120,20 @@ h3. Digging Deeper <% guide("Contributing to Rails", 'contributing_to_rails.html') do %> Rails is not "somebody else's framework." This guide covers a variety of ways that you can get involved in the ongoing development of Rails. <% end %> - + + +h3. Release Notes + +
    +<% guide("Ruby on Rails 3.0 Release Notes", '3_0_release_notes.html') do %> + Release notes for Rails 3.0. +<% end %> + +<% guide("Ruby on Rails 2.3 Release Notes", '2_3_release_notes.html') do %> + Release notes for Rails 2.3. +<% end %> + +<% guide("Ruby on Rails 2.2 Release Notes", '2_2_release_notes.html') do %> + Release notes for Rails 2.2. +<% end %>
    diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb index 7dfcf4a507..a21f1bbeed 100644 --- a/railties/guides/source/layout.html.erb +++ b/railties/guides/source/layout.html.erb @@ -67,6 +67,12 @@
    Caching with Rails
    Contributing to Rails
    +
    +
    Release Notes
    +
    Ruby on Rails 3.0 Release Notes
    +
    Ruby on Rails 2.3 Release Notes
    +
    Ruby on Rails 2.2 Release Notes
    +
  • Contribute
  • @@ -87,7 +93,7 @@
    - <%= yield.html_safe! %> + <%= yield.html_safe %>
    diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index 0cee413ac3..2cb98e9ee6 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -27,21 +27,74 @@ I'll cover each of these methods in turn. But first, a few words about the very h4. Rendering by Default: Convention Over Configuration in Action -You've heard that Rails promotes "convention over configuration." Default rendering is an excellent example of this. By default, controllers in Rails automatically render views with names that correspond to actions. For example, if you have this code in your +BooksController+ class: +You've heard that Rails promotes "convention over configuration." Default rendering is an excellent example of this. By default, controllers in Rails automatically render views with names that correspond to valid routes. For example, if you have this code in your +BooksController+ class: -def show - @book = Book.find(params[:id]) +class BooksController < ApplicationController end -Rails will automatically render +app/views/books/show.html.erb+ after running the method. In fact, if you have the default catch-all route in place (+map.connect ':controller/:action/:id'+), Rails will even render views that don't have any code at all in the controller. For example, if you have the default route in place and a request comes in for +/books/sale_list+, Rails will render +app/views/books/sale_list.html.erb+ in response. +And the following in your routes file: -NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). You'll also find +.rhtml+ used for ERB templates and +.rxml+ for Builder templates, but those extensions are now formally deprecated and will be removed from a future version of Rails. + +resources :books + + +And you have a view file +app/views/books/index.html.erb+: + + +

    Books are coming soon!

    +
    + +Rails will automatically render +app/views/books/index.html.erb+ when you navigate to +/books+ and you will see on your screen that "Books are coming soon!" + +However a coming soon screen is only minimally useful, so you will soon create your +Book+ model and add the index action to +BooksController+: + + +class BooksController < ApplicationController + def index + @books = Book.all + end +end + + +Note that again, we have convention over configuration, in that there is no explicit render at the end of this index action. The rule is that if you do not explicitly render something by the end of the controller action, rails will look for the +action_name.html.erb+ template in the controllers view path and then render that, so in this case, Rails will render the +app/views/books/index.html.erb+ file. + +So in our view, we want to display the properties of all the books, we could do this with an ERB template like this: + + +

    Listing Books

    + + + + + + + + + + +<% @books.each do |book| %> + + + + + + + +<% end %> +
    TitleSummary
    <%= book.title %><%= book.content %><%= link_to 'Show', book %><%= link_to 'Edit', edit_book_path(book) %><%= link_to 'Remove', book, :confirm => 'Are you sure?', :method => :delete %>
    + +
    + +<%= link_to 'New book', new_book_path %> +
    + +NOTE: The actual rendering is done by subclasses of +ActionView::TemplateHandlers+. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. In Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), +.rjs+ for RJS (javascript with embedded ruby) and +.builder+ for Builder (XML generator). h4. Using +render+ -In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customize the behavior of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. +In most cases, the +ActionController::Base#render+ method does the heavy lifting of rendering your application's content for use by a browser. There are a variety of ways to customise the behaviour of +render+. You can render the default view for a Rails template, or a specific template, or a file, or inline code, or nothing at all. You can render text, JSON, or XML. You can specify the content type or HTTP status of the rendered response as well. TIP: If you want to see the exact results of a call to +render+ without needing to inspect it in a browser, you can call +render_to_string+. This method takes exactly the same options as +render+, but it returns a string instead of sending a response back to the browser. @@ -53,7 +106,24 @@ Perhaps the simplest thing you can do with +render+ is to render nothing at all: render :nothing => true
    -This will send an empty response to the browser (though it will include any status headers you set with the :status option, discussed below). +If you look at the response for this using Curl you will see the following: + + + $ curl -i 127.0.0.1:3000/books +HTTP/1.1 200 OK +Connection: close +Date: Sun, 24 Jan 2010 09:25:18 GMT +Transfer-Encoding: chunked +Content-Type: */*; charset=utf-8 +X-Runtime: 0.014297 +Set-Cookie: _blog_session=...snip...; path=/; HttpOnly +Cache-Control: no-cache + + + $ + + +We see there is an empty response (no data after the +Cache-Control+ line), but that Rails has set the response to 200 OK, so the request was successful. You can set the +:status+ options on render to change this response. Rendering nothing can be useful for AJAX requests where all you want to send back to the browser is an acknowledgement that the request was completed. TIP: You should probably be using the +head+ method, discussed later in this guide, instead of +render :nothing+. This provides additional flexibility and makes it explicit that you're only generating HTTP headers. @@ -73,7 +143,7 @@ def update end
    -If the call to +update_attributes+ fails, calling the +update+ action in this controller will render the +edit.html.erb+ template belonging to the same controller. +If the call to +update_attributes+ fails, calling the +update+ action in this controller will render the +edit.html.erb+ template belonging to the same controller. If you prefer, you can use a symbol instead of a string to specify the action to render: @@ -89,7 +159,7 @@ def update end
    -To be explicit, you can use +render+ with the +:action+ option (though this is no longer necessary as of Rails 2.3): +To be explicit, you can use +render+ with the +:action+ option (though this is no longer necessary in Rails 3.0): def update @@ -140,6 +210,31 @@ NOTE: By default, the file is rendered without using the current layout. If you TIP: If you're running on Microsoft Windows, you should use the +:file+ option to render a file, because Windows filenames do not have the same format as Unix filenames. +h5. Wrapping it up + +The above three methods of render (rendering another template within the controller, rendering a template within another controller and rendering an arbitrary file on the file system) are actually all variants of the same action. + +In fact, in the BooksController method, inside of the edit action where we want to render the edit template if the book does not update successfully, all of the following render calls would all render the +edit.html.erb+ template in the +views/books+ directory: + + +render :edit +render :action => :edit +render 'edit' +render 'edit.html.erb' +render :action => 'edit' +render :action => 'edit.html.erb' +render 'books/edit' +render 'books/edit.html.erb' +render :template => 'books/edit' +render :template => 'books/edit.html.erb' +render '/path/to/rails/app/views/books/edit' +render '/path/to/rails/app/views/books/edit.html.erb' +render :file => '/path/to/rails/app/views/books/edit' +render :file => '/path/to/rails/app/views/books/edit.html.erb' + + +Which one you use is really a matter of style and convention, but the rule of thumb is to use the simplest one that makes sense for the code you are writing. + h5. Using +render+ with +:inline+ The +render+ method can do without a view completely, if you're willing to use the +:inline+ option to supply ERB as part of the method call. This is perfectly valid: @@ -180,7 +275,7 @@ render :text => "OK" TIP: Rendering pure text is most useful when you're responding to AJAX or web service requests that are expecting something other than proper HTML. -NOTE: By default, if you use the +:text+ option, the file is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the +:layout => true+ option +NOTE: By default, if you use the +:text+ option the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the +:layout => true+ option. h5. Rendering JSON @@ -314,21 +409,21 @@ end Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout: +You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example: + class ProductsController < ApplicationController - layout proc { |controller| controller.request.xhr? ? 'popup' : 'application' } - # ... + layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'application' } end h6. Conditional Layouts -Layouts specified at the controller level support +:only+ and +:except+ options that take either a method name or an array of method names: +Layouts specified at the controller level support +:only+ and +:except+ options that take either a method name or an array of method names which correspond to method names within the controller: class ProductsController < ApplicationController layout "product", :except => [:index, :rss] - #... end @@ -343,7 +438,6 @@ Layouts are shared downwards in the hierarchy, and more specific layouts always class ApplicationController < ActionController::Base layout "main" - #... end @@ -351,7 +445,6 @@ end class PostsController < ApplicationController - # ... end @@ -360,7 +453,6 @@ end class SpecialPostsController < PostsController layout "special" - # ... end @@ -418,6 +510,8 @@ def show end +Make sure you use +and return+ and not +&& return+ because while the former will work, the latter will not due to operator precedence in the Ruby Language. + Note that the implicit render done by ActionController detects if +render+ has been called, and thus avoids this error. Therefore, the following will work without errors: @@ -463,11 +557,11 @@ Consider these actions to see the difference: def index - @books = Book.find(:all) + @books = Book.all end def show - @book = Book.find(params[:id]) + @book = Book.find_by_id(params[:id]) if @book.nil? render :action => "index" end @@ -478,19 +572,39 @@ With the code in this form, there will be likely be a problem if the +@book+ var def index - @books = Book.find(:all) + @books = Book.all end def show - @book = Book.find(params[:id]) + @book = Book.find_by_id(params[:id]) if @book.nil? - redirect_to :action => "index" + redirect_to :action => :index end end With this code, the browser will make a fresh request for the index page, the code in the +index+ method will run, and all will be well. +The only downside to this code, is that it requires a round trip to the browser, the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 301 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen. + +While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be: + + +def index + @books = Book.all +end + +def show + @book = Book.find_by_id(params[:id]) + if @book.nil? + @books = Book.all + render "index", :alert => 'Your book was not found!' + end +end + + +Which would detect that there are no books populate the +@books+ instance variable with all the books in the database and then directly render the +index.html.erb+ template returning it to the browser with a flash alert message telling the user what happened. + h4. Using +head+ To Build Header-Only Responses The +head+ method exists to let you send back responses to the browser that have only headers. It provides a more obvious alternative to calling +render :nothing+. The +head+ method takes one response, which is interpreted as a hash of header names and values. For example, you can return only an error header: @@ -499,12 +613,39 @@ The +head+ method exists to let you send back responses to the browser that have head :bad_request +Which would produce the following header: + + +HTTP/1.1 400 Bad Request +Connection: close +Date: Sun, 24 Jan 2010 12:15:53 GMT +Transfer-Encoding: chunked +Content-Type: text/html; charset=utf-8 +X-Runtime: 0.013483 +Set-Cookie: _blog_session=...snip...; path=/; HttpOnly +Cache-Control: no-cache + + Or you can use other HTTP headers to convey additional information: head :created, :location => photo_path(@photo) +Which would produce: + + +HTTP/1.1 201 Created +Connection: close +Date: Sun, 24 Jan 2010 12:16:44 GMT +Transfer-Encoding: chunked +Location: /photos/1 +Content-Type: text/html; charset=utf-8 +X-Runtime: 0.083496 +Set-Cookie: _blog_session=...snip...; path=/; HttpOnly +Cache-Control: no-cache + + h3. Structuring Layouts When Rails renders a view as a response, it does so by combining the view with the current layout (using the rules for finding the current layout that were covered earlier in this guide). Within a layout, you have access to three tools for combining different bits of output to form the overall response: @@ -517,12 +658,14 @@ I'll discuss each of these in turn. h4. Asset Tags -Asset tags provide methods for generating HTML that links views to assets like images, javascript, stylesheets, and feeds. There are four types of include tag: +Asset tags provide methods for generating HTML that links views to assets like images, videos, audio, javascript, stylesheets, and feeds. There are six types of include tag: * +auto_discovery_link_tag+ * +javascript_include_tag+ * +stylesheet_link_tag+ * +image_tag+ +* +video_tag+ +* +audio_tag+ You can use these tags in layouts or other views, although the tags other than +image_tag+ are most commonly used in the +<head>+ section of a layout. @@ -663,10 +806,10 @@ You can even use dynamic paths such as +cache/#{current_site}/main/display+. h5. Linking to Images with +image_tag+ -The +image_tag+ helper builds an HTML +<image>+ tag to the specified file. By default, files are loaded from +public/images+. If you don't specify an extension, +.png+ is assumed by default: +The +image_tag+ helper builds an HTML +<image />+ tag to the specified file. By default, files are loaded from +public/images+, note, you must specify the extension, previous versions of Rails would allow you to just call the image name and would append +.png+ if no extension was given, Rails 3.0 does not. -<%= image_tag "header" %> +<%= image_tag "header.png" %> You can supply a path to the image if you like: @@ -678,14 +821,93 @@ You can supply a path to the image if you like: You can supply a hash of additional HTML options: -<%= image_tag "icons/delete.gif", :height => 45 %> +<%= image_tag "icons/delete.gif", {:height => 45} %> -There are also three special options you can use with +image_tag+: +You can also supply an alternate image to show on mouseover: -* +:alt+ specifies the alt text for the image (which defaults to the file name of the file, capitalized and with no extension) -* +:size+ specifies both width and height, in the format "{width}x{height}" (for example, "150x125") -* +:mouseover+ sets an alternate image to be used when the onmouseover event is fired. + +<%= image_tag "home.gif", :onmouseover => "menu/home_highlight.gif" %> + + +Or alternate text if the user has rendering images turned off in their browser, if you do not specify an explicit alt tag, it defaults to the file name of the file, capitalized and with no extension, for example, these two image tags would return the same code: + + +<%= image_tag "home.gif" %> +<%= image_tag "home.gif", :alt => "Home" %> + + +You can also specify a special size tag, in the format "{width}x{height}": + + +<%= image_tag "home.gif", :size => "50x20" %> + + +In addition to the above special tags, you can supply a final hash of standard HTML options, such as +:class+ or +:id+ or +:name+: + + +<%= image_tag "home.gif", :alt => "Go Home", + :id => "HomeImage", + :class => 'nav_bar' %> + + +h5. Linking to Videos with +video_tag+ + +The +video_tag+ helper builds an HTML 5 +<video>+ tag to the specified file. By default, files are loaded from +public/videos+. + + +<%= video_tag "movie.ogg" %> + + +Produces + + + + +Like an +image_tag+ you can supply a path, either absolute, or relative to the +public/videos+ directory. Additionally you can specify the +:size => "#{width}x#{height}"+ option just like an +image_tag+. Video tags can also have any of the HTML options specified at the end (+id+, +class+ et al). + +The video tag also supports all of the +<video>+ HTML options through the HTML options hash, including: + +* +:poster => 'image_name.png'+, provides an image to put in place of the video before it starts playing. +* +:autoplay => true+, starts playing the video on page load. +* +:loop => true+, loops the video once it gets to the end. +* +:controls => true+, provides browser supplied controls for the user to interact with the video. +* +:autobuffer => true+, the video will pre load the file for the user on page load. + +You can also specify multiple videos to play by passing an array of videos to the +video_tag+: + + +<%= video_tag ["trailer.ogg", "movie.ogg"] %> + + +This will produce: + + + + + +h5. Linking to Audio files with +audio_tag+ + +The +audio_tag+ helper builds an HTML 5 +<audio>+ tag to the specified file. By default, files are loaded from +public/audios+. + + +<%= audio_tag "music.mp3" %> + + +You can supply a path to the image if you like: + + +<%= image_tag "music/first_song.mp3" %> + + +You can also supply a hash of additional options, such as +:id+, +:class+ etc. + +Like the +video_tag+, the +audio_tag+ has special options: + +* +:autoplay => true+, starts playing the audio on page load +* +:controls => true+, provides browser supplied controls for the user to interact with the audio. +* +:autobuffer => true+, the audio will pre load the file for the user on page load. h4. Understanding +yield+ @@ -752,13 +974,13 @@ h5. Naming Partials To render a partial as part of a view, you use the +render+ method within the view, and include the +:partial+ option: -<%= render :partial => "menu" %> +<%= render "menu" %> This will render a file named +_menu.html.erb+ at that point within the view being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: -<%= render :partial => "shared/menu" %> +<%= render "shared/menu" %> That code will pull in the partial from +app/views/shared/_menu.html.erb+. @@ -768,14 +990,14 @@ h5. Using Partials to Simplify Views One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looked like this: -<%= render :partial => "shared/ad_banner" %> +<%= render "shared/ad_banner" %>

    Products

    Here are a few of our fine products:

    ... -<%= render :partial => "shared/footer" %> +<%= render "shared/footer" %>
    Here, the +_ad_banner.html.erb+ and +_footer.html.erb+ partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. @@ -787,7 +1009,7 @@ h5. Partial Layouts A partial can use its own layout file, just as a view can use a layout. For example, you might call a partial like this: -<%= render :partial => "link_area", :layout => "graybar" %> +<%= render "link_area", :layout => "graybar" %> This would look for a partial named +_link_area.html.erb+ and render it using the layout +_graybar.html.erb+. Note that layouts for partials follow the same leading-underscore naming as regular partials, and are placed in the same folder with the partial that they belong to (not in the master +layouts+ folder). @@ -801,8 +1023,7 @@ You can also pass local variables into partials, making them even more powerful

    New zone

    <%= error_messages_for :zone %> -<%= render :partial => "form", :locals => - { :button_label => "Create zone", :zone => @zone } %> +<%= render :partial => "form", :locals => { :zone => @zone } %>
    * +edit.html.erb+ @@ -810,8 +1031,7 @@ You can also pass local variables into partials, making them even more powerful

    Editing zone

    <%= error_messages_for :zone %> -<%= render :partial => "form", :locals => - { :button_label => "Update zone", :zone => @zone } %> +<%= render :partial => "form", :locals => { :zone => @zone } %>
    * +_form.html.erb+ @@ -823,12 +1043,12 @@ You can also pass local variables into partials, making them even more powerful <%= f.text_field :name %>

    - <%= f.submit button_label %> + <%= f.submit %>

    <% end %> -Although the same partial will be rendered into both views, the label on the submit button is controlled by a local variable passed into the partial. +Although the same partial will be rendered into both views, Action View's submit helper will return "Create Zone" for the new action and "Update Zone" for the edit action. Every partial also has a local variable with the same name as the partial (minus the underscore). You can pass an object in to this local variable via the +:object+ option: @@ -838,15 +1058,15 @@ Every partial also has a local variable with the same name as the partial (minus Within the +customer+ partial, the +customer+ variable will refer to +@new_customer+ from the parent view. -WARNING: In previous versions of Rails, the default local variable would look for an instance variable with the same name as the partial in the parent. This behavior is deprecated in Rails 2.2 and will be removed in a future version. +WARNING: In previous versions of Rails, the default local variable would look for an instance variable with the same name as the partial in the parent. This behavior was deprecated in 2.3 and has been removed in Rails 3.0. If you have an instance of a model to render into a partial, you can use a shorthand syntax: -<%= render :partial => @customer %> +<%= render @customer %> -Assuming that the +@customer+ instance variable contains an instance of the +Customer+ model, this will use +_customer.html.erb+ to render it. +Assuming that the +@customer+ instance variable contains an instance of the +Customer+ model, this will use +_customer.html.erb+ to render it and will pass the local variable +customer+ into the partial which will refer to the +@customer+ instance variable in the parent view. h5. Rendering Collections @@ -865,39 +1085,16 @@ Partials are very useful in rendering collections. When you pass a collection to

    Product Name: <%= product.name %>

    -When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is +_product+, and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered. To use a custom local variable name within the partial, specify the +:as+ option in the call to the partial: +When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is +_product+, and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered. - -<%= render :partial => "product", :collection => @products, :as => :item %> - - -With this change, you can access an instance of the +@products+ collection as the +item+ local variable within the partial. - -TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by +_counter+. For example, if you're rendering +@products+, within the partial you can refer to +product_counter+ to tell you how many times the partial has been rendered. - -You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option: - - -<%= render :partial => "product", :collection => @products, - :spacer_template => "product_ruler" %> - - -Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials. - -There's also a shorthand syntax available for rendering collections. For example, if +@products+ is a collection of products, you can render the collection this way: - -* +index.html.erb+ +In Rails 3.0 there is also a shorthand for this, assuming +@posts+ is a collection of +post+ instances, you can simply do in the +index.html.erb+:

    Products

    -<%= render :partial => @products %> +<%= render @products %>
    -* +_product.html.erb+ - - -

    Product Name: <%= product.name %>

    -
    +To produce the same result. Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection: @@ -905,24 +1102,54 @@ Rails determines the name of the partial to use by looking at the model name in

    Contacts

    -<%= render :partial => - [customer1, employee1, customer2, employee2] %> +<%= render [customer1, employee1, customer2, employee2] %>
    -* +_customer.html.erb+ +* +customers/_customer.html.erb+ -

    Name: <%= customer.name %>

    +

    Customer: <%= customer.name %>

    -* +_employee.html.erb+ +* +employees/_employee.html.erb+ -

    Name: <%= employee.name %>

    +

    Employee: <%= employee.name %>

    In this case, Rails will use the customer or employee partials as appropriate for each member of the collection. +h5. Local Variables + +To use a custom local variable name within the partial, specify the +:as+ option in the call to the partial: + + +<%= render :partial => "product", :collection => @products, :as => :item %> + + +With this change, you can access an instance of the +@products+ collection as the +item+ local variable within the partial. + +You can also pass in arbitrary local variables to any partial you are rendering with the +:locals => {}+ option: + + +<%= render :partial => 'products', :collection => @products, + :as => :item, :locals => {:title => "Products Page"} %> + + +Would render a partial +_products.html.erb+ once for each instance of +product+ in the +@products+ instance variable passing the instance to the partial as a local variable called +item+ and to each partial, make the local variable +title+ available with the value +Products Page+. + +TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by +_counter+. For example, if you're rendering +@products+, within the partial you can refer to +product_counter+ to tell you how many times the partial has been rendered. This does not work in conjunction with the +:as => :value+ option. + +You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option: + +h5. Spacer Templates + + +<%= render @products, :spacer_template => "product_ruler" %> + + +Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials. + h4. Using Nested Layouts You may find that your application requires a layout that differs slightly from your regular application layout to support one particular controller. Rather than repeating the main layout and editing it, you can accomplish this by using nested layouts (sometimes called sub-templates). Here's an example: @@ -964,12 +1191,13 @@ On pages generated by +NewsController+, you want to hide the top menu and add a That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div. -There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the +ActionView::render+ method via +render :file => 'layouts/news'+ to base a new layout on the News layout. If one is sure she will not subtemplate the +News+ layout, she can omit the +yield(:news_content) or + part. +There are several ways of getting similar results with different sub-templating schemes using this technique. Note that there is no limit in nesting levels. One can use the +ActionView::render+ method via +render :file => 'layouts/news'+ to base a new layout on the News layout. If you are sure you will not subtemplate the +News+ layout, you can replace the +yield(:news_content) or yield+ with simply +yield+. h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/15 +* January 25, 2010: Rails 3.0 Update by "Mikel Lindsaar":credits.html#raasdnil * December 27, 2008: Merge patch from Rodrigo Rosenfeld Rosas covering subtemplates * December 27, 2008: Information on new rendering defaults by "Mike Gunderloy":credits.html#mgunderloy * November 9, 2008: Added partial collection counter by "Mike Gunderloy":credits.html#mgunderloy diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index 71e1a7e3d3..2db421aa91 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -104,7 +104,7 @@ To make it easy to organize your files and to make the plugin more compatible wi `-- init.rb -*vendor/plugins/yaffle/rails/init.rb* +*vendor/plugins/yaffle/init.rb* require 'yaffle' @@ -341,13 +341,15 @@ $ rails console h4. Working with +init.rb+ -When rails loads plugins it looks for the file named 'init.rb' or 'rails/init.rb'. However, when the plugin is initialized, 'init.rb' is invoked via +eval+ (not +require+) so it has slightly different behavior. +When Rails loads plugins it looks for a file named +init.rb+. However, when the plugin is initialized, +init.rb+ is invoked via +eval+ (not +require+) so it has slightly different behavior. -Under certain circumstances if you reopen classes or modules in 'init.rb' you may inadvertently create a new class, rather than reopening an existing class. A better alternative is to reopen the class in a different file, and require that file from +init.rb+, as shown above. +NOTE: The plugins loader also looks for +rails/init.rb+, but that one is deprecated in favor of the top-level +init.rb+ aforementioned. + +Under certain circumstances if you reopen classes or modules in +init.rb+ you may inadvertently create a new class, rather than reopening an existing class. A better alternative is to reopen the class in a different file, and require that file from +init.rb+, as shown above. If you must reopen a class in +init.rb+ you can use +module_eval+ or +class_eval+ to avoid any issues: -* *vendor/plugins/yaffle/rails/init.rb* +* *vendor/plugins/yaffle/init.rb* Hash.class_eval do @@ -359,7 +361,7 @@ end Another way is to explicitly define the top-level module space for all modules and classes, like +::Hash+: -* *vendor/plugins/yaffle/rails/init.rb* +* *vendor/plugins/yaffle/init.rb* class ::Hash @@ -1333,15 +1335,15 @@ yaffle:squawk # Prints out the word 'Yaffle' You can add as many files as you want in the tasks directory, and if they end in .rake Rails will pick them up. -Note that tasks from 'vendor/plugins/yaffle/Rakefile' are not available to the main app. +Note that tasks from +vendor/plugins/yaffle/Rakefile+ are not available to the main app. -h3. PluginGems +h3. Plugins as Gems Turning your rails plugin into a gem is a simple and straightforward task. This section will cover how to turn your plugin into a gem. It will not cover how to distribute that gem. -Historically rails plugins loaded the plugin's 'init.rb' file. In fact some plugins contain all of their code in that one file. To be compatible with plugins, 'init.rb' was moved to 'rails/init.rb'. +The initialization file has to be called +rails/init.rb+, the root +init.rb+ file, if any, is ignored by Rails. Also, the name of the plugin now is relevant since +config.gem+ tries to load it. Either name the main file after your gem, or document that users should use the +:lib+ option. -It's common practice to put any developer-centric rake tasks (such as tests, rdoc and gem package tasks) in 'Rakefile'. A rake task that packages the gem might look like this: +It's common practice to put any developer-centric rake tasks (such as tests, rdoc and gem package tasks) in +Rakefile+. A rake task that packages the gem might look like this: * *vendor/plugins/yaffle/Rakefile:* @@ -1383,7 +1385,7 @@ rake gem sudo gem install pkg/yaffle-0.0.1.gem -To test this, create a new rails app, add 'config.gem "yaffle"' to environment.rb and all of your plugin's functionality will be available to you. +To test this, create a new rails app, add +config.gem "yaffle"+ to +config/environment.rb+ and all of your plugin's functionality will be available to you. h3. RDoc Documentation diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 24f0578545..8cf084494b 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -4,7 +4,7 @@ This guide covers the user-facing features of Rails routing. By referring to thi * Understand the purpose of routing * Decipher the code in +routes.rb+ -* Construct your own routes, using either the classic hash style or the now-preferred RESTful style +* Construct your own routes, using either the @match@ method or the preferred RESTful style * Identify how a route will map to a controller and action endprologue. @@ -37,7 +37,7 @@ Routing also works in reverse. If your application contains this code: Then the routing engine is the piece that translates that to a link to a URL such as +http://example.com/patients/17+. By using routing in this way, you can reduce the brittleness of your application as compared to one with hard-coded URLs, and make your code easier to read and understand. -NOTE: Patient needs to be declared as a resource for this style of translation via a named route to be available. +NOTE: Patient needs to be declared as a Restful resource for this style of translation to be available. h3. Quick Tour of +routes.rb+ @@ -45,7 +45,7 @@ There are two components to routing in Rails: the routing engine itself, which i h4. Processing the File -In format, +routes.rb+ is nothing more than one big block sent to +ActionController::Routing::Routes.draw+. Within this block, you can have comments, but it's likely that most of your content will be individual lines of code - each line being a route in your application. You'll find five main types of content in this file: +In format, +routes.rb+ is nothing more than one big block sent to +ApplicationName::Application.routes.draw+. Within this block, you can have comments, but it's likely that most of your content will be individual lines of code - each line being a route in your application. You'll find five main types of content in this file: * RESTful Routes * Named Routes @@ -55,31 +55,39 @@ In format, +routes.rb+ is nothing more than one big block sent to +ActionControl Each of these types of route is covered in more detail later in this guide. -The +routes.rb+ file is processed from top to bottom when a request comes in. The request will be dispatched to the first matching route. If there is no matching route, then Rails returns HTTP status 404 to the caller. +The +routes.rb+ file is processed from top to bottom when a request comes in. The request will be dispatched to the first matching route, and then proceeds to the next. If there is no matching route, then Rails returns HTTP status 404 to the caller. h4. RESTful Routes -RESTful routes take advantage of the built-in REST orientation of Rails to wrap up a lot of routing information in a single declaration. A RESTful route looks like this: +RESTful routes take advantage of the built-in REST orientation of Rails to wrap up a lot of routing information with a single declaration. A RESTful route looks like this: -map.resources :books +resources :books h4. Named Routes Named routes give you very readable links in your code, as well as handling incoming requests. Here's a typical named route: + +match 'login' => 'sessions#new', :as => 'login' + + +If you're coming from Rails 2, this route will be equivalent to: + map.login '/login', :controller => 'sessions', :action => 'new' +You will also notice that +sessions#new+ is a shorthand for +:controller => 'sessions', :action => 'new'+. By declaring a named route such as this, you can use +login_path+ or +login_url+ in your controllers and views to generate the URLs for this route. + h4. Nested Routes Nested routes let you declare that one resource is contained within another resource. You'll see later on how this translates to URLs and paths in your code. For example, if your application includes parts, each of which belongs to an assembly, you might have this nested route declaration: -map.resources :assemblies do |assemblies| - assemblies.resources :parts +resources :assemblies do + resources :parts end @@ -88,19 +96,18 @@ h4. Regular Routes In many applications, you'll also see non-RESTful routing, which explicitly connects the parts of a URL to a particular action. For example, -map.connect 'parts/:number', :controller => 'inventory', :action => 'show' +match 'parts/:number' => 'inventory#show' h4. Default Routes -The default routes are a safety net that catch otherwise-unrouted requests. Many Rails applications will contain this pair of default routes: +The default route is a safety net that catches otherwise-unrouted requests. Many Rails applications will contain this default route: -map.connect ':controller/:action/:id' -map.connect ':controller/:action/:id.:format' +match ':controller(/:action(/:id(.:format)))' -These default routes are automatically generated when you create a new Rails application. If you're using RESTful routing for everything in your application, you will probably want to remove them. But be sure you're not using the default routes before you remove them! +In Rails 3, this route is commented out advising to use RESTful routes as much as possible. So if you're using RESTful routing for everything in your application, you will probably want to leave it like that. h3. RESTful Routing: the Rails Default @@ -126,7 +133,7 @@ h4. CRUD, Verbs, and Actions In Rails, a RESTful route provides a mapping between HTTP verbs, controller actions, and (implicitly) CRUD operations in a database. A single entry in the routing file, such as -map.resources :photos +resources :photos creates seven different routes in your application: @@ -142,11 +149,9 @@ creates seven different routes in your application: For the specific routes (those that reference just a single resource), the identifier for the resource will be available within the corresponding controller action as +params[:id]+. -TIP: If you consistently use RESTful routes in your application, you should disable the default routes in +routes.rb+ so that Rails will enforce the mapping between HTTP verbs and routes. - h4. URLs and Paths -Creating a RESTful route will also make available a pile of helpers within your application: +Creating a RESTful route will also make available a pile of helpers within your application, something that requires explicit mention otherwise: * +photos_url+ and +photos_path+ map to the path for the index and create actions * +new_photo_url+ and +new_photo_path+ map to the path for the new action @@ -164,26 +169,26 @@ photos_path # => "/photos" h4. Defining Multiple Resources at the Same Time -If you need to create routes for more than one RESTful resource, you can save a bit of typing by defining them all with a single call to +map.resources+: +If you need to create routes for more than one RESTful resource, you can save a bit of typing by defining them all with a single call to +resources+: -map.resources :photos, :books, :videos +resources :photos, :books, :videos This has exactly the same effect as -map.resources :photos -map.resources :books -map.resources :videos +resources :photos +resources :books +resources :videos h4. Singular Resources -You can also apply RESTful routing to singleton resources within your application. In this case, you use +map.resource+ instead of +map.resources+ and the route generation is slightly different. For example, a routing entry of +You can also apply RESTful routing to singleton resources within your application. In this case, you use +resource+ instead of +resources+ and the route generation is slightly different. For example, a routing entry of -map.resource :geocoder +resource :geocoder creates six different routes in your application: @@ -214,19 +219,17 @@ Although the conventions of RESTful routing are likely to be sufficient for many * +:conditions+ * +:as+ * +:path_names+ -* +:path_prefix+ -* +:name_prefix+ * +:only+ * +:except+ -You can also add additional routes via the +:member+ and +:collection+ options, which are discussed later in this guide. +You can also add additional routes via the +member+ and +collection+ blocks, which are discussed later in this guide. h5. Using +:controller+ The +:controller+ option lets you use a controller name that is different from the public-facing resource name. For example, this routing entry: -map.resources :photos, :controller => "images" +resources :photos, :controller => "images" will recognize incoming URLs containing +photo+ but route the requests to the Images controller: @@ -247,35 +250,37 @@ h4. Controller Namespaces and Routing Rails allows you to group your controllers into namespaces by saving them in folders underneath +app/controllers+. The +:controller+ option provides a convenient way to use these routes. For example, you might have a resource whose controller is purely for admin users in the +admin+ folder: -map.resources :adminphotos, :controller => "admin/photos" +resources :photos, :controller => "admin/photos" -If you use controller namespaces, you need to be aware of a subtlety in the Rails routing code: it always tries to preserve as much of the namespace from the previous request as possible. For example, if you are on a view generated from the +adminphoto_path+ helper, and you follow a link generated with +<%= link_to "show", adminphoto(1) %>+ you will end up on the view generated by +admin/photos/show+, but you will also end up in the same place if you have +<%= link_to "show", {:controller => "photos", :action => "show"} %>+ because Rails will generate the show URL relative to the current URL. +If you use controller namespaces, you need to be aware of a subtlety in the Rails routing code: it always tries to preserve as much of the namespace from the previous request as possible. For example, if you are on a view generated from the +photo_path+ helper, and you follow a link generated with +<%= link_to "show", photo_path(1) %>+ you will end up on the view generated by +admin/photos/show+, but you will also end up in the same place if you have +<%= link_to "show", {:controller => "photos", :action => "show"} %>+ because Rails will generate the show URL relative to the current URL. TIP: If you want to guarantee that a link goes to a top-level controller, use a preceding slash to anchor the controller name: +<%= link_to "show", {:controller => "/photos", :action => "show"} %>+ You can also specify a controller namespace with the +:namespace+ option instead of a path: -map.resources :adminphotos, :namespace => "admin", :controller => "photos" +resources :adminphotos, :namespace => "admin", :controller => "photos" -This can be especially useful when combined with +with_options+ to map multiple namespaced routes together: +This can be especially useful when map multiple namespaced routes together using +namespace+ block by: -map.with_options(:namespace => "admin") do |admin| - admin.resources :photos, :videos +namespace :admin do + resources :photos, :videos end -That would give you routing for +admin/photos+ and +admin/videos+ controllers. +That would give you routing for +admin/photos+ and +admin/videos+ controllers. + +The difference between generating routes through +namespace+ and the +:controller+ key is that the +namespace+ will add +admin+ to the generated helpers as well, so the above route generates +admin_photos_path+. h5. Using +:singular+ If for some reason Rails isn't doing what you want in converting the plural resource name to a singular name in member routes, you can override its judgment with the +:singular+ option: -map.resources :teeth, :singular => "tooth" +resources :teeth, :singular => "tooth" TIP: Depending on the other code in your application, you may prefer to add additional rules to the +Inflector+ class instead. @@ -285,7 +290,7 @@ h5. Using +:requirements+ You can use the +:requirements+ option in a RESTful route to impose a format on the implied +:id+ parameter in the singular routes. For example: -map.resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/} +resources :photos, :requirements => {:id => /[A-Z][A-Z][0-9]+/} This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, +/photos/1+ would no longer be recognized by this route, but +/photos/RR27+ would. @@ -299,7 +304,7 @@ h5. Using +:as+ The +:as+ option lets you override the normal naming for the actual generated paths. For example: -map.resources :photos, :as => "images" +resources :photos, :as => "images" will recognize incoming URLs containing +image+ but route the requests to the Photos controller: @@ -320,7 +325,7 @@ h5. Using +:path_names+ The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs: -map.resources :photos, :path_names => { :new => 'make', :edit => 'change' } +resources :photos, :path_names => { :new => 'make', :edit => 'change' } This would cause the routing to recognize URLs such as @@ -343,7 +348,7 @@ h5. Using +:path_prefix+ The +:path_prefix+ option lets you add additional parameters that will be prefixed to the recognized paths. For example, suppose each photo in your application belongs to a particular photographer. In that case, you might declare this route: -map.resources :photos, :path_prefix => '/photographers/:photographer_id' +resources :photos, :path_prefix => '/photographers/:photographer_id' Routes recognized by this entry would include: @@ -362,9 +367,9 @@ h5. Using +:name_prefix+ You can use the :name_prefix option to avoid collisions between routes. This is most useful when you have two resources with the same name that use +:path_prefix+ to map differently. For example: -map.resources :photos, :path_prefix => '/photographers/:photographer_id', +resources :photos, :path_prefix => '/photographers/:photographer_id', :name_prefix => 'photographer_' -map.resources :photos, :path_prefix => '/agencies/:agency_id', +resources :photos, :path_prefix => '/agencies/:agency_id', :name_prefix => 'agency_' @@ -377,7 +382,7 @@ h5. Using +:only+ and +:except+ By default, Rails creates routes for all seven of the default actions (index, show, new, create, edit, update, and destroy) for every RESTful route in your application. You can use the +:only+ and +:except+ options to fine-tune this behavior. The +:only+ option specifies that only certain routes should be generated: -map.resources :photos, :only => [:index, :show] +resources :photos, :only => [:index, :show] With this declaration, a +GET+ request to +/photos+ would succeed, but a +POST+ request to +/photos+ (which would ordinarily be routed to the create action) will fail. @@ -385,7 +390,7 @@ With this declaration, a +GET+ request to +/photos+ would succeed, but a +POST+ The +:except+ option specifies a route or list of routes that should _not_ be generated: -map.resources :photos, :except => :destroy +resources :photos, :except => :destroy In this case, all of the normal routes except the route for +destroy+ (a +DELETE+ request to +/photos/id+) will be generated. @@ -411,12 +416,12 @@ end Each ad is logically subservient to one magazine. Nested routes allow you to capture this relationship in your routing. In this case, you might include this route declaration: -map.resources :magazines do |magazine| - magazine.resources :ads +resources :magazines do + resources :ads end -TIP: Further below you'll learn about a convenient shortcut for this construct:
    +map.resources :magazines, :has_many => :ads+ +TIP: Further below you'll learn about a convenient shortcut for this construct:
    +resources :magazines, :has_many => :ads+ In addition to the routes for magazines, this declaration will also create routes for ads, each of which requires the specification of a magazine in the URL: @@ -437,16 +442,16 @@ h5. Using +:name_prefix+ The +:name_prefix+ option overrides the automatically-generated prefix in nested route helpers. For example, -map.resources :magazines do |magazine| - magazine.resources :ads, :name_prefix => 'periodical' +resources :magazines do + resources :ads, :name_prefix => 'periodical' end This will create routing helpers such as +periodical_ads_url+ and +periodical_edit_ad_path+. You can even use +:name_prefix+ to suppress the prefix entirely: -map.resources :magazines do |magazine| - magazine.resources :ads, :name_prefix => nil +resources :magazines do + resources :ads, :name_prefix => nil end @@ -462,16 +467,16 @@ h5. Using +:has_one+ and +:has_many+ The +:has_one+ and +:has_many+ options provide a succinct notation for simple nested routes. Use +:has_one+ to nest a singleton resource, or +:has_many+ to nest a plural resource: -map.resources :photos, :has_one => :photographer, :has_many => [:publications, :versions] +resources :photos, :has_one => :photographer, :has_many => [:publications, :versions] This has the same effect as this set of declarations: -map.resources :photos do |photo| - photo.resource :photographer - photo.resources :publications - photo.resources :versions +resources :photos do + resource :photographer + resources :publications + resources :versions end @@ -480,9 +485,9 @@ h5. Limits to Nesting You can nest resources within other nested resources if you like. For example: -map.resources :publishers do |publisher| - publisher.resources :magazines do |magazine| - magazine.resources :photos +resources :publishers do + resources :magazines do + resources :photos end end @@ -502,9 +507,9 @@ h5. Shallow Nesting The +:shallow+ option provides an elegant solution to the difficulties of deeply-nested routes. If you specify this option at any level of routing, then paths for nested resources which reference a specific member (that is, those with an +:id+ parameter) will not use the parent path prefix or name prefix. To see what this means, consider this set of routes: -map.resources :publishers, :shallow => true do |publisher| - publisher.resources :magazines do |magazine| - magazine.resources :photos +resources :publishers, :shallow => true do + resources :magazines do + resources :photos end end @@ -522,7 +527,7 @@ This will enable recognition of (among others) these routes: With shallow nesting, you need only supply enough information to uniquely identify the resource that you want to work with. If you like, you can combine shallow nesting with the +:has_one+ and +:has_many+ options: -map.resources :publishers, :has_many => { :magazines => :photos }, :shallow => true +resources :publishers, :has_many => { :magazines => :photos }, :shallow => true h4. Route Generation from Arrays @@ -530,8 +535,8 @@ h4. Route Generation from Arrays In addition to using the generated routing helpers, Rails can also generate RESTful routes from an array of parameters. For example, suppose you have a set of routes generated with these entries in routes.rb: -map.resources :magazines do |magazine| - magazine.resources :ads +resources :magazines do + resources :ads end @@ -554,17 +559,16 @@ h4. Namespaced Resources It's possible to do some quite complex things by combining +:path_prefix+ and +:name_prefix+. For example, you can use the combination of these two options to move administrative resources to their own folder in your application: -map.resources :photos, :path_prefix => 'admin', :controller => 'admin/photos' -map.resources :tags, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_tags' -map.resources :ratings, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_ratings' +resources :photos, :path_prefix => 'admin', :controller => 'admin/photos' +resources :tags, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_tags' +resources :ratings, :name_prefix => 'admin_photo_', :path_prefix => 'admin/photos/:photo_id', :controller => 'admin/photo_ratings' The good news is that if you find yourself using this level of complexity, you can stop. Rails supports _namespaced resources_ to make placing resources in their own folder a snap. Here's the namespaced version of those same three routes: -map.namespace(:admin) do |admin| - admin.resources :photos, - :has_many => { :tags, :ratings} +namespace :admin do + resources :photos, :has_many => { :tags, :ratings } end @@ -576,18 +580,24 @@ You are not limited to the seven routes that RESTful routing creates by default. h5. Adding Member Routes -To add a member route, use the +:member+ option: +To add a member route, just add +member+ block into resource block: -map.resources :photos, :member => { :preview => :get } +resources :photos do + member do + get :preview + end +end This will enable Rails to recognize URLs such as +/photos/1/preview+ using the GET HTTP verb, and route them to the preview action of the Photos controller. It will also create the +preview_photo_url+ and +preview_photo_path+ route helpers. -Within the hash of member routes, each route name specifies the HTTP verb that it will recognize. You can use +:get+, +:put+, +:post+, +:delete+, or +:any+ here. You can also specify an array of methods, if you need more than one but you don't want to allow just anything: +Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, +delete+, or +any+ here. If you don't have multiple +member+ route, you can also passing +:on+ to the routing. -map.resources :photos, :member => { :prepare => [:get, :post] } +resources :photos do + get :preview, :on => :member +end h5. Adding Collection Routes @@ -595,32 +605,35 @@ h5. Adding Collection Routes To add a collection route, use the +:collection+ option: -map.resources :photos, :collection => { :search => :get } +resources :photos do + collection do + get :search + end +end This will enable Rails to recognize URLs such as +/photos/search+ using the GET HTTP verb, and route them to the search action of the Photos controller. It will also create the +search_photos_url+ and +search_photos_path+ route helpers. -Just as with member routes, you can specify an array of methods for a collection route: +Just as with member routes, you can passing +:on+ to the routing. -map.resources :photos, :collection => { :search => [:get, :post] } +resources :photos do + get :search, :on => :collection +end h5. Adding New Routes -To add a new route (one that creates a new resource), use the +:new+ option: +As of writing, Rails 3 has deprecated +:new+ option from routing. You will need to explicit define the route using +match+ method -map.resources :photos, :new => { :upload => :post } +resources :photos +match 'photos/new/upload' => 'photos#upload', :as => 'upload_new_photos' -This will enable Rails to recognize URLs such as +/photos/new/upload+ using the POST HTTP verb, and route them to the upload action of the Photos controller. It will also create the +upload_new_photos_path+ and +upload_new_photos_url+ route helpers. - -TIP: If you want to redefine the verbs accepted by one of the standard actions, you can do so by explicitly mapping that action. For example:
    +map.resources :photos, :new => { :new => :any }+
    This will allow the new action to be invoked by any request to +photos/new+, no matter what HTTP verb you use. - h5. A Note of Caution -If you find yourself adding many extra actions to a RESTful route, it's time to stop and ask yourself whether you're disguising the presence of another resource that would be better split off on its own. When the +:member+ and +:collection+ hashes become a dumping-ground, RESTful routes lose the advantage of easy readability that is one of their strongest points. +If you find yourself adding many extra actions to a RESTful route, it's time to stop and ask yourself whether you're disguising the presence of another resource that would be better split off on its own. When the +member+ and +collection+ hashes become a dumping-ground, RESTful routes lose the advantage of easy readability that is one of their strongest points. h3. Regular Routes @@ -633,7 +646,7 @@ h4. Bound Parameters When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: +:controller+ maps to the name of a controller in your application, and +:action+ maps to the name of an action within that controller. For example, consider one of the default Rails routes: -map.connect ':controller/:action/:id' +match ':controller(/:action(/:id))' If an incoming request of +/photos/show/1+ is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the +show+ action of the +Photos+ controller, and to make the final parameter (1) available as +params[:id]+. @@ -643,7 +656,7 @@ h4. Wildcard Components You can set up as many wildcard symbols within a regular route as you like. Anything other than +:controller+ or +:action+ will be available to the matching action as part of the params hash. So, if you set up this route: -map.connect ':controller/:action/:id/:user_id' +match ':controller/:action/:id/:user_id' An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +Photos+ controller. +params[:id]+ will be set to 1, and +params[:user_id]+ will be set to 2. @@ -653,7 +666,7 @@ h4. Static Text You can specify static text when creating a route. In this case, the static text is used only for matching the incoming requests: -map.connect ':controller/:action/:id/with_user/:user_id' +match ':controller/:action/:id/with_user/:user_id' This route would respond to URLs such as +/photos/show/1/with_user/2+. @@ -663,17 +676,17 @@ h4. Querystring Parameters Rails routing automatically picks up querystring parameters and makes them available in the +params+ hash. For example, with this route: -map.connect ':controller/:action/:id' +match ':controller/:action/:id An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params[:id]+ will be set to 1, and +params[:user_id]+ will be equal to 2. h4. Defining Defaults -You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply defaults for these two parameters in a hash: +You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply defaults for these two parameters by putting it after +=>+: -map.connect 'photos/:id', :controller => 'photos', :action => 'show' +match 'photos/:id' => 'photos#show' With this route, an incoming URL of +/photos/12+ would be dispatched to the +show+ action within the +Photos+ controller. @@ -681,8 +694,7 @@ With this route, an incoming URL of +/photos/12+ would be dispatched to the +sho You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that are not explicitly defined elsewhere in the route. For example: -map.connect 'photos/:id', :controller => 'photos', :action => 'show', - :defaults => { :format => 'jpg' } +match 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' } With this route, an incoming URL of +photos/12+ would be dispatched to the +show+ action within the +Photos+ controller, and +params[:format]+ will be set to +jpg+. @@ -692,7 +704,7 @@ h4. Named Routes Regular routes need not use the +connect+ method. You can use any other name here to create a _named route_. For example, -map.logout '/logout', :controller => 'sessions', :action => 'destroy' +match 'logout' => 'sessions#destroy', :as => :logout This will do two things. First, requests to +/logout+ will be sent to the +destroy+ action of the +Sessions+ controller. Second, Rails will maintain the +logout_path+ and +logout_url+ helpers for use within your code. @@ -702,15 +714,13 @@ h4. Route Requirements You can use the +:requirements+ option to enforce a format for any parameter in a route: -map.connect 'photo/:id', :controller => 'photos', :action => 'show', - :requirements => { :id => /[A-Z]\d{5}/ } +match 'photo/:id' => 'photos#show', :requirements => { :id => /[A-Z]\d{5}/ } This route would respond to URLs such as +/photo/A12345+. You can more succinctly express the same route this way: -map.connect 'photo/:id', :controller => 'photos', :action => 'show', - :id => /[A-Z]\d{5}/ +match 'photo/:id' => 'photos#show', :id => /[A-Z]\d{5}/ h4. Route Conditions @@ -718,8 +728,7 @@ h4. Route Conditions Route conditions (introduced with the +:conditions+ option) are designed to implement restrictions on routes. Currently, the only supported restriction is +:method+: -map.connect 'photo/:id', :controller => 'photos', :action => 'show', - :conditions => { :method => :get } +match 'photo/:id' => 'photos#show', :conditions => { :method => :get } As with conditions in RESTful routes, you can specify +:get+, +:post+, +:put+, +:delete+, or +:any+ for the acceptable method. @@ -729,7 +738,7 @@ h4. Route Globbing Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example -map.connect 'photo/*other', :controller => 'photos', :action => 'unknown', +match 'photo/*other' => 'photos#unknown' This route would match +photo/12+ or +/photo/long/path/to/12+ equally well, creating an array of path segments as the value of +params[:other]+. @@ -755,7 +764,7 @@ There's one more way in which routing can do different things depending on diffe For instance, consider the second of the default routes in the boilerplate +routes.rb+ file: -map.connect ':controller/:action/:id.:format' +match ':controller(/:action(/:id(.:format)))' This route matches requests such as +/photo/edit/1.xml+ or +/photo/show/2.rss+. Within the appropriate action code, you can issue different responses depending on the requested format: @@ -781,11 +790,10 @@ Mime::Type.register "image/jpg", :jpg h3. The Default Routes -When you create a new Rails application, +routes.rb+ is initialized with two default routes: +When you create a new Rails application, +routes.rb+ is initialized with a default route: -map.connect ':controller/:action/:id' -map.connect ':controller/:action/:id.:format' +match ':controller(/:action(/:id(.:format)))' These routes provide reasonable defaults for many URLs, if you're not using RESTful routing. @@ -796,31 +804,24 @@ h3. The Empty Route Don't confuse the default routes with the empty route. The empty route has one specific purpose: to route requests that come in to the root of the web site. For example, if your site is example.com, then requests to +http://example.com+ or +http://example.com/+ will be handled by the empty route. -h4. Using +map.root+ +h4. Using +root+ -The preferred way to set up the empty route is with the +map.root+ command: +The preferred way to set up the empty route is with the +root+ command: -map.root :controller => "pages", :action => "main" +root :to => 'pages#main' The use of the +root+ method tells Rails that this route applies to requests for the root of the site. -For better readability, you can specify an already-created route in your call to +map.root+: - - -map.index 'index', :controller => "pages", :action => "main" -map.root :index - - -Because of the top-down processing of the file, the named route must be specified _before_ the call to +map.root+. +Because of the top-down processing of the file, the named route must be specified _before_ the call to +root+. h4. Connecting the Empty String You can also specify an empty route by explicitly connecting the empty string: -map.connect '', :controller => "pages", :action => "main" +match '' => 'pages#main' TIP: If the empty route does not seem to be working in your application, make sure that you have deleted the file +public/index.html+ from your Rails tree. @@ -898,6 +899,7 @@ h3. Changelog "Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/3 +* Febuary 1, 2010: Modifies the routing documentation to match new routing DSL in Rails 3, by Prem Sichanugrist * October 4, 2008: Added additional detail on specifying verbs for resource member/collection routes, by "Mike Gunderloy":credits.html#mgunderloy * September 23, 2008: Added section on namespaced controllers and routing, by "Mike Gunderloy":credits.html#mgunderloy * September 10, 2008: initial version by "Mike Gunderloy":credits.html#mgunderloy diff --git a/railties/lib/generators/erb.rb b/railties/lib/generators/erb.rb deleted file mode 100644 index d468d012dc..0000000000 --- a/railties/lib/generators/erb.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'rails/generators/named_base' - -module Erb - module Generators - class Base < Rails::Generators::NamedBase #:nodoc: - end - end -end diff --git a/railties/lib/generators/erb/mailer/mailer_generator.rb b/railties/lib/generators/erb/mailer/mailer_generator.rb deleted file mode 100644 index 408c942cef..0000000000 --- a/railties/lib/generators/erb/mailer/mailer_generator.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'generators/erb' - -module Erb - module Generators - class MailerGenerator < Base - argument :actions, :type => :array, :default => [], :banner => "method method" - - def create_view_folder - empty_directory File.join("app/views", file_path) - end - - def create_view_files - actions.each do |action| - @action, @path = action, File.join(file_path, action) - template "view.text.erb", File.join("app/views", "#{@path}.text.erb") - end - end - end - end -end diff --git a/railties/lib/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/generators/test_unit/controller/templates/functional_test.rb deleted file mode 100644 index 62fa5d86fd..0000000000 --- a/railties/lib/generators/test_unit/controller/templates/functional_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'test_helper' - -class <%= class_name %>ControllerTest < ActionController::TestCase - # Replace this with your real tests. - test "the truth" do - assert true - end -end diff --git a/railties/lib/generators/test_unit/mailer/templates/fixture b/railties/lib/generators/test_unit/mailer/templates/fixture deleted file mode 100644 index 171648d6fd..0000000000 --- a/railties/lib/generators/test_unit/mailer/templates/fixture +++ /dev/null @@ -1,3 +0,0 @@ -<%= class_name %>#<%= @action %> - -Hi, find me in app/views/<%= @path %> diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 3d3151bd8f..9d02da104d 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -26,6 +26,9 @@ end module Rails + autoload :Info, 'rails/info' + autoload :InfoController, 'rails/info_controller' + class << self def application @@application ||= nil diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index f43e8847ac..0084309ea4 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -1,9 +1,45 @@ require 'fileutils' -require 'rails/railties_path' require 'rails/plugin' require 'rails/engine' module Rails + # In Rails 3.0, a Rails::Application object was introduced which is nothing more than + # an Engine but with the responsibility of coordinating the whole boot process. + # + # Opposite to Rails::Engine, you can only have one Rails::Application instance + # in your process and both Rails::Application and YourApplication::Application + # points to it. + # + # In other words, Rails::Application is Singleton and whenever you are accessing + # Rails::Application.config or YourApplication::Application.config, you are actually + # accessing YourApplication::Application.instance.config. + # + # == Initialization + # + # Rails::Application is responsible for executing all railties, engines and plugin + # initializers. Besides, it also executed some bootstrap initializers (check + # Rails::Application::Bootstrap) and finishing initializers, after all the others + # are executed (check Rails::Application::Finisher). + # + # == Configuration + # + # Besides providing the same configuration as Rails::Engine and Rails::Railtie, + # the application object has several specific configurations, for example + # "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters", + # "logger", "metals", "reload_engines", "reload_plugins" and so forth. + # + # Check Rails::Application::Configuration to see them all. + # + # == Routes + # + # The application object is also responsible for holding the routes and reloading routes + # whenever the files change in development. + # + # == Middlewares and metals + # + # The Application is also responsible for building the middleware stack and setting up + # both application and engines metals. + # class Application < Engine autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configurable, 'rails/application/configurable' @@ -45,6 +81,8 @@ def method_missing(*args, &block) end end + delegate :metal_loader, :to => :config + def require_environment! environment = config.paths.config.environment.to_a.first require environment if environment diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index d3a0ecb243..44635ff4f6 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -6,7 +6,7 @@ class Configuration < ::Rails::Engine::Configuration include ::Rails::Configuration::Deprecated attr_accessor :allow_concurrency, :cache_classes, :cache_store, - :consider_all_requests_local, :dependency_loading, + :cookie_secret, :consider_all_requests_local, :dependency_loading, :filter_parameters, :log_level, :logger, :metals, :plugins, :preload_frameworks, :reload_engines, :reload_plugins, :serve_static_assets, :time_zone, :whiny_nils @@ -19,10 +19,16 @@ def initialize(*) @serve_static_assets = true @time_zone = "UTC" @consider_all_requests_local = true + @session_store = :cookie_store + @session_options = {} end def middleware - @@default_middleware_stack ||= default_middleware + @middleware ||= default_middleware_stack + end + + def metal_loader + @metal_loader ||= Rails::Application::MetalLoader.new end def paths @@ -78,7 +84,7 @@ def cache_store end def builtin_controller - File.join(RAILTIES_PATH, "builtin", "rails_info") if Rails.env.development? + File.expand_path('../info_routes', __FILE__) if Rails.env.development? end def log_level @@ -94,6 +100,51 @@ def colorize_logging=(val) Rails::LogSubscriber.colorize_logging = val self.generators.colorize_logging = val end + + def session_store(*args) + if args.empty? + case @session_store + when :disabled + nil + when :active_record_store + ActiveRecord::SessionStore + when Symbol + ActionDispatch::Session.const_get(@session_store.to_s.camelize) + else + @session_store + end + else + @session_store = args.shift + @session_options = args.shift || {} + end + end + + protected + + def session_options + return @session_options unless @session_store == :cookie_store + @session_options.merge(:secret => @cookie_secret) + end + + def default_middleware_stack + ActionDispatch::MiddlewareStack.new.tap do |middleware| + middleware.use('::ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { serve_static_assets }) + middleware.use('::Rack::Lock', :if => lambda { !allow_concurrency }) + middleware.use('::Rack::Runtime') + middleware.use('::Rails::Rack::Logger') + middleware.use('::ActionDispatch::ShowExceptions', lambda { consider_all_requests_local }) + middleware.use("::ActionDispatch::RemoteIp", lambda { action_dispatch.ip_spoofing_check }, lambda { action_dispatch.trusted_proxies }) + middleware.use('::Rack::Sendfile', lambda { action_dispatch.x_sendfile_header }) + middleware.use('::ActionDispatch::Callbacks', lambda { !cache_classes }) + middleware.use('::ActionDispatch::Cookies') + middleware.use(lambda { session_store }, lambda { session_options }) + middleware.use('::ActionDispatch::Flash', :if => lambda { session_store }) + middleware.use(lambda { metal_loader.build_middleware(metals) }, :if => lambda { metal_loader.metals.any? }) + middleware.use('ActionDispatch::ParamsParser') + middleware.use('::Rack::MethodOverride') + middleware.use('::ActionDispatch::Head') + end + end end end -end \ No newline at end of file +end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index cb38d5a5db..978490f25f 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -23,7 +23,7 @@ module Finisher initializer :add_builtin_route do |app| if Rails.env.development? - app.routes_reloader.paths << File.join(RAILTIES_PATH, 'builtin', 'routes.rb') + app.routes_reloader.paths << File.expand_path('../../info_routes.rb', __FILE__) end end @@ -45,4 +45,4 @@ module Finisher end end end -end \ No newline at end of file +end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 6972e25b29..12748da18b 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -30,7 +30,7 @@ require 'rails/commands/generate' when 'c', 'console' require 'rails/commands/console' - require APP_PATH + require ENV_PATH Rails::Console.start(Rails::Application) when 's', 'server' require 'rails/commands/server' diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb index fe2f89ee98..438c976b35 100644 --- a/railties/lib/rails/commands/application.rb +++ b/railties/lib/rails/commands/application.rb @@ -8,6 +8,6 @@ require 'rubygems' if ARGV.include?("--dev") require 'rails/generators' -require 'generators/rails/app/app_generator' +require 'rails/generators/rails/app/app_generator' Rails::Generators::AppGenerator.start \ No newline at end of file diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index 593e2d8ee3..68982b9f52 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -91,6 +91,18 @@ def find_cmd(*commands) args << config['database'] exec(find_cmd('sqlite3'), *args) + + when "oracle", "oracle_enhanced" + logon = "" + + if config['username'] + logon = config['username'] + logon << "/#{config['password']}" if config['password'] && include_password + logon << "@#{config['database']}" if config['database'] + end + + exec(find_cmd('sqlplus'), logon) + else abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!" end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 85b4ff8470..85cae75bce 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -3,6 +3,89 @@ require 'pathname' 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. + # + # 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. + # + # == Creating an Engine + # + # In Rails versions before to 3.0, your gems automatically behaved as Engine, 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 with how we spceify a Railtie): + # + # # lib/my_engine.rb + # module MyEngine + # class Engine < Rails::Engine + # engine_name :my_engine + # 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, helpers and metals + # inside app, load routes at "config/routes.rb", load locales at "config/locales/*", + # load tasks at "lib/tasks/*". + # + # == Configuration + # + # Besides the Railtie configuration which is shared across the application, in a + # Rails::Engine you can access load_paths, eager_load_paths and load_once_paths, + # which differently from a Railtie, are scoped to the current Engine. + # + # Example: + # + # class MyEngine < Rails::Engine + # # config.middleware is shared configururation + # config.middleware.use MyEngine::Middleware + # + # # Add a load path for this specific Engine + # config.load_paths << File.expand_path("../lib/some/path", __FILE__) + # end + # + # == 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", + # 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: + # + # 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": + # + # class MyEngine < Rails::Engine + # paths.app.controllers << "lib/controllers" + # end + # + # The available paths in an Engine are: + # + # class MyEngine < Rails::Engine + # paths.app = "app" + # paths.app.controllers = "app/controllers" + # paths.app.helpers = "app/helpers" + # paths.app.models = "app/models" + # paths.app.metals = "app/metal" + # paths.app.views = "app/views" + # paths.lib = "lib" + # paths.lib.tasks = "lib/tasks" + # paths.config = "config" + # paths.config.initializers = "config/initializers" + # paths.config.locales = "config/locales" + # 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. + # class Engine < Railtie autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" @@ -10,11 +93,11 @@ class Engine < Railtie class << self attr_accessor :called_from - alias :engine_name :railtie_name - alias :engine_names :railtie_names + # TODO Remove this. It's deprecated. + alias :engine_name :railtie_name def inherited(base) - unless abstract_railtie?(base) + unless base.abstract_railtie? base.called_from = begin # Remove the line number from backtraces making sure we don't leave anything behind call_stack = caller.map { |p| p.split(':')[0..-2].join(':') } @@ -41,7 +124,7 @@ def find_root_with_flag(flag, default=nil) end end - delegate :middleware, :paths, :metal_loader, :root, :to => :config + delegate :middleware, :paths, :root, :to => :config def load_tasks super diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 3c902ce0d4..57462707f4 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -202,6 +202,10 @@ def self.help(command = 'generate') rails.delete("app") print_list("rails", rails) + groups.delete("active_record") if options[:rails][:orm] == :active_record + groups.delete("test_unit") if options[:rails][:test_framework] == :test_unit + groups.delete("erb") if options[:rails][:template_engine] == :erb + groups.sort.each { |b, n| print_list(b, n) } end @@ -238,7 +242,7 @@ def self.lookup(namespaces) #:nodoc: paths = namespaces_to_paths(namespaces) paths.each do |raw_path| - ["rails_generators", "generators"].each do |base| + ["rails/generators", "generators"].each do |base| path = "#{base}/#{raw_path}_generator" begin @@ -261,7 +265,7 @@ def self.lookup! #:nodoc: load_generators_from_railties! $LOAD_PATH.each do |base| - Dir[File.join(base, "{generators,rails_generators}", "**", "*_generator.rb")].each do |path| + Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path| begin require path rescue Exception => e diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 9624c35c0b..0da85ea4a4 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -1,4 +1,12 @@ -require 'thor/group' +begin + require 'thor/group' +rescue LoadError + puts "Thor is not available.\nIf you ran this command from a git checkout " \ + "of Rails, please make sure thor is installed,\nand run this command " \ + "as `ruby /path/to/rails myapp --dev`" + exit +end + require 'rails/generators/actions' module Rails @@ -17,7 +25,7 @@ class Base < Thor::Group def self.source_root @_rails_source_root ||= begin if base_name && generator_name - File.expand_path(File.join("../../generators", base_name, generator_name, 'templates'), File.dirname(__FILE__)) + File.expand_path(File.join(base_name, generator_name, 'templates'), File.dirname(__FILE__)) end end end diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb new file mode 100644 index 0000000000..3e6371268f --- /dev/null +++ b/railties/lib/rails/generators/erb.rb @@ -0,0 +1,21 @@ +require 'rails/generators/named_base' + +module Erb + module Generators + class Base < Rails::Generators::NamedBase #:nodoc: + protected + + def format + :html + end + + def handler + :erb + end + + def filename_with_extensions(name) + [name, format, handler].compact.join(".") + end + end + end +end diff --git a/railties/lib/generators/erb/controller/controller_generator.rb b/railties/lib/rails/generators/erb/controller/controller_generator.rb similarity index 65% rename from railties/lib/generators/erb/controller/controller_generator.rb rename to railties/lib/rails/generators/erb/controller/controller_generator.rb index ab7b273662..ac57140c23 100644 --- a/railties/lib/generators/erb/controller/controller_generator.rb +++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb @@ -1,19 +1,18 @@ -require 'generators/erb' +require 'rails/generators/erb' module Erb module Generators class ControllerGenerator < Base argument :actions, :type => :array, :default => [], :banner => "action action" - def create_view_files + def copy_view_files base_path = File.join("app/views", class_path, file_name) empty_directory base_path actions.each do |action| @action = action - @path = File.join(base_path, "#{action}.html.erb") - - template 'view.html.erb', @path + @path = File.join(base_path, filename_with_extensions(action)) + template filename_with_extensions(:view), @path end end end diff --git a/railties/lib/generators/erb/controller/templates/view.html.erb b/railties/lib/rails/generators/erb/controller/templates/view.html.erb similarity index 100% rename from railties/lib/generators/erb/controller/templates/view.html.erb rename to railties/lib/rails/generators/erb/controller/templates/view.html.erb diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb new file mode 100644 index 0000000000..943d0c9f8d --- /dev/null +++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb @@ -0,0 +1,13 @@ +require 'rails/generators/erb/controller/controller_generator' + +module Erb + module Generators + class MailerGenerator < ControllerGenerator + protected + + def format + :text + end + end + end +end diff --git a/railties/lib/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb similarity index 100% rename from railties/lib/generators/erb/mailer/templates/view.text.erb rename to railties/lib/rails/generators/erb/mailer/templates/view.text.erb diff --git a/railties/lib/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb similarity index 50% rename from railties/lib/generators/erb/scaffold/scaffold_generator.rb rename to railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb index 846540476f..f607e580a5 100644 --- a/railties/lib/generators/erb/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb @@ -1,4 +1,4 @@ -require 'generators/erb' +require 'rails/generators/erb' require 'rails/generators/resource_helpers' module Erb @@ -15,39 +15,27 @@ def create_root_folder empty_directory File.join("app/views", controller_file_path) end - def copy_index_file - return if options[:singleton] - copy_view :index - end + def copy_view_files + views = available_views + views.delete("index") if options[:singleton] - def copy_edit_file - copy_view :edit - end - - def copy_show_file - copy_view :show - end - - def copy_new_file - copy_view :new - end - - def copy_form_file - copy_view :_form + views.each do |view| + filename = filename_with_extensions(view) + template filename, File.join("app/views", controller_file_path, filename) + end end def copy_layout_file return unless options[:layout] - template "layout.html.erb", - File.join("app/views/layouts", controller_class_path, "#{controller_file_name}.html.erb") + template filename_with_extensions(:layout), + File.join("app/views/layouts", controller_class_path, filename_with_extensions(controller_file_name)) end - protected - - def copy_view(view) - template "#{view}.html.erb", File.join("app/views", controller_file_path, "#{view}.html.erb") - end + protected + def available_views + %w(index edit show new _form) + end end end end diff --git a/railties/lib/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb similarity index 100% rename from railties/lib/generators/erb/scaffold/templates/_form.html.erb rename to railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb diff --git a/railties/lib/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb similarity index 100% rename from railties/lib/generators/erb/scaffold/templates/edit.html.erb rename to railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb diff --git a/railties/lib/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb similarity index 89% rename from railties/lib/generators/erb/scaffold/templates/index.html.erb rename to railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index b5c7fd1e58..d30d306d42 100644 --- a/railties/lib/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -24,4 +24,4 @@
    -<%%= link_to 'New <%= singular_name %>', new_<%= singular_name %>_path %> +<%%= link_to 'New <%= human_name %>', new_<%= singular_name %>_path %> diff --git a/railties/lib/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb similarity index 100% rename from railties/lib/generators/erb/scaffold/templates/layout.html.erb rename to railties/lib/rails/generators/erb/scaffold/templates/layout.html.erb diff --git a/railties/lib/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb similarity index 100% rename from railties/lib/generators/erb/scaffold/templates/new.html.erb rename to railties/lib/rails/generators/erb/scaffold/templates/new.html.erb diff --git a/railties/lib/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb similarity index 100% rename from railties/lib/generators/erb/scaffold/templates/show.html.erb rename to railties/lib/rails/generators/erb/scaffold/templates/show.html.erb diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 12e918731e..8d1dfbd947 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -27,6 +27,10 @@ def class_name @class_name ||= (class_path + [file_name]).map!{ |m| m.camelize }.join('::') end + def human_name + @human_name ||= singular_name.humanize + end + def plural_name @plural_name ||= singular_name.pluralize end diff --git a/railties/lib/generators/rails/app/USAGE b/railties/lib/rails/generators/rails/app/USAGE similarity index 100% rename from railties/lib/generators/rails/app/USAGE rename to railties/lib/rails/generators/rails/app/USAGE diff --git a/railties/lib/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb similarity index 95% rename from railties/lib/generators/rails/app/app_generator.rb rename to railties/lib/rails/generators/rails/app/app_generator.rb index 92e0d37436..fccae9190a 100644 --- a/railties/lib/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -5,7 +5,7 @@ module Rails::Generators # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. - RAILS_DEV_PATH = File.expand_path("../../../../..", File.dirname(__FILE__)) + RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) RESERVED_NAMES = %w[generate console server dbconsole application destroy benchmarker profiler @@ -178,7 +178,8 @@ def apply_rails_template end def bundle_if_dev_or_edge - run "bundle install" if dev_or_edge? + bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') + run "#{bundle_command} install" if dev_or_edge? end protected @@ -220,6 +221,8 @@ def valid_app_const? raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers." elsif RESERVED_NAMES.include?(app_name) raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words." + elsif Object.const_defined?(app_const_base) + raise Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name." end end diff --git a/railties/lib/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile similarity index 100% rename from railties/lib/generators/rails/app/templates/Gemfile rename to railties/lib/rails/generators/rails/app/templates/Gemfile diff --git a/railties/lib/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README similarity index 96% rename from railties/lib/generators/rails/app/templates/README rename to railties/lib/rails/generators/rails/app/templates/README index b175146797..ded8570c42 100644 --- a/railties/lib/generators/rails/app/templates/README +++ b/railties/lib/rails/generators/rails/app/templates/README @@ -36,7 +36,8 @@ link:files/vendor/rails/actionpack/README.html. == Web Servers -By default, Rails will try to use Mongrel if it's are installed when started with rails server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails +By default, Rails will try to use Mongrel if it's installed when started with rails server, otherwise +Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails with a variety of other web servers. Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is @@ -207,7 +208,7 @@ app/views/layouts app/helpers Holds view helpers that should be named like weblogs_helper.rb. These are generated - for you automatically when using rails generate for controllers. Helpers can be used to + for you automatically when using rails generate for controllers. Helpers can be used to wrap functionality for your views into methods. config @@ -234,7 +235,7 @@ script Helper scripts for automation and generation. test - Unit and functional tests along with fixtures. When using the rails generate scripts, template + Unit and functional tests along with fixtures. When using the rails generate command, template test files will be generated for you and placed in this directory. vendor diff --git a/railties/lib/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile similarity index 100% rename from railties/lib/generators/rails/app/templates/Rakefile rename to railties/lib/rails/generators/rails/app/templates/Rakefile diff --git a/railties/lib/generators/rails/app/templates/app/controllers/application_controller.rb b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/app/controllers/application_controller.rb rename to railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb diff --git a/railties/lib/generators/rails/app/templates/app/helpers/application_helper.rb b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/app/helpers/application_helper.rb rename to railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb diff --git a/railties/lib/generators/rails/app/templates/app/models/.empty_directory b/railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory similarity index 100% rename from railties/lib/generators/rails/app/templates/app/models/.empty_directory rename to railties/lib/rails/generators/rails/app/templates/app/models/.empty_directory diff --git a/railties/lib/generators/rails/app/templates/app/views/layouts/.empty_directory b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/.empty_directory similarity index 100% rename from railties/lib/generators/rails/app/templates/app/views/layouts/.empty_directory rename to railties/lib/rails/generators/rails/app/templates/app/views/layouts/.empty_directory diff --git a/railties/lib/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru similarity index 100% rename from railties/lib/generators/rails/app/templates/config.ru rename to railties/lib/rails/generators/rails/app/templates/config.ru diff --git a/railties/lib/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/config/application.rb rename to railties/lib/rails/generators/rails/app/templates/config/application.rb diff --git a/railties/lib/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/config/boot.rb rename to railties/lib/rails/generators/rails/app/templates/config/boot.rb diff --git a/railties/lib/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml similarity index 100% rename from railties/lib/generators/rails/app/templates/config/databases/frontbase.yml rename to railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml diff --git a/railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml similarity index 100% rename from railties/lib/generators/rails/app/templates/config/databases/ibm_db.yml rename to railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml diff --git a/railties/lib/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml similarity index 100% rename from railties/lib/generators/rails/app/templates/config/databases/mysql.yml rename to railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml diff --git a/railties/lib/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml similarity index 100% rename from railties/lib/generators/rails/app/templates/config/databases/oracle.yml rename to railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml diff --git a/railties/lib/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml similarity index 100% rename from railties/lib/generators/rails/app/templates/config/databases/postgresql.yml rename to railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml diff --git a/railties/lib/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml similarity index 100% rename from railties/lib/generators/rails/app/templates/config/databases/sqlite3.yml rename to railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml diff --git a/railties/lib/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/config/environment.rb rename to railties/lib/rails/generators/rails/app/templates/config/environment.rb diff --git a/railties/lib/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt similarity index 100% rename from railties/lib/generators/rails/app/templates/config/environments/development.rb.tt rename to railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt diff --git a/railties/lib/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt similarity index 76% rename from railties/lib/generators/rails/app/templates/config/environments/production.rb.tt rename to railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 917052c3df..f902120453 100644 --- a/railties/lib/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -9,6 +9,15 @@ config.consider_all_requests_local = false config.action_controller.perform_caching = true + # Specifies the header that your server uses for sending files + config.action_dispatch.x_sendfile_header = "X-Sendfile" + + # For nginx: + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' + + # If you have no front-end server that supports something like X-Sendfile, + # just comment this out and Rails will serve the files + # See everything in the log (default is :info) # config.log_level = :debug diff --git a/railties/lib/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt similarity index 100% rename from railties/lib/generators/rails/app/templates/config/environments/test.rb.tt rename to railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt diff --git a/railties/lib/generators/rails/app/templates/config/initializers/backtrace_silencers.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/config/initializers/backtrace_silencers.rb rename to railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb diff --git a/railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt similarity index 100% rename from railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt rename to railties/lib/rails/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt diff --git a/railties/lib/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/config/initializers/inflections.rb rename to railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb diff --git a/railties/lib/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/config/initializers/mime_types.rb rename to railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb diff --git a/railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt similarity index 100% rename from railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt rename to railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt diff --git a/railties/lib/generators/rails/app/templates/config/locales/en.yml b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml similarity index 100% rename from railties/lib/generators/rails/app/templates/config/locales/en.yml rename to railties/lib/rails/generators/rails/app/templates/config/locales/en.yml diff --git a/railties/lib/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/config/routes.rb rename to railties/lib/rails/generators/rails/app/templates/config/routes.rb diff --git a/railties/lib/generators/rails/app/templates/db/seeds.rb b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/db/seeds.rb rename to railties/lib/rails/generators/rails/app/templates/db/seeds.rb diff --git a/railties/lib/generators/rails/app/templates/doc/README_FOR_APP b/railties/lib/rails/generators/rails/app/templates/doc/README_FOR_APP similarity index 100% rename from railties/lib/generators/rails/app/templates/doc/README_FOR_APP rename to railties/lib/rails/generators/rails/app/templates/doc/README_FOR_APP diff --git a/railties/lib/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore similarity index 100% rename from railties/lib/generators/rails/app/templates/gitignore rename to railties/lib/rails/generators/rails/app/templates/gitignore diff --git a/railties/lib/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html similarity index 100% rename from railties/lib/generators/rails/app/templates/public/404.html rename to railties/lib/rails/generators/rails/app/templates/public/404.html diff --git a/railties/lib/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html similarity index 100% rename from railties/lib/generators/rails/app/templates/public/422.html rename to railties/lib/rails/generators/rails/app/templates/public/422.html diff --git a/railties/lib/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html similarity index 100% rename from railties/lib/generators/rails/app/templates/public/500.html rename to railties/lib/rails/generators/rails/app/templates/public/500.html diff --git a/railties/lib/generators/rails/app/templates/public/favicon.ico b/railties/lib/rails/generators/rails/app/templates/public/favicon.ico similarity index 100% rename from railties/lib/generators/rails/app/templates/public/favicon.ico rename to railties/lib/rails/generators/rails/app/templates/public/favicon.ico diff --git a/railties/lib/generators/rails/app/templates/public/images/rails.png b/railties/lib/rails/generators/rails/app/templates/public/images/rails.png similarity index 100% rename from railties/lib/generators/rails/app/templates/public/images/rails.png rename to railties/lib/rails/generators/rails/app/templates/public/images/rails.png diff --git a/railties/lib/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html similarity index 100% rename from railties/lib/generators/rails/app/templates/public/index.html rename to railties/lib/rails/generators/rails/app/templates/public/index.html diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/application.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js similarity index 100% rename from railties/lib/generators/rails/app/templates/public/javascripts/application.js rename to railties/lib/rails/generators/rails/app/templates/public/javascripts/application.js diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/controls.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js similarity index 100% rename from railties/lib/generators/rails/app/templates/public/javascripts/controls.js rename to railties/lib/rails/generators/rails/app/templates/public/javascripts/controls.js diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js similarity index 100% rename from railties/lib/generators/rails/app/templates/public/javascripts/dragdrop.js rename to railties/lib/rails/generators/rails/app/templates/public/javascripts/dragdrop.js diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/effects.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js similarity index 100% rename from railties/lib/generators/rails/app/templates/public/javascripts/effects.js rename to railties/lib/rails/generators/rails/app/templates/public/javascripts/effects.js diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js similarity index 100% rename from railties/lib/generators/rails/app/templates/public/javascripts/prototype.js rename to railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js diff --git a/railties/lib/generators/rails/app/templates/public/javascripts/rails.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js similarity index 96% rename from railties/lib/generators/rails/app/templates/public/javascripts/rails.js rename to railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js index f7ddba390a..7342e1b830 100644 --- a/railties/lib/generators/rails/app/templates/public/javascripts/rails.js +++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js @@ -15,8 +15,7 @@ document.observe("dom:loaded", function() { params = element.serialize(true); } else { method = element.readAttribute('data-method') || 'get'; - // TODO: data-url support is going away, just use href - url = element.readAttribute('data-url') || element.readAttribute('href'); + url = element.readAttribute('href'); params = {}; } diff --git a/railties/lib/generators/rails/app/templates/public/robots.txt b/railties/lib/rails/generators/rails/app/templates/public/robots.txt similarity index 100% rename from railties/lib/generators/rails/app/templates/public/robots.txt rename to railties/lib/rails/generators/rails/app/templates/public/robots.txt diff --git a/railties/lib/generators/rails/app/templates/public/stylesheets/.empty_directory b/railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory similarity index 100% rename from railties/lib/generators/rails/app/templates/public/stylesheets/.empty_directory rename to railties/lib/rails/generators/rails/app/templates/public/stylesheets/.empty_directory diff --git a/railties/lib/generators/rails/app/templates/script/rails b/railties/lib/rails/generators/rails/app/templates/script/rails similarity index 100% rename from railties/lib/generators/rails/app/templates/script/rails rename to railties/lib/rails/generators/rails/app/templates/script/rails diff --git a/railties/lib/generators/rails/app/templates/test/fixtures/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory similarity index 100% rename from railties/lib/generators/rails/app/templates/test/fixtures/.empty_directory rename to railties/lib/rails/generators/rails/app/templates/test/fixtures/.empty_directory diff --git a/railties/lib/generators/rails/app/templates/test/functional/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory similarity index 100% rename from railties/lib/generators/rails/app/templates/test/functional/.empty_directory rename to railties/lib/rails/generators/rails/app/templates/test/functional/.empty_directory diff --git a/railties/lib/generators/rails/app/templates/test/integration/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory similarity index 100% rename from railties/lib/generators/rails/app/templates/test/integration/.empty_directory rename to railties/lib/rails/generators/rails/app/templates/test/integration/.empty_directory diff --git a/railties/lib/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/test/performance/browsing_test.rb rename to railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb diff --git a/railties/lib/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb similarity index 100% rename from railties/lib/generators/rails/app/templates/test/test_helper.rb rename to railties/lib/rails/generators/rails/app/templates/test/test_helper.rb diff --git a/railties/lib/generators/rails/app/templates/test/unit/.empty_directory b/railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory similarity index 100% rename from railties/lib/generators/rails/app/templates/test/unit/.empty_directory rename to railties/lib/rails/generators/rails/app/templates/test/unit/.empty_directory diff --git a/railties/lib/generators/rails/controller/USAGE b/railties/lib/rails/generators/rails/controller/USAGE similarity index 100% rename from railties/lib/generators/rails/controller/USAGE rename to railties/lib/rails/generators/rails/controller/USAGE diff --git a/railties/lib/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb similarity index 76% rename from railties/lib/generators/rails/controller/controller_generator.rb rename to railties/lib/rails/generators/rails/controller/controller_generator.rb index 91470be833..9788c0d0bc 100644 --- a/railties/lib/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -8,6 +8,12 @@ def create_controller_files template 'controller.rb', File.join('app/controllers', class_path, "#{file_name}_controller.rb") end + def add_routes + actions.reverse.each do |action| + route %{get "#{file_name}/#{action}"} + end + end + hook_for :template_engine, :test_framework, :helper end end diff --git a/railties/lib/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb similarity index 100% rename from railties/lib/generators/rails/controller/templates/controller.rb rename to railties/lib/rails/generators/rails/controller/templates/controller.rb diff --git a/railties/lib/generators/rails/generator/USAGE b/railties/lib/rails/generators/rails/generator/USAGE similarity index 100% rename from railties/lib/generators/rails/generator/USAGE rename to railties/lib/rails/generators/rails/generator/USAGE diff --git a/railties/lib/generators/rails/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb similarity index 100% rename from railties/lib/generators/rails/generator/generator_generator.rb rename to railties/lib/rails/generators/rails/generator/generator_generator.rb diff --git a/railties/lib/generators/rails/generator/templates/%file_name%_generator.rb.tt b/railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt similarity index 100% rename from railties/lib/generators/rails/generator/templates/%file_name%_generator.rb.tt rename to railties/lib/rails/generators/rails/generator/templates/%file_name%_generator.rb.tt diff --git a/railties/lib/generators/rails/generator/templates/USAGE.tt b/railties/lib/rails/generators/rails/generator/templates/USAGE.tt similarity index 100% rename from railties/lib/generators/rails/generator/templates/USAGE.tt rename to railties/lib/rails/generators/rails/generator/templates/USAGE.tt diff --git a/railties/lib/generators/rails/generator/templates/templates/.empty_directory b/railties/lib/rails/generators/rails/generator/templates/templates/.empty_directory similarity index 100% rename from railties/lib/generators/rails/generator/templates/templates/.empty_directory rename to railties/lib/rails/generators/rails/generator/templates/templates/.empty_directory diff --git a/railties/lib/generators/rails/helper/USAGE b/railties/lib/rails/generators/rails/helper/USAGE similarity index 100% rename from railties/lib/generators/rails/helper/USAGE rename to railties/lib/rails/generators/rails/helper/USAGE diff --git a/railties/lib/generators/rails/helper/helper_generator.rb b/railties/lib/rails/generators/rails/helper/helper_generator.rb similarity index 100% rename from railties/lib/generators/rails/helper/helper_generator.rb rename to railties/lib/rails/generators/rails/helper/helper_generator.rb diff --git a/railties/lib/generators/rails/helper/templates/helper.rb b/railties/lib/rails/generators/rails/helper/templates/helper.rb similarity index 100% rename from railties/lib/generators/rails/helper/templates/helper.rb rename to railties/lib/rails/generators/rails/helper/templates/helper.rb diff --git a/railties/lib/generators/rails/integration_test/USAGE b/railties/lib/rails/generators/rails/integration_test/USAGE similarity index 100% rename from railties/lib/generators/rails/integration_test/USAGE rename to railties/lib/rails/generators/rails/integration_test/USAGE diff --git a/railties/lib/generators/rails/integration_test/integration_test_generator.rb b/railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb similarity index 100% rename from railties/lib/generators/rails/integration_test/integration_test_generator.rb rename to railties/lib/rails/generators/rails/integration_test/integration_test_generator.rb diff --git a/railties/lib/generators/rails/mailer/USAGE b/railties/lib/rails/generators/rails/mailer/USAGE similarity index 100% rename from railties/lib/generators/rails/mailer/USAGE rename to railties/lib/rails/generators/rails/mailer/USAGE diff --git a/railties/lib/generators/rails/mailer/mailer_generator.rb b/railties/lib/rails/generators/rails/mailer/mailer_generator.rb similarity index 100% rename from railties/lib/generators/rails/mailer/mailer_generator.rb rename to railties/lib/rails/generators/rails/mailer/mailer_generator.rb diff --git a/railties/lib/generators/rails/mailer/templates/mailer.rb b/railties/lib/rails/generators/rails/mailer/templates/mailer.rb similarity index 100% rename from railties/lib/generators/rails/mailer/templates/mailer.rb rename to railties/lib/rails/generators/rails/mailer/templates/mailer.rb diff --git a/railties/lib/generators/rails/metal/USAGE b/railties/lib/rails/generators/rails/metal/USAGE similarity index 100% rename from railties/lib/generators/rails/metal/USAGE rename to railties/lib/rails/generators/rails/metal/USAGE diff --git a/railties/lib/generators/rails/metal/metal_generator.rb b/railties/lib/rails/generators/rails/metal/metal_generator.rb similarity index 100% rename from railties/lib/generators/rails/metal/metal_generator.rb rename to railties/lib/rails/generators/rails/metal/metal_generator.rb diff --git a/railties/lib/generators/rails/metal/templates/metal.rb b/railties/lib/rails/generators/rails/metal/templates/metal.rb similarity index 100% rename from railties/lib/generators/rails/metal/templates/metal.rb rename to railties/lib/rails/generators/rails/metal/templates/metal.rb diff --git a/railties/lib/generators/rails/migration/USAGE b/railties/lib/rails/generators/rails/migration/USAGE similarity index 100% rename from railties/lib/generators/rails/migration/USAGE rename to railties/lib/rails/generators/rails/migration/USAGE diff --git a/railties/lib/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb similarity index 100% rename from railties/lib/generators/rails/migration/migration_generator.rb rename to railties/lib/rails/generators/rails/migration/migration_generator.rb diff --git a/railties/lib/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE similarity index 100% rename from railties/lib/generators/rails/model/USAGE rename to railties/lib/rails/generators/rails/model/USAGE diff --git a/railties/lib/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb similarity index 100% rename from railties/lib/generators/rails/model/model_generator.rb rename to railties/lib/rails/generators/rails/model/model_generator.rb diff --git a/railties/lib/generators/rails/model_subclass/model_subclass_generator.rb b/railties/lib/rails/generators/rails/model_subclass/model_subclass_generator.rb similarity index 100% rename from railties/lib/generators/rails/model_subclass/model_subclass_generator.rb rename to railties/lib/rails/generators/rails/model_subclass/model_subclass_generator.rb diff --git a/railties/lib/generators/rails/observer/USAGE b/railties/lib/rails/generators/rails/observer/USAGE similarity index 100% rename from railties/lib/generators/rails/observer/USAGE rename to railties/lib/rails/generators/rails/observer/USAGE diff --git a/railties/lib/generators/rails/observer/observer_generator.rb b/railties/lib/rails/generators/rails/observer/observer_generator.rb similarity index 100% rename from railties/lib/generators/rails/observer/observer_generator.rb rename to railties/lib/rails/generators/rails/observer/observer_generator.rb diff --git a/railties/lib/generators/rails/performance_test/USAGE b/railties/lib/rails/generators/rails/performance_test/USAGE similarity index 100% rename from railties/lib/generators/rails/performance_test/USAGE rename to railties/lib/rails/generators/rails/performance_test/USAGE diff --git a/railties/lib/generators/rails/performance_test/performance_test_generator.rb b/railties/lib/rails/generators/rails/performance_test/performance_test_generator.rb similarity index 100% rename from railties/lib/generators/rails/performance_test/performance_test_generator.rb rename to railties/lib/rails/generators/rails/performance_test/performance_test_generator.rb diff --git a/railties/lib/generators/rails/plugin/USAGE b/railties/lib/rails/generators/rails/plugin/USAGE similarity index 100% rename from railties/lib/generators/rails/plugin/USAGE rename to railties/lib/rails/generators/rails/plugin/USAGE diff --git a/railties/lib/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb similarity index 94% rename from railties/lib/generators/rails/plugin/plugin_generator.rb rename to railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 8f01dcd589..40ed2062d3 100644 --- a/railties/lib/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -1,4 +1,4 @@ -require 'generators/rails/generator/generator_generator' +require 'rails/generators/rails/generator/generator_generator' module Rails module Generators diff --git a/railties/lib/generators/rails/plugin/templates/MIT-LICENSE.tt b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt similarity index 100% rename from railties/lib/generators/rails/plugin/templates/MIT-LICENSE.tt rename to railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt diff --git a/railties/lib/generators/rails/plugin/templates/README.tt b/railties/lib/rails/generators/rails/plugin/templates/README.tt similarity index 100% rename from railties/lib/generators/rails/plugin/templates/README.tt rename to railties/lib/rails/generators/rails/plugin/templates/README.tt diff --git a/railties/lib/generators/rails/plugin/templates/Rakefile.tt b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt similarity index 100% rename from railties/lib/generators/rails/plugin/templates/Rakefile.tt rename to railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt diff --git a/railties/lib/generators/rails/plugin/templates/init.rb b/railties/lib/rails/generators/rails/plugin/templates/init.rb similarity index 100% rename from railties/lib/generators/rails/plugin/templates/init.rb rename to railties/lib/rails/generators/rails/plugin/templates/init.rb diff --git a/railties/lib/generators/rails/plugin/templates/install.rb b/railties/lib/rails/generators/rails/plugin/templates/install.rb similarity index 100% rename from railties/lib/generators/rails/plugin/templates/install.rb rename to railties/lib/rails/generators/rails/plugin/templates/install.rb diff --git a/railties/lib/generators/rails/plugin/templates/lib/%file_name%.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/lib/%file_name%.rb.tt similarity index 100% rename from railties/lib/generators/rails/plugin/templates/lib/%file_name%.rb.tt rename to railties/lib/rails/generators/rails/plugin/templates/lib/%file_name%.rb.tt diff --git a/railties/lib/generators/rails/plugin/templates/lib/tasks/%file_name%_tasks.rake.tt b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%file_name%_tasks.rake.tt similarity index 100% rename from railties/lib/generators/rails/plugin/templates/lib/tasks/%file_name%_tasks.rake.tt rename to railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%file_name%_tasks.rake.tt diff --git a/railties/lib/generators/rails/plugin/templates/uninstall.rb b/railties/lib/rails/generators/rails/plugin/templates/uninstall.rb similarity index 100% rename from railties/lib/generators/rails/plugin/templates/uninstall.rb rename to railties/lib/rails/generators/rails/plugin/templates/uninstall.rb diff --git a/railties/lib/generators/rails/resource/USAGE b/railties/lib/rails/generators/rails/resource/USAGE similarity index 100% rename from railties/lib/generators/rails/resource/USAGE rename to railties/lib/rails/generators/rails/resource/USAGE diff --git a/railties/lib/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb similarity index 89% rename from railties/lib/generators/rails/resource/resource_generator.rb rename to railties/lib/rails/generators/rails/resource/resource_generator.rb index 5acb839f39..1e78945a7e 100644 --- a/railties/lib/generators/rails/resource/resource_generator.rb +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -1,5 +1,5 @@ require 'rails/generators/resource_helpers' -require 'generators/rails/model/model_generator' +require 'rails/generators/rails/model/model_generator' module Rails module Generators @@ -16,6 +16,7 @@ class ResourceGenerator < ModelGenerator #metagenerator class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller" def add_resource_route + return if options[:actions].present? route "resource#{:s unless options[:singleton]} :#{pluralize?(file_name)}" end diff --git a/railties/lib/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE similarity index 100% rename from railties/lib/generators/rails/scaffold/USAGE rename to railties/lib/rails/generators/rails/scaffold/USAGE diff --git a/railties/lib/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb similarity index 81% rename from railties/lib/generators/rails/scaffold/scaffold_generator.rb rename to railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index fdea5bf52b..779f933785 100644 --- a/railties/lib/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -1,4 +1,4 @@ -require 'generators/rails/resource/resource_generator' +require 'rails/generators/rails/resource/resource_generator' module Rails module Generators diff --git a/railties/lib/generators/rails/scaffold_controller/USAGE b/railties/lib/rails/generators/rails/scaffold_controller/USAGE similarity index 100% rename from railties/lib/generators/rails/scaffold_controller/USAGE rename to railties/lib/rails/generators/rails/scaffold_controller/USAGE diff --git a/railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb similarity index 100% rename from railties/lib/generators/rails/scaffold_controller/scaffold_controller_generator.rb rename to railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb diff --git a/railties/lib/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb similarity index 96% rename from railties/lib/generators/rails/scaffold_controller/templates/controller.rb rename to railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 874e96a2b4..bbdce669dc 100644 --- a/railties/lib/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -46,7 +46,7 @@ def create respond_to do |format| if @<%= orm_instance.save %> - format.html { redirect_to(@<%= file_name %>, :notice => '<%= class_name %> was successfully created.') } + format.html { redirect_to(@<%= file_name %>, :notice => '<%= human_name %> was successfully created.') } format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> } else format.html { render :action => "new" } @@ -62,7 +62,7 @@ def update respond_to do |format| if @<%= orm_instance.update_attributes("params[:#{file_name}]") %> - format.html { redirect_to(@<%= file_name %>, :notice => '<%= class_name %> was successfully updated.') } + format.html { redirect_to(@<%= file_name %>, :notice => '<%= human_name %> was successfully updated.') } format.xml { head :ok } else format.html { render :action => "edit" } diff --git a/railties/lib/generators/rails/session_migration/USAGE b/railties/lib/rails/generators/rails/session_migration/USAGE similarity index 100% rename from railties/lib/generators/rails/session_migration/USAGE rename to railties/lib/rails/generators/rails/session_migration/USAGE diff --git a/railties/lib/generators/rails/session_migration/session_migration_generator.rb b/railties/lib/rails/generators/rails/session_migration/session_migration_generator.rb similarity index 100% rename from railties/lib/generators/rails/session_migration/session_migration_generator.rb rename to railties/lib/rails/generators/rails/session_migration/session_migration_generator.rb diff --git a/railties/lib/generators/rails/stylesheets/USAGE b/railties/lib/rails/generators/rails/stylesheets/USAGE similarity index 100% rename from railties/lib/generators/rails/stylesheets/USAGE rename to railties/lib/rails/generators/rails/stylesheets/USAGE diff --git a/railties/lib/generators/rails/stylesheets/stylesheets_generator.rb b/railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb similarity index 100% rename from railties/lib/generators/rails/stylesheets/stylesheets_generator.rb rename to railties/lib/rails/generators/rails/stylesheets/stylesheets_generator.rb diff --git a/railties/lib/generators/rails/stylesheets/templates/scaffold.css b/railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css similarity index 100% rename from railties/lib/generators/rails/stylesheets/templates/scaffold.css rename to railties/lib/rails/generators/rails/stylesheets/templates/scaffold.css diff --git a/railties/lib/generators/test_unit.rb b/railties/lib/rails/generators/test_unit.rb similarity index 100% rename from railties/lib/generators/test_unit.rb rename to railties/lib/rails/generators/test_unit.rb diff --git a/railties/lib/generators/test_unit/controller/controller_generator.rb b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb similarity index 72% rename from railties/lib/generators/test_unit/controller/controller_generator.rb rename to railties/lib/rails/generators/test_unit/controller/controller_generator.rb index b57a6e794f..20f3bd8965 100644 --- a/railties/lib/generators/test_unit/controller/controller_generator.rb +++ b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb @@ -1,8 +1,9 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators class ControllerGenerator < Base + argument :actions, :type => :array, :default => [], :banner => "action action" check_class_collision :suffix => "ControllerTest" def create_test_files diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb new file mode 100644 index 0000000000..0d4185846d --- /dev/null +++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +class <%= class_name %>ControllerTest < ActionController::TestCase +<% if actions.empty? -%> + # Replace this with your real tests. + test "the truth" do + assert true + end +<% else -%> +<% for action in actions -%> + test "should get <%= action %>" do + get :<%= action %> + assert_response :success + end + +<% end -%> +<% end -%> +end diff --git a/railties/lib/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb similarity index 88% rename from railties/lib/generators/test_unit/helper/helper_generator.rb rename to railties/lib/rails/generators/test_unit/helper/helper_generator.rb index 9ecfaa45ab..4ea80bf7be 100644 --- a/railties/lib/generators/test_unit/helper/helper_generator.rb +++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/generators/test_unit/helper/templates/helper_test.rb b/railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb similarity index 100% rename from railties/lib/generators/test_unit/helper/templates/helper_test.rb rename to railties/lib/rails/generators/test_unit/helper/templates/helper_test.rb diff --git a/railties/lib/generators/test_unit/integration/integration_generator.rb b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb similarity index 88% rename from railties/lib/generators/test_unit/integration/integration_generator.rb rename to railties/lib/rails/generators/test_unit/integration/integration_generator.rb index d9d9b3bf1d..32d0fac029 100644 --- a/railties/lib/generators/test_unit/integration/integration_generator.rb +++ b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb similarity index 100% rename from railties/lib/generators/test_unit/integration/templates/integration_test.rb rename to railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb diff --git a/railties/lib/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb similarity index 59% rename from railties/lib/generators/test_unit/mailer/mailer_generator.rb rename to railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb index a0d73db1b0..1a49286d41 100644 --- a/railties/lib/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators @@ -9,13 +9,6 @@ class MailerGenerator < Base def create_test_files template "functional_test.rb", File.join('test/functional', class_path, "#{file_name}_test.rb") end - - def create_fixtures_files - actions.each do |action| - @action, @path = action, File.join(file_path, action) - template "fixture", File.join("test/fixtures", @path) - end - end end end end diff --git a/railties/lib/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb similarity index 51% rename from railties/lib/generators/test_unit/mailer/templates/functional_test.rb rename to railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb index a2b1f1ed05..80ac7f0feb 100644 --- a/railties/lib/generators/test_unit/mailer/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb @@ -3,12 +3,11 @@ class <%= class_name %>Test < ActionMailer::TestCase <% for action in actions -%> test "<%= action %>" do - @expected.subject = <%= action.to_s.humanize.inspect %> - @expected.to = "to@example.org" - @expected.from = "from@example.com" - @expected.body = read_fixture("<%= action %>") - - assert_equal @expected, <%= class_name %>.<%= action %> + mail = <%= class_name %>.<%= action %> + assert_equal <%= action.to_s.humanize.inspect %>, mail.subject + assert_equal ["to@example.org"], mail.to + assert_equal ["from@example.com"], mail.from + assert_match "Hi", mail.body.encoded end <% end -%> diff --git a/railties/lib/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb similarity index 94% rename from railties/lib/generators/test_unit/model/model_generator.rb rename to railties/lib/rails/generators/test_unit/model/model_generator.rb index 469306e6c5..609b815683 100644 --- a/railties/lib/generators/test_unit/model/model_generator.rb +++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml similarity index 100% rename from railties/lib/generators/test_unit/model/templates/fixtures.yml rename to railties/lib/rails/generators/test_unit/model/templates/fixtures.yml diff --git a/railties/lib/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb similarity index 100% rename from railties/lib/generators/test_unit/model/templates/unit_test.rb rename to railties/lib/rails/generators/test_unit/model/templates/unit_test.rb diff --git a/railties/lib/generators/test_unit/observer/observer_generator.rb b/railties/lib/rails/generators/test_unit/observer/observer_generator.rb similarity index 88% rename from railties/lib/generators/test_unit/observer/observer_generator.rb rename to railties/lib/rails/generators/test_unit/observer/observer_generator.rb index 14181f4e49..6cc1158c21 100644 --- a/railties/lib/generators/test_unit/observer/observer_generator.rb +++ b/railties/lib/rails/generators/test_unit/observer/observer_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/generators/test_unit/observer/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb similarity index 100% rename from railties/lib/generators/test_unit/observer/templates/unit_test.rb rename to railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb diff --git a/railties/lib/generators/test_unit/performance/performance_generator.rb b/railties/lib/rails/generators/test_unit/performance/performance_generator.rb similarity index 88% rename from railties/lib/generators/test_unit/performance/performance_generator.rb rename to railties/lib/rails/generators/test_unit/performance/performance_generator.rb index 0d9c646b26..99edda5461 100644 --- a/railties/lib/generators/test_unit/performance/performance_generator.rb +++ b/railties/lib/rails/generators/test_unit/performance/performance_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/generators/test_unit/performance/templates/performance_test.rb b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb similarity index 100% rename from railties/lib/generators/test_unit/performance/templates/performance_test.rb rename to railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb diff --git a/railties/lib/generators/test_unit/plugin/plugin_generator.rb b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb similarity index 84% rename from railties/lib/generators/test_unit/plugin/plugin_generator.rb rename to railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb index 05adf58c4f..4d65cd7d89 100644 --- a/railties/lib/generators/test_unit/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' module TestUnit module Generators diff --git a/railties/lib/generators/test_unit/plugin/templates/%file_name%_test.rb.tt b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt similarity index 100% rename from railties/lib/generators/test_unit/plugin/templates/%file_name%_test.rb.tt rename to railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt diff --git a/railties/lib/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb similarity index 100% rename from railties/lib/generators/test_unit/plugin/templates/test_helper.rb rename to railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb diff --git a/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb similarity index 93% rename from railties/lib/generators/test_unit/scaffold/scaffold_generator.rb rename to railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index a95916ae13..c0315c7fe6 100644 --- a/railties/lib/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -1,4 +1,4 @@ -require 'generators/test_unit' +require 'rails/generators/test_unit' require 'rails/generators/resource_helpers' module TestUnit diff --git a/railties/lib/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb similarity index 71% rename from railties/lib/generators/test_unit/scaffold/templates/functional_test.rb rename to railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index 9380aa49b6..4f8ddbffcf 100644 --- a/railties/lib/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -1,6 +1,10 @@ require 'test_helper' class <%= controller_class_name %>ControllerTest < ActionController::TestCase + setup do + @<%= file_name %> = <%= table_name %>(:one) + end + <% unless options[:singleton] -%> test "should get index" do get :index @@ -16,30 +20,30 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase test "should create <%= file_name %>" do assert_difference('<%= class_name %>.count') do - post :create, :<%= file_name %> => <%= table_name %>(:one).attributes + post :create, :<%= file_name %> => @<%= file_name %>.attributes end assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) end test "should show <%= file_name %>" do - get :show, :id => <%= table_name %>(:one).to_param + get :show, :id => @<%= file_name %>.to_param assert_response :success end test "should get edit" do - get :edit, :id => <%= table_name %>(:one).to_param + get :edit, :id => @<%= file_name %>.to_param assert_response :success end test "should update <%= file_name %>" do - put :update, :id => <%= table_name %>(:one).to_param, :<%= file_name %> => <%= table_name %>(:one).attributes + put :update, :id => @<%= file_name %>.to_param, :<%= file_name %> => @<%= file_name %>.attributes assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) end test "should destroy <%= file_name %>" do assert_difference('<%= class_name %>.count', -1) do - delete :destroy, :id => <%= table_name %>(:one).to_param + delete :destroy, :id => @<%= file_name %>.to_param end assert_redirected_to <%= table_name %>_path diff --git a/railties/builtin/rails_info/rails/info.rb b/railties/lib/rails/info.rb similarity index 100% rename from railties/builtin/rails_info/rails/info.rb rename to railties/lib/rails/info.rb diff --git a/railties/builtin/rails_info/rails/info_controller.rb b/railties/lib/rails/info_controller.rb similarity index 100% rename from railties/builtin/rails_info/rails/info_controller.rb rename to railties/lib/rails/info_controller.rb diff --git a/railties/builtin/routes.rb b/railties/lib/rails/info_routes.rb similarity index 100% rename from railties/builtin/routes.rb rename to railties/lib/rails/info_routes.rb diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 98f329cc17..0997be1b6f 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -2,6 +2,21 @@ require 'active_support/core_ext/array/conversions' module Rails + # Rails::Plugin is nothing more than a Rails::Engine, but since it's loaded too late + # in the boot process, it does not have the same configuration powers as a bare + # Rails::Engine. + # + # Opposite to Rails::Railtie and Rails::Engine, you are not supposed to inherit from + # Rails::Plugin. Rails::Plugin is automatically configured to be an engine by simply + # placing inside vendor/plugins. Since this is done automatically, you actually cannot + # declare a Rails::Engine inside your Plugin, otherwise it would cause the same files + # to be loaded twice. This means that if you want to ship an Engine as gem it cannot + # be used as plugin and vice-versa. + # + # Besides this conceptual difference, the only difference between Rails::Engine and + # Rails::Plugin is that plugins automatically load the file "init.rb" at the plugin + # root during the boot process. + # class Plugin < Engine def self.inherited(base) raise "You cannot inherit from Rails::Plugin" diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 37c802fb60..0d68abb323 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -3,13 +3,183 @@ require 'active_support/inflector' module Rails + # Railtie is the core of the Rails Framework and provides several hooks to extend + # Rails and/or modify the initialization process. + # + # Every major component of Rails (Action Mailer, Action Controller, + # Action View, Active Record and Active Resource) are all Railties, so each of + # them is responsible to set their own initialization. This makes, for example, + # Rails absent of any ActiveRecord hook, allowing any other ORM framework to hook in. + # + # Developing a Rails extension does _not_ require any implementation of + # Railtie, but if you need to interact with the Rails framework during + # or after boot, then Railtie is what you need to do that interaction. + # + # For example, the following would need you to implement Railtie in your + # plugin: + # + # * creating initializers + # * configuring a Rails framework or the Application, like setting a generator + # * adding Rails config.* keys to the environment + # * setting up a subscriber to the Rails +ActiveSupport::Notifications+ + # * adding rake tasks into rails + # + # == Creating your Railtie + # + # Implementing Railtie in your Rails extension is done by creating a class + # Railtie that has your extension name and making sure that this gets loaded + # during boot time of the Rails stack. + # + # You can do this however you wish, but here is an example if you want to provide + # it for a gem that can be used with or without Rails: + # + # * Create a file (say, lib/my_gem/railtie.rb) which contains class Railtie inheriting from + # Rails::Railtie and is namespaced to your gem: + # + # # lib/my_gem/railtie.rb + # module MyGem + # class Railtie < Rails::Railtie + # railtie_name :mygem + # end + # end + # + # * Require your own gem as well as rails in this file: + # + # # lib/my_gem/railtie.rb + # require 'my_gem' + # require 'rails' + # + # module MyGem + # class Railtie < Rails::Railtie + # railtie_name :mygem + # end + # end + # + # * Make sure your Gem loads the railtie.rb file if Rails is loaded first, an easy + # way to check is by checking for the Rails constant which will exist if Rails + # has started: + # + # # lib/my_gem.rb + # module MyGem + # require 'lib/my_gem/railtie' if defined?(Rails) + # end + # + # * Or instead of doing the require automatically, you can ask your users to require + # it for you in their Gemfile: + # + # # #{USER_RAILS_ROOT}/Gemfile + # gem "my_gem", :require_as => ["my_gem", "my_gem/railtie"] + # + # == Initializers + # + # To add an initialization step from your Railtie to Rails boot process, you just need + # to create an initializer block: + # + # class MyRailtie < Rails::Railtie + # initializer "my_railtie.configure_rails_initialization" do + # # some initialization behavior + # end + # end + # + # If specified, the block can also receive the application object, in case you + # need to access some application specific configuration: + # + # class MyRailtie < Rails::Railtie + # initializer "my_railtie.configure_rails_initialization" do |app| + # if app.config.cache_classes + # # some initialization behavior + # end + # end + # end + # + # Finally, you can also pass :before and :after as option to initializer, in case + # you want to couple it with a specific step in the initialization process. + # + # == Configuration + # + # Inside the Railtie class, you can access a config object which contains configuration + # shared by all railties and the application: + # + # class MyRailtie < Rails::Railtie + # # Customize the ORM + # config.generators.orm :my_railtie_orm + # + # # Add a middleware + # config.middlewares.use MyRailtie::Middleware + # + # # Add a to_prepare block which is executed once in production + # # and before which request in development + # config.to_prepare do + # MyRailtie.setup! + # end + # end + # + # == Loading rake tasks and generators + # + # If your railtie has rake tasks, you can tell Rails to load them through the method + # rake tasks: + # + # class MyRailtie < Railtie + # rake_tasks do + # load "path/to/my_railtie.tasks" + # end + # end + # + # By default, Rails load generators from your load path. However, if you want to place + # your generators at a different location, you can specify in your Railtie a block which + # will load them during normal generators lookup: + # + # class MyRailtie < Railtie + # generators do + # require "path/to/my_railtie_generator" + # end + # end + # + # == Adding your subscriber + # + # Since version 3.0, Rails ships with a notification system which is used for several + # purposes, including logging. If you are sending notifications in your Railtie, you may + # want to add a subscriber to consume such notifications for logging purposes. + # + # The subscriber is added under the railtie_name namespace and only consumes notifications + # under the given namespace. For example, let's suppose your railtie is publishing the + # following "something_expensive" instrumentation: + # + # ActiveSupport::Notifications.instrument "my_railtie.something_expensive" do + # # something expensive + # end + # + # You can log this instrumentation with your own Rails::Subscriber: + # + # class MyRailtie::Subscriber < Rails::Subscriber + # def something_expensive(event) + # info("Something expensive took %.1fms" % event.duration) + # end + # end + # + # By registering it: + # + # class MyRailtie < Railtie + # subscriber MyRailtie::Subscriber.new + # end + # + # Take a look in Rails::Subscriber docs for more information. + # + # == Application, Plugin and Engine + # + # A Rails::Engine is nothing more than a Railtie with some initializers already set. + # And since Rails::Application and Rails::Plugin are engines, the same configuration + # described here can be used in all three. + # + # Be sure to look at the documentation of those specific classes for more information. + # class Railtie autoload :Configurable, "rails/railtie/configurable" autoload :Configuration, "rails/railtie/configuration" include Initializable - ABSTRACT_RAILTIES = %w(Rails::Plugin Rails::Engine Rails::Application) + ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Plugin Rails::Engine Rails::Application) class << self def subclasses @@ -17,23 +187,18 @@ def subclasses end def inherited(base) - unless abstract_railtie?(base) + unless base.abstract_railtie? base.send(:include, self::Configurable) subclasses << base end end - def railtie_name(railtie_name = nil) - @railtie_name = railtie_name if railtie_name - @railtie_name ||= default_name + def railtie_name(*) + ActiveSupport::Deprecation.warn "railtie_name is deprecated and has no effect", caller end - def railtie_names - subclasses.map { |p| p.railtie_name } - end - - def log_subscriber(log_subscriber) - Rails::LogSubscriber.add(railtie_name, log_subscriber) + def log_subscriber(name, log_subscriber) + Rails::LogSubscriber.add(name, log_subscriber) end def rake_tasks(&blk) @@ -48,14 +213,8 @@ def generators(&blk) @generators end - protected - - def abstract_railtie?(base) - ABSTRACT_RAILTIES.include?(base.name) - end - - def default_name - ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name)) + def abstract_railtie? + ABSTRACT_RAILTIES.include?(name) end end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 828ccec3d0..16eccaccc4 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -3,11 +3,8 @@ module Rails class Railtie class Configuration - attr_accessor :cookie_secret - def initialize - @session_store = :cookie_store - @session_options = {} + @@options ||= {} end # Holds generators configuration: @@ -48,76 +45,18 @@ def to_prepare(&blk) end def respond_to?(name) - super || name.to_s =~ config_key_regexp - end - - def metal_loader - @metal_loader ||= Rails::Application::MetalLoader.new - end - - def session_store(*args) - if args.empty? - case @session_store - when :disabled - nil - when :active_record_store - ActiveRecord::SessionStore - when Symbol - ActionDispatch::Session.const_get(@session_store.to_s.camelize) - else - @session_store - end - else - @session_store = args.shift - @session_options = args.shift || {} - end + super || @@options.key?(name.to_sym) end private def method_missing(name, *args, &blk) - if name.to_s =~ config_key_regexp - return $2 == '=' ? options[$1] = args.first : options[$1] - end - super - end - - def session_options - return @session_options unless @session_store == :cookie_store - @session_options.merge(:secret => @cookie_secret) - end - - def config_key_regexp - bits = config_keys.map { |n| Regexp.escape(n.to_s) }.join('|') - /^(#{bits})(?:=)?$/ - end - - def config_keys - (Railtie.railtie_names + Engine.engine_names).map { |n| n.to_s }.uniq - end - - def options - @@options ||= Hash.new { |h,k| h[k] = ActiveSupport::OrderedOptions.new } - end - - def default_middleware - require 'action_dispatch' - ActionDispatch::MiddlewareStack.new.tap do |middleware| - middleware.use('::ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { serve_static_assets }) - middleware.use('::Rack::Lock', :if => lambda { !allow_concurrency }) - middleware.use('::Rack::Runtime') - middleware.use('::Rails::Rack::Logger') - middleware.use('::ActionDispatch::ShowExceptions', lambda { consider_all_requests_local }) - middleware.use("::ActionDispatch::RemoteIp", lambda { action_dispatch.ip_spoofing_check }, lambda { action_dispatch.trusted_proxies }) - middleware.use('::Rack::Sendfile', lambda { action_dispatch.x_sendfile_header }) - middleware.use('::ActionDispatch::Callbacks', lambda { !cache_classes }) - middleware.use('::ActionDispatch::Cookies') - middleware.use(lambda { session_store }, lambda { session_options }) - middleware.use('::ActionDispatch::Flash', :if => lambda { session_store }) - middleware.use(lambda { metal_loader.build_middleware(metals) }, :if => lambda { metal_loader.metals.any? }) - middleware.use('ActionDispatch::ParamsParser') - middleware.use('::Rack::MethodOverride') - middleware.use('::ActionDispatch::Head') + if name.to_s =~ /=$/ + @@options[$`.to_sym] = args.first + elsif @@options.key?(name) + @@options[name] + else + super end end end diff --git a/railties/lib/rails/railties_path.rb b/railties/lib/rails/railties_path.rb deleted file mode 100644 index e291fc23ea..0000000000 --- a/railties/lib/rails/railties_path.rb +++ /dev/null @@ -1 +0,0 @@ -RAILTIES_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')) diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index abf9b33ae5..f2fee45594 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -66,13 +66,14 @@ namespace :doc do task :plugins => plugins.collect { |plugin| "doc:plugins:#{plugin}" } desc "Remove plugin documentation" - task :clobber_plugins do + task :clobber_plugins do rm_rf 'doc/plugins' rescue nil end desc "Generate Rails guides" task :guides do - require File.join(RAILTIES_PATH, "guides/rails_guides") + # FIXME: Reaching outside lib directory is a bad idea + require File.expand_path('../../../../guides/rails_guides', __FILE__) RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate end @@ -92,7 +93,7 @@ namespace :doc do files.include("#{plugin_base}/lib/**/*.rb") if File.exist?("#{plugin_base}/README") - files.include("#{plugin_base}/README") + files.include("#{plugin_base}/README") options << "--main '#{plugin_base}/README'" end files.include("#{plugin_base}/CHANGELOG") if File.exist?("#{plugin_base}/CHANGELOG") diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index dbe2ac54ed..738f7f5301 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -2,18 +2,18 @@ namespace :rails do namespace :freeze do desc "The rails:freeze:gems is deprecated, please use bundle install instead" task :gems do - puts "The rails:freeze:gems is deprecated, please use bundle install instead" + abort "The rails:freeze:gems is deprecated, please use bundle install instead" end desc 'The freeze:edge command has been deprecated, specify the path setting in your app Gemfile instead and bundle install' task :edge do - puts 'The freeze:edge command has been deprecated, specify the path setting in your app Gemfile instead and bundle install' + abort 'The freeze:edge command has been deprecated, specify the path setting in your app Gemfile instead and bundle install' end end desc 'The unfreeze command has been deprecated, please use bundler commands instead' task :unfreeze do - puts 'The unfreeze command has been deprecated, please use bundler commands instead' + abort 'The unfreeze command has been deprecated, please use bundler commands instead' end desc "Update both configs, scripts and public/javascripts from Rails" @@ -25,7 +25,7 @@ namespace :rails do template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} require 'rails/generators' - require 'generators/rails/app/app_generator' + require 'rails/generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new [ Rails.root ], {}, :destination_root => Rails.root generator.apply template, :verbose => false end @@ -38,10 +38,11 @@ namespace :rails do def app_generator @app_generator ||= begin require 'rails/generators' - require 'generators/rails/app/app_generator' + require 'rails/generators/rails/app/app_generator' gen = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, :destination_root => Rails.root - gen.send(:valid_app_const?) + File.exists?(Rails.root.join("config", "application.rb")) ? + gen.send(:app_const) : gen.send(:valid_app_const?) gen end end diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake index 48fce92215..0926707a04 100644 --- a/railties/lib/rails/tasks/misc.rake +++ b/railties/lib/rails/tasks/misc.rake @@ -15,8 +15,6 @@ end desc 'Explain the current environment' task :about do - $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" - require 'rails/info' puts Rails::Info end @@ -26,12 +24,12 @@ namespace :time do task :all do build_time_zone_list(:all) end - + desc 'Displays names of US time zones recognized by the Rails TimeZone class, grouped by offset. Results can be filtered with optional OFFSET parameter, e.g., OFFSET=-6' task :us do build_time_zone_list(:us_zones) end - + desc 'Displays names of time zones recognized by the Rails TimeZone class with the same offset as the system local time' task :local do require 'active_support' @@ -41,7 +39,7 @@ namespace :time do offset = jan_offset < jul_offset ? jan_offset : jul_offset build_time_zone_list(:all, offset) end - + # to find UTC -06:00 zones, OFFSET can be set to either -6, -6:00 or 21600 def build_time_zone_list(method, offset = ENV['OFFSET']) require 'active_support' diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 2ed5353755..6522c94ad6 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -4,14 +4,18 @@ require 'test/unit' require 'active_support/core_ext/kernel/requires' +require 'active_support/test_case' +require 'action_controller/test_case' +require 'action_dispatch/testing/integration' -# TODO: Figure out how to get the Rails::BacktraceFilter into minitest/unit if defined?(Test::Unit::Util::BacktraceFilter) && ENV['BACKTRACE'].nil? require 'rails/backtrace_cleaner' Test::Unit::Util::BacktraceFilter.module_eval { include Rails::BacktraceFilterForTestUnit } end if defined?(ActiveRecord) + require 'active_record/test_case' + class ActiveSupport::TestCase include ActiveRecord::TestFixtures self.fixture_path = "#{Rails.root}/test/fixtures/" diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index d99325a6d8..e3fafc4b9d 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -1,7 +1,5 @@ module Rails class TestUnitRailtie < Rails::Railtie - railtie_name :test_unit - config.generators do |c| c.test_framework :test_unit, :fixture => true, :fixture_replacement => nil diff --git a/railties/railties.gemspec b/railties/railties.gemspec index b9d2739539..aea07efe96 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -13,7 +13,7 @@ s.homepage = 'http://www.rubyonrails.org' s.rubyforge_project = 'rails' - s.files = Dir['CHANGELOG', 'README', 'bin/**/*', 'builtin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] + s.files = Dir['CHANGELOG', 'README', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] s.require_path = 'lib' s.bindir = 'bin' s.executables = ['rails'] diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index aa66dbb9be..d04a2aa1f3 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -1,7 +1,6 @@ ORIG_ARGV = ARGV.dup require File.expand_path("../../../load_paths", __FILE__) -$:.unshift File.expand_path("../../builtin/rails_info", __FILE__) require 'stringio' require 'test/unit' diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 54cd751f4e..68ca2acaad 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -140,7 +140,7 @@ def teardown require "#{app_path}/config/environment" end end - + test "filter_parameters should be able to set via config.filter_parameters" do add_to_config <<-RUBY config.filter_parameters += [ :foo, 'bar', lambda { |key, value| @@ -172,16 +172,27 @@ def teardown assert $prepared end - test "config.action_dispatch.x_sendfile_header defaults to X-Sendfile" do + def make_basic_app require "rails" require "action_controller/railtie" - class MyApp < Rails::Application - config.cookie_secret = "3b7cd727ee24e8444053437c36cc66c4" - config.session_store :cookie_store, :key => "_myapp_session" + app = Class.new(Rails::Application) + + yield app if block_given? + + app.config.session_store :disabled + app.initialize! + + app.routes.draw do + match "/" => "omg#index" end - MyApp.initialize! + require 'rack/test' + extend Rack::Test::Methods + end + + test "config.action_dispatch.x_sendfile_header defaults to ''" do + make_basic_app class ::OmgController < ActionController::Base def index @@ -189,44 +200,53 @@ def index end end - MyApp.routes.draw do - match "/" => "omg#index" + get "/" + assert_equal File.read(__FILE__), last_response.body + end + + test "config.action_dispatch.x_sendfile_header can be set" do + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = "X-Sendfile" end - require 'rack/test' - extend Rack::Test::Methods + class ::OmgController < ActionController::Base + def index + send_file __FILE__ + end + end get "/" assert_equal File.expand_path(__FILE__), last_response.headers["X-Sendfile"] end test "config.action_dispatch.x_sendfile_header is sent to Rack::Sendfile" do - require "rails" - require "action_controller/railtie" - - class MyApp < Rails::Application - config.cookie_secret = "3b7cd727ee24e8444053437c36cc66c4" - config.session_store :cookie_store, :key => "_myapp_session" - config.action_dispatch.x_sendfile_header = 'X-Lighttpd-Send-File' + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = 'X-Lighttpd-Send-File' end - MyApp.initialize! - class ::OmgController < ActionController::Base def index send_file __FILE__ end end - MyApp.routes.draw do - match "/" => "omg#index" - end - - require 'rack/test' - extend Rack::Test::Methods - get "/" assert_equal File.expand_path(__FILE__), last_response.headers["X-Lighttpd-Send-File"] end + + test "protect from forgery is the default in a new app" do + make_basic_app + + class ::OmgController < ActionController::Base + protect_from_forgery + + def index + render :inline => "<%= csrf_meta_tag %>" + end + end + + get "/" + assert last_response.body =~ /csrf\-param/ + end end end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 511b8b629a..589e515d05 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -51,8 +51,6 @@ def assert_not_in_load_path(*path) assert_path @paths.config.environment, "config", "environments", "development.rb" assert_equal root("app", "controllers"), @paths.app.controllers.to_a.first - assert_equal Pathname.new(File.dirname(__FILE__)).join("..", "..", "builtin", "rails_info").expand_path, - Pathname.new(@paths.app.controllers.to_a[1]).expand_path end test "booting up Rails yields a list of paths that are eager" do @@ -80,21 +78,5 @@ def assert_not_in_load_path(*path) assert_not_in_load_path "tmp" assert_not_in_load_path "tmp", "cache" end - - test "controller paths include builtin in development mode" do - Rails.env.replace "development" - assert Rails::Application::Configuration.new("/").paths.app.controllers.paths.any? { |p| p =~ /builtin/ } - end - - test "controller paths does not have builtin_directories in test mode" do - Rails.env.replace "test" - assert !Rails::Application::Configuration.new("/").paths.app.controllers.paths.any? { |p| p =~ /builtin/ } - end - - test "controller paths does not have builtin_directories in production mode" do - Rails.env.replace "production" - assert !Rails::Application::Configuration.new("/").paths.app.controllers.paths.any? { |p| p =~ /builtin/ } - end - end end diff --git a/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb index 7a4edb8bcb..a7d079a1bc 100644 --- a/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb +++ b/railties/test/fixtures/lib/generators/active_record/fixjour_generator.rb @@ -1,4 +1,4 @@ -require 'generators/active_record' +require 'rails/generators/active_record' module ActiveRecord module Generators diff --git a/railties/test/fixtures/lib/rails_generators/foobar/foobar_generator.rb b/railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb similarity index 100% rename from railties/test/fixtures/lib/rails_generators/foobar/foobar_generator.rb rename to railties/test/fixtures/lib/rails/generators/foobar/foobar_generator.rb diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 3585e6e7c0..44e0640552 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -1,8 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/app/app_generator' - -# TODO This line shouldn't be required -require 'generators/rails/model/model_generator' +require 'rails/generators/rails/app/app_generator' class ActionsTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 412034029e..95ca2acf2c 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' require 'generators/generators_test_helper' -require 'generators/rails/app/app_generator' +require 'rails/generators/rails/app/app_generator' class AppGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -9,6 +9,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def setup super Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') end def teardown @@ -65,6 +66,13 @@ def test_invalid_application_name_raises_an_error assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content end + def test_application_name_raises_an_error_if_name_already_used_constant + %w{ String Hash Class Module Set Symbol }.each do |ruby_class| + content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] } + assert_equal "Invalid application name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another application name.\n", content + end + end + def test_invalid_application_name_is_fixed run_generator [File.join(destination_root, "things-43")] assert_file "things-43/config/environment.rb", /Things43::Application\.initialize!/ @@ -161,14 +169,14 @@ def test_file_is_added_for_backwards_compatibility end def test_dev_option - generator([destination_root], :dev => true).expects(:run).with("bundle install") + generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") silence(:stdout){ generator.invoke } rails_path = File.expand_path('../../..', Rails.root) assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ end def test_edge_option - generator([destination_root], :edge => true).expects(:run).with("bundle install") + generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") silence(:stdout){ generator.invoke } assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$/ end diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 79a4e5bf17..be99dc068d 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -1,10 +1,12 @@ require 'generators/generators_test_helper' -require 'generators/rails/controller/controller_generator' +require 'rails/generators/rails/controller/controller_generator' class ControllerGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(Account foo bar) + setup :copy_routes + def test_help_does_not_show_invoked_generators_options_if_they_already_exist content = run_generator ["--help"] assert_no_match /Helper options\:/, content @@ -23,8 +25,6 @@ def test_check_class_collision Object.send :remove_const, :ObjectController end - # No need to spec content since it's already spec'ed on helper generator. - # def test_invokes_helper run_generator assert_file "app/helpers/account_helper.rb" @@ -49,8 +49,13 @@ def test_does_not_invoke_test_framework_if_required def test_invokes_default_template_engine run_generator - assert_file "app/views/account/foo.html.erb", /app\/views\/account\/foo/ - assert_file "app/views/account/bar.html.erb", /app\/views\/account\/bar/ + assert_file "app/views/account/foo.html.erb", %r(app/views/account/foo\.html\.erb) + assert_file "app/views/account/bar.html.erb", %r(app/views/account/bar\.html\.erb) + end + + def test_add_routes + run_generator + assert_file "config/routes.rb", /get "account\/foo"/, /get "account\/bar"/ end def test_invokes_default_template_engine_even_with_no_action diff --git a/railties/test/generators/generator_generator_test.rb b/railties/test/generators/generator_generator_test.rb index f3fd688e4f..26f975a191 100644 --- a/railties/test/generators/generator_generator_test.rb +++ b/railties/test/generators/generator_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/generator/generator_generator' +require 'rails/generators/rails/generator/generator_generator' class GeneratorGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 32be99b144..d8bdb344f2 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -26,4 +26,11 @@ def self.included(base) end end end + + def copy_routes + routes = File.expand_path("../../../lib/rails/generators/rails/app/templates/config/routes.rb", __FILE__) + destination = File.join(destination_root, "config") + FileUtils.mkdir_p(destination) + FileUtils.cp File.expand_path(routes), destination + end end \ No newline at end of file diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index 6d7168738e..f0bfebd57f 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/helper/helper_generator' +require 'rails/generators/rails/helper/helper_generator' ObjectHelper = Class.new AnotherObjectHelperTest = Class.new diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb index d7fc324c88..cf282a0911 100644 --- a/railties/test/generators/integration_test_generator_test.rb +++ b/railties/test/generators/integration_test_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/integration_test/integration_test_generator' +require 'rails/generators/rails/integration_test/integration_test_generator' class IntegrationTestGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index e6fc1bbb5c..81d6afa221 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/mailer/mailer_generator' +require 'rails/generators/rails/mailer/mailer_generator' class MailerGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -28,20 +28,22 @@ def test_check_class_collision def test_invokes_default_test_framework run_generator - assert_file "test/functional/notifier_test.rb", /class NotifierTest < ActionMailer::TestCase/ - assert_file "test/fixtures/notifier/foo", /app\/views\/notifier\/foo/ - assert_file "test/fixtures/notifier/bar", /app\/views\/notifier\/bar/ + assert_file "test/functional/notifier_test.rb" do |test| + assert_match /class NotifierTest < ActionMailer::TestCase/, test + assert_match /test "foo"/, test + assert_match /test "bar"/, test + end end def test_invokes_default_template_engine run_generator assert_file "app/views/notifier/foo.text.erb" do |view| - assert_match /app\/views\/notifier\/foo/, view + assert_match %r(app/views/notifier/foo\.text\.erb), view assert_match /<%= @greeting %>/, view end assert_file "app/views/notifier/bar.text.erb" do |view| - assert_match /app\/views\/notifier\/bar/, view + assert_match %r(app/views/notifier/bar\.text\.erb), view assert_match /<%= @greeting %>/, view end end diff --git a/railties/test/generators/metal_generator_test.rb b/railties/test/generators/metal_generator_test.rb index e3a2384885..615122c882 100644 --- a/railties/test/generators/metal_generator_test.rb +++ b/railties/test/generators/metal_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/metal/metal_generator' +require 'rails/generators/rails/metal/metal_generator' class MetalGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 811a712fd5..762f84d579 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/migration/migration_generator' +require 'rails/generators/rails/migration/migration_generator' class MigrationGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 79ce9a2a7b..f5cfd8eeca 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/model/model_generator' +require 'rails/generators/rails/model/model_generator' class ModelGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index f327fb1282..e73dd237fb 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/scaffold_controller/scaffold_controller_generator' +require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' # Mock out what we need from AR::Base. module ActiveRecord @@ -15,6 +15,20 @@ class NamedBaseTest < Rails::Generators::TestCase include GeneratorsTestHelper tests Rails::Generators::ScaffoldControllerGenerator + def test_named_generator_with_underscore + g = generator ['line_item'] + assert_name g, 'line_item', :name + assert_name g, %w(), :class_path + assert_name g, 'LineItem', :class_name + assert_name g, 'line_item', :file_path + assert_name g, 'line_item', :file_name + assert_name g, 'Line item', :human_name + assert_name g, 'line_item', :singular_name + assert_name g, 'line_items', :plural_name + assert_name g, 'line_item', :i18n_scope + assert_name g, 'line_items', :table_name + end + def test_named_generator_attributes g = generator ['admin/foo'] assert_name g, 'admin/foo', :name @@ -22,6 +36,7 @@ def test_named_generator_attributes assert_name g, 'Admin::Foo', :class_name assert_name g, 'admin/foo', :file_path assert_name g, 'foo', :file_name + assert_name g, 'Foo', :human_name assert_name g, 'foo', :singular_name assert_name g, 'foos', :plural_name assert_name g, 'admin.foo', :i18n_scope @@ -36,6 +51,7 @@ def test_named_generator_attributes_as_ruby assert_name g, 'admin/foo', :file_path assert_name g, 'foo', :file_name assert_name g, 'foo', :singular_name + assert_name g, 'Foo', :human_name assert_name g, 'foos', :plural_name assert_name g, 'admin.foo', :i18n_scope assert_name g, 'admin_foos', :table_name @@ -45,6 +61,8 @@ def test_named_generator_attributes_without_pluralized ActiveRecord::Base.pluralize_table_names = false g = generator ['admin/foo'] assert_name g, 'admin_foo', :table_name + ensure + ActiveRecord::Base.pluralize_table_names = true end def test_scaffold_plural_names diff --git a/railties/test/generators/observer_generator_test.rb b/railties/test/generators/observer_generator_test.rb index 058a19228d..45fe8dfbd3 100644 --- a/railties/test/generators/observer_generator_test.rb +++ b/railties/test/generators/observer_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/observer/observer_generator' +require 'rails/generators/rails/observer/observer_generator' class ObserverGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/performance_test_generator_test.rb b/railties/test/generators/performance_test_generator_test.rb index c95063a127..8fda83b36f 100644 --- a/railties/test/generators/performance_test_generator_test.rb +++ b/railties/test/generators/performance_test_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/performance_test/performance_test_generator' +require 'rails/generators/rails/performance_test/performance_test_generator' class PerformanceTestGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 065dbe1423..c1f646f7c1 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/plugin/plugin_generator' +require 'rails/generators/rails/plugin/plugin_generator' class PluginGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 959934bd71..96fd7a0a72 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -1,19 +1,11 @@ require 'generators/generators_test_helper' -require 'generators/rails/resource/resource_generator' +require 'rails/generators/rails/resource/resource_generator' class ResourceGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(account) - def setup - super - routes = Rails::Generators::ResourceGenerator.source_root - routes = File.join(routes, "..", "..", "app", "templates", "config", "routes.rb") - destination = File.join(destination_root, "config") - - FileUtils.mkdir_p(destination) - FileUtils.cp File.expand_path(routes), destination - end + setup :copy_routes def test_help_with_inherited_options content = run_generator ["--help"] diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index f971598d18..77ed63b1bb 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/scaffold_controller/scaffold_controller_generator' +require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' module Unknown module Generators diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index a7e9c8a231..6cf2efca45 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -1,19 +1,11 @@ require 'generators/generators_test_helper' -require 'generators/rails/scaffold/scaffold_generator' +require 'rails/generators/rails/scaffold/scaffold_generator' class ScaffoldGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(product_line title:string price:integer) - def setup - super - routes = Rails::Generators::ResourceGenerator.source_root - routes = File.join(routes, "..", "..", "app", "templates", "config", "routes.rb") - destination = File.join(destination_root, "config") - - FileUtils.mkdir_p(destination) - FileUtils.cp File.expand_path(routes), destination - end + setup :copy_routes def test_scaffold_on_invoke run_generator diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb index de28b4c75b..9fee948d7c 100644 --- a/railties/test/generators/session_migration_generator_test.rb +++ b/railties/test/generators/session_migration_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/session_migration/session_migration_generator' +require 'rails/generators/rails/session_migration/session_migration_generator' class SessionMigrationGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators/stylesheets_generator_test.rb b/railties/test/generators/stylesheets_generator_test.rb index 718fcb1fa7..aaeb686daa 100644 --- a/railties/test/generators/stylesheets_generator_test.rb +++ b/railties/test/generators/stylesheets_generator_test.rb @@ -1,5 +1,5 @@ require 'generators/generators_test_helper' -require 'generators/rails/stylesheets/stylesheets_generator' +require 'rails/generators/rails/stylesheets/stylesheets_generator' class StylesheetsGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index dd17f8f756..16f8f43b99 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -1,6 +1,6 @@ require 'generators/generators_test_helper' -require 'generators/rails/model/model_generator' -require 'generators/test_unit/model/model_generator' +require 'rails/generators/rails/model/model_generator' +require 'rails/generators/test_unit/model/model_generator' require 'mocha' class GeneratorsTest < Rails::Generators::TestCase @@ -104,11 +104,25 @@ def test_rails_generators_help_with_builtin_information def test_rails_generators_with_others_information output = capture(:stdout){ Rails::Generators.help } - assert_match /ActiveRecord:/, output assert_match /Fixjour:/, output + assert_match /^ fixjour$/, output + end + + def test_rails_generators_does_not_show_activerecord_info_if_its_the_default + output = capture(:stdout){ Rails::Generators.help } + assert_no_match /ActiveRecord:/, output + assert_no_match /^ active_record:model$/, output + assert_no_match /^ active_record:fixjour$/, output + end + + def test_rails_generators_shows_activerecord_info_if_its_not_the_default + Rails::Generators.options[:rails][:orm] = :data_mapper + output = capture(:stdout){ Rails::Generators.help } + assert_match /ActiveRecord:/, output assert_match /^ active_record:model$/, output assert_match /^ active_record:fixjour$/, output - assert_match /^ fixjour$/, output + ensure + Rails::Generators.options[:rails][:orm] = :active_record end def test_no_color_sets_proper_shell diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index d904d7b461..05ec359f61 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -1,9 +1,6 @@ require 'abstract_unit' require 'action_controller' -require 'rails/info' -require 'rails/info_controller' - module ActionController class Base include ActionController::Testing diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 9fefb285b4..546bf5e143 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -32,28 +32,25 @@ class Foo < Rails::Railtie ; end end test "config name is available for the railtie" do - class Foo < Rails::Railtie ; config.foo.greetings = "hello" ; end + class Foo < Rails::Railtie + config.foo = ActiveSupport::OrderedOptions.new + config.foo.greetings = "hello" + end assert_equal "hello", Foo.config.foo.greetings end test "railtie configurations are available in the application" do - class Foo < Rails::Railtie ; config.foo.greetings = "hello" ; end + class Foo < Rails::Railtie + config.foo = ActiveSupport::OrderedOptions.new + config.foo.greetings = "hello" + end require "#{app_path}/config/application" assert_equal "hello", AppTemplate::Application.config.foo.greetings end - test "railtie config merges are deep" do - class Foo < Rails::Railtie ; config.foo.greetings = 'hello' ; end - class Bar < Rails::Railtie - config.foo.bar = "bar" - end - assert_equal "hello", Bar.config.foo.greetings - assert_equal "bar", Bar.config.foo.bar - end - test "railtie can add log subscribers" do begin - class Foo < Rails::Railtie ; log_subscriber(Rails::LogSubscriber.new) ; end + class Foo < Rails::Railtie ; log_subscriber(:foo, Rails::LogSubscriber.new) ; end assert_kind_of Rails::LogSubscriber, Rails::LogSubscriber.log_subscribers[0] ensure Rails::LogSubscriber.log_subscribers.clear