diff options
327 files changed, 4232 insertions, 2388 deletions
@@ -10,7 +10,7 @@ end gem "coffee-script" gem "sass" -gem "uglifier", :git => "git://github.com/lautis/uglifier.git" +gem "uglifier", ">= 1.0.0" gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -41,10 +41,11 @@ platforms :ruby do end gem "json" gem "yajl-ruby" - gem "nokogiri", ">= 1.4.4" + gem "nokogiri", ">= 1.4.5" group :test do - gem "ruby-prof" + gem "ruby-prof" if RUBY_VERSION < "1.9.3" + end # AR gem "sqlite3", "~> 1.3.3" @@ -52,7 +53,7 @@ platforms :ruby do group :db do gem "pg", ">= 0.11.0" gem "mysql", ">= 2.8.1" - gem "mysql2", ">= 0.3.0" + gem "mysql2", ">= 0.3.6" end end diff --git a/README.rdoc b/README.rdoc index 5abc6f927c..bca2126559 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,7 +1,7 @@ == Welcome to Rails Rails is a web-application framework that includes everything needed to create -database-backed web applications according to the Model-View-Controller pattern. +database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern. Understanding the MVC pattern is key to understanding Rails. MVC divides your application into three layers, each with a specific responsibility. @@ -18,7 +18,7 @@ you to present the data from database rows as objects and embellish these data o with business logic methods. Although most Rails models are backed by a database, models can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as provided by the ActiveModel module. You can read more about Active Record in its -{README}[link:files/activerecord/README_rdoc.html]. +{README}[link:blob/master/activerecord/README.rdoc]. The Controller layer is responsible for handling incoming HTTP requests and providing a suitable response. Usually this means returning HTML, but Rails controllers can also @@ -29,7 +29,7 @@ In Rails, the Controller and View layers are handled together by Action Pack. These two layers are bundled in a single package due to their heavy interdependence. This is unlike the relationship between the Active Record and Action Pack which are independent. Each of these packages can be used independently outside of Rails. You -can read more about Action Pack in its {README}[link:files/actionpack/README_rdoc.html]. +can read more about Action Pack in its {README}[link:blob/master/actionpack/README.rdoc]. == Getting Started @@ -51,9 +51,31 @@ desc "Generate documentation for the Rails framework" RDoc::Task.new do |rdoc| RDOC_MAIN = 'RDOC_MAIN.rdoc' + # This is a hack. + # + # Backslashes are needed to prevent RDoc from autolinking "Rails" to the + # documentation of the Rails module. On the other hand, as of this + # writing README.rdoc is displayed in the front page of the project in + # GitHub, where backslashes are shown and look weird. + # + # The temporary solution is to have a README.rdoc without backslashes for + # GitHub, and gsub it to generate the main page of the API. + # + # Also, relative links in GitHub have to point to blobs, whereas in the API + # they need to point to files. + # + # The idea for the future is to have totally different files, since the + # API is no longer a generic entry point to Rails and deserves a + # dedicated main page specifically thought as an API entry point. rdoc.before_running_rdoc do rdoc_main = File.read('README.rdoc') - rdoc_main.gsub!(/\b(?=Rails)\b/) { '\\' } + + # The ^(?=\S) assertion prevents code blocks from being processed, + # since no autolinking happens there and RDoc displays the backslash + # otherwise. + rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" } + rdoc_main.gsub!(%r{link:blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html") + File.open(RDOC_MAIN, 'w') do |f| f.write(rdoc_main) end @@ -72,7 +94,7 @@ RDoc::Task.new do |rdoc| rdoc.rdoc_files.include('railties/MIT-LICENSE') rdoc.rdoc_files.include('railties/README.rdoc') rdoc.rdoc_files.include('railties/lib/**/*.rb') - rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/*') + rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb') rdoc.rdoc_files.include('activerecord/README.rdoc') rdoc.rdoc_files.include('activerecord/CHANGELOG') diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 3afdbea42a..cb289fd693 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -57,7 +57,7 @@ module ActionMailer #:nodoc: # will accept (any valid Email header including optional fields). # # The mail method, if not passed a block, will inspect your views and send all the views with - # the same name as the method, so the above action would send the +welcome.text.plain.erb+ view + # the same name as the method, so the above action would send the +welcome.text.erb+ view # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email. # # If you want to explicitly render only certain templates, pass a block: @@ -88,7 +88,7 @@ module ActionMailer #:nodoc: # # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same # name as the method in your mailer model. For example, in the mailer defined above, the template at - # <tt>app/views/notifier/signup_notification.text.plain.erb</tt> would be used to generate the email. + # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email. # # Variables defined in the model are accessible as instance variables in the view. # @@ -122,18 +122,19 @@ module ActionMailer #:nodoc: # # <%= users_url(:host => "example.com") %> # - # You want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't - # make sense to generate relative URLs in email messages. + # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the + # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will + # have no concept of a current URL from which to determine a relative path. # # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> # option as a configuration option in <tt>config/application.rb</tt>: # # config.action_mailer.default_url_options = { :host => "example.com" } # - # If you do decide to set a default <tt>:host</tt> for your mailers you want to use the - # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are - # generated because the <tt>url_for</tt> view helper will, by default, generate relative URLs when a - # <tt>:host</tt> option isn't explicitly provided. + # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the + # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper + # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing + # <tt>:only_path => false</tt> will ensure that absolute URLs are generated. # # = Sending mail # @@ -148,12 +149,12 @@ module ActionMailer #:nodoc: # # = Multipart Emails # - # Multipart messages can also be used implicitly because Action Mailer will automatically - # detect and use multipart templates, where each template is named after the name of the action, followed - # by the content type. Each such detected template will be added as separate part to the message. + # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use + # multipart templates, where each template is named after the name of the action, followed by the content + # type. Each such detected template will be added as a separate part to the message. # # For example, if the following templates exist: - # * signup_notification.text.plain.erb + # * signup_notification.text.erb # * signup_notification.text.html.erb # * signup_notification.text.xml.builder # * signup_notification.text.yaml.erb @@ -178,7 +179,7 @@ module ActionMailer #:nodoc: # end # end # - # Which will (if it had both a <tt>welcome.text.plain.erb</tt> and <tt>welcome.text.html.erb</tt> + # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.text.html.erb</tt> # template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts, # the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside, # and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book @@ -213,14 +214,14 @@ module ActionMailer #:nodoc: # = Observing and Intercepting Mails # # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to - # register objects that are called during the mail delivery life cycle. + # register classes that are called during the mail delivery life cycle. # - # An observer object must implement the <tt>:delivered_email(message)</tt> method which will be + # An observer class must implement the <tt>:delivered_email(message)</tt> method which will be # called once for every email sent after the email has been sent. # - # An interceptor object must implement the <tt>:delivering_email(message)</tt> method which will be + # An interceptor class must implement the <tt>:delivering_email(message)</tt> method which will be # called before the email is sent, allowing you to make modifications to the email before it hits - # the delivery agents. Your object should make any needed modifications directly to the passed + # the delivery agents. Your class should make any needed modifications directly to the passed # in Mail::Message instance. # # = Default Hash @@ -365,7 +366,7 @@ module ActionMailer #:nodoc: Mail.register_observer(delivery_observer) end - # Register an Inteceptor which will be called before mail is sent. + # Register an Interceptor which will be called before mail is sent. # Either a class or a string can be passed in as the Interceptor. If a string is passed in # it will be <tt>constantize</tt>d. def register_interceptor(interceptor) @@ -732,3 +733,4 @@ module ActionMailer #:nodoc: ActiveSupport.run_load_hooks(:action_mailer, self) end end + diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index b324ba790d..d1467fb526 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -1,7 +1,7 @@ require 'tmpdir' module ActionMailer - # This modules handles everything related to the delivery, from registering new + # This module handles everything related to mail delivery, from registering new # delivery methods to configuring the mail object to be sent. module DeliveryMethods extend ActiveSupport::Concern diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 48b3e5bfff..f4b6464bdc 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,64 @@ +*Rails 3.2.0 (unreleased)* + +* Generate hidden input before select with :multiple option set to true. + This is useful when you rely on the fact that when no options is set, + the state of select will be sent to rails application. Without hidden field + nothing is sent according to HTML spec [Bogdan Gusiev] + +* Refactor ActionController::TestCase cookies [Andrew White] + + Assigning cookies for test cases should now use cookies[], e.g: + + cookies[:email] = 'user@example.com' + get :index + assert_equal 'user@example.com', cookies[:email] + + To clear the cookies, use clear, e.g: + + cookies.clear + get :index + assert_nil cookies[:email] + + We now no longer write out HTTP_COOKIE and the cookie jar is + persistent between requests so if you need to manipulate the environment + for your test you need to do it before the cookie jar is created. + + *Rails 3.1.0 (unreleased)* +* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove] + +* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist] + +* Fix escape_js to work correctly with the new SafeBuffer restriction [Paul Gallagher] + +* Brought back alternative convention for namespaced models in i18n [thoefer] + + Now the key can be either "namespace.model" or "namespace/model" until further deprecation. + +* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove] + + The old behavior of SafeBuffer allowed you to mutate string in place via + method like `sub!`. These methods can add unsafe strings to a safe buffer, + and the safe buffer will continue to be marked as safe. + + An example problem would be something like this: + + <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %> + + In the above example, an untrusted string (`params[:xss]`) is added to the + safe buffer returned by `link_to`, and the untrusted content is successfully + sent to the client without being escaped. To prevent this from happening + `sub!` and other similar methods will now raise an exception when they are called on a safe buffer. + + In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example: + + <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %> + + The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe. + + You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb + * Added 'ActionView::Helpers::FormHelper.fields_for_with_index', similar to fields_for but allows to have access to the current iteration index [Jorge Bejar] * Warn if we cannot verify CSRF token authenticity [José Valim] diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index d7c8aeadd3..16b1e78b4d 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.3.0') s.add_dependency('rack-test', '~> 0.6.0') s.add_dependency('rack-mount', '~> 0.8.1') - s.add_dependency('sprockets', '~> 2.0.0.beta.8') + s.add_dependency('sprockets', '= 2.0.0.beta.10') s.add_dependency('tzinfo', '~> 0.3.27') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 8f73e244d7..d6f75bded0 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -81,11 +81,12 @@ module AbstractController # class EmployeeController < BankController # layout nil # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # The TellerController uses +teller.html.erb+, and TillController inherits that layout and - # uses it as well. + # In these examples: + # * The InformationController uses the "bank_standard" layout, inherited from BankController. + # * The TellerController follows convention and uses +app/views/layouts/teller.html.erb+. + # * The TillController inherits the layout from TellerController and uses +teller.html.erb+ as well. + # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method. + # * The EmployeeController does not use a layout at all. # # == Types of layouts # @@ -138,8 +139,8 @@ module AbstractController # # end # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. + # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will + # be rendered directly, without wrapping a layout around the rendered view. # # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. @@ -158,7 +159,7 @@ module AbstractController # end # end # - # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. + # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. module Layouts extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index c03c77cb4a..d14c5f940b 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -31,7 +31,7 @@ module ActionController # "302 Moved" HTTP response that takes the user to the index action. # # These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. - # Most actions are variations of these themes. + # Most actions are variations on these themes. # # == Requests # @@ -116,8 +116,8 @@ module ActionController # # Title: <%= @post.title %> # - # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use - # the manual rendering methods: + # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates + # will use the manual rendering methods: # # def search # @results = Search.find(params[:query]) @@ -132,9 +132,9 @@ module ActionController # # == Redirects # - # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database, - # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to) - # a <tt>show</tt> action that we'll assume has already been created. The code might look like this: + # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the + # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're + # going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this: # # def create # @entry = Entry.new(params[:entry]) @@ -146,7 +146,9 @@ module ActionController # end # end # - # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed. + # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed. + # Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action), + # and not some internal re-routing which calls both "create" and then "show" within one request. # # Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting. # diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 0be04b70a1..2bdb23c4d7 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -109,7 +109,6 @@ module ActionController #:nodoc: def expire_fragment(key, options = nil) return unless cache_configured? key = fragment_cache_key(key) unless key.is_a?(Regexp) - message = nil instrument_fragment_cache :expire_fragment, key do if key.is_a?(Regexp) diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index e9db0d97b6..938a6ae81c 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -61,6 +61,7 @@ module ActionController #:nodoc: end def after(controller) + self.controller = controller callback(:after) if controller.perform_caching # Clean up, so that the controller can be collected after this request self.controller = nil diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 8d813a8e38..35e29398e6 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -10,7 +10,7 @@ module ActionController format = payload[:format] format = format.to_s.upcase if format.is_a?(Symbol) - info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}" + info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}" info " Parameters: #{params.inspect}" unless params.empty? end @@ -20,10 +20,11 @@ module ActionController status = payload[:status] if status.nil? && payload[:exception].present? - status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil - end + status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil + end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? + message << "\n" info(message) end @@ -59,4 +60,4 @@ module ActionController end end -ActionController::LogSubscriber.attach_to :action_controller
\ No newline at end of file +ActionController::LogSubscriber.attach_to :action_controller diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 2ad571b2b5..7420a5e7e9 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -106,7 +106,7 @@ module ActionController module ControllerMethods extend ActiveSupport::Concern - + module ClassMethods def http_basic_authenticate_with(options = {}) before_filter(options.except(:name, :password, :realm)) do @@ -116,7 +116,7 @@ module ActionController end end end - + def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure) authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm) end @@ -423,7 +423,7 @@ module ActionController # Returns an Array of [String, Hash] if a token is present. # Returns nil if no token is found. def token_and_options(request) - if header = request.authorization.to_s[/^Token (.*)/] + if request.authorization.to_s[/^Token (.*)/] values = Hash[$1.split(',').map do |value| value.strip! # remove any spaces between commas and values key, value = value.split(/\=\"?/) # split key=value pairs diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 5500f88930..2d8afc3a78 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -64,7 +64,7 @@ module ActionController # end # # will try to check if +Admin::User+ or +User+ model exists, and use it to - # determine the wrapper key respectively. If both of the model doesn't exists, + # determine the wrapper key respectively. If both models don't exist, # it will then fallback to use +user+ as the key. module ParamsWrapper extend ActiveSupport::Concern @@ -88,14 +88,14 @@ module ActionController # # wraps parameters into +params[:person]+ hash # # wrap_parameters Person - # # wraps parameters by determine the wrapper key from Person class + # # wraps parameters by determining the wrapper key from Person class # (+person+, in this case) and the list of attribute names # # wrap_parameters :include => [:username, :title] # # wraps only +:username+ and +:title+ attributes from parameters. # # wrap_parameters false - # # disable parameters wrapping for this controller altogether. + # # disables parameters wrapping for this controller altogether. # # ==== Options # * <tt>:format</tt> - The list of formats in which the parameters wrapper diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 55c650df6c..dee7eb1ec8 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -43,6 +43,7 @@ module ActionController # # The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an # integer, or a symbol representing the downcased, underscored and symbolized description. + # Note that the status code must be a 3xx HTTP code, or redirection will not occur. # # It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names # +alert+ and +notice+ as well as a general purpose +flash+ bucket. diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 3892a12407..0bb436a476 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -260,4 +260,3 @@ module ActionController #:nodoc: end end end -
\ No newline at end of file diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 2def78b51a..c11d676c5e 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -40,7 +40,7 @@ module ActionController # dom_class(post, :edit) # => "edit_post" # dom_class(Person, :edit) # => "edit_person" def dom_class(record_or_class, prefix = nil) - singular = ActiveModel::Naming.singular(record_or_class) + singular = ActiveModel::Naming.param_key(record_or_class) prefix ? "#{prefix}#{JOIN}#{singular}" : singular end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index bfb820fcdf..45bb641aee 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -175,10 +175,6 @@ module ActionController end def recycle! - write_cookies! - @env.delete('HTTP_COOKIE') if @cookies.blank? - @env.delete('action_dispatch.cookies') - @cookies = nil @formats = nil @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } @@ -186,6 +182,14 @@ module ActionController @method = @request_method = nil @fullpath = @ip = @remote_ip = nil @env['action_dispatch.request.query_parameters'] = {} + @set_cookies ||= {} + @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }]) + deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies") + @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) } + cookie_jar.update(rack_cookies) + cookie_jar.update(cookies) + cookie_jar.update(@set_cookies) + cookie_jar.recycle! end end @@ -206,7 +210,7 @@ module ActionController DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS def initialize(session = {}) - @env, @by = nil, nil + super(nil, nil) replace(session.stringify_keys) @loaded = true end @@ -301,18 +305,17 @@ module ActionController # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another # action call which can then be asserted against. # - # == Manipulating the request collections + # == Manipulating session and cookie variables # - # The collections described above link to the response, so you can test if what the actions were expected to do happened. But - # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions - # and cookies, though. For sessions, you just do: + # Sometimes you need to set up the session and cookie variables for a test. + # To do this just assign a value to the session or cookie collection: # - # @request.session[:key] = "value" - # @request.cookies[:key] = "value" + # session[:key] = "value" + # cookies[:key] = "value" # - # To clear the cookies for a test just clear the request's cookies hash: + # To clear the cookies for a test just clear the cookie collection: # - # @request.cookies.clear + # cookies.clear # # == \Testing named routes # @@ -450,7 +453,6 @@ module ActionController @controller.process_with_new_base_test(@request, @response) @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {} @request.session.delete('flash') if @request.session['flash'].blank? - @request.cookies.merge!(@response.cookies) @response end diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 1e43104f0a..505d5560b1 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -3,9 +3,10 @@ require 'active_support/memoizable' module ActionDispatch module Http class Headers < ::Hash - extend ActiveSupport::Memoizable + @@env_cache = Hash.new { |h,k| h[k] = "HTTP_#{k.upcase.gsub(/-/, '_')}" } def initialize(*args) + if args.size == 1 && args[0].is_a?(Hash) super() update(args[0]) @@ -25,9 +26,8 @@ module ActionDispatch private # Converts a HTTP header name to an environment variable name. def env_name(header_name) - "HTTP_#{header_name.upcase.gsub(/-/, '_')}" + @@env_cache[header_name] end - memoize :env_name end end end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 37effade4f..a15ad28f16 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -4,7 +4,7 @@ module ActionDispatch attr_accessor :original_filename, :content_type, :tempfile, :headers def initialize(hash) - @original_filename = hash[:filename] + @original_filename = encode_filename(hash[:filename]) @content_type = hash[:type] @headers = hash[:head] @tempfile = hash[:tempfile] @@ -30,6 +30,16 @@ module ActionDispatch def size @tempfile.size end + + private + def encode_filename(filename) + # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8 + if "ruby".encoding_aware? && filename + filename.force_encoding("UTF-8").encode! + else + filename + end + end end module Upload diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 47c4bad489..8cee9ecdc4 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,4 +1,5 @@ -require "active_support/core_ext/object/blank" +require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/hash/keys' module ActionDispatch class Request @@ -129,7 +130,7 @@ module ActionDispatch end def update(other_hash) - @cookies.update other_hash + @cookies.update other_hash.stringify_keys self end @@ -185,6 +186,11 @@ module ActionDispatch value end + # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie + def clear(options = {}) + @cookies.each_key{ |k| delete(k, options) } + end + # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: # # cookies.permanent[:prefers_open_id] = true @@ -222,6 +228,11 @@ module ActionDispatch @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) } end + def recycle! #:nodoc: + @set_cookies.clear + @delete_cookies.clear + end + private def write_cookie?(cookie) diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb index 56e2d2f2a8..f1906a3ab3 100644 --- a/actionpack/lib/action_dispatch/middleware/head.rb +++ b/actionpack/lib/action_dispatch/middleware/head.rb @@ -8,7 +8,7 @@ module ActionDispatch if env["REQUEST_METHOD"] == "HEAD" env["REQUEST_METHOD"] = "GET" env["rack.methodoverride.original_method"] = "HEAD" - status, headers, body = @app.call(env) + status, headers, _ = @app.call(env) [status, headers, []] else @app.call(env) diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 74c090f260..1dcd83ceb5 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -161,7 +161,7 @@ module ActionDispatch # Consider the following route, which you will find commented out at the # bottom of your generated <tt>config/routes.rb</tt>: # - # match ':controller(/:action(/:id(.:format)))' + # match ':controller(/:action(/:id))(.:format)' # # This route states that it expects requests to consist of a # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ec76d1da1e..65895590bf 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -49,6 +49,9 @@ module ActionDispatch class Mapping #:nodoc: IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] + ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + SHORTHAND_REGEX = %r{^/[\w/]+$} + WILDCARD_PATH = %r{\*([^/]+)$} def initialize(set, scope, path, options) @set, @scope = set, scope @@ -77,7 +80,7 @@ module ActionDispatch # segment_keys.include?(k.to_s) || k == :controller next unless Regexp === requirement && !constraints[name] - if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + if requirement.source =~ ANCHOR_CHARACTERS_REGEX raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" end if requirement.multiline? @@ -88,7 +91,7 @@ module ActionDispatch # match "account/overview" def using_match_shorthand?(path, options) - path && options.except(:via, :anchor, :to, :as).empty? && path =~ %r{^/[\w\/]+$} + path && options.except(:via, :anchor, :to, :as).empty? && path =~ SHORTHAND_REGEX end def normalize_path(path) @@ -107,7 +110,7 @@ module ActionDispatch # Add a constraint for wildcard route to make it non-greedy and match the # optional format part of the route by default - if path.match(/\*([^\/]+)$/) && @options[:format] != false + if path.match(WILDCARD_PATH) && @options[:format] != false @options.reverse_merge!(:"#{$1}" => /.+?/) end @@ -1102,9 +1105,9 @@ module ActionDispatch # # The +comments+ resource here will have the following routes generated for it: # - # post_comments GET /sekret/posts/:post_id/comments(.:format) - # post_comments POST /sekret/posts/:post_id/comments(.:format) - # new_post_comment GET /sekret/posts/:post_id/comments/new(.:format) + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) # edit_comment GET /sekret/comments/:id/edit(.:format) # comment GET /sekret/comments/:id(.:format) # comment PUT /sekret/comments/:id(.:format) diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 9791ba58e2..aae5752c93 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -207,9 +207,6 @@ module ActionDispatch "*/*;q=0.5" unless defined? @named_routes_configured - # install the named routes in this session instance. - klass = singleton_class - # the helpers are made protected by default--we make them public for # easier access during testing and troubleshooting. @named_routes_configured = true diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 397bda41d5..f668b81b45 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,3 +1,4 @@ +require 'action_dispatch/middleware/cookies' require 'action_dispatch/middleware/flash' require 'active_support/core_ext/hash/indifferent_access' @@ -22,7 +23,7 @@ module ActionDispatch end def cookies - @request.cookies.merge(@response.cookies).with_indifferent_access + @request.cookie_jar end def redirect_to_url @@ -38,7 +39,7 @@ module ActionDispatch # # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary) def fixture_file_upload(path, mime_type = nil, binary = false) - fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path) + fixture_path = self.class.fixture_path if self.class.respond_to?(:fixture_path) Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary) end end diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index 822adb6a47..7280e9a93b 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/reverse_merge' require 'rack/utils' @@ -14,18 +15,11 @@ module ActionDispatch env = Rails.application.env_config.merge(env) if defined?(Rails.application) super(DEFAULT_ENV.merge(env)) - @cookies = nil self.host = 'test.host' self.remote_addr = '0.0.0.0' self.user_agent = 'Rails Testing' end - def env - write_cookies! - delete_nil_values! - super - end - def request_method=(method) @env['REQUEST_METHOD'] = method.to_s.upcase end @@ -71,23 +65,10 @@ module ActionDispatch @env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",") end + alias :rack_cookies :cookies + def cookies - @cookies ||= super + @cookies ||= {}.with_indifferent_access end - - private - def write_cookies! - unless @cookies.blank? - @env['HTTP_COOKIE'] = @cookies.map { |name, value| escape_cookie(name, value) }.join('; ') - end - end - - def escape_cookie(name, value) - "#{Rack::Utils.escape(name)}=#{Rack::Utils.escape(value)}" - end - - def delete_nil_values! - @env.delete_if { |k, v| v.nil? } - end end end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 78eddb7530..d7229419a9 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -30,6 +30,7 @@ module ActionView extend ActiveSupport::Autoload eager_autoload do + autoload :AssetPaths autoload :Base autoload :Context autoload :Helpers diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb new file mode 100644 index 0000000000..2b1fe545a6 --- /dev/null +++ b/actionpack/lib/action_view/asset_paths.rb @@ -0,0 +1,79 @@ +require 'active_support/core_ext/file' + +module ActionView + class AssetPaths #:nodoc: + attr_reader :config, :controller + + def initialize(config, controller) + @config = config + @controller = controller + end + + # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. + # Prefix with <tt>/dir/</tt> 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) + source = source.to_s + return source if is_uri?(source) + + source = rewrite_extension(source, dir, ext) if ext + source = rewrite_asset_path(source, dir) + + if controller && include_host + has_request = controller.respond_to?(:request) + source = rewrite_host_and_protocol(source, has_request) + end + + source + end + + def is_uri?(path) + path =~ %r{^[-a-z]+://|^cid:|^//} + end + + private + + def rewrite_extension(source, dir, ext) + raise NotImplementedError + end + + def rewrite_asset_path(source, path = nil) + raise NotImplementedError + end + + def rewrite_relative_url_root(source, relative_url_root) + relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source + end + + def rewrite_host_and_protocol(source, has_request) + source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request + host = compute_asset_host(source) + if has_request && host && !is_uri?(host) + host = "#{controller.request.protocol}#{host}" + end + "#{host}#{source}" + end + + # Pick an asset host for this source. Returns +nil+ if no host is set, + # the host if no wildcard is set, the host interpolated with the + # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), + # or the value returned from invoking call on an object responding to call + # (proc or otherwise). + def compute_asset_host(source) + if host = config.asset_host + if host.respond_to?(:call) + case host.is_a?(Proc) ? host.arity : host.method(:call).arity + when 2 + request = controller.respond_to?(:request) && controller.request + host.call(source, request) + else + host.call(source) + end + else + (host =~ /%d/) ? host % (source.hash % 4) : host + end + end + end + end +end diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb index 089fc68706..be7f65c2ce 100644 --- a/actionpack/lib/action_view/buffers.rb +++ b/actionpack/lib/action_view/buffers.rb @@ -35,9 +35,9 @@ module ActionView def html_safe? true end - + def html_safe self end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index 9a99c3cf52..fae2e4fc1c 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -1,83 +1,7 @@ -require 'active_support/core_ext/file' +ActiveSupport::Deprecation.warn "ActionView::Helpers::AssetPaths is deprecated. Please use ActionView::AssetPaths instead." module ActionView module Helpers - - class AssetPaths #:nodoc: - attr_reader :config, :controller - - def initialize(config, controller) - @config = config - @controller = controller - end - - # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. - # Prefix with <tt>/dir/</tt> 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) - source = source.to_s - return source if is_uri?(source) - - source = rewrite_extension(source, dir, ext) if ext - source = rewrite_asset_path(source, dir) - - if controller && include_host - has_request = controller.respond_to?(:request) - source = rewrite_host_and_protocol(source, has_request) - end - - source - end - - def is_uri?(path) - path =~ %r{^[-a-z]+://|^cid:|^//} - end - - private - - def rewrite_extension(source, dir, ext) - raise NotImplementedError - end - - def rewrite_asset_path(source, path = nil) - raise NotImplementedError - end - - def rewrite_relative_url_root(source, relative_url_root) - relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source - end - - def rewrite_host_and_protocol(source, has_request) - source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request - host = compute_asset_host(source) - if has_request && host && !is_uri?(host) - host = "#{controller.request.protocol}#{host}" - end - "#{host}#{source}" - end - - # Pick an asset host for this source. Returns +nil+ if no host is set, - # the host if no wildcard is set, the host interpolated with the - # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), - # or the value returned from invoking call on an object responding to call - # (proc or otherwise). - def compute_asset_host(source) - if host = config.asset_host - if host.respond_to?(:call) - case host.is_a?(Proc) ? host.arity : host.method(:call).arity - when 2 - request = controller.respond_to?(:request) && controller.request - host.call(source, request) - else - host.call(source) - end - else - (host =~ /%d/) ? host % (source.hash % 4) : host - end - end - end - end - + AssetPaths = ::ActionView::AssetPaths end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 2d49823412..12a304b395 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -1,11 +1,10 @@ require 'active_support/core_ext/file' -require 'action_view/helpers/asset_paths' module ActionView module Helpers module AssetTagHelper - class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: + class AssetPaths < ::ActionView::AssetPaths #:nodoc: # You can enable or disable the asset tag ids cache. # With the cache enabled, the asset tag helper methods will make fewer # expensive file system calls (the default implementation checks the file diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index e81d03b537..f81ce3e31c 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -51,7 +51,11 @@ module ActionView # This dance is needed because Builder can't use capture pos = output_buffer.length yield + output_safe = output_buffer.html_safe? fragment = output_buffer.slice!(pos..-1) + if output_safe + self.output_buffer = output_buffer.html_safe + end controller.write_fragment(name, fragment, options) end end diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb index db59bca159..1a583e62ae 100644 --- a/actionpack/lib/action_view/helpers/controller_helper.rb +++ b/actionpack/lib/action_view/helpers/controller_helper.rb @@ -20,4 +20,4 @@ module ActionView end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 56e3af683b..6a724749f4 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -621,7 +621,6 @@ module ActionView end class DateTimeSelector #:nodoc: - extend ActiveSupport::Memoizable include ActionView::Helpers::TagHelper DEFAULT_PREFIX = 'date'.freeze @@ -786,11 +785,12 @@ module ActionView # Returns translated month names, but also ensures that a custom month # name array has a leading nil element. def month_names - month_names = @options[:use_month_names] || translated_month_names - month_names.unshift(nil) if month_names.size < 13 - month_names + @month_names ||= begin + month_names = @options[:use_month_names] || translated_month_names + month_names.unshift(nil) if month_names.size < 13 + month_names + end end - memoize :month_names # Returns translated month names. # => [nil, "January", "February", "March", @@ -825,9 +825,8 @@ module ActionView end def date_order - @options[:order] || translated_date_order + @date_order ||= @options[:order] || translated_date_order end - memoize :date_order def translated_date_order I18n.translate(:'date.order', :locale => @options[:locale]) || [] diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index cd67851642..c0cc7d347c 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -30,7 +30,7 @@ module ActionView begin Marshal::dump(object) "<pre class='debug_dump'>#{h(object.to_yaml).gsub(" ", " ")}</pre>".html_safe - rescue Exception => e # errors from Marshal or YAML + rescue Exception # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback "<code class='debug_dump'>#{h(object.inspect)}</code>".html_safe end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index cb1c13912a..0ef2357368 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -202,13 +202,13 @@ module ActionView # # is equivalent to something like: # - # <%= form_for @post, :as => :post, :url => post_path(@post), :html => { :class => "new_post", :id => "new_post" } do |f| %> + # <%= form_for @post, :as => :post, :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_posts_path) do |f| %> # ... # <% end %> # @@ -219,9 +219,9 @@ module ActionView # <% end %> # # If you have an object that needs to be represented as a different - # parameter, like a Client that acts as a Person: + # parameter, like a Person that acts as a Client: # - # <%= form_for(@post, :as => :client) do |f| %> + # <%= form_for(@person, :as => :client) do |f| %> # ... # <% end %> # @@ -290,7 +290,7 @@ module ActionView # # Example: # - # <%= form(@post) do |f| %> + # <%= form_for(@post) do |f| %> # <% f.fields_for(:comments, :include_id => false) do |cf| %> # ... # <% end %> @@ -517,6 +517,18 @@ module ActionView # end # end # + # Note that the <tt>projects_attributes=</tt> writer method is in fact + # required for fields_for to correctly identify <tt>:projects</tt> as a + # collection, and the correct indices to be set in the form markup. + # + # When projects is already an association on Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects + # end + # # This model can now be used with a nested fields_for. The block given to # the nested fields_for call will be repeated for each instance in the # collection: @@ -568,14 +580,6 @@ module ActionView # ... # <% end %> # - # When projects is already an association on Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects - # end - # # If you want to destroy any of the associated models through the # form, you have to enable it first using the <tt>:allow_destroy</tt> # option for +accepts_nested_attributes_for+: @@ -1237,7 +1241,7 @@ module ActionView end def fields_for(record_name, record_object = nil, fields_options = {}, &block) - fields_options, record_object = record_object, nil if record_object.is_a?(Hash) + fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] fields_options[:parent_builder] = self diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 794132637c..3dc6d65432 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -128,6 +128,28 @@ module ActionView # By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection # or <tt>:selected => nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option # tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled. + # + # ==== Gotcha + # + # The HTML specification says when +multiple+ parameter passed to select and all options got deselected + # web browsers do not send any value to server. Unfortunately this introduces a gotcha: + # if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user + # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So, + # any mass-assignment idiom like + # + # @user.update_attributes(params[:user]) + # + # wouldn't update roles. + # + # To prevent this the helper generates an auxiliary hidden field before + # every multiple select. The hidden field has the same name as multiple select and blank value. + # + # This way, the client either sends only the hidden field (representing + # the deselected multiple select box), or both fields. Since the HTML specification + # says key/value pairs have to be sent in the same order they appear in the + # form, and parameters extraction gets the last occurrence of any repeated + # key in the query string, that works for ordinary forms. + # def select(object, method, choices, options = {}, html_options = {}) InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options) end @@ -427,7 +449,7 @@ module ActionView # # Sample usage (Hash): # grouped_options = { - # 'North America' => [['United States','US], 'Canada'], + # 'North America' => [['United States','US'], 'Canada'], # 'Europe' => ['Denmark','Germany','France'] # } # grouped_options_for_select(grouped_options) @@ -552,43 +574,26 @@ module ActionView include FormOptionsHelper def to_select_tag(choices, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - selected_value = options.has_key?(:selected) ? options[:selected] : value - disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil - content_tag("select", add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options) + selected_value = options.has_key?(:selected) ? options[:selected] : value(object) + select_content_tag(options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]), options, html_options) end def to_collection_select_tag(collection, value_method, text_method, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil - selected_value = options.has_key?(:selected) ? options[:selected] : value - content_tag( - "select", add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options + selected_value = options.has_key?(:selected) ? options[:selected] : value(object) + select_content_tag( + options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options ) end def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - content_tag( - "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options + select_content_tag( + option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options ) end def to_time_zone_select_tag(priority_zones, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - content_tag("select", - add_options( - time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), - options, value - ), html_options + select_content_tag( + time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options ) end @@ -603,6 +608,17 @@ module ActionView end option_tags.html_safe end + + def select_content_tag(option_tags, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + select = content_tag("select", add_options(option_tags, options, value(object)), html_options) + if html_options["multiple"] + tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select + else + select + end + end end class FormBuilder diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index a91e86f4db..72bc4510b5 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -597,6 +597,12 @@ module ActionView number_field_tag(name, value, options.stringify_keys.update("type" => "range")) end + # Creates the hidden UTF8 enforcer tag. Override this method in a helper + # to customize the tag. + def utf8_enforcer_tag + tag(:input, :type => "hidden", :name => "utf8", :value => "✓".html_safe) + end + private def html_options_for_form(url_for_options, options, *parameters_for_url) options.stringify_keys.tap do |html_options| @@ -611,9 +617,6 @@ module ActionView end def extra_tags_for_form(html_options) - snowman_tag = tag(:input, :type => "hidden", - :name => "utf8", :value => "✓".html_safe) - authenticity_token = html_options.delete("authenticity_token") method = html_options.delete("method").to_s @@ -629,7 +632,7 @@ module ActionView tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token) end - tags = snowman_tag << method_tag + tags = utf8_enforcer_tag << method_tag content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index d7228bab67..4484390fde 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -18,7 +18,8 @@ module ActionView # $('some_element').replaceWith('<%=j render 'some/element_template' %>'); def escape_javascript(javascript) if javascript - javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] } + result = javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) {|match| JS_ESCAPE_MAP[match] } + javascript.html_safe? ? result.html_safe : result else '' end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 63d13a0f0b..fe0288521f 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -211,7 +211,7 @@ module ActionView defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) options = options.reverse_merge(defaults) - parts = number.to_s.split('.') + parts = number.to_s.to_str.split('.') parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") parts.join(options[:separator]).html_safe @@ -342,7 +342,7 @@ module ActionView 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) - + base = options[:prefix] == :si ? 1000 : 1024 if number.to_i < base diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 75422a343d..ae71ade588 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -255,9 +255,11 @@ module ActionView # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false) # # => "<p><span>I'm allowed!</span> It's true.</p>" def simple_format(text, html_options={}, options={}) - text = ''.html_safe if text.nil? + text = '' if text.nil? + text = text.dup if text.frozen? start_tag = tag('p', html_options, true) text = sanitize(text) unless options[:sanitize] == false + text = text.to_str text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br @@ -279,7 +281,7 @@ module ActionView # @items = [1,2,3,4] # <table> # <% @items.each do |item| %> - # <tr class="<%= cycle("even", "odd") -%>"> + # <tr class="<%= cycle("odd", "even") -%>"> # <td>item</td> # </tr> # <% end %> diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index fd8fe417d0..26b6e8b599 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -5,7 +5,7 @@ module I18n class ExceptionHandler include Module.new { def call(exception, locale, key, options) - exception.is_a?(MissingTranslation) ? super.html_safe : super + exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super end } end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 25411856cb..d70ae4196b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -55,9 +55,9 @@ module ActionView # # ==== Relying on named routes # - # If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter, - # you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing - # a Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as + # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will + # trigger the named route for that record. The lookup will happen on the name of the class. So passing a + # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). # # ==== Examples @@ -112,13 +112,13 @@ module ActionView end end - # Creates a link tag of the given +name+ using a URL created by the set - # of +options+. See the valid options in the documentation for - # +url_for+. It's also possible to pass a string instead - # of an options hash to get a link tag that uses the value of the string as the - # href for the link, or use <tt>:back</tt> to link to the referrer - a JavaScript back - # link will be used in place of a referrer if none exists. If +nil+ is passed as - # a name, the link itself will become the name. + # Creates a link tag of the given +name+ using a URL created by the set of +options+. + # See the valid options in the documentation for +url_for+. It's also possible to + # pass a String instead of an options hash, which generates a link tag that uses the + # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead + # of an options hash will generate a link to the referrer (a JavaScript back link + # will be used in place of a referrer if none exists). If +nil+ is passed as the name + # the value of the link itself will become the name. # # ==== Signatures # @@ -497,14 +497,14 @@ module ActionView }.compact extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&')) - email_address_obfuscated = email_address.dup + email_address_obfuscated = email_address.to_str email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.key?("replace_at") email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.key?("replace_dot") case encode when "javascript" string = '' html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)) - html = escape_javascript(html) + html = escape_javascript(html.to_str) "document.write('#{html}');".each_byte do |c| string << sprintf("%%%x", c) end @@ -619,7 +619,9 @@ module ActionView end def add_method_to_attributes!(html_options, method) - html_options["rel"] = "nofollow" if method.to_s.downcase != "get" + if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/ + html_options["rel"] = "#{html_options["rel"]} nofollow".strip + end html_options["data-method"] = method end diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb index 29ffbd6fdd..bf90d012bf 100644 --- a/actionpack/lib/action_view/log_subscriber.rb +++ b/actionpack/lib/action_view/log_subscriber.rb @@ -4,7 +4,7 @@ module ActionView # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber def render_template(event) - message = "Rendered #{from_rails_root(event.payload[:identifier])}" + message = " Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] message << (" (%.1fms)" % event.duration) info(message) diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index f0ed3425de..560df15e82 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -10,7 +10,7 @@ module ActionView # this key is generated just once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: attr_accessor :prefixes - + mattr_accessor :fallbacks @@fallbacks = FallbackFileSystemResolver.instances diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index a09cef8fef..d04c00fd40 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -64,7 +64,7 @@ module ActionView layout =~ /^\// ? with_fallbacks { find_template(layout, nil, false, keys) } : find_template(layout, nil, false, keys) end - rescue ActionView::MissingTemplate => e + rescue ActionView::MissingTemplate update_details(:formats => nil) do raise unless template_exists?(layout) end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index d4448a7b33..587e37a84f 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -31,7 +31,6 @@ module ActionView def initialize(paths, path, prefixes, partial, details, *) @path = path prefixes = Array.wrap(prefixes) - display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial "partial" elsif path =~ /layouts/i diff --git a/actionpack/lib/action_view/testing/resolvers.rb b/actionpack/lib/action_view/testing/resolvers.rb index 773dfcbb1d..7afa2fa613 100644 --- a/actionpack/lib/action_view/testing/resolvers.rb +++ b/actionpack/lib/action_view/testing/resolvers.rb @@ -34,7 +34,7 @@ module ActionView #:nodoc: templates << Template.new(source, _path, handler, :virtual_path => path.virtual, :format => format, :updated_at => updated_at) end - + templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } end end diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index a99dcad81d..9f1f0f3b68 100644 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ b/actionpack/lib/sprockets/helpers/rails_helper.rb @@ -1,5 +1,4 @@ -require "action_view/helpers/asset_paths" -require "action_view/helpers/asset_tag_helper" +require "action_view/helpers" module Sprockets module Helpers @@ -15,42 +14,48 @@ module Sprockets end end - def javascript_include_tag(source, options = {}) + def javascript_include_tag(*sources) + options = sources.extract_options! debug = options.key?(:debug) ? options.delete(:debug) : debug_assets? body = options.key?(:body) ? options.delete(:body) : false - if debug && asset = asset_paths.asset_for(source, 'js') - asset.to_a.map { |dep| - javascript_include_tag(dep, :debug => false, :body => true) - }.join("\n").html_safe - else - options = { - 'type' => "text/javascript", - 'src' => asset_path(source, 'js', body) - }.merge(options.stringify_keys) - - content_tag 'script', "", options - end + sources.collect do |source| + if debug && asset = asset_paths.asset_for(source, 'js') + asset.to_a.map { |dep| + javascript_include_tag(dep, :debug => false, :body => true) + }.join("\n").html_safe + else + tag_options = { + 'type' => "text/javascript", + 'src' => asset_path(source, 'js', body) + }.merge(options.stringify_keys) + + content_tag 'script', "", tag_options + end + end.join("\n").html_safe end - def stylesheet_link_tag(source, options = {}) + def stylesheet_link_tag(*sources) + options = sources.extract_options! debug = options.key?(:debug) ? options.delete(:debug) : debug_assets? body = options.key?(:body) ? options.delete(:body) : false - if debug && asset = asset_paths.asset_for(source, 'css') - asset.to_a.map { |dep| - stylesheet_link_tag(dep, :debug => false, :body => true) - }.join("\n").html_safe - else - options = { - 'rel' => "stylesheet", - 'type' => "text/css", - 'media' => "screen", - 'href' => asset_path(source, 'css', body) - }.merge(options.stringify_keys) - - tag 'link', options - end + sources.collect do |source| + if debug && asset = asset_paths.asset_for(source, 'css') + asset.to_a.map { |dep| + stylesheet_link_tag(dep, :debug => false, :body => true) + }.join("\n").html_safe + else + tag_options = { + 'rel' => "stylesheet", + 'type' => "text/css", + 'media' => "screen", + 'href' => asset_path(source, 'css', body) + }.merge(options.stringify_keys) + + tag 'link', tag_options + end + end.join("\n").html_safe end private @@ -61,13 +66,13 @@ module Sprockets def asset_path(source, default_ext = nil, body = false) source = source.logical_path if source.respond_to?(:logical_path) - path = asset_paths.compute_public_path(source, 'assets', default_ext, true) + path = asset_paths.compute_public_path(source, Rails.application.config.assets.prefix, default_ext, true) body ? "#{path}?body=1" : path end - class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: + class AssetPaths < ::ActionView::AssetPaths #:nodoc: def compute_public_path(source, dir, ext=nil, include_host=true) - super(source, 'assets', ext, include_host) + super(source, Rails.application.config.assets.prefix, ext, include_host) end def asset_for(source, ext) diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 7b8a7ad3bb..ab5101f6fc 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -46,43 +46,61 @@ module Sprockets end protected + def asset_environment(app) + require "sprockets" - def asset_environment(app) - require "sprockets" - assets = app.config.assets - env = Sprockets::Environment.new(app.root.to_s) - env.static_root = File.join(app.root.join("public"), assets.prefix) - env.paths.concat assets.paths - env.logger = Rails.logger - env.js_compressor = expand_js_compressor(assets.js_compressor) if assets.compress - env.css_compressor = expand_css_compressor(assets.css_compressor) if assets.compress - env - end + assets = app.config.assets + + env = Sprockets::Environment.new(app.root.to_s) + + env.static_root = File.join(app.root.join("public"), assets.prefix) + + if env.respond_to?(:append_path) + assets.paths.each { |path| env.append_path(path) } + else + env.paths.concat assets.paths + end + + env.logger = Rails.logger + + if env.respond_to?(:cache) + env.cache = Rails.cache + end - def expand_js_compressor(sym) - case sym - when :closure - require 'closure-compiler' - Closure::Compiler.new - when :uglifier - require 'uglifier' - Uglifier.new - when :yui - require 'yui/compressor' - YUI::JavaScriptCompressor.new - else - sym + if assets.compress + # temporarily hardcode default JS compressor to uglify. Soon, it will work + # the same as SCSS, where a default plugin sets the default. + env.js_compressor = expand_js_compressor(assets.js_compressor || :uglifier) + env.css_compressor = expand_css_compressor(assets.css_compressor) + end + + env + end + + def expand_js_compressor(sym) + case sym + when :closure + require 'closure-compiler' + Closure::Compiler.new + when :uglifier + require 'uglifier' + Uglifier.new + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new + else + sym + end end - end - def expand_css_compressor(sym) - case sym - when :yui - require 'yui/compressor' - YUI::CssCompressor.new - else - sym + def expand_css_compressor(sym) + case sym + when :yui + require 'yui/compressor' + YUI::CssCompressor.new + else + sym + end end - end end end diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 981c023d38..5823e64637 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -178,16 +178,10 @@ module AbstractController end end - class Me5 < WithLayouts - def index - render - end - end - class TestLayouts < ActiveSupport::TestCase test "layouts are included" do controller = Me4.new - result = controller.process(:index) + controller.process(:index) assert_equal "Me4 Enter : Hello from me4/index.erb : Exit", controller.response_body end end @@ -241,11 +235,11 @@ module AbstractController assert_dispatch ActionMissingRespondToActionController, "success", :ohai end - test "a method is available as an action if respond_to_action? returns true" do + test "a method is available as an action if method_for_action returns true" do assert_dispatch RespondToActionController, "success", :index end - test "raises ActionNotFound if method is defined but respond_to_action? returns false" do + test "raises ActionNotFound if method is defined but method_for_action returns false" do assert_raise(ActionNotFound) { RespondToActionController.new.process(:fail) } end end diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 3bdde86291..5d1a703c55 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -131,7 +131,7 @@ module AbstractController end def authenticate - @list = [] + @list ||= [] @authenticated = "true" end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index 5ed6aa68b5..86208899f8 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -87,18 +87,6 @@ module AbstractControllerTests end end - class WithSymbolReturningString < Base - layout :no_hello - - def index - render :template => ActionView::Template::Text.new("Hello missing symbol!") - end - private - def no_hello - nil - end - end - class WithSymbolReturningNil < Base layout :nilz diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 60534a9746..24d071df39 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -151,6 +151,7 @@ class BasicController config.assets_dir = public_dir config.javascripts_dir = "#{public_dir}/javascripts" config.stylesheets_dir = "#{public_dir}/stylesheets" + config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) config end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 7f3d943bba..a714e8bbcc 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -100,9 +100,6 @@ class AssertResponseWithUnexpectedErrorController < ActionController::Base end end -class UserController < ActionController::Base -end - module Admin class InnerModuleController < ActionController::Base def index diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 82c2c23607..da3314fe6d 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -127,7 +127,7 @@ class PageCachingTest < ActionController::TestCase assert_equal 'I am xml', @response.body end - def test_should_cache_with_trailing_slash_on_url + def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/' assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") end @@ -141,7 +141,7 @@ class PageCachingTest < ActionController::TestCase [:ok, :no_content, :found, :not_found].each do |status| [:get, :post, :put, :delete].each do |method| - unless method == :get and status == :ok + unless method == :get && status == :ok define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do send(method, status) assert_response status diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index b12c798302..d51882066d 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -74,6 +74,7 @@ class ContentTypeTest < ActionController::TestCase get :render_defaults assert_equal "utf-16", @response.charset assert_equal Mime::HTML, @response.content_type + ensure OldContentTypeController.default_charset = "utf-8" end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 9e44e8e088..d5e3da4d88 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -530,6 +530,11 @@ class FilterTest < ActionController::TestCase assert sweeper.before(TestController.new) end + def test_after_method_of_sweeper_should_always_return_nil + sweeper = ActionController::Caching::Sweeper.send(:new) + assert_nil sweeper.after(TestController.new) + end + def test_non_yielding_around_filters_not_returning_false_do_not_raise controller = NonYieldingAroundFilterController.new controller.instance_variable_set "@filter_return_value", true diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 899435ff38..043d44500a 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -593,13 +593,13 @@ XML end def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set - @request.cookies['foo'] = 'bar' + cookies['foo'] = 'bar' get :no_op assert_equal 'bar', cookies['foo'] end def test_should_detect_if_cookie_is_deleted - @request.cookies['foo'] = 'bar' + cookies['foo'] = 'bar' get :delete_cookie assert_nil cookies['foo'] end @@ -612,7 +612,6 @@ XML send(method, :test_remote_addr) assert false, "expected RuntimeError, got nothing" rescue RuntimeError => error - assert true assert_match(%r{@#{variable} is nil}, error.message) rescue => error assert false, "expected RuntimeError, got #{error.class}" @@ -650,6 +649,13 @@ XML end + def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case + TestTest.stubs(:fixture_path).returns(FILES_DIR) + + uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png') + assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + end + def test_test_uploaded_file_with_binary filename = 'mona_lisa.jpg' path = "#{FILES_DIR}/#{filename}" diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 3f3d6dcc2f..484e996f31 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -293,8 +293,8 @@ module AbstractController first_class.default_url_options[:host] = first_host second_class.default_url_options[:host] = second_host - assert_equal first_class.default_url_options[:host], first_host - assert_equal second_class.default_url_options[:host], second_host + assert_equal first_host, first_class.default_url_options[:host] + assert_equal second_host, second_class.default_url_options[:host] end def test_with_stringified_keys diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 621fb79915..ae8588cbb0 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -30,7 +30,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_check_parameters with_test_route_set do get "/" - assert_blank @controller.response.body + assert_equal '', @controller.response.body end end @@ -162,7 +162,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest def test_use_xml_ximple_with_empty_request with_test_route_set do assert_nothing_raised { post "/", "", {'CONTENT_TYPE' => 'application/xml'} } - assert_blank @controller.response.body + assert_equal '', @controller.response.body end end diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index e42c39f527..8e5fd97cc6 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -417,7 +417,7 @@ class CookiesTest < ActionController::TestCase assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" end - + def test_cookies_hash_is_indifferent_access get :symbol_key assert_equal "david", cookies[:user_name] @@ -430,54 +430,95 @@ class CookiesTest < ActionController::TestCase def test_setting_request_cookies_is_indifferent_access - @request.cookies.clear - @request.cookies[:user_name] = "andrew" + cookies.clear + cookies[:user_name] = "andrew" get :string_key_mock - assert_equal "david", cookies[:user_name] + assert_equal "david", cookies['user_name'] - @request.cookies.clear - @request.cookies['user_name'] = "andrew" + cookies.clear + cookies['user_name'] = "andrew" get :symbol_key_mock - assert_equal "david", cookies['user_name'] + assert_equal "david", cookies[:user_name] end def test_cookies_retained_across_requests get :symbol_key - assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"] + assert_cookie_header "user_name=david; path=/" assert_equal "david", cookies[:user_name] get :noop assert_nil @response.headers["Set-Cookie"] - assert_equal "user_name=david", @request.env['HTTP_COOKIE'] assert_equal "david", cookies[:user_name] get :noop assert_nil @response.headers["Set-Cookie"] - assert_equal "user_name=david", @request.env['HTTP_COOKIE'] assert_equal "david", cookies[:user_name] end def test_cookies_can_be_cleared get :symbol_key - assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"] assert_equal "david", cookies[:user_name] - @request.cookies.clear + cookies.clear get :noop - assert_nil @response.headers["Set-Cookie"] - assert_nil @request.env['HTTP_COOKIE'] assert_nil cookies[:user_name] get :symbol_key - assert_equal "user_name=david; path=/", @response.headers["Set-Cookie"] assert_equal "david", cookies[:user_name] end - def test_cookies_are_escaped - @request.cookies[:user_ids] = '1;2' + def test_can_set_http_cookie_header + @request.env['HTTP_COOKIE'] = 'user_name=david' + get :noop + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] + + get :noop + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] + + @request.env['HTTP_COOKIE'] = 'user_name=andrew' + get :noop + assert_equal 'andrew', cookies['user_name'] + assert_equal 'andrew', cookies[:user_name] + end + + def test_can_set_request_cookies + @request.cookies['user_name'] = 'david' + get :noop + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] + + get :noop + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] + + @request.cookies[:user_name] = 'andrew' + get :noop + assert_equal 'andrew', cookies['user_name'] + assert_equal 'andrew', cookies[:user_name] + end + + def test_cookies_precedence_over_http_cookie + @request.env['HTTP_COOKIE'] = 'user_name=andrew' + get :authenticate + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] + + get :noop + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] + end + + def test_cookies_precedence_over_request_cookies + @request.cookies['user_name'] = 'andrew' + get :authenticate + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] + get :noop - assert_equal "user_ids=1%3B2", @request.env['HTTP_COOKIE'] - assert_equal "1;2", cookies[:user_ids] + assert_equal 'david', cookies['user_name'] + assert_equal 'david', cookies[:user_name] end private diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 31ce97a25b..904398f563 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -29,7 +29,6 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase end def test_clear_emptys_session - params = {:one => 'one', :two => 'two'} session = ActionController::TestSession.new({:one => 'one', :two => 'two'}) session.clear assert_nil(session[:one]) diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index 81a8c24525..aa115608ef 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -34,12 +34,24 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal({}, req.cookies) assert_equal nil, req.env["HTTP_COOKIE"] - req.cookies["user_name"] = "david" - assert_equal({"user_name" => "david"}, req.cookies) - assert_equal "user_name=david", req.env["HTTP_COOKIE"] + req.cookie_jar["user_name"] = "david" + assert_cookies({"user_name" => "david"}, req.cookie_jar) - req.cookies["login"] = "XJ-122" - assert_equal({"user_name" => "david", "login" => "XJ-122"}, req.cookies) - assert_equal %w(login=XJ-122 user_name=david), req.env["HTTP_COOKIE"].split(/; /).sort + req.cookie_jar["login"] = "XJ-122" + assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar) + + req.cookie_jar.delete(:login) + assert_cookies({"user_name" => "david"}, req.cookie_jar) + + req.cookie_jar.clear + assert_cookies({}, req.cookie_jar) + + req.cookie_jar.update(:user_name => "david") + assert_cookies({"user_name" => "david"}, req.cookie_jar) end + + private + def assert_cookies(expected, cookie_jar) + assert_equal(expected, cookie_jar.instance_variable_get("@cookies")) + end end diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index e2a7f1bad7..7e4a1519fb 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -12,6 +12,13 @@ module ActionDispatch uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) assert_equal 'foo', uf.original_filename end + + if "ruby".encoding_aware? + def test_filename_should_be_in_utf_8 + uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) + assert_equal "UTF-8", uf.original_filename.encoding.to_s + end + end def test_content_type uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new) diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 67baf369e2..cbef74f992 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -170,6 +170,17 @@ class Author < Comment def post_attributes=(attributes); end end +class HashBackedAuthor < Hash + extend ActiveModel::Naming + include ActiveModel::Conversion + + def persisted?; false; end + + def name + "hash backed author" + end +end + module Blog def self._railtie self diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb index 30f6d1a213..790ab1c74c 100644 --- a/actionpack/test/template/erb_util_test.rb +++ b/actionpack/test/template/erb_util_test.rb @@ -16,6 +16,16 @@ class ErbUtilTest < Test::Unit::TestCase end end + def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings + value = json_escape("asdf") + assert !value.html_safe? + end + + def test_json_escape_returns_safe_strings_when_passed_safe_strings + value = json_escape("asdf".html_safe) + assert value.html_safe? + end + def test_html_escape_is_html_safe escaped = h("<p>") assert_equal "<p>", escaped diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index f2a49f7a68..bf65a9359b 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -696,13 +696,13 @@ class FormHelperTest < ActionView::TestCase concat f.submit('Edit post') end - expected = - "<form accept-charset='UTF-8' action='/posts/44' method='post'>" + - snowman + - "<label for='post_title'>The Title</label>" + + expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => "put") do "<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" + - "<input name='commit' id='post_submit' type='submit' value='Edit post' />" + + "<input name='commit' type='submit' value='Edit post' />" + "</form>" + end + + assert_dom_equal expected, output_buffer end def test_form_for_with_symbol_object_name @@ -1689,6 +1689,22 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_for_with_hash_like_model + @author = HashBackedAuthor.new + + form_for(@post) do |f| + concat f.fields_for(:author, @author) { |af| + concat af.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="hash backed author" />' + end + + assert_dom_equal expected, output_buffer + end + def test_fields_for output_buffer = fields_for(:post, @post) do |f| concat f.text_field(:title) @@ -1890,7 +1906,7 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def snowman(method = nil) + def hidden_fields(method = nil) txt = %{<div style="margin:0;padding:0;display:inline">} txt << %{<input name="utf8" type="hidden" value="✓" />} if method && !method.to_s.in?(['get', 'post']) @@ -1918,7 +1934,7 @@ class FormHelperTest < ActionView::TestCase method = options end - form_text(action, id, html_class, remote, multipart, method) + snowman(method) + contents + "</form>" + form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" end def test_default_form_builder diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index b92e1d9890..f3969895ae 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -457,6 +457,22 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_multiple_to_add_hidden_input + output_buffer = select(:post, :category, "", {}, :multiple => true) + assert_dom_equal( + "<input type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>", + output_buffer + ) + end + + def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input + output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true) + assert_dom_equal( + "<input disabled=\"disabled\"type=\"hidden\" name=\"post[category][]\" value=\"\"/><select multiple=\"multiple\" disabled=\"disabled\" id=\"post_category\" name=\"post[category][]\"></select>", + output_buffer + ) + end + def test_select_with_blank @post = Post.new @post.category = "<mus>" @@ -649,11 +665,11 @@ class FormOptionsHelperTest < ActionView::TestCase ) end - def test_collection_select_with_multiple_option_appends_array_brackets + def test_collection_select_with_multiple_option_appends_array_brackets_and_hidden_input @post = Post.new @post.author_name = "Babe" - expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>" + expected = "<input type=\"hidden\" name=\"post[author_name][]\" value=\"\"/><select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>" # Should suffix default name with []. assert_dom_equal expected, collection_select("post", "author_name", dummy_posts, "author_name", "author_name", { :include_blank => true }, :multiple => true) diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index f95308b847..979251bfd1 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -9,7 +9,7 @@ class FormTagHelperTest < ActionView::TestCase @controller = BasicController.new end - def snowman(options = {}) + def hidden_fields(options = {}) method = options[:method] txt = %{<div style="margin:0;padding:0;display:inline">} @@ -34,7 +34,7 @@ class FormTagHelperTest < ActionView::TestCase end def whole_form(action = "http://www.example.com", options = {}) - out = form_text(action, options) + snowman(options) + out = form_text(action, options) + hidden_fields(options) if block_given? out << yield << "</form>" diff --git a/actionpack/test/template/html-scanner/document_test.rb b/actionpack/test/template/html-scanner/document_test.rb index ddfb351595..3db2fba783 100644 --- a/actionpack/test/template/html-scanner/document_test.rb +++ b/actionpack/test/template/html-scanner/document_test.rb @@ -123,7 +123,7 @@ HTML def test_parse_invalid_document assert_nothing_raised do - doc = HTML::Document.new("<html> + HTML::Document.new("<html> <table> <tr> <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> @@ -135,7 +135,7 @@ HTML def test_invalid_document_raises_exception_when_strict assert_raise RuntimeError do - doc = HTML::Document.new("<html> + HTML::Document.new("<html> <table> <tr> <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td> diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index 538e0e9874..dd8b7b7cd5 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -30,6 +30,15 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal %(dont <\\/close> tags), j(%(dont </close> tags)) end + def test_escape_javascript_with_safebuffer + given = %('quoted' "double-quoted" new-line:\n </closed>) + expect = %(\\'quoted\\' \\"double-quoted\\" new-line:\\n <\\/closed>) + assert_equal expect, escape_javascript(given) + assert_equal expect, escape_javascript(ActiveSupport::SafeBuffer.new(given)) + assert_instance_of String, escape_javascript(given) + assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given)) + end + def test_button_to_function assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />), button_to_function("Greeting", "alert('Hello world!')") diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 0104c20bc7..0e3475d98b 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -283,33 +283,40 @@ class NumberHelperTest < ActionView::TestCase assert number_to_human(1).html_safe? assert !number_to_human("<script></script>").html_safe? assert number_to_human("asdf".html_safe).html_safe? + assert number_to_human("1".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("<script></script>").html_safe? assert number_to_human_size("asdf".html_safe).html_safe? + assert number_to_human_size("1".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("<script></script>").html_safe? assert number_with_precision("asdf".html_safe).html_safe? + assert number_with_precision("1".html_safe).html_safe? assert number_to_currency(1).html_safe? assert !number_to_currency("<script></script>").html_safe? assert number_to_currency("asdf".html_safe).html_safe? + assert number_to_currency("1".html_safe).html_safe? assert number_to_percentage(1).html_safe? assert !number_to_percentage("<script></script>").html_safe? assert number_to_percentage("asdf".html_safe).html_safe? + assert number_to_percentage("1".html_safe).html_safe? assert number_to_phone(1).html_safe? assert_equal "<script></script>", number_to_phone("<script></script>") assert number_to_phone("<script></script>").html_safe? assert number_to_phone("asdf".html_safe).html_safe? + assert number_to_phone("1".html_safe).html_safe? assert number_with_delimiter(1).html_safe? assert !number_with_delimiter("<script></script>").html_safe? assert number_with_delimiter("asdf".html_safe).html_safe? + assert number_with_delimiter("1".html_safe).html_safe? end def test_number_helpers_should_raise_error_if_invalid_when_specified diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 74d7bba4fe..1ba14e8bc9 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -48,14 +48,12 @@ class RecordTagHelperTest < ActionView::TestCase end def test_block_works_with_content_tag_for_in_erb - __in_erb_template = '' expected = %(<tr class="post" id="post_45">#{@post.body}</tr>) actual = content_tag_for(:tr, @post) { concat @post.body } assert_dom_equal expected, actual end def test_div_for_in_erb - __in_erb_template = '' expected = %(<div class="post bar" id="post_45">#{@post.body}</div>) actual = div_for(@post, :class => "bar") { concat @post.body } assert_dom_equal expected, actual diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 86d08a43a5..4187a0ac78 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -370,7 +370,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_incompatible_external_encoding with_external_encoding Encoding::SHIFT_JIS do begin - result = @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield") + @view.render(:file => "test/utf8.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' rescue ActionView::Template::Error => error assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message @@ -381,7 +381,7 @@ class LazyViewRenderTest < ActiveSupport::TestCase def test_render_utf8_template_with_partial_with_incompatible_encoding with_external_encoding Encoding::SHIFT_JIS do begin - result = @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield") + @view.render(:file => "test/utf8_magic_with_bare_partial.html.erb", :layouts => "layouts/yield") flunk 'Should have raised incompatible encoding error' rescue ActionView::Template::Error => error assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index ebb7e48d70..b1317d0a35 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -94,6 +94,9 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "<script src=\"/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js?body=1\" type=\"text/javascript\"></script>\n<script src=\"/assets/application-d41d8cd98f00b204e9800998ecf8427e.js?body=1\" type=\"text/javascript\"></script>", javascript_include_tag(:application, :debug => true) + + assert_equal "<script src=\"/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js\" type=\"text/javascript\"></script>\n<script src=\"/assets/extra-d41d8cd98f00b204e9800998ecf8427e.js\" type=\"text/javascript\"></script>", + javascript_include_tag("xmlhr", "extra") end test "stylesheet path" do @@ -127,5 +130,8 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "<link href=\"/assets/style-d41d8cd98f00b204e9800998ecf8427e.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/assets/application-68b329da9893e34099c7d8ad5cb9c940.css?body=1\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />", stylesheet_link_tag(:application, :debug => true) + + assert_equal "<link href=\"/assets/style-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />\n<link href=\"/assets/extra-d41d8cd98f00b204e9800998ecf8427e.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />", + stylesheet_link_tag("style", "extra") end end diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 81fb34b80f..b0ca7de0b6 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -153,7 +153,6 @@ class TestERBTemplate < ActiveSupport::TestCase def test_encoding_can_be_specified_with_magic_comment_in_erb with_external_encoding Encoding::UTF_8 do @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil) - result = render assert_equal Encoding::UTF_8, render.encoding assert_equal "hello \u{fc}mlat", render end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 740f577a6e..f7c3986bb1 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -36,8 +36,8 @@ class TextHelperTest < ActionView::TestCase text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text) - assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') - assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') + assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') + assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') end def test_simple_format_should_sanitize_input_when_sanitize_option_is_not_false @@ -48,6 +48,13 @@ class TextHelperTest < ActionView::TestCase assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false) end + def test_simple_format_should_not_change_the_frozen_text_passed + text = "<b>Ok</b><script>code!</script>" + text_clone = text.dup + simple_format(text.freeze) + assert_equal text_clone, text + end + def test_truncate_should_not_be_html_safe assert !truncate("Hello World!", :length => 12).html_safe? end @@ -291,7 +298,7 @@ class TextHelperTest < ActionView::TestCase end def test_cycle_class_with_no_arguments - assert_raise(ArgumentError) { value = Cycle.new() } + assert_raise(ArgumentError) { Cycle.new } end def test_cycle @@ -304,7 +311,7 @@ class TextHelperTest < ActionView::TestCase end def test_cycle_with_no_arguments - assert_raise(ArgumentError) { value = cycle() } + assert_raise(ArgumentError) { cycle } end def test_cycle_resets_with_new_values diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 9b5c6d127c..cd9f54e04c 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -38,11 +38,19 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_returns_missing_translation_message_wrapped_into_span expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>' assert_equal expected, translate(:"translations.missing") + assert_equal true, translate(:"translations.missing").html_safe? end def test_returns_missing_translation_message_using_nil_as_rescue_format expected = 'translation missing: en.translations.missing' assert_equal expected, translate(:"translations.missing", :rescue_format => nil) + assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe? + end + + def test_i18n_translate_defaults_to_nil_rescue_format + expected = 'translation missing: en.translations.missing' + assert_equal expected, I18n.translate(:"translations.missing") + assert_equal false, I18n.translate(:"translations.missing").html_safe? end def test_translation_returning_an_array diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 4aa45c8bf0..a70c02a429 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -15,6 +15,7 @@ class UrlHelperTest < ActiveSupport::TestCase routes.draw do match "/" => "foo#bar" match "/other" => "foo#other" + match "/article/:id" => "foo#article", :as => :article end include routes.url_helpers @@ -244,6 +245,13 @@ class UrlHelperTest < ActiveSupport::TestCase ) end + def test_link_tag_using_post_javascript_and_rel + assert_dom_equal( + "<a href='http://www.example.com' data-method=\"post\" rel=\"example nofollow\">Hello</a>", + link_to("Hello", "http://www.example.com", :method => :post, :rel => 'example') + ) + end + def test_link_tag_using_post_javascript_and_confirm assert_dom_equal( "<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>", @@ -264,6 +272,13 @@ class UrlHelperTest < ActiveSupport::TestCase assert_equal '<a href="/">Example site</a>', out end + def test_link_tag_with_html_safe_string + assert_dom_equal( + "<a href=\"/article/Gerd_M%C3%BCller\">Gerd Müller</a>", + link_to("Gerd Müller", article_path("Gerd_Müller".html_safe)) + ) + end + def test_link_to_unless assert_equal "Showing", link_to_unless(true, "Showing", url_hash) diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG index b1ad315c46..c38349b95e 100644 --- a/activemodel/CHANGELOG +++ b/activemodel/CHANGELOG @@ -2,6 +2,9 @@ *Rails 3.1.0 (unreleased)* +* Alternate I18n namespace lookup is no longer supported. + Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead [José Valim] + * attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis] * Add support for proc or lambda as an option for InclusionValidator, diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 0e2a2fd5e2..71ece15b71 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -248,7 +248,7 @@ module ActiveModel # # company = Company.create(:address => '123 First St.') # company.errors.full_messages # => - # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"] + # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"] def full_messages map { |attribute, message| if attribute == :base diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index a7b4706906..3f9feb7631 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/array/wrap' require 'active_model/mass_assignment_security/permission_set' require 'active_model/mass_assignment_security/sanitizer' @@ -110,8 +111,11 @@ module ActiveModel options = args.extract_options! role = options[:as] || :default - self._protected_attributes = protected_attributes_configs.dup - self._protected_attributes[role] = self.protected_attributes(role) + args + self._protected_attributes = protected_attributes_configs.dup + + Array.wrap(role).each do |name| + self._protected_attributes[name] = self.protected_attributes(name) + args + end self._active_authorizer = self._protected_attributes end @@ -169,8 +173,11 @@ module ActiveModel options = args.extract_options! role = options[:as] || :default - self._accessible_attributes = accessible_attributes_configs.dup - self._accessible_attributes[role] = self.accessible_attributes(role) + args + self._accessible_attributes = accessible_attributes_configs.dup + + Array.wrap(role).each do |name| + self._accessible_attributes[name] = self.accessible_attributes(name) + args + end self._active_authorizer = self._accessible_attributes end diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb index ee43a6694f..bb0526adc3 100644 --- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb +++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb @@ -19,7 +19,7 @@ module ActiveModel removed_keys = attributes.keys - sanitized_attributes.keys process_removed_attributes(removed_keys) if removed_keys.any? end - + def process_removed_attributes(attrs) raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten" end diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 0bfbf2aa06..0405705b35 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -22,13 +22,13 @@ module ActiveModel # of +as_json+. If true (the default) +as_json+ will emit a single root # node named after the object's type. For example: # - # konata = User.find(1) - # konata.as_json + # user = User.find(1) + # user.as_json # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} } # # ActiveRecord::Base.include_root_in_json = false - # konata.as_json + # user.as_json # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} # @@ -38,30 +38,30 @@ module ActiveModel # Without any +options+, the returned JSON string will include all the model's # attributes. For example: # - # konata = User.find(1) - # konata.as_json + # user = User.find(1) + # user.as_json # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true} # # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes # included, and work similar to the +attributes+ method. For example: # - # konata.as_json(:only => [ :id, :name ]) + # user.as_json(:only => [ :id, :name ]) # # => {"id": 1, "name": "Konata Izumi"} # - # konata.as_json(:except => [ :id, :created_at, :age ]) + # user.as_json(:except => [ :id, :created_at, :age ]) # # => {"name": "Konata Izumi", "awesome": true} # # To include the result of some method calls on the model use <tt>:methods</tt>: # - # konata.as_json(:methods => :permalink) + # user.as_json(:methods => :permalink) # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true, # "permalink": "1-konata-izumi"} # # To include associations use <tt>:include</tt>: # - # konata.as_json(:include => :posts) + # user.as_json(:include => :posts) # # => {"id": 1, "name": "Konata Izumi", "age": 16, # "created_at": "2006/08/01", "awesome": true, # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"}, @@ -69,7 +69,7 @@ module ActiveModel # # Second level and higher order associations work as well: # - # konata.as_json(:include => { :posts => { + # user.as_json(:include => { :posts => { # :include => { :comments => { # :only => :body } }, # :only => :title } }) diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index f6aae24324..9812af43d6 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -139,8 +139,8 @@ module ActiveModel # Without any +options+, the returned XML string will include all the model's # attributes. For example: # - # konata = User.find(1) - # konata.to_xml + # user = User.find(1) + # user.to_xml # # <?xml version="1.0" encoding="UTF-8"?> # <user> diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 48d14978fa..144e73904e 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -62,14 +62,14 @@ module ActiveModel # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time: # # class Person < ActiveRecord::Base - # validates_length_of :first_name, :maximum=>30 - # validates_length_of :last_name, :maximum=>30, :message=>"less than 30 if you don't mind" + # validates_length_of :first_name, :maximum => 30 + # validates_length_of :last_name, :maximum => 30, :message => "less than 30 if you don't mind" # validates_length_of :fax, :in => 7..32, :allow_nil => true # validates_length_of :phone, :in => 7..32, :allow_blank => true # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" # validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters" # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me." - # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words."), :tokenizer => lambda {|str| str.scan(/\w+/) } + # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words.", :tokenizer => lambda { |str| str.scan(/\w+/) } # end # # Configuration options: diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index 022c6716bd..9840e3364c 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -78,7 +78,6 @@ class AttributeMethodsTest < ActiveModel::TestCase test '#define_attribute_method generates attribute method with invalid identifier characters' do ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') - ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b' assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b') diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index a778240827..be07e59a2f 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -43,6 +43,20 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end + def test_attributes_accessible_with_roles_given_as_array + user = Account.new + expected = { "name" => "John Smith", "email" => "john@smith.com" } + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) + assert_equal expected, sanitized + end + + def test_attributes_accessible_with_admin_role_when_roles_given_as_array + user = Account.new + expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } + sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) + assert_equal expected, sanitized + end + def test_attributes_protected_by_default firm = Firm.new expected = { } diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb index 53b37369ff..1d123fa58c 100644 --- a/activemodel/test/models/mass_assignment_specific.rb +++ b/activemodel/test/models/mass_assignment_specific.rb @@ -20,6 +20,14 @@ class Person public :sanitize_for_mass_assignment end +class Account + include ActiveModel::MassAssignmentSecurity + attr_accessible :name, :email, :as => [:default, :admin] + attr_accessible :admin, :as => :admin + + public :sanitize_for_mass_assignment +end + class Firm include ActiveModel::MassAssignmentSecurity @@ -65,4 +73,4 @@ end class TightDescendant < TightPerson attr_accessible :phone_number attr_accessible :super_powers, :as => :admin -end
\ No newline at end of file +end diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index b05d3970c7..ead2a244e5 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -240,7 +240,6 @@ def up create_table :posts do |t| t.belongs_to :user - t.timestamps end diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index 0d00749901..822276589b 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -3,7 +3,7 @@ Active Record connects classes to relational database tables to establish an almost zero-configuration persistence layer for applications. The library provides a base class that, when subclassed, sets up a mapping between the new -class and an existing table in the database. In context of an application, +class and an existing table in the database. In the context of an application, these classes are commonly referred to as *models*. Models can also be connected to other models; this is done by defining *associations*. @@ -70,7 +70,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Validations.html] -* Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.) +* Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.). class Person < ActiveRecord::Base before_destroy :invalidate_payment_plan @@ -80,7 +80,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Callbacks.html] -* Observers that react to changes in a model +* Observers that react to changes in a model. class CommentObserver < ActiveRecord::Observer def after_create(comment) # is called just after Comment#save @@ -91,7 +91,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Observer.html] -* Inheritance hierarchies +* Inheritance hierarchies. class Company < ActiveRecord::Base; end class Firm < Company; end @@ -101,7 +101,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Base.html] -* Transactions +* Transactions. # Database transaction Account.transaction do @@ -112,7 +112,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] -* Reflections on columns, associations, and aggregations +* Reflections on columns, associations, and aggregations. reflection = Firm.reflect_on_association(:clients) reflection.klass # => Client (class) @@ -121,7 +121,7 @@ A short rundown of some of the major features: {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] -* Database abstraction through simple adapters +* Database abstraction through simple adapters. # connect to SQLite3 ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "dbfile.sqlite3") @@ -141,13 +141,13 @@ A short rundown of some of the major features: SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. -* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] +* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") -* Database agnostic schema management with Migrations +* Database agnostic schema management with Migrations. class AddSystemSettings < ActiveRecord::Migration def self.up diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS index b3d376772e..6a2e23b01f 100644 --- a/activerecord/RUNNING_UNIT_TESTS +++ b/activerecord/RUNNING_UNIT_TESTS @@ -1,43 +1,39 @@ -== Creating the test database +== Configure databases -The default names for the test databases are "activerecord_unittest" and -"activerecord_unittest2". If you want to use another database name, then be sure -to update the connection adapter setups you want to test within -test/connections/<your database>/connection.rb. -When you have the database online, you can import the fixture tables with -the test/schema/*.sql files. +Copy test/config.example.yml to test/config.yml and edit as needed. Or just run the tests for +the first time, which will do the copy automatically and use the default (sqlite3). -Make sure that you create database objects with the same user that you specified in -connection.rb otherwise (on Postgres, at least) tests for default values will fail. +You can build postgres and mysql databases using the build_postgresql and build_mysql rake tasks. -== Running with Rake +== Running the tests -The easiest way to run the unit tests is through Rake. The default task runs -the entire test suite for all the adapters. You can also run the suite on just -one adapter by using the tasks test_mysql, test_sqlite3, test_postgresql or any -of the other test_ tasks. For more information, checkout the full array of rake -tasks with "rake -T" +You can run a particular test file from the command line, e.g. -Rake can be found at http://rake.rubyforge.org + $ ruby -Itest test/cases/base_test.rb -== Running by hand +To run a specific test: -Unit tests are located in test/cases directory. If you only want to run a single test suite, -you can do so with: + $ ruby -Itest test/cases/base_test.rb -n test_something_works - rake test_mysql TEST=test/cases/base_test.rb +You can run with a database other than the default you set in test/config.yml, using the ARCONN +environment variable: -That'll run the base suite using the MySQL-Ruby adapter. Some tests rely on the schema -being initialized - you can initialize the schema with: + $ ARCONN=postgresql ruby -Itest test/cases/base_test.rb - rake test_mysql TEST=test/cases/aaa_create_tables_test.rb - rake mysql:build_databases +You can run all the tests for a given database via rake: -To setup the testing environment for PostgreSQL use this command: + $ rake test_mysql - rake postgresql:build_databases +The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql. -The incantation for running a particular test looks like this +== Identity Map - rake test TEST=test/cases/datatype_test_postgresql.rb TESTOPTS="-n test_timestamp_with_zone_values_without_rails_time_zone_support" +By default the tests run with the Identity Map turned off. But all tests should pass whether or +not the identity map is on or off. You can turn it on using the IM env variable: + $ IM=true ruby -Itest test/case/base_test.rb + +== Config file + +By default, the config file is expected to be at the path test/config.yml. You can specify a +custom location with the ARCONFIG environment variable. diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 346c7e8142..d769a73dba 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -4,8 +4,7 @@ require 'rake/packagetask' require 'rubygems/package_task' require File.expand_path(File.dirname(__FILE__)) + "/test/config" - -MYSQL_DB_USER = 'rails' +require File.expand_path(File.dirname(__FILE__)) + "/test/support/config" def run_without_aborting(*tasks) errors = [] @@ -43,9 +42,8 @@ end %w( mysql mysql2 postgresql sqlite3 sqlite3_mem firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| Rake::TestTask.new("test_#{adapter}") { |t| - connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}" adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] - t.libs << "test" << connection_path + t.libs << 'test' t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { |x| x =~ /\/adapters\// } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort @@ -55,21 +53,27 @@ end } task "isolated_test_#{adapter}" do - connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}" adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] - puts [adapter, adapter_short, connection_path].inspect + puts [adapter, adapter_short].inspect ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) (Dir["test/cases/**/*_test.rb"].reject { |x| x =~ /\/adapters\// } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| - sh(ruby, "-Ilib:test:#{connection_path}", file) + sh(ruby, "-Itest", file) end or raise "Failures" end namespace adapter do task :test => "test_#{adapter}" task :isolated_test => "isolated_test_#{adapter}" + + # Set the connection environment for the adapter + task(:env) { ENV['ARCONN'] = adapter } end + + # Make sure the adapter test evaluates the env setting task + task "test_#{adapter}" => "#{adapter}:env" + task "isolated_test_#{adapter}" => "#{adapter}:env" end rule '.sqlite3' do |t| @@ -84,14 +88,16 @@ task :test_sqlite3 => [ namespace :mysql do desc 'Build the MySQL test databases' task :build_databases do - %x( mysql --user=#{MYSQL_DB_USER} -e "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") - %x( mysql --user=#{MYSQL_DB_USER} -e "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + config = ARTest.config['connections']['mysql'] + %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") end desc 'Drop the MySQL test databases' task :drop_databases do - %x( mysqladmin --user=#{MYSQL_DB_USER} -f drop activerecord_unittest ) - %x( mysqladmin --user=#{MYSQL_DB_USER} -f drop activerecord_unittest2 ) + config = ARTest.config['connections']['mysql'] + %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} ) + %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} ) end desc 'Rebuild the MySQL test databases' @@ -106,14 +112,16 @@ task :rebuild_mysql_databases => 'mysql:rebuild_databases' namespace :postgresql do desc 'Build the PostgreSQL test databases' task :build_databases do - %x( createdb -E UTF8 activerecord_unittest ) - %x( createdb -E UTF8 activerecord_unittest2 ) + config = ARTest.config['connections']['postgresql'] + %x( createdb -E UTF8 #{config['arunit']['database']} ) + %x( createdb -E UTF8 #{config['arunit2']['database']} ) end desc 'Drop the PostgreSQL test databases' task :drop_databases do - %x( dropdb activerecord_unittest ) - %x( dropdb activerecord_unittest2 ) + config = ARTest.config['connections']['postgresql'] + %x( dropdb #{config['arunit']['database']} ) + %x( dropdb #{config['arunit2']['database']} ) end desc 'Rebuild the PostgreSQL test databases' @@ -152,8 +160,9 @@ namespace :frontbase do DISCONNECT ALL; ) end - create_activerecord_unittest = build_frontbase_database['activerecord_unittest', File.join(SCHEMA_ROOT, 'frontbase.sql')] - create_activerecord_unittest2 = build_frontbase_database['activerecord_unittest2', File.join(SCHEMA_ROOT, 'frontbase2.sql')] + config = ARTest.config['connections']['frontbase'] + create_activerecord_unittest = build_frontbase_database[config['arunit']['database'], File.join(SCHEMA_ROOT, 'frontbase.sql')] + create_activerecord_unittest2 = build_frontbase_database[config['arunit2']['database'], File.join(SCHEMA_ROOT, 'frontbase2.sql')] execute_frontbase_sql = Proc.new do |sql| system(<<-SHELL) /Library/FrontBase/bin/sql92 <<-SQL diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3acfa5f729..2cbfa42718 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -452,12 +452,12 @@ module ActiveRecord # end # # Some extensions can only be made to work with knowledge of the association's internals. - # Extensions can access relevant state using the following methods (where 'items' is the + # Extensions can access relevant state using the following methods (where +items+ is the # name of the association): # - # * +record.association(:items).owner+ - Returns the object the association is part of. - # * +record.association(:items).reflection+ - Returns the reflection object that describes the association. - # * +record.association(:items).target+ - Returns the associated object for +belongs_to+ and +has_one+, or + # * <tt>record.association(:items).owner</tt> - Returns the object the association is part of. + # * <tt>record.association(:items).reflection</tt> - Returns the reflection object that describes the association. + # * <tt>record.association(:items).target</tt> - Returns the associated object for +belongs_to+ and +has_one+, or # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+. # # === Association Join Models diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 687b668634..c3aee90124 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -141,7 +141,7 @@ module ActiveRecord @target ||= find_target end end - loaded! + loaded! unless loaded? target rescue ActiveRecord::RecordNotFound reset @@ -177,9 +177,7 @@ module ActiveRecord # Sets the owner attributes on the given record def set_owner_attributes(record) - if owner.persisted? - creation_attributes.each { |key, value| record[key] = value } - end + creation_attributes.each { |key, value| record[key] = value } end # Should be true if there is a foreign key present on the owner which diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 02cc455a4e..337a0d48f1 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -114,19 +114,13 @@ module ActiveRecord # Add +records+ to this association. Returns +self+ so method calls may be chained. # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically. def concat(*records) - result = true load_target if owner.new_record? - transaction do - records.flatten.each do |record| - raise_on_type_mismatch(record) - add_to_target(record) do |r| - result &&= insert_record(record) unless owner.new_record? - end - end + if owner.new_record? + concat_records(records) + else + transaction { concat_records(records) } end - - result && records end # Starts a transaction in the association class's database connection. @@ -295,14 +289,10 @@ module ActiveRecord other_array.each { |val| raise_on_type_mismatch(val) } original_target = load_target.dup - transaction do - delete(target - other_array) - - unless concat(other_array - target) - @target = original_target - raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ - "new records could not be saved." - end + if owner.new_record? + replace_records(other_array, original_target) + else + transaction { replace_records(other_array, original_target) } end end @@ -436,7 +426,6 @@ module ActiveRecord def build_record(attributes, options) record = reflection.build_association(attributes, options) record.assign_attributes(create_scope.except(*record.changed), :without_protection => true) - record.assign_attributes(attributes, options) record end @@ -445,14 +434,20 @@ module ActiveRecord records.each { |record| raise_on_type_mismatch(record) } existing_records = records.reject { |r| r.new_record? } - transaction do - records.each { |record| callback(:before_remove, record) } + if existing_records.empty? + remove_records(existing_records, records, method) + else + transaction { remove_records(existing_records, records, method) } + end + end - delete_records(existing_records, method) if existing_records.any? - records.each { |record| target.delete(record) } + def remove_records(existing_records, records, method) + records.each { |record| callback(:before_remove, record) } - records.each { |record| callback(:after_remove, record) } - end + delete_records(existing_records, method) if existing_records.any? + records.each { |record| target.delete(record) } + + records.each { |record| callback(:after_remove, record) } end # Delete the given records from the association, using one of the methods :destroy, @@ -461,6 +456,29 @@ module ActiveRecord raise NotImplementedError end + def replace_records(new_target, original_target) + delete(target - new_target) + + unless concat(new_target - target) + @target = original_target + raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ + "new records could not be saved." + end + end + + def concat_records(records) + result = true + + records.flatten.each do |record| + raise_on_type_mismatch(record) + add_to_target(record) do |r| + result &&= insert_record(record) unless owner.new_record? + end + end + + result && records + end + def callback(method, record) callbacks_for(method).each do |callback| case callback diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 81b4a26b04..5267116c42 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -67,7 +67,7 @@ module ActiveRecord def method_missing(method, *args, &block) match = DynamicFinderMatch.match(method) if match && match.instantiator? - record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| + send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| @association.send :set_owner_attributes, r @association.send :add_to_target, r yield(r) if block_given? diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 2f3a6e71f1..6b8201973a 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -14,8 +14,8 @@ module ActiveRecord end if record - set_inverse_instance(record) set_owner_attributes(record) + set_inverse_instance(record) if owner.persisted? && save && !record.save nullify_owner_attributes(record) diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 5f06452247..e3f221c773 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -48,7 +48,7 @@ module ActiveRecord end attr_accessor :original_primary_key - + # Attribute writer for the primary key column def primary_key=(value) @quoted_primary_key = nil diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 08cc282d09..6a5d282973 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -428,10 +428,6 @@ module ActiveRecord #:nodoc: class_attribute :default_scopes, :instance_writer => false self.default_scopes = [] - # Boolean flag to prevent infinite recursion when evaluating default scopes - class_attribute :apply_default_scope, :instance_writer => false - self.apply_default_scope = true - # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. class_attribute :serialized_attributes @@ -659,8 +655,8 @@ module ActiveRecord #:nodoc: def set_table_name(value = nil, &block) @quoted_table_name = nil define_attr_method :table_name, value, &block + @arel_table = nil - @arel_table = Arel::Table.new(table_name, arel_engine) @relation = Relation.new(self, arel_table) end alias :table_name= :set_table_name @@ -892,7 +888,7 @@ module ActiveRecord #:nodoc: end def arel_table - Arel::Table.new(table_name, arel_engine) + @arel_table ||= Arel::Table.new(table_name, arel_engine) end def arel_engine @@ -1265,11 +1261,11 @@ MSG self.default_scopes = default_scopes + [scope] end - # The apply_default_scope flag is used to prevent an infinite recursion situation where + # The @ignore_default_scope flag is used to prevent an infinite recursion situation where # a default scope references a scope which has a default scope which references a scope... def build_default_scope #:nodoc: - return unless apply_default_scope - self.apply_default_scope = false + return if defined?(@ignore_default_scope) && @ignore_default_scope + @ignore_default_scope = true if method(:default_scope).owner != Base.singleton_class default_scope @@ -1285,7 +1281,7 @@ MSG end end ensure - self.apply_default_scope = true + @ignore_default_scope = false end # Returns the class type of the record using the current module as a prefix. So descendants of @@ -1717,10 +1713,13 @@ MSG attributes.each do |k, v| if k.include?("(") multi_parameter_attributes << [ k, v ] - elsif respond_to?("#{k}=") - send("#{k}=", v) else - raise(UnknownAttributeError, "unknown attribute: #{k}") + method_name = "#{k}=" + if respond_to?(method_name) + method(method_name).arity == -2 ? send(method_name, v, options) : send(method_name, v) + else + raise(UnknownAttributeError, "unknown attribute: #{k}") + end end end @@ -2006,7 +2005,7 @@ MSG set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]} begin Date.new(*set_values) - rescue ArgumentError => ex # if Date.new raises an exception on an invalid date + rescue ArgumentError # if Date.new raises an exception on an invalid date instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 8ffd40f7e5..090f6bc6fe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -119,6 +119,7 @@ module ActiveRecord with_connection do |conn| conn.tables.each { |table| @tables[table] = true } + @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name) end @tables.key? name @@ -425,6 +426,14 @@ module ActiveRecord @testing = testing end + def method_missing(method_sym, *arguments, &block) + @body.send(method_sym, *arguments, &block) + end + + def respond_to?(method_sym, include_private = false) + super || @body.respond_to?(method_sym) + end + def each(&block) body.each(&block) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 70a8f6bb58..82f564e41d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -328,7 +328,7 @@ module ActiveRecord end # Checks to see if a column exists. See SchemaStatements#column_exists? - def column_exists?(column_name, type = nil, options = nil) + def column_exists?(column_name, type = nil, options = {}) @base.column_exists?(@table_name, column_name, type, options) end @@ -386,13 +386,13 @@ module ActiveRecord # Removes the given index from the table. # # ===== Examples - # ====== Remove the suppliers_name_index in the suppliers table - # t.remove_index :name - # ====== Remove the index named accounts_branch_id_index in the accounts table + # ====== Remove the index_table_name_on_column in the table_name table + # t.remove_index :column + # ====== Remove the index named index_table_name_on_branch_id in the table_name table # t.remove_index :column => :branch_id - # ====== Remove the index named accounts_branch_id_party_id_index in the accounts table + # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table # t.remove_index :column => [:branch_id, :party_id] - # ====== Remove the index named by_branch_party in the accounts table + # ====== Remove the index named by_branch_party in the table_name table # t.remove_index :name => :by_branch_party def remove_index(options = {}) @base.remove_index(@table_name, options) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 74c07c624d..8e3ba1297e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -346,11 +346,11 @@ module ActiveRecord # Remove the given index from the table. # - # Remove the suppliers_name_index in the suppliers table. - # remove_index :suppliers, :name - # Remove the index named accounts_branch_id_index in the accounts table. + # Remove the index_accounts_on_column in the accounts table. + # remove_index :accounts, :column + # Remove the index named index_accounts_on_branch_id in the accounts table. # remove_index :accounts, :column => :branch_id - # Remove the index named accounts_branch_id_party_id_index in the accounts table. + # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table. # remove_index :accounts, :column => [:branch_id, :party_id] # Remove the index named by_branch_party in the accounts table. # remove_index :accounts, :name => :by_branch_party diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 3eddb69e73..a7856539b7 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -1,3 +1,5 @@ +require 'set' + module ActiveRecord # :stopdoc: module ConnectionAdapters diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 24d8c8cad2..d6c167ad36 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ # encoding: utf-8 -gem 'mysql2', '~> 0.3.0' +gem 'mysql2', '~> 0.3.6' require 'mysql2' module ActiveRecord diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 8d7dd8bacf..9e6cb13cca 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -707,7 +707,7 @@ module ActiveRecord def quoted_columns_for_index(column_names, options = {}) length = options[:length] if options.is_a?(Hash) - quoted_column_names = case length + case length when Hash column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } when Fixnum diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3e390ba994..29dd0a9ea3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -466,10 +466,11 @@ module ActiveRecord # Executes an INSERT query and returns the new record's ID def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - # Extract the table from the insert sql. Yuck. - _, table = extract_schema_and_table(sql.split(" ", 4)[2]) - - pk ||= primary_key(table) + unless pk + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref + end if pk select_value("#{sql} RETURNING #{quote_column_name(pk)}") @@ -565,9 +566,9 @@ module ActiveRecord def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk - _, table = extract_schema_and_table(sql.split(" ", 4)[2]) - - pk = primary_key(table) + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref end sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk @@ -665,34 +666,33 @@ module ActiveRecord SQL end + # Returns true if table exists. + # If the schema is not specified as part of +name+ then it will only find tables within + # the current schema search path (regardless of permissions to access tables in other schemas) def table_exists?(name) schema, table = extract_schema_and_table(name.to_s) + return false unless table - binds = [[nil, table.gsub(/(^"|"$)/,'')]] + binds = [[nil, table]] binds << [nil, schema] if schema exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 - SELECT COUNT(*) - FROM pg_tables - WHERE tablename = $1 - #{schema ? "AND schemaname = $2" : ''} + SELECT COUNT(*) + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind in ('v','r') + AND c.relname = $1 + AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} SQL end - # Extracts the table and schema name from +name+ - def extract_schema_and_table(name) - schema, table = name.split('.', 2) - - unless table # A table was provided without a schema - table = schema - schema = nil - end - - if name =~ /^"/ # Handle quoted table names - table = name - schema = nil - end - [schema, table] + # Returns true if schema exists. + def schema_exists?(name) + exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0 + SELECT COUNT(*) + FROM pg_namespace + WHERE nspname = $1 + SQL end # Returns an array of indexes for the given table. @@ -742,6 +742,11 @@ module ActiveRecord query('select current_database()')[0][0] end + # Returns the current schema name. + def current_schema + query('SELECT current_schema', 'SCHEMA')[0][0] + end + # Returns the current database encoding format. def encoding query(<<-end_sql)[0][0] @@ -806,7 +811,7 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) - + select_value <<-end_sql, 'Reset sequence' SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql @@ -835,7 +840,7 @@ module ActiveRecord else sequence = result.second+'.'+result.last end - + [result.first, sequence] rescue nil @@ -935,7 +940,7 @@ module ActiveRecord # Construct a clean list of column names from the ORDER BY clause, removing # any ASC/DESC modifiers - order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s } + order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') } order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } @@ -960,27 +965,27 @@ module ActiveRecord end private - def exec_no_cache(sql, binds) - @connection.async_exec(sql) - end - - def exec_cache(sql, binds) - unless @statements.key? sql - nextkey = "a#{@statements.length + 1}" - @connection.prepare nextkey, sql - @statements[sql] = nextkey + def exec_no_cache(sql, binds) + @connection.async_exec(sql) end - key = @statements[sql] + def exec_cache(sql, binds) + unless @statements.key? sql + nextkey = "a#{@statements.length + 1}" + @connection.prepare nextkey, sql + @statements[sql] = nextkey + end - # Clear the queue - @connection.get_last_result - @connection.send_query_prepared(key, binds.map { |col, val| - type_cast(val, col) - }) - @connection.block - @connection.get_last_result - end + key = @statements[sql] + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result + end # The internal PostgreSQL identifier of the money data type. MONEY_COLUMN_TYPE_OID = 790 #:nodoc: @@ -1080,9 +1085,29 @@ module ActiveRecord end end - def table_definition - TableDefinition.new(self) - end + # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. + # +schema_name+ is nil if not specified in +name+. + # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) + # +name+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * <tt>table_name</tt> + # * <tt>"table.name"</tt> + # * <tt>schema_name.table_name</tt> + # * <tt>schema_name."table.name"</tt> + # * <tt>"schema.name"."table name"</tt> + def extract_schema_and_table(name) + table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse + [schema, table] + end + + def extract_table_ref_from_insert_sql(sql) + sql[/into\s+([^\(]*).*values\s*\(/i] + $1.strip if $1 + end + + def table_definition + TableDefinition.new(self) + end end end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 640111096d..3d1bc5c1e0 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -53,7 +53,7 @@ module ActiveRecord # # This migration will add a boolean flag to the accounts table and remove it # if you're backing out of the migration. It shows how all migrations have - # two class methods +up+ and +down+ that describes the transformations + # two methods +up+ and +down+ that describes the transformations # required to implement or remove the migration. These methods can consist # of both the migration specific methods like add_column and remove_column, # but may also contain regular Ruby code for generating data needed for the @@ -116,8 +116,10 @@ module ActiveRecord # with the name of the column. Other options include # <tt>:name</tt> and <tt>:unique</tt> (e.g. # <tt>{ :name => "users_name_index", :unique => true }</tt>). - # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified - # by +index_name+. + # * <tt>remove_index(table_name, :column => column_name)</tt>: Removes the index + # specified by +column_name+. + # * <tt>remove_index(table_name, :name => index_name)</tt>: Removes the index + # specified by +index_name+. # # == Irreversible transformations # diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index ed8e5ae140..f01e94169f 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -1,6 +1,6 @@ module ActiveRecord class Migration - # ActiveRecord::Migration::CommandRecorder records commands done during + # <tt>ActiveRecord::Migration::CommandRecorder</tt> records commands done during # a migration and knows how to reverse those commands. The CommandRecorder # knows how to invert the following commands: # @@ -23,7 +23,7 @@ module ActiveRecord # record +command+. +command+ should be a method name and arguments. # For example: # - # recorder.record(:method_name, [:arg1, arg2]) + # recorder.record(:method_name, [:arg1, :arg2]) def record(*command) @commands << command end @@ -34,7 +34,7 @@ module ActiveRecord # recorder.record(:rename_table, [:old, :new]) # recorder.inverse # => [:rename_table, [:new, :old]] # - # This method will raise an IrreversibleMigration exception if it cannot + # This method will raise an +IrreversibleMigration+ exception if it cannot # invert the +commands+. def inverse @commands.reverse.map { |name, args| @@ -50,9 +50,9 @@ module ActiveRecord [:create_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 - def #{method}(*args) - record(:"#{method}", args) - end + def #{method}(*args) # def create_table(*args) + record(:"#{method}", args) # record(:create_table, args) + end # end EOV end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 08b27b6a8e..f51fd21077 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -276,15 +276,15 @@ module ActiveRecord type = (reflection.collection? ? :collection : :one_to_one) - # def pirate_attributes=(attributes) - # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) + # def pirate_attributes=(attributes, assignment_opts = {}) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes, assignment_opts) # end 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) + def #{association_name}_attributes=(attributes, assignment_opts = {}) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, assignment_opts) end eoruby else @@ -319,21 +319,21 @@ module ActiveRecord # If the given attributes include a matching <tt>:id</tt> attribute, or # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value, # then the existing record will be marked for destruction. - def assign_nested_attributes_for_one_to_one_association(association_name, attributes) + def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {}) options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) && (options[:update_only] || record.id.to_s == attributes['id'].to_s) - assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes) - elsif attributes['id'].present? + elsif attributes['id'].present? && !assignment_opts[:without_protection] raise_nested_attributes_record_not_found(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) - send(method, attributes.except(*UNASSIGNABLE_KEYS)) + send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) else raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" end @@ -367,7 +367,7 @@ module ActiveRecord # { :name => 'John' }, # { :id => '2', :_destroy => true } # ]) - def assign_nested_attributes_for_collection_association(association_name, attributes_collection) + def assign_nested_attributes_for_collection_association(association_name, attributes_collection, assignment_opts = {}) options = self.nested_attributes_options[association_name] unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) @@ -401,7 +401,7 @@ module ActiveRecord if attributes['id'].blank? unless reject_new_record?(association_name, attributes) - association.build(attributes.except(*UNASSIGNABLE_KEYS)) + association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } unless association.loaded? || call_reject_if(association_name, attributes) @@ -418,8 +418,10 @@ module ActiveRecord end if !call_reject_if(association_name, attributes) - assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy], assignment_opts) end + elsif assignment_opts[:without_protection] + association.build(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) else raise_nested_attributes_record_not_found(association_name, attributes['id']) end @@ -428,8 +430,8 @@ module ActiveRecord # Updates a record with the +attributes+ or marks it for destruction if # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. - def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) - record.attributes = attributes.except(*UNASSIGNABLE_KEYS) + def assign_to_or_mark_for_destruction(record, attributes, allow_destroy, assignment_opts) + record.assign_attributes(attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy end @@ -458,5 +460,9 @@ module ActiveRecord def raise_nested_attributes_record_not_found(association_name, record_id) raise RecordNotFound, "Couldn't find #{self.class.reflect_on_association(association_name).klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}" end + + def unassignable_keys(assignment_opts) + assignment_opts[:without_protection] ? UNASSIGNABLE_KEYS - %w[id] : UNASSIGNABLE_KEYS + end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 4ec0431b8c..367a1be4d9 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -279,7 +279,7 @@ module ActiveRecord @changed_attributes.except!(*changes.keys) primary_key = self.class.primary_key - self.class.update_all(changes, { primary_key => self[primary_key] }) == 1 + self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1 end end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 4e61671473..e485901440 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -33,6 +33,14 @@ module ActiveRecord @target = target end + def method_missing(method_sym, *arguments, &block) + @target.send(method_sym, *arguments, &block) + end + + def respond_to?(method_sym, include_private = false) + super || @target.respond_to?(method_sym) + end + def each(&block) @target.each(&block) end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6f8f84d50b..399f3c5d03 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -369,7 +369,7 @@ db_namespace = namespace :db do ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password'] search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? - search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") + search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ") end `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}` raise 'Error dumping database' if $?.exitstatus == 1 @@ -377,8 +377,7 @@ db_namespace = namespace :db do dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile'] `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql` when 'sqlserver' - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r` - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r` + `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\#{Rails.env}_structure.sql -A -U` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) @@ -423,7 +422,7 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` when 'sqlserver' - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql` when 'oci', 'oracle' ActiveRecord::Base.establish_connection(:test) IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl| @@ -453,9 +452,11 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] File.delete(dbfile) if File.exist?(dbfile) when 'sqlserver' - dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}` - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + test = abcs.deep_dup['test'] + test_database = test['database'] + test['database'] = 'master' + ActiveRecord::Base.establish_connection(test) + ActiveRecord::Base.connection.recreate_database!(test_database) when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| @@ -498,7 +499,7 @@ namespace :railties do # desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2" task :migrations => :'db:load_config' do to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } - railties = {} + railties = ActiveSupport::OrderedHash.new Rails.application.railties.all do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index aabe5c269b..9c5f603c01 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -146,10 +146,16 @@ module ActiveRecord if options.except(:distinct).present? apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct]) else - if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) - construct_relation_for_association_calculations.calculate(operation, column_name, options) + relation = with_default_scope + + if relation.equal?(self) + if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) + construct_relation_for_association_calculations.calculate(operation, column_name, options) + else + perform_calculation(operation, column_name, options) + end else - perform_calculation(operation, column_name, options) + relation.calculate(operation, column_name, options) end end rescue ThrowResult @@ -196,7 +202,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = with_default_scope.reorder(nil) + relation = reorder(nil) if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. @@ -245,7 +251,7 @@ module ActiveRecord "#{field} AS #{aliaz}" } - relation = with_default_scope.except(:group).group(group.join(',')) + relation = except(:group).group(group.join(',')) relation.select_values = select_values calculated_data = @klass.connection.select_all(relation.to_sql) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 739363415c..7fbbefeea2 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -305,9 +305,18 @@ module ActiveRecord def reverse_sql_order(order_query) order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? - order_query.join(', ').split(',').collect do |s| - s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') - end + order_query.map do |o| + case o + when Arel::Nodes::Ordering + o.reverse + when String, Symbol + o.to_s.split(',').collect do |s| + s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + end + else + o + end + end.flatten end def array_of_strings?(o) diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index c61428e104..ffe9b08dce 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -31,27 +31,30 @@ module ActiveRecord end def assert_sql(*patterns_to_match) - $queries_executed = [] + ActiveRecord::SQLCounter.log = [] yield - $queries_executed + ActiveRecord::SQLCounter.log ensure failed_patterns = [] patterns_to_match.each do |pattern| - failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql } + failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql } end - assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}" + assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" end def assert_queries(num = 1) - $queries_executed = [] + ActiveRecord::SQLCounter.log = [] yield ensure - %w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) } - assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}" + assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" end def assert_no_queries(&block) + prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql + ActiveRecord::SQLCounter.ignored_sql = [] assert_queries(0, &block) + ensure + ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql end def with_kcode(kcode) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index d363f36108..ae97a3f3ca 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -165,7 +165,7 @@ module ActiveRecord # writing, the only database that we're aware of that supports true nested # transactions, is MS-SQL. Because of this, Active Record emulates nested # transactions by using savepoints on MySQL and PostgreSQL. See - # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html + # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html # for more information about savepoints. # # === Callbacks diff --git a/activerecord/test/.gitignore b/activerecord/test/.gitignore new file mode 100644 index 0000000000..a0ec5967dd --- /dev/null +++ b/activerecord/test/.gitignore @@ -0,0 +1 @@ +/config.yml diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 49b2e945c3..07de544868 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -43,7 +43,7 @@ class AdapterTest < ActiveRecord::TestCase def test_current_database if @connection.respond_to?(:current_database) - assert_equal ENV['ARUNIT_DB_NAME'] || "activerecord_unittest", @connection.current_database + assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database end end @@ -68,7 +68,12 @@ class AdapterTest < ActiveRecord::TestCase begin assert_nothing_raised do ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database)) - ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses" + + config = ARTest.connection_config + ActiveRecord::Base.connection.execute( + "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \ + "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses" + ) end ensure ActiveRecord::Base.establish_connection 'arunit' diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb index 9673e2bb46..3d1330efb8 100644 --- a/activerecord/test/cases/adapters/mysql/quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -23,4 +23,3 @@ module ActiveRecord end end end - diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 752b864818..3a9744e78f 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -87,8 +87,8 @@ class MysqlReservedWordTest < ActiveRecord::TestCase assert_nothing_raised { x.save } x.order = 'y' assert_nothing_raised { x.save } - assert_nothing_raised { y = Group.find_by_order('y') } - assert_nothing_raised { y = Group.find(1) } + assert_nothing_raised { Group.find_by_order('y') } + assert_nothing_raised { Group.find(1) } x = Group.find(1) end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 7c49236854..d57794daf8 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -10,6 +10,45 @@ module ActiveRecord @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end + def test_primary_key + assert_equal 'id', @connection.primary_key('ex') + end + + def test_non_standard_primary_key + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(data character varying(255) primary key)') + assert_equal 'data', @connection.primary_key('ex') + end + + def test_primary_key_returns_nil_for_no_pk + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer)') + assert_nil @connection.primary_key('ex') + end + + def test_primary_key_raises_error_if_table_not_found + assert_raises(ActiveRecord::StatementInvalid) do + @connection.primary_key('unobtainium') + end + end + + def test_insert_sql_with_proprietary_returning_clause + id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") + assert_equal "5150", id + end + + def test_insert_sql_with_quoted_schema_and_table_name + id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end + + def test_insert_sql_with_no_space_after_table_name + id = @connection.insert_sql("insert into ex(number) values(5150)") + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end + def test_serial_sequence assert_equal 'public.accounts_id_seq', @connection.serial_sequence('accounts', 'id') @@ -35,6 +74,36 @@ module ActiveRecord @connection.default_sequence_name('zomg') end + def test_pk_and_sequence_for + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @connection.default_sequence_name('ex', 'id'), seq + end + + def test_pk_and_sequence_for_with_non_standard_primary_key + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(code serial primary key)') + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @connection.default_sequence_name('ex', 'code'), seq + end + + def test_pk_and_sequence_for_returns_nil_if_no_seq + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer primary key)') + assert_nil @connection.pk_and_sequence_for('ex') + end + + def test_pk_and_sequence_for_returns_nil_if_no_pk + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer)') + assert_nil @connection.pk_and_sequence_for('ex') + end + + def test_pk_and_sequence_for_returns_nil_if_table_not_found + assert_nil @connection.pk_and_sequence_for('unobtainium') + end + def test_exec_insert_number insert(@connection, 'number' => 10) diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index a5c3e69af9..27e7b1a1c3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -20,6 +20,7 @@ class SchemaTest < ActiveRecord::TestCase 'email character varying(50)', 'moment timestamp without time zone default now()' ] + PK_TABLE_NAME = 'table_with_pk' class Thing1 < ActiveRecord::Base set_table_name "test_schema.things" @@ -49,6 +50,7 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" + @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" end def teardown @@ -63,12 +65,30 @@ class SchemaTest < ActiveRecord::TestCase end end + def test_table_exists_when_on_schema_search_path + with_schema_search_path(SCHEMA_NAME) do + assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found") + end + end + + def test_table_exists_when_not_on_schema_search_path + with_schema_search_path('PUBLIC') do + assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found") + end + end + def test_table_exists_wrong_schema assert(!@connection.table_exists?("foo.things"), "table should not exist") end - def test_table_exists_quoted_table - assert(@connection.table_exists?('"things.table"'), "table should exist") + def test_table_exists_quoted_names + [ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given| + assert(@connection.table_exists?(given), "table should exist when specified as #{given}") + end + with_schema_search_path(SCHEMA_NAME) do + given = %("#{TABLE_NAME}") + assert(@connection.table_exists?(given), "table should exist when specified as #{given}") + end end def test_with_schema_prefixed_table_name @@ -91,7 +111,6 @@ class SchemaTest < ActiveRecord::TestCase end end - def test_proper_encoding_of_table_name assert_equal '"table_name"', @connection.quote_table_name('table_name') assert_equal '"table.name"', @connection.quote_table_name('"table.name"') @@ -164,6 +183,79 @@ class SchemaTest < ActiveRecord::TestCase ActiveRecord::Base.connection.schema_search_path = "public" end + def test_primary_key_with_schema_specified + [ + %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) + ].each do |given| + assert_equal 'id', @connection.primary_key(given), "primary key should be found when table referenced as #{given}" + end + end + + def test_primary_key_assuming_schema_search_path + with_schema_search_path(SCHEMA_NAME) do + assert_equal 'id', @connection.primary_key(PK_TABLE_NAME), "primary key should be found" + end + end + + def test_primary_key_raises_error_if_table_not_found_on_schema_search_path + with_schema_search_path(SCHEMA2_NAME) do + assert_raises(ActiveRecord::StatementInvalid) do + @connection.primary_key(PK_TABLE_NAME) + end + end + end + + def test_pk_and_sequence_for_with_schema_specified + [ + %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) + ].each do |given| + pk, seq = @connection.pk_and_sequence_for(given) + assert_equal 'id', pk, "primary key should be found when table referenced as #{given}" + assert_equal "#{SCHEMA_NAME}.#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" + end + end + + def test_extract_schema_and_table + { + %(table_name) => [nil,'table_name'], + %("table.name") => [nil,'table.name'], + %(schema.table_name) => %w{schema table_name}, + %("schema".table_name) => %w{schema table_name}, + %(schema."table_name") => %w{schema table_name}, + %("schema"."table_name") => %w{schema table_name}, + %("even spaces".table) => ['even spaces','table'], + %(schema."table.name") => ['schema', 'table.name'] + }.each do |given,expect| + assert_equal expect, @connection.send(:extract_schema_and_table, given) + end + end + + def test_current_schema + { + %('$user',public) => 'public', + SCHEMA_NAME => SCHEMA_NAME, + %(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME, + %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => 'public' + }.each do |given,expect| + with_schema_search_path(given) { assert_equal expect, @connection.current_schema } + end + end + + def test_schema_exists? + { + 'public' => true, + SCHEMA_NAME => true, + SCHEMA2_NAME => true, + 'darkside' => false + }.each do |given,expect| + assert_equal expect, @connection.schema_exists?(given) + end + end + private def columns(table_name) @connection.send(:column_definitions, table_name).map do |name, type, default| diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb new file mode 100644 index 0000000000..303ba9245a --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -0,0 +1,49 @@ +require "cases/helper" + +class ViewTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + SCHEMA_NAME = 'test_schema' + TABLE_NAME = 'things' + VIEW_NAME = 'view_things' + COLUMNS = [ + 'id integer', + 'name character varying(50)', + 'email character varying(50)', + 'moment timestamp without time zone' + ] + + class ThingView < ActiveRecord::Base + set_table_name 'test_schema.view_things' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" + @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})" + @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}" + end + + def teardown + @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" + end + + def test_table_exists + name = ThingView.table_name + assert @connection.table_exists?(name), "'#{name}' table should exist" + end + + def test_column_definitions + assert_nothing_raised do + assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}") + end + end + + private + def columns(table_name) + @connection.send(:column_definitions, table_name).map do |name, type, default| + "#{name} #{type}" + (default ? " default #{default}" : '') + end + end + +end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 5a900e0605..7338513b41 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -296,6 +296,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 1, Topic.find(topic.id)[:replies_count] end + def test_belongs_to_counter_when_update_column + topic = Topic.create!(:title => "37s") + topic.replies.create!(:title => "re: 37s", :content => "rails") + assert_equal 1, Topic.find(topic.id)[:replies_count] + + topic.update_column(:content, "rails is wonderfull") + assert_equal 1, Topic.find(topic.id)[:replies_count] + end + def test_assignment_before_child_saved final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index e5735988d0..d8d2a113ff 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -100,7 +100,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 'c1', record[0] assert_equal 't1', record[1] end - + def test_proper_usage_of_primary_keys_and_join_table setup_data_for_habtm_case diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 49999630b6..9af1f7249f 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -537,6 +537,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 3, companies(:first_firm).clients_of_firm(true).size end + def test_transactions_when_adding_to_persisted + good = Client.new(:name => "Good") + bad = Client.new(:name => "Bad", :raise_on_save => true) + + begin + companies(:first_firm).clients_of_firm.concat(good, bad) + rescue Client::RaisedOnSave + end + + assert !companies(:first_firm).clients_of_firm(true).include?(good) + end + + def test_transactions_when_adding_to_new_record + assert_no_queries do + firm = Firm.new + firm.clients_of_firm.concat(Client.new("name" => "Natural Company")) + end + end + def test_new_aliased_to_build company = companies(:first_firm) new_client = assert_no_queries { company.clients_of_firm.new("name" => "Another Client") } @@ -778,6 +797,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, companies(:first_firm).clients_of_firm(true).size end + def test_transaction_when_deleting_persisted + good = Client.new(:name => "Good") + bad = Client.new(:name => "Bad", :raise_on_destroy => true) + + companies(:first_firm).clients_of_firm = [good, bad] + + begin + companies(:first_firm).clients_of_firm.destroy(good, bad) + rescue Client::RaisedOnDestroy + end + + assert_equal [good, bad], companies(:first_firm).clients_of_firm(true) + end + + def test_transaction_when_deleting_new_record + assert_no_queries do + firm = Firm.new + client = Client.new("name" => "New Client") + firm.clients_of_firm << client + firm.clients_of_firm.destroy(client) + end + end + def test_clearing_an_association_collection firm = companies(:first_firm) client_id = firm.clients_of_firm.first.id @@ -1111,6 +1153,27 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal orig_accounts, firm.accounts end + def test_transactions_when_replacing_on_persisted + good = Client.new(:name => "Good") + bad = Client.new(:name => "Bad", :raise_on_save => true) + + companies(:first_firm).clients_of_firm = [good] + + begin + companies(:first_firm).clients_of_firm = [bad] + rescue Client::RaisedOnSave + end + + assert_equal [good], companies(:first_firm).clients_of_firm(true) + end + + def test_transactions_when_replacing_on_new_record + assert_no_queries do + firm = Firm.new + firm.clients_of_firm = [Client.new("name" => "New Client")] + end + end + def test_get_ids assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index b59ce4efeb..4ce8b85098 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -139,7 +139,21 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_set_polymorphic_has_one tagging = tags(:misc).taggings.create posts(:thinking).tagging = tagging - assert_equal "Post", tagging.taggable_type + + assert_equal "Post", tagging.taggable_type + assert_equal posts(:thinking).id, tagging.taggable_id + assert_equal posts(:thinking), tagging.taggable + end + + def test_set_polymorphic_has_one_on_new_record + tagging = tags(:misc).taggings.create + post = Post.new :title => "foo", :body => "bar" + post.tagging = tagging + post.save! + + assert_equal "Post", tagging.taggable_type + assert_equal post.id, tagging.taggable_id + assert_equal post, tagging.taggable end def test_create_polymorphic_has_many_with_scope @@ -708,8 +722,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_with_pluralize_table_names_false - engine = Engine.create!(:car_id => 1) - aircraft = Aircraft.create!(:name => "Airbus 380", :id => 1) + aircraft = Aircraft.create!(:name => "Airbus 380") + engine = Engine.create!(:car_id => aircraft.id) assert_equal aircraft.engines, [engine] end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 8f55b7ebe6..4ad2cdfc7e 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -837,7 +837,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase @pirate.parrots.each { |parrot| parrot.mark_for_destruction } assert @pirate.save - assert_no_queries do + assert_queries(0) do assert @pirate.save end end diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index 85871aebdf..a1d1177289 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -77,6 +77,13 @@ module ActiveRecord @management.call(@env) assert ActiveRecord::Base.connection_handler.active_connections? end + + test "proxy is polite to it's body and responds to it" do + body = Class.new(String) { def to_path; "/path"; end }.new + proxy = ConnectionManagement::Proxy.new(body) + assert proxy.respond_to?(:to_path) + assert_equal proxy.to_path, "/path" + end end end end diff --git a/activerecord/test/cases/fixtures/file_test.rb b/activerecord/test/cases/fixtures/file_test.rb index 8dbf92ae9a..e623fbe4d1 100644 --- a/activerecord/test/cases/fixtures/file_test.rb +++ b/activerecord/test/cases/fixtures/file_test.rb @@ -1,4 +1,4 @@ -require "cases/helper" +require 'cases/helper' require 'tempfile' module ActiveRecord diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 9644ac1b02..6735bc521b 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -1,8 +1,5 @@ require File.expand_path('../../../../load_paths', __FILE__) -lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") -$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) - require 'config' require 'test/unit' @@ -11,24 +8,24 @@ require 'mocha' require 'active_record' require 'active_support/dependencies' -begin - require 'connection' -rescue LoadError - # If we cannot load connection we assume that driver was not loaded for this test case, so we load sqlite3 as default one. - # This allows for running separate test cases by simply running test file. - connection_type = defined?(JRUBY_VERSION) ? 'jdbc' : 'native' - require "test/connections/#{connection_type}_sqlite3/connection" -end + +require 'support/config' +require 'support/connection' + +# TODO: Move all these random hacks into the ARTest namespace and into the support/ dir # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true -# Quote "type" if it's a reserved word for the current connection. -QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') - # Enable Identity Map only when ENV['IM'] is set to "true" ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true") +# Connect to the database +ARTest.connect + +# Quote "type" if it's a reserved word for the current connection. +QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') + def current_adapter?(*types) types.any? do |type| ActiveRecord::ConnectionAdapters.const_defined?(type) && @@ -61,15 +58,15 @@ end module ActiveRecord class SQLCounter - IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/] + cattr_accessor :ignored_sql + self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] # FIXME: this needs to be refactored so specific database can add their own # ignored SQL. This ignored SQL is for Oracle. - IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] + ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] - def initialize - $queries_executed = [] - end + cattr_accessor :log + self.log = [] def call(name, start, finish, message_id, values) sql = values[:sql] @@ -77,10 +74,11 @@ module ActiveRecord # FIXME: this seems bad. we should probably have a better way to indicate # the query was cached unless 'CACHE' == values[:name] - $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r } + self.class.log << sql unless self.class.ignored_sql.any? { |r| sql =~ r } end end end + ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) end diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb index 469f513e68..a428f1d87b 100644 --- a/activerecord/test/cases/i18n_test.rb +++ b/activerecord/test/cases/i18n_test.rb @@ -43,4 +43,3 @@ class ActiveRecordI18nTests < ActiveRecord::TestCase assert_equal 'topic model', Reply.model_name.human end end - diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb index a0e16400d2..cd9c358799 100644 --- a/activerecord/test/cases/identity_map_test.rb +++ b/activerecord/test/cases/identity_map_test.rb @@ -140,7 +140,7 @@ class IdentityMapTest < ActiveRecord::TestCase assert_not_same(p1, p2) end end - + def test_inherited_with_type_attribute_without_identity_map ActiveRecord::IdentityMap.without do c = comments(:sub_special_comment) diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 33737e12a8..ef35f3341e 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -384,81 +384,81 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase # build - def test_has_one_build_with_attr_protected_attributes + def test_belongs_to_build_with_attr_protected_attributes best_friend = @person.build_best_friend_of(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_attr_accessible_attributes + def test_belongs_to_build_with_attr_accessible_attributes best_friend = @person.build_best_friend_of(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_protected_attributes + def test_belongs_to_build_with_admin_role_with_attr_protected_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_accessible_attributes + def test_belongs_to_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_without_protection + def test_belongs_to_build_without_protection best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create - def test_has_one_create_with_attr_protected_attributes + def test_belongs_to_create_with_attr_protected_attributes best_friend = @person.create_best_friend_of(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_attr_accessible_attributes + def test_belongs_to_create_with_attr_accessible_attributes best_friend = @person.create_best_friend_of(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_protected_attributes + def test_belongs_to_create_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_accessible_attributes + def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_without_protection + def test_belongs_to_create_without_protection best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create! - def test_has_one_create_with_bang_with_attr_protected_attributes + def test_belongs_to_create_with_bang_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_attr_accessible_attributes + def test_belongs_to_create_with_bang_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes + def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes + def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_without_protection + def test_belongs_to_create_with_bang_without_protection best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end @@ -472,83 +472,328 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase # build - def test_has_one_build_with_attr_protected_attributes + def test_has_many_build_with_attr_protected_attributes best_friend = @person.best_friends.build(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_attr_accessible_attributes + def test_has_many_build_with_attr_accessible_attributes best_friend = @person.best_friends.build(attributes_hash) assert_default_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_protected_attributes + def test_has_many_build_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_with_admin_role_with_attr_accessible_attributes + def test_has_many_build_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.build(attributes_hash, :as => :admin) assert_admin_attributes(best_friend) end - def test_has_one_build_without_protection + def test_has_many_build_without_protection best_friend = @person.best_friends.build(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create - def test_has_one_create_with_attr_protected_attributes + def test_has_many_create_with_attr_protected_attributes best_friend = @person.best_friends.create(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_attr_accessible_attributes + def test_has_many_create_with_attr_accessible_attributes best_friend = @person.best_friends.create(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_protected_attributes + def test_has_many_create_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_admin_role_with_attr_accessible_attributes + def test_has_many_create_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_without_protection + def test_has_many_create_without_protection best_friend = @person.best_friends.create(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end # create! - def test_has_one_create_with_bang_with_attr_protected_attributes + def test_has_many_create_with_bang_with_attr_protected_attributes best_friend = @person.best_friends.create!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_attr_accessible_attributes + def test_has_many_create_with_bang_with_attr_accessible_attributes best_friend = @person.best_friends.create!(attributes_hash) assert_default_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes + def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes + def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) assert_admin_attributes(best_friend, true) end - def test_has_one_create_with_bang_without_protection + def test_has_many_create_with_bang_without_protection best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true) assert_all_attributes(best_friend) end end + + +class MassAssignmentSecurityNestedAttributesTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + + def nested_attributes_hash(association, collection = false, except = [:id]) + if collection + { :first_name => 'David' }.merge(:"#{association}_attributes" => [attributes_hash.except(*except)]) + else + { :first_name => 'David' }.merge(:"#{association}_attributes" => attributes_hash.except(*except)) + end + end + + # build + + def test_has_one_new_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend) + end + + def test_has_one_new_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend) + end + + def test_has_one_new_with_admin_role_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend) + end + + def test_has_one_new_with_admin_role_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend) + end + + def test_has_one_new_without_protection + person = LoosePerson.new(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend) + end + + def test_belongs_to_new_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of) + end + + def test_belongs_to_new_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of) + end + + def test_belongs_to_new_with_admin_role_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of) + end + + def test_belongs_to_new_with_admin_role_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of) + end + + def test_belongs_to_new_without_protection + person = LoosePerson.new(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend_of) + end + + def test_has_many_new_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first) + end + + def test_has_many_new_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first) + end + + def test_has_many_new_with_admin_role_with_attr_protected_attributes + person = LoosePerson.new(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first) + end + + def test_has_many_new_with_admin_role_with_attr_accessible_attributes + person = TightPerson.new(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first) + end + + def test_has_many_new_without_protection + person = LoosePerson.new(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) + assert_all_attributes(person.best_friends.first) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_without_protection + person = LoosePerson.create(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend) + end + + def test_belongs_to_create_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_without_protection + person = LoosePerson.create(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend_of) + end + + def test_has_many_create_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_without_protection + person = LoosePerson.create(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) + assert_all_attributes(person.best_friends.first) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend)) + assert_default_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend), :as => :admin) + assert_admin_attributes(person.best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + person = LoosePerson.create!(nested_attributes_hash(:best_friend, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend) + end + + def test_belongs_to_create_with_bang_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend_of)) + assert_default_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friend_of), :as => :admin) + assert_admin_attributes(person.best_friend_of, true) + end + + def test_belongs_to_create_with_bang_without_protection + person = LoosePerson.create!(nested_attributes_hash(:best_friend_of, false, nil), :without_protection => true) + assert_all_attributes(person.best_friend_of) + end + + def test_has_many_create_with_bang_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friends, true)) + assert_default_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_with_admin_role_with_attr_protected_attributes + person = LoosePerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_with_admin_role_with_attr_accessible_attributes + person = TightPerson.create!(nested_attributes_hash(:best_friends, true), :as => :admin) + assert_admin_attributes(person.best_friends.first, true) + end + + def test_has_many_create_with_bang_without_protection + person = LoosePerson.create!(nested_attributes_hash(:best_friends, true, nil), :without_protection => true) + assert_all_attributes(person.best_friends.first) + end + +end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index bf7565a0d0..93a1249e43 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1071,6 +1071,18 @@ if ActiveRecord::Base.connection.supports_migrations? Person.connection.drop_table :testings rescue nil end + def test_column_exists_on_table_with_no_options_parameter_supplied + Person.connection.create_table :testings do |t| + t.string :foo + end + Person.connection.change_table :testings do |t| + assert t.column_exists?(:foo) + assert !(t.column_exists?(:bar)) + end + ensure + Person.connection.drop_table :testings rescue nil + end + def test_add_table assert !Reminder.table_exists? diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 34188e4915..2afe3c8f32 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -456,6 +456,14 @@ class NamedScopeTest < ActiveRecord::TestCase end end + def test_scopes_to_get_newest + post = posts(:welcome) + old_last_comment = post.comments.newest + new_comment = post.comments.create(:body => "My new comment") + assert_equal new_comment, post.comments.newest + assert_not_equal old_last_comment, post.comments.newest + end + def test_scopes_are_reset_on_association_reload post = posts(:welcome) diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index a61180cfaf..ad17f6f83a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -203,3 +203,14 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase end end end + +class QueryCacheBodyProxyTest < ActiveRecord::TestCase + + test "is polite to it's body and responds to it" do + body = Class.new(String) { def to_path; "/path"; end }.new + proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body) + assert proxy.respond_to?(:to_path) + assert_equal proxy.to_path, "/path" + end + +end
\ No newline at end of file diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index a6e08f95d0..0e158df6a0 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -498,4 +498,11 @@ class DefaultScopingTest < ActiveRecord::TestCase lowest_salary_dev = DeveloperOrderedBySalary.find(developers(:poor_jamis).id) assert_equal lowest_salary_dev, DeveloperOrderedBySalary.last end + + def test_default_scope_include_with_count + d = DeveloperWithIncludes.create! + d.audit_logs.create! :message => 'foo' + + assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count + end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fc9df8c7a3..d88bde3860 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -145,6 +145,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:first).title, topics.first.title end + + def test_finding_with_arel_order + topics = Topic.order(Topic.arel_table[:id].asc) + assert_equal 4, topics.to_a.size + assert_equal topics(:first).title, topics.first.title + end + + def test_finding_last_with_arel_order + topics = Topic.order(Topic.arel_table[:id].asc) + assert_equal topics(:fourth).title, topics.last.title + end + def test_finding_with_order_concatenated topics = Topic.order('author_name').order('title') assert_equal 4, topics.to_a.size @@ -164,6 +176,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal entrants(:first).name, entrants.first.name end + def test_finding_with_cross_table_order_and_limit + tags = Tag.includes(:taggings) \ + .order("tags.name asc, taggings.taggable_id asc, REPLACE('abc', taggings.taggable_type, taggings.taggable_type)") \ + .limit(1).to_a + assert_equal 1, tags.length + end + def test_finding_with_complex_order_and_limit tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a assert_equal 1, tags.length diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index e8f2f44189..4adecf8e83 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -230,4 +230,3 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved" end end - diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index ceb1452afd..4445a12e1d 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -11,6 +11,7 @@ class TimestampTest < ActiveRecord::TestCase def setup @developer = Developer.first + @developer.update_attribute(:updated_at, Time.now.prev_month) @previously_updated_at = @developer.updated_at end @@ -40,6 +41,15 @@ class TimestampTest < ActiveRecord::TestCase assert_equal previous_salary, @developer.salary end + def test_touching_a_record_with_default_scope_that_excludes_it_updates_its_timestamp + developer = @developer.becomes(DeveloperCalledJamis) + + developer.touch + assert_not_equal @previously_updated_at, developer.updated_at + developer.reload + assert_not_equal @previously_updated_at, developer.updated_at + end + def test_saving_when_record_timestamps_is_false_doesnt_update_its_timestamp Developer.record_timestamps = false @developer.name = "John Smith" diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml new file mode 100644 index 0000000000..8c1a45430e --- /dev/null +++ b/activerecord/test/config.example.yml @@ -0,0 +1,136 @@ +default_connection: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %> + +connections: + jdbcderby: + arunit: activerecord_unittest + arunit2: activerecord_unittest2 + + jdbch2: + arunit: activerecord_unittest + arunit2: activerecord_unittest2 + + jdbchsqldb: + arunit: activerecord_unittest + arunit2: activerecord_unittest2 + + jdbcmysql: + arunit: + username: rails + encoding: utf8 + arunit2: + username: rails + encoding: utf8 + + jdbcpostgresql: + arunit: + username: <%= ENV['user'] || 'rails' %> + arunit2: + username: <%= ENV['user'] || 'rails' %> + + jdbcsqlite3: + arunit: + database: <%= FIXTURES_ROOT %>/fixture_database.sqlite3 + timeout: 5000 + arunit2: + database: <%= FIXTURES_ROOT %>/fixture_database_2.sqlite3 + timeout: 5000 + + db2: + arunit: + host: localhost + username: arunit + password: arunit + database: arunit + arunit2: + host: localhost + username: arunit + password: arunit + database: arunit2 + + firebird: + arunit: + host: localhost + username: rails + password: rails + charset: UTF8 + arunit2: + host: localhost + username: rails + password: rails + charset: UTF8 + + frontbase: + arunit: + host: localhost + username: rails + session_name: unittest-<%= $$ %> + arunit2: + host: localhost + username: rails + session_name: unittest-<%= $$ %> + + mysql: + arunit: + username: rails + encoding: utf8 + arunit2: + username: rails + encoding: utf8 + + mysql2: + arunit: + username: rails + encoding: utf8 + arunit2: + username: rails + encoding: utf8 + + openbase: + arunit: + username: admin + arunit2: + username: admin + + oracle: + arunit: + adapter: oracle_enhanced + database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %> + username: <%= ENV['ARUNIT_USER_NAME'] || 'arunit' %> + password: <%= ENV['ARUNIT_PASSWORD'] || 'arunit' %> + emulate_oracle_adapter: true + arunit2: + adapter: oracle_enhanced + database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %> + username: <%= ENV['ARUNIT2_USER_NAME'] || 'arunit2' %> + password: <%= ENV['ARUNIT2_PASSWORD'] || 'arunit2' %> + emulate_oracle_adapter: true + + postgresql: + arunit: + min_messages: warning + arunit2: + min_messages: warning + + sqlite3: + arunit: + database: <%= FIXTURES_ROOT %>/fixture_database.sqlite3 + timeout: 5000 + arunit2: + database: <%= FIXTURES_ROOT %>/fixture_database_2.sqlite3 + timeout: 5000 + + sqlite3_mem: + arunit: + adapter: sqlite3 + database: ':memory:' + arunit2: + adapter: sqlite3 + database: ':memory:' + + sybase: + arunit: + host: database_ASE + username: sa + arunit2: + host: database_ASE + username: sa diff --git a/activerecord/test/connections/jdbc_jdbcderby/connection.rb b/activerecord/test/connections/jdbc_jdbcderby/connection.rb deleted file mode 100644 index 222ef5db38..0000000000 --- a/activerecord/test/connections/jdbc_jdbcderby/connection.rb +++ /dev/null @@ -1,18 +0,0 @@ -print "Using Derby via JRuby, activerecord-jdbc-adapter and activerecord-jdbcderby-adapter\n" -require_dependency 'models/course' -require 'logger' -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'jdbcderby', - :database => 'activerecord_unittest' - }, - 'arunit2' => { - :adapter => 'jdbcderby', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/jdbc_jdbch2/connection.rb b/activerecord/test/connections/jdbc_jdbch2/connection.rb deleted file mode 100644 index 9d2875e8e7..0000000000 --- a/activerecord/test/connections/jdbc_jdbch2/connection.rb +++ /dev/null @@ -1,18 +0,0 @@ -print "Using H2 via JRuby, activerecord-jdbc-adapter and activerecord-jdbch2-adapter\n" -require_dependency 'models/course' -require 'logger' -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'jdbch2', - :database => 'activerecord_unittest' - }, - 'arunit2' => { - :adapter => 'jdbch2', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb b/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb deleted file mode 100644 index fa943c2c76..0000000000 --- a/activerecord/test/connections/jdbc_jdbchsqldb/connection.rb +++ /dev/null @@ -1,18 +0,0 @@ -print "Using HSQLDB via JRuby, activerecord-jdbc-adapter and activerecord-jdbchsqldb-adapter\n" -require_dependency 'models/course' -require 'logger' -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'jdbchsqldb', - :database => 'activerecord_unittest' - }, - 'arunit2' => { - :adapter => 'jdbchsqldb', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/jdbc_jdbcmysql/connection.rb b/activerecord/test/connections/jdbc_jdbcmysql/connection.rb deleted file mode 100644 index e2517a50eb..0000000000 --- a/activerecord/test/connections/jdbc_jdbcmysql/connection.rb +++ /dev/null @@ -1,26 +0,0 @@ -print "Using MySQL via JRuby, activerecord-jdbc-adapter and activerecord-jdbcmysql-adapter\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost'; -# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost'; - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'jdbcmysql', - :username => 'rails', - :encoding => 'utf8', - :database => 'activerecord_unittest', - }, - 'arunit2' => { - :adapter => 'jdbcmysql', - :username => 'rails', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' - diff --git a/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb b/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb deleted file mode 100644 index 0685da4433..0000000000 --- a/activerecord/test/connections/jdbc_jdbcpostgresql/connection.rb +++ /dev/null @@ -1,26 +0,0 @@ -print "Using Postgrsql via JRuby, activerecord-jdbc-adapter and activerecord-postgresql-adapter\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -# createuser rails --createdb --no-superuser --no-createrole -# createdb -O rails activerecord_unittest -# createdb -O rails activerecord_unittest2 - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'jdbcpostgresql', - :username => ENV['USER'] || 'rails', - :database => 'activerecord_unittest' - }, - 'arunit2' => { - :adapter => 'jdbcpostgresql', - :username => ENV['USER'] || 'rails', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' - diff --git a/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb b/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb deleted file mode 100644 index 26d4676ff3..0000000000 --- a/activerecord/test/connections/jdbc_jdbcsqlite3/connection.rb +++ /dev/null @@ -1,25 +0,0 @@ -print "Using SQLite3 via JRuby, activerecord-jdbc-adapter and activerecord-jdbcsqlite3-adapter\n" -require_dependency 'models/course' -require 'logger' -ActiveRecord::Base.logger = Logger.new("debug.log") - -class SqliteError < StandardError -end - -BASE_DIR = FIXTURES_ROOT -sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3" -sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3" - -def make_connection(clazz, db_file) - ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'jdbcsqlite3', :database => db_file, :timeout => 5000 } } - unless File.exist?(db_file) - puts "SQLite3 database not found at #{db_file}. Rebuilding it." - sqlite_command = %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"} - puts "Executing '#{sqlite_command}'" - raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command) - end - clazz.establish_connection(clazz.name) -end - -make_connection(ActiveRecord::Base, sqlite_test_db) -make_connection(Course, sqlite_test_db2) diff --git a/activerecord/test/connections/native_db2/connection.rb b/activerecord/test/connections/native_db2/connection.rb deleted file mode 100644 index 324315d2c8..0000000000 --- a/activerecord/test/connections/native_db2/connection.rb +++ /dev/null @@ -1,25 +0,0 @@ -print "Using native DB2\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'db2', - :host => 'localhost', - :username => 'arunit', - :password => 'arunit', - :database => 'arunit' - }, - 'arunit2' => { - :adapter => 'db2', - :host => 'localhost', - :username => 'arunit', - :password => 'arunit', - :database => 'arunit2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/native_firebird/connection.rb b/activerecord/test/connections/native_firebird/connection.rb deleted file mode 100644 index 67a936ca97..0000000000 --- a/activerecord/test/connections/native_firebird/connection.rb +++ /dev/null @@ -1,26 +0,0 @@ -print "Using native Firebird\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'firebird', - :host => 'localhost', - :username => 'rails', - :password => 'rails', - :database => 'activerecord_unittest', - :charset => 'UTF8' - }, - 'arunit2' => { - :adapter => 'firebird', - :host => 'localhost', - :username => 'rails', - :password => 'rails', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/native_frontbase/connection.rb b/activerecord/test/connections/native_frontbase/connection.rb deleted file mode 100644 index c01d864a8b..0000000000 --- a/activerecord/test/connections/native_frontbase/connection.rb +++ /dev/null @@ -1,27 +0,0 @@ -puts 'Using native Frontbase' -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'frontbase', - :host => 'localhost', - :username => 'rails', - :password => '', - :database => 'activerecord_unittest', - :session_name => "unittest-#{$$}" - }, - 'arunit2' => { - :adapter => 'frontbase', - :host => 'localhost', - :username => 'rails', - :password => '', - :database => 'activerecord_unittest2', - :session_name => "unittest-#{$$}" - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/native_mysql/connection.rb b/activerecord/test/connections/native_mysql/connection.rb deleted file mode 100644 index 140e06d631..0000000000 --- a/activerecord/test/connections/native_mysql/connection.rb +++ /dev/null @@ -1,25 +0,0 @@ -print "Using native MySQL\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost'; -# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost'; - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'mysql', - :username => 'rails', - :encoding => 'utf8', - :database => 'activerecord_unittest', - }, - 'arunit2' => { - :adapter => 'mysql', - :username => 'rails', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/native_mysql2/connection.rb b/activerecord/test/connections/native_mysql2/connection.rb deleted file mode 100644 index c6f198b1ac..0000000000 --- a/activerecord/test/connections/native_mysql2/connection.rb +++ /dev/null @@ -1,25 +0,0 @@ -print "Using native Mysql2\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost'; -# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost'; - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'mysql2', - :username => 'rails', - :encoding => 'utf8', - :database => 'activerecord_unittest', - }, - 'arunit2' => { - :adapter => 'mysql2', - :username => 'rails', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/native_openbase/connection.rb b/activerecord/test/connections/native_openbase/connection.rb deleted file mode 100644 index 655cb9ca26..0000000000 --- a/activerecord/test/connections/native_openbase/connection.rb +++ /dev/null @@ -1,21 +0,0 @@ -print "Using native OpenBase\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'openbase', - :username => 'admin', - :database => 'activerecord_unittest', - }, - 'arunit2' => { - :adapter => 'openbase', - :username => 'admin', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb deleted file mode 100644 index 99f921879c..0000000000 --- a/activerecord/test/connections/native_oracle/connection.rb +++ /dev/null @@ -1,35 +0,0 @@ -# uses oracle_enhanced adapter in ENV['ORACLE_ENHANCED_PATH'] or from github.com/rsim/oracle-enhanced.git -require 'active_record/connection_adapters/oracle_enhanced_adapter' - -# otherwise failed with silence_warnings method missing exception -require 'active_support/core_ext/kernel/reporting' - -print "Using Oracle\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -# Set these to your database connection strings -ENV['ARUNIT_DB_NAME'] ||= 'orcl' - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'oracle_enhanced', - :database => ENV['ARUNIT_DB_NAME'], - :username => 'arunit', - :password => 'arunit', - :emulate_oracle_adapter => true - }, - 'arunit2' => { - :adapter => 'oracle_enhanced', - :database => ENV['ARUNIT_DB_NAME'], - :username => 'arunit2', - :password => 'arunit2', - :emulate_oracle_adapter => true - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' - diff --git a/activerecord/test/connections/native_postgresql/connection.rb b/activerecord/test/connections/native_postgresql/connection.rb deleted file mode 100644 index 3b5ff90003..0000000000 --- a/activerecord/test/connections/native_postgresql/connection.rb +++ /dev/null @@ -1,21 +0,0 @@ -print "Using native PostgreSQL\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'postgresql', - :database => 'activerecord_unittest', - :min_messages => 'warning' - }, - 'arunit2' => { - :adapter => 'postgresql', - :database => 'activerecord_unittest2', - :min_messages => 'warning' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/connections/native_sqlite3/connection.rb b/activerecord/test/connections/native_sqlite3/connection.rb deleted file mode 100644 index c2aff5551f..0000000000 --- a/activerecord/test/connections/native_sqlite3/connection.rb +++ /dev/null @@ -1,16 +0,0 @@ -print "Using native SQLite3\n" -require_dependency 'models/course' -require 'logger' -ActiveRecord::Base.logger = Logger.new("debug.log") - -BASE_DIR = FIXTURES_ROOT -sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3" -sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3" - -def make_connection(clazz, db_file) - ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite3', :database => db_file, :timeout => 5000 } } - clazz.establish_connection(clazz.name) -end - -make_connection(ActiveRecord::Base, sqlite_test_db) -make_connection(Course, sqlite_test_db2) diff --git a/activerecord/test/connections/native_sqlite3_mem/connection.rb b/activerecord/test/connections/native_sqlite3_mem/connection.rb deleted file mode 100644 index 14e10900d1..0000000000 --- a/activerecord/test/connections/native_sqlite3_mem/connection.rb +++ /dev/null @@ -1,19 +0,0 @@ -# This file connects to an in-memory SQLite3 database, which is a very fast way to run the tests. -# The downside is that disconnect from the database results in the database effectively being -# wiped. For this reason, pooled_connections_test.rb is disabled when using an in-memory database. - -print "Using native SQLite3 (in memory)\n" -require_dependency 'models/course' -require 'logger' -ActiveRecord::Base.logger = Logger.new("debug.log") - -class SqliteError < StandardError -end - -def make_connection(clazz) - ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite3', :database => ':memory:' } } - clazz.establish_connection(clazz.name) -end - -make_connection(ActiveRecord::Base) -make_connection(Course) diff --git a/activerecord/test/connections/native_sybase/connection.rb b/activerecord/test/connections/native_sybase/connection.rb deleted file mode 100644 index 3282d26922..0000000000 --- a/activerecord/test/connections/native_sybase/connection.rb +++ /dev/null @@ -1,23 +0,0 @@ -print "Using native Sybase Open Client\n" -require_dependency 'models/course' -require 'logger' - -ActiveRecord::Base.logger = Logger.new("debug.log") - -ActiveRecord::Base.configurations = { - 'arunit' => { - :adapter => 'sybase', - :host => 'database_ASE', - :username => 'sa', - :database => 'activerecord_unittest' - }, - 'arunit2' => { - :adapter => 'sybase', - :host => 'database_ASE', - :username => 'sa', - :database => 'activerecord_unittest2' - } -} - -ActiveRecord::Base.establish_connection 'arunit' -Course.establish_connection 'arunit2' diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 2a4c37089a..43650c0427 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -6,7 +6,8 @@ class Comment < ActiveRecord::Base scope :for_first_author, :joins => :post, :conditions => { "posts.author_id" => 1 } - + scope :created + belongs_to :post, :counter_cache => true has_many :ratings diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index e0b30efd51..c1f7a4171a 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -124,6 +124,18 @@ class Client < Company has_many :accounts, :through => :firm belongs_to :account + class RaisedOnSave < RuntimeError; end + attr_accessor :raise_on_save + before_save do + raise RaisedOnSave if raise_on_save + end + + class RaisedOnDestroy < RuntimeError; end + attr_accessor :raise_on_destroy + before_destroy do + raise RaisedOnDestroy if raise_on_destroy + end + # Record destruction so we can test whether firm.clients.clear has # is calling client.destroy, deleting from the database, or setting # foreign keys to NULL. diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 98d6aa22f7..f182a7fa97 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -91,6 +91,12 @@ class DeveloperWithSelect < ActiveRecord::Base default_scope select('name') end +class DeveloperWithIncludes < ActiveRecord::Base + self.table_name = 'developers' + has_many :audit_logs, :foreign_key => :developer_id + default_scope includes(:audit_logs) +end + class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope :order => 'salary DESC' diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index a58c9bf572..967a3625aa 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -59,8 +59,9 @@ class LoosePerson < ActiveRecord::Base has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id - has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id + + accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end class LooseDescendant < LoosePerson; end @@ -70,11 +71,14 @@ class TightPerson < ActiveRecord::Base attr_accessible :first_name, :gender attr_accessible :first_name, :gender, :comments, :as => :admin + attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes + attr_accessible :best_friend_attributes, :best_friend_of_attributes, :best_friends_attributes, :as => :admin has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id - has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id + + accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends end class TightDescendant < TightPerson; end
\ No newline at end of file diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index f2ab7b053e..affa37b02d 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -36,6 +36,10 @@ class Post < ActiveRecord::Base def find_most_recent find(:first, :order => "id DESC") end + + def newest + created.last + end end has_many :author_favorites, :through => :author diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb new file mode 100644 index 0000000000..6d123688a3 --- /dev/null +++ b/activerecord/test/support/config.rb @@ -0,0 +1,43 @@ +require 'yaml' +require 'erubis' +require 'fileutils' +require 'pathname' + +module ARTest + class << self + def config + @config ||= read_config + end + + private + + def config_file + Pathname.new(ENV['ARCONFIG'] || TEST_ROOT + '/config.yml') + end + + def read_config + unless config_file.exist? + FileUtils.cp TEST_ROOT + '/config.example.yml', config_file + end + + erb = Erubis::Eruby.new(config_file.read) + expand_config(YAML.parse(erb.result(binding)).transform) + end + + def expand_config(config) + config['connections'].each do |adapter, connection| + dbs = [['arunit', 'activerecord_unittest'], ['arunit2', 'activerecord_unittest2']] + dbs.each do |name, dbname| + unless connection[name].is_a?(Hash) + connection[name] = { 'database' => connection[name] } + end + + connection[name]['database'] ||= dbname + connection[name]['adapter'] ||= adapter + end + end + + config + end + end +end diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb new file mode 100644 index 0000000000..a39794fa39 --- /dev/null +++ b/activerecord/test/support/connection.rb @@ -0,0 +1,20 @@ +require 'logger' +require_dependency 'models/course' + +module ARTest + def self.connection_name + ENV['ARCONN'] || config['default_connection'] + end + + def self.connection_config + config['connections'][connection_name] + end + + def self.connect + puts "Using #{connection_name} with Identity Map #{ActiveRecord::IdentityMap.enabled? ? 'on' : 'off'}" + ActiveRecord::Base.logger = Logger.new("debug.log") + ActiveRecord::Base.configurations = connection_config + ActiveRecord::Base.establish_connection 'arunit' + Course.establish_connection 'arunit2' + end +end diff --git a/activeresource/lib/active_resource/schema.rb b/activeresource/lib/active_resource/schema.rb index 3fd37a9bb6..5957969aa2 100644 --- a/activeresource/lib/active_resource/schema.rb +++ b/activeresource/lib/active_resource/schema.rb @@ -20,8 +20,8 @@ module ActiveResource # :nodoc: # end # # The schema stores the name and type of each attribute. That is then - # read out by the schema method to populate the actual - # Resource's schema + # read out by the schema method to populate the schema of the actual + # resource. def initialize @attrs = {} end @@ -40,6 +40,12 @@ module ActiveResource # :nodoc: # The following are the attribute types supported by Active Resource # migrations. KNOWN_ATTRIBUTE_TYPES.each do |attr_type| + # def string(*args) + # options = args.extract_options! + # attr_names = args + # + # attr_names.each { |name| attribute(name, 'string', options) } + # end class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{attr_type.to_s}(*args) options = args.extract_options! diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb index d6b04cfaa8..0d030148d0 100644 --- a/activeresource/test/cases/base/load_test.rb +++ b/activeresource/test/cases/base/load_test.rb @@ -72,7 +72,6 @@ class BaseLoadTest < Test::Unit::TestCase def test_after_load_attributes_are_accessible_via_indifferent_access assert_equal Hash.new, @person.attributes - matz_attributes = @matz.values.first assert_equal @matz.stringify_keys, @person.load(@matz).attributes assert_equal @matz[:name], @person.attributes['name'] assert_equal @matz[:name], @person.attributes[:name] diff --git a/activeresource/test/cases/finder_test.rb b/activeresource/test/cases/finder_test.rb index 9c51f2a390..5fbbfeef6e 100644 --- a/activeresource/test/cases/finder_test.rb +++ b/activeresource/test/cases/finder_test.rb @@ -95,7 +95,7 @@ class FinderTest < Test::Unit::TestCase def test_find_all_sub_objects_not_found assert_nothing_raised do - addys = StreetAddress.find(:all, :params => { :person_id => 2 }) + StreetAddress.find(:all, :params => { :person_id => 2 }) end end diff --git a/activeresource/test/cases/log_subscriber_test.rb b/activeresource/test/cases/log_subscriber_test.rb index b9143f5b67..ab5c22a783 100644 --- a/activeresource/test/cases/log_subscriber_test.rb +++ b/activeresource/test/cases/log_subscriber_test.rb @@ -23,7 +23,7 @@ class LogSubscriberTest < ActiveSupport::TestCase end def test_request_notification - matz = Person.find(1) + Person.find(1) wait assert_equal 2, @logger.logged(:info).size assert_equal "GET http://37s.sunrise.i:3000/people/1.json", @logger.logged(:info)[0] diff --git a/activeresource/test/cases/observing_test.rb b/activeresource/test/cases/observing_test.rb index ca3ab5d03d..58d3d389ff 100644 --- a/activeresource/test/cases/observing_test.rb +++ b/activeresource/test/cases/observing_test.rb @@ -37,7 +37,7 @@ class ObservingTest < Test::Unit::TestCase end def test_create_fires_save_and_create_notifications - rick = Person.create(:name => 'Rick') + Person.create(:name => 'Rick') assert_equal [:before_save, :before_create, :after_create, :after_save], self.history end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 6b7044aeae..034d03533f 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,6 +1,14 @@ *Rails 3.2.0 (unreleased)* -* Removed ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton] +* The inflector understands acronyms. [dlee] + +* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim] + +* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH] + +* Added instance_accessor: false as an option to Class#cattr_accessor and friends [DHH] + +* Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library [Jon Leighton] * ActiveSupport::OrderedHash now has different behavior for #each and #each_pair when given a block accepting its parameters with a splat. [Andrew Radev] diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 0e6bc30fa2..8f8deb9692 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -1,12 +1,12 @@ module ActiveSupport - # Many backtraces include too much information that's not relevant for the context. This makes it hard to find the signal - # in the backtrace and adds debugging time. With a BacktraceCleaner, you can setup filters and silencers for your particular - # context, so only the relevant lines are included. + # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the + # signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to + # remove the noisy lines, so that only the most relevant lines remain. # - # If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always - # call BacktraceCleaner#remove_silencers! Also, if you need to reconfigure an existing BacktraceCleaner so that it does not - # filter or modify the paths of any lines of the backtrace, you can call BacktraceCleaner#remove_filters! These two methods - # will give you a completely untouched backtrace. + # Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case + # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory + # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the + # backtrace, so that you can focus on the rest. # # ==== Example: # @@ -15,13 +15,18 @@ module ActiveSupport # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems # + # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can + # always call <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the backtrace to a pristine state. If you + # need to reconfigure an existing BacktraceCleaner so that it does not filter or modify the paths of any lines of the + # backtrace, you can call BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace. + # # Inspired by the Quiet Backtrace gem by Thoughtbot. class BacktraceCleaner def initialize @filters, @silencers = [], [] end - # Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers. + # Returns the backtrace after all filters and silencers have been run against it. Filters run first, then silencers. def clean(backtrace, kind = :silent) filtered = filter(backtrace) @@ -45,8 +50,8 @@ module ActiveSupport @filters << block end - # Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the - # clean backtrace. + # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from + # the clean backtrace. # # Example: # @@ -57,7 +62,7 @@ module ActiveSupport end # Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as - # you suspect a bug in the libraries you use. + # you suspect a bug in one of the libraries you use. def remove_silencers! @silencers = [] end diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 7fabae3138..f420270dc4 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -14,7 +14,7 @@ class Array # This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences: # # * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> - # moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns + # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns # such a +nil+ right away. # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value. diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 7baba75ad3..ca9b2c1b60 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/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/array/extract_options' class Class # Declare a class-level attribute whose value is inheritable by subclasses. @@ -56,11 +57,18 @@ class Class # object.setting # => false # Base.setting # => true # + # To opt out of the instance reader method, pass :instance_reader => false. + # + # object.setting # => NoMethodError + # object.setting? # => NoMethodError + # # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError def class_attribute(*attrs) - instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer] + options = attrs.extract_options! + instance_reader = options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -84,13 +92,15 @@ class Class val end - remove_possible_method :#{name} - def #{name} - defined?(@#{name}) ? @#{name} : self.class.#{name} - end + if instance_reader + remove_possible_method :#{name} + def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} + end - def #{name}? - !!#{name} + def #{name}? + !!#{name} + end end RUBY 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 a903735acf..268303aaf2 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -17,6 +17,7 @@ require 'active_support/core_ext/array/extract_options' # # To opt out of the instance writer method, pass :instance_writer => false. # To opt out of the instance reader method, pass :instance_reader => false. +# To opt out of both instance methods, pass :instance_accessor => false. # # class Person # cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false @@ -38,7 +39,7 @@ class Class end EOS - unless options[:instance_reader] == false + unless options[:instance_reader] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym} @@#{sym} @@ -61,7 +62,7 @@ class Class end EOS - unless options[:instance_writer] == false + unless options[:instance_writer] == false || options[:instance_accessor] == false class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{sym}=(obj) @@#{sym} = obj diff --git a/activesupport/lib/active_support/core_ext/date/freeze.rb b/activesupport/lib/active_support/core_ext/date/freeze.rb index 4edd715ba2..a731f8345e 100644 --- a/activesupport/lib/active_support/core_ext/date/freeze.rb +++ b/activesupport/lib/active_support/core_ext/date/freeze.rb @@ -5,7 +5,7 @@ # first call will result in a frozen object error since the memo # instance variable is uninitialized. # -# Work around by eagerly memoizing before freezing. +# Work around by eagerly memoizing before the first freeze. # # Ruby 1.9 uses a preinitialized instance variable so it's unaffected. # This hack is as close as we can get to feature detection: @@ -17,9 +17,11 @@ if RUBY_VERSION < '1.9' if frozen_object_error.message =~ /frozen/ class Date #:nodoc: def freeze - self.class.private_instance_methods(false).each do |m| - if m.to_s =~ /\A__\d+__\Z/ - instance_variable_set(:"@#{m}", [send(m)]) + unless frozen? + self.class.private_instance_methods(false).each do |m| + if m.to_s =~ /\A__\d+__\Z/ + instance_variable_set(:"@#{m}", [send(m)]) + end end end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index c2a6476604..0b368fe7b7 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -11,7 +11,7 @@ class Hash end # Called when object is nested under an object that receives - # #with_indifferent_access. This method with be called on the current object + # #with_indifferent_access. This method will be called on the current object # by the enclosing object and is aliased to #with_indifferent_access by # default. Subclasses of Hash may overwrite this method to return +self+ if # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index c6920098a8..526b8378a5 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -59,7 +59,7 @@ module Kernel raise unless exception_classes.any? { |cls| e.kind_of?(cls) } end end - + # Captures the given stream and returns it: # # stream = capture(:stdout) { puts "Cool" } diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 3a7652f5bf..1777a4b32d 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -113,7 +113,7 @@ class Module raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." end - prefix = options[:prefix] && "#{options[:prefix] == true ? to : options[:prefix]}_" || '' + prefix = options[:prefix] ? "#{options[:prefix] == true ? to : options[:prefix]}_" : '' file, line = caller.first.split(':', 2) line = line.to_i diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2f0676f567..002688d6c0 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,4 @@ require 'active_support/inflector/methods' -require 'active_support/inflector/inflections' require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. 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 c27cbc37c5..a19b5c95c7 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -51,7 +51,8 @@ class ERB # <%=j @person.to_json %> # def json_escape(s) - s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + s.html_safe? ? result.html_safe : result end alias j json_escape @@ -74,10 +75,34 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String - alias safe_concat concat + UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze + + alias_method :original_concat, :concat + private :original_concat + + class SafeConcatError < StandardError + def initialize + super "Could not concatenate to the buffer because it is not html safe." + end + end + + def safe_concat(value) + raise SafeConcatError if dirty? + original_concat(value) + end + + def initialize(*) + @dirty = false + super + end + + def initialize_copy(other) + super + @dirty = other.dirty? + end def concat(value) - if value.html_safe? + if dirty? || value.html_safe? super(value) else super(ERB::Util.h(value)) @@ -90,15 +115,15 @@ module ActiveSupport #:nodoc: end def html_safe? - true + !dirty? end - def html_safe + def to_s self end - def to_s - self + def to_param + to_str end def encode_with(coder) @@ -107,17 +132,31 @@ module ActiveSupport #:nodoc: def to_yaml(*args) return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? - to_str.to_yaml(*args) end + + UNSAFE_STRING_METHODS.each do |unsafe_method| + class_eval <<-EOT, __FILE__, __LINE__ + def #{unsafe_method}(*args, &block) + to_str.#{unsafe_method}(*args, &block) + end + + def #{unsafe_method}!(*args) + @dirty = true + super + end + EOT + end + + protected + + def dirty? + @dirty + end end end class String - def html_safe! - raise "You can't call html_safe! on a String" - end - def html_safe ActiveSupport::SafeBuffer.new(self) end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 00fda2b370..c5fbbcf890 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -226,7 +226,7 @@ class Time end alias :at_end_of_quarter :end_of_quarter - # Returns a new Time representing the start of the year (1st of january, 0:00) + # Returns a new Time representing the start of the year (1st of january, 0:00) def beginning_of_year change(:month => 1, :day => 1, :hour => 0) end @@ -248,6 +248,31 @@ class Time advance(:days => 1) end + # Returns a Range representing the whole day of the current time. + def all_day + beginning_of_day..end_of_day + end + + # Returns a Range representing the whole week of the current time. + def all_week + beginning_of_week..end_of_week + end + + # Returns a Range representing the whole month of the current time. + def all_month + beginning_of_month..end_of_month + end + + # Returns a Range representing the whole quarter of the current time. + def all_quarter + beginning_of_quarter..end_of_quarter + end + + # Returns a Range representing the whole year of the current time. + def all_year + beginning_of_year..end_of_year + end + def plus_with_duration(other) #:nodoc: if ActiveSupport::Duration === other other.since(self) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 26c5c157cb..d1543c4c58 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -628,17 +628,6 @@ module ActiveSupport #:nodoc: return [] end - class LoadingModule #:nodoc: - # Old style environment.rb referenced this method directly. Please note, it doesn't - # actually *do* anything any more. - def self.root(*args) - if defined?(Rails) && Rails.logger - Rails.logger.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases." - Rails.logger.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19" - end - end - end - # Convert the provided const desc to a qualified constant name (as a string). # A module, class, symbol, or string may be provided. def to_constant_name(desc) #:nodoc: diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index d5d55b7207..90bb62f57b 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -20,10 +20,61 @@ module ActiveSupport @__instance__ ||= new end - attr_reader :plurals, :singulars, :uncountables, :humans + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex def initialize - @plurals, @singulars, @uncountables, @humans = [], [], [], [] + @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ + end + + # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore + # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`. + # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will + # convert the acronym into a non-delimited single lowercase word when passed to +underscore+. + # + # Examples: + # acronym 'HTML' + # titleize 'html' #=> 'HTML' + # camelize 'html' #=> 'HTML' + # underscore 'MyHTML' #=> 'my_html' + # + # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it: + # + # acronym 'HTTP' + # camelize 'my_http_delimited' #=> 'MyHTTPDelimited' + # camelize 'https' #=> 'Https', not 'HTTPs' + # underscore 'HTTPS' #=> 'http_s', not 'https' + # + # acronym 'HTTPS' + # camelize 'https' #=> 'HTTPS' + # underscore 'HTTPS' #=> 'https' + # + # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as + # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an + # acronym as well: + # + # acronym 'API' + # camelize(pluralize('api')) #=> 'Apis' + # + # acronym 'APIs' + # camelize(pluralize('api')) #=> 'APIs' + # + # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard + # capitalization. The only restriction is that the word must begin with a capital letter. + # + # Examples: + # acronym 'RESTful' + # underscore 'RESTful' #=> 'restful' + # underscore 'RESTfulController' #=> 'restful_controller' + # titleize 'RESTfulController' #=> 'RESTful Controller' + # camelize 'restful' #=> 'RESTful' + # camelize 'restful_controller' #=> 'RESTfulController' + # + # acronym 'McDonald' + # underscore 'McDonald' #=> 'mcdonald' + # camelize 'mcdonald' #=> 'McDonald' + def acronym(word) + @acronyms[word.downcase] = word + @acronym_regex = /#{@acronyms.values.join("|")}/ end # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. @@ -117,95 +168,5 @@ module ActiveSupport Inflections.instance end end - - # Returns the plural form of the word in the string. - # - # Examples: - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # Examples: - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - result = word.to_s.dup - - if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. - # - # Examples: - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" - def humanize(lower_case_and_underscored_word) - result = lower_case_and_underscored_word.to_s.dup - - inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - # Capitalizes all the words and replaces some characters in the string to create - # a nicer looking title. +titleize+ is meant for creating pretty output. It is not - # used in the Rails internals. - # - # +titleize+ is also aliased as as +titlecase+. - # - # Examples: - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" - def titleize(word) - humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } - end - - # Create the name of a table like Rails does for models to table names. This method - # uses the +pluralize+ method on the last word in the string. - # - # Examples - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end - - # Create a class name from a plural table name like Rails does for table names to models. - # Note that this returns a string and not a Class. (To convert to an actual class - # follow +classify+ with +constantize+.) - # - # Examples: - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" - # - # Singular names are not handled correctly: - # "business".classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a2c4f7bfda..3d28d33f40 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,3 +1,5 @@ +require 'active_support/inflector/inflections' + module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept @@ -10,6 +12,44 @@ module ActiveSupport module Inflector extend self + # Returns the plural form of the word in the string. + # + # Examples: + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.include?(result.downcase) + result + else + inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # Examples: + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singularize # => "sheep" + # "word".singularize # => "word" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + result = word.to_s.dup + + if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } + result + else + inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. # @@ -25,12 +65,14 @@ module ActiveSupport # though there are cases where that does not hold: # # "SSLError".underscore.camelize # => "SslError" - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + def camelize(term, uppercase_first_letter = true) + string = term.to_s + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } else - lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] + string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end + string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') end # Makes an underscored, lowercase form from the expression in the string. @@ -48,13 +90,66 @@ module ActiveSupport def underscore(camel_cased_word) word = camel_cased_word.to_s.dup word.gsub!(/::/, '/') - word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") word.downcase! word end + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # Examples: + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result.gsub!(/_id$/, "") + result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase } + end + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as as +titlecase+. + # + # Examples: + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + def titleize(word) + humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # Examples: + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end + # Replaces underscores with dashes in the string. # # Example: diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 1c4dd24227..6296c1d4b8 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -3,8 +3,8 @@ require 'active_support/core_ext/class/attribute' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications - # with solely purpose of logging. The log subscriber dispatches notifications to a - # registered object based on its given namespace. + # with the sole purpose of logging them. The log subscriber dispatches notifications to + # a registered object based on its given namespace. # # An example would be Active Record log subscriber responsible for logging queries: # @@ -109,8 +109,8 @@ module ActiveSupport # Set color by using a string or one of the defined constants. If a third # option is set to true, it also adds bold to the string. This is based - # on Highline implementation and it automatically appends CLEAR to the end - # of the returned String. + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. # def color(text, color, bold=false) return text unless colorize_logging diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index 0a7bcd5bb8..4c67676ad5 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -1,8 +1,15 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/aliasing' +require 'active_support/deprecation' module ActiveSupport module Memoizable + def self.extended(base) + ActiveSupport::Deprecation.warn "ActiveSupport::Memoizable is deprecated and will be removed in future releases," \ + "simply use Ruby memoization pattern instead.", caller + super + end + def self.memoized_ivar_for(symbol) "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym end @@ -79,7 +86,11 @@ module ActiveSupport else # else def #{symbol}(*args) # def mime_type(*args) #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen? - reload = args.pop if args.last == true || args.last == :reload # reload = args.pop if args.last == true || args.last == :reload + args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity + if args.length == args_length + 1 && # if args.length == args_length + 1 && + (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload) + reload = args.pop # reload = args.pop + end # end # if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args) diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 02c19448fd..dd23f8d82d 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -9,7 +9,7 @@ module ActiveSupport module Testing module Performance extend ActiveSupport::Concern - + included do superclass_delegating_accessor :profile_options self.profile_options = {} @@ -20,7 +20,7 @@ module ActiveSupport include ForClassicTestUnit end end - + # each implementation should define metrics and freeze the defaults DEFAULTS = if ARGV.include?('--benchmark') # HAX for rake test @@ -32,7 +32,7 @@ module ActiveSupport :output => 'tmp/performance', :benchmark => false } end - + def full_profile_options DEFAULTS.merge(profile_options) end @@ -40,7 +40,7 @@ module ActiveSupport def full_test_name "#{self.class.name}##{method_name}" end - + module ForMiniTest def run(runner) @runner = runner @@ -53,7 +53,7 @@ module ActiveSupport end end end - + return end @@ -122,7 +122,7 @@ module ActiveSupport protected # overridden by each implementation def run_gc; end - + def run_warmup run_gc @@ -132,7 +132,7 @@ module ActiveSupport run_gc end - + def run_profile(metric) klass = full_profile_options[:benchmark] ? Benchmarker : Profiler performer = klass.new(self, metric) @@ -163,7 +163,7 @@ module ActiveSupport "#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}" end end - + # overridden by each implementation class Profiler < Performer def time_with_block @@ -171,7 +171,7 @@ module ActiveSupport yield Time.now - before end - + def run; end def record; end end @@ -181,10 +181,10 @@ module ActiveSupport super @supported = @metric.respond_to?('measure') end - + def run return unless @supported - + full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } @total = @metric.total end @@ -237,7 +237,7 @@ module ActiveSupport "#{super}.csv" end end - + module Metrics def self.[](name) const_get(name.to_s.camelize) @@ -247,7 +247,7 @@ module ActiveSupport class Base include ActionView::Helpers::NumberHelper - + attr_reader :total def initialize @@ -265,15 +265,15 @@ module ActiveSupport @total += (measure - before) end end - + # overridden by each implementation def profile; end - + protected # overridden by each implementation def with_gc_stats; end end - + class Time < Base def measure ::Time.now.to_f @@ -287,19 +287,19 @@ module ActiveSupport end end end - + class Amount < Base def format(measurement) number_with_delimiter(measurement.floor) end end - + class DigitalInformationUnit < Base def format(measurement) number_to_human_size(measurement, :precision => 2) end end - + # each implementation provides its own metrics like ProcessTime, Memory or GcRuns end end diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb index 326904bbac..b347539f13 100644 --- a/activesupport/lib/active_support/testing/performance/jruby.rb +++ b/activesupport/lib/active_support/testing/performance/jruby.rb @@ -1,5 +1,5 @@ require 'jruby/profiler' -require 'java' +require 'java' java_import java.lang.management.ManagementFactory module ActiveSupport @@ -12,21 +12,21 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc ManagementFactory.memory_mx_bean.gc - end + end class Profiler < Performer def initialize(*args) super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @total = time_with_block do @data = JRuby::Profiler.profile do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } @@ -36,7 +36,7 @@ module ActiveSupport def record return unless @supported - + klasses = full_profile_options[:formats].map { |f| JRuby::Profiler.const_get("#{f.to_s.camelize}ProfilePrinter") }.compact klasses.each do |klass| @@ -61,7 +61,7 @@ module ActiveSupport end end - module Metrics + module Metrics class Base def profile yield @@ -85,7 +85,7 @@ module ActiveSupport ManagementFactory.thread_mx_bean.get_current_thread_cpu_time / 1000 / 1000 / 1000.0 # seconds end end - + class UserTime < Time def measure ManagementFactory.thread_mx_bean.get_current_thread_user_time / 1000 / 1000 / 1000.0 # seconds @@ -97,7 +97,7 @@ module ActiveSupport ManagementFactory.memory_mx_bean.non_heap_memory_usage.used + ManagementFactory.memory_mx_bean.heap_memory_usage.used end end - + class GcRuns < Amount def measure ManagementFactory.garbage_collector_mx_beans.inject(0) { |total_runs, current_gc| total_runs += current_gc.collection_count } diff --git a/activesupport/lib/active_support/testing/performance/rubinius.rb b/activesupport/lib/active_support/testing/performance/rubinius.rb index 198d235548..d9ebfbe352 100644 --- a/activesupport/lib/active_support/testing/performance/rubinius.rb +++ b/activesupport/lib/active_support/testing/performance/rubinius.rb @@ -10,12 +10,12 @@ module ActiveSupport { :metrics => [:wall_time], :formats => [:flat, :graph] } end).freeze - + protected def run_gc GC.run(true) end - + class Performer; end class Profiler < Performer @@ -23,35 +23,35 @@ module ActiveSupport super @supported = @metric.is_a?(Metrics::WallTime) end - + def run return unless @supported - + @profiler = Rubinius::Profiler::Instrumenter.new - + @total = time_with_block do @profiler.profile(false) do full_profile_options[:runs].to_i.times { run_test(@metric, :profile) } end end end - + def record return unless @supported - + if(full_profile_options[:formats].include?(:flat)) create_path_and_open_file(:flat) do |file| @profiler.show(file) end end - + if(full_profile_options[:formats].include?(:graph)) create_path_and_open_file(:graph) do |file| @profiler.show(file) end end end - + protected def create_path_and_open_file(printer_name) fname = "#{output_filename}_#{printer_name}.txt" @@ -62,10 +62,10 @@ module ActiveSupport end end - module Metrics + module Metrics class Base attr_reader :loopback - + def profile yield end diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb index b29ec6719c..7d6d047ef6 100644 --- a/activesupport/lib/active_support/testing/performance/ruby.rb +++ b/activesupport/lib/active_support/testing/performance/ruby.rb @@ -16,7 +16,7 @@ module ActiveSupport :metrics => [:process_time, :memory, :objects], :formats => [:flat, :graph_html, :call_tree, :call_stack] } end).freeze - + protected def run_gc GC.start @@ -77,7 +77,7 @@ module ActiveSupport def measure_mode self.class::Mode end - + def profile RubyProf.resume yield @@ -91,7 +91,7 @@ module ActiveSupport yield end end - + class ProcessTime < Time Mode = RubyProf::PROCESS_TIME if RubyProf.const_defined?(:PROCESS_TIME) diff --git a/activesupport/lib/active_support/testing/performance/ruby/mri.rb b/activesupport/lib/active_support/testing/performance/ruby/mri.rb index 86e650050b..142279dd6e 100644 --- a/activesupport/lib/active_support/testing/performance/ruby/mri.rb +++ b/activesupport/lib/active_support/testing/performance/ruby/mri.rb @@ -15,7 +15,7 @@ module ActiveSupport end end end - + class Memory < DigitalInformationUnit # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_memory) @@ -24,7 +24,7 @@ module ActiveSupport end end end - + class Objects < Amount # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_allocations) @@ -33,7 +33,7 @@ module ActiveSupport end end end - + class GcRuns < Amount # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_gc_runs) @@ -42,7 +42,7 @@ module ActiveSupport end end end - + class GcTime < Time # Ruby 1.8 + ruby-prof wrapper if RubyProf.respond_to?(:measure_gc_time) @@ -55,5 +55,3 @@ module ActiveSupport end end end - - diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb index 62095a8fe4..7873262331 100644 --- a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb +++ b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb @@ -15,7 +15,7 @@ module ActiveSupport end end end - + class Memory < DigitalInformationUnit # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocated_size) @@ -24,7 +24,7 @@ module ActiveSupport end end end - + class Objects < Amount # Ruby 1.9 + GCdata patch if GC.respond_to?(:malloc_allocations) @@ -33,7 +33,7 @@ module ActiveSupport end end end - + class GcRuns < Amount # Ruby 1.9 if GC.respond_to?(:count) @@ -42,7 +42,7 @@ module ActiveSupport end end end - + class GcTime < Time # Ruby 1.9 with GC::Profiler if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time) diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 456f4b7948..6b50f8db37 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -5,8 +5,9 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase def setup @class = Class.new do cattr_accessor :foo - cattr_accessor :bar, :instance_writer => false - cattr_reader :shaq, :instance_reader => false + cattr_accessor :bar, :instance_writer => false + cattr_reader :shaq, :instance_reader => false + cattr_accessor :camp, :instance_accessor => false end @object = @class.new end @@ -35,4 +36,10 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase assert_respond_to @class, :shaq assert !@object.respond_to?(:shaq) end + + def test_should_not_create_instance_accessors + assert_respond_to @class, :camp + assert !@object.respond_to?(:camp) + assert !@object.respond_to?(:camp=) + end end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index d58b60482b..e290a6e012 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -60,6 +60,12 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting = 'boom' } end + test 'disabling instance reader' do + object = Class.new { class_attribute :setting, :instance_reader => false }.new + assert_raise(NoMethodError) { object.setting } + assert_raise(NoMethodError) { object.setting? } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index d81693209f..b4f848cd44 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -454,4 +454,10 @@ class DateExtBehaviorTest < Test::Unit::TestCase Date.today.freeze.inspect end end + + def test_can_freeze_twice + assert_nothing_raised do + Date.today.freeze.freeze + end + end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 32675c884a..7a8cda160d 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -251,7 +251,7 @@ class StringInflectionsTest < Test::Unit::TestCase # And changes the original string: assert_equal original, expected end - + def test_string_inquiry assert "production".inquiry.production? assert !"production".inquiry.development? @@ -451,6 +451,12 @@ class OutputSafetyTest < ActiveSupport::TestCase assert !'ruby'.encoding_aware? end end + + test "call to_param returns a normal string" do + string = @string.html_safe + assert string.html_safe? + assert !string.to_param.html_safe? + 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 44e02109b1..c4c4381957 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -767,6 +767,26 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal false, Time === DateTime.civil(2000) end + def test_all_day + assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day + end + + def test_all_week + assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week + end + + def test_all_month + assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month + end + + def test_all_quarter + assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter + end + + def test_all_year + assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb index a7db96eb71..20768b777a 100644 --- a/activesupport/test/flush_cache_on_private_memoization_test.rb +++ b/activesupport/test/flush_cache_on_private_memoization_test.rb @@ -2,7 +2,9 @@ require 'abstract_unit' require 'test/unit' class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end def test_public assert_method_unmemoizable :pub diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 95f18126d4..b9e299af75 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -94,6 +94,88 @@ class InflectorTest < Test::Unit::TestCase assert_equal('capital', ActiveSupport::Inflector.camelize('Capital', false)) end + def test_camelize_with_underscores + assert_equal("CamelCase", ActiveSupport::Inflector.camelize('Camel_Case')) + end + + def test_acronyms + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + inflect.acronym("HTTP") + inflect.acronym("RESTful") + inflect.acronym("W3C") + inflect.acronym("PhD") + inflect.acronym("RoR") + inflect.acronym("SSL") + end + + # camelize underscore humanize titleize + [ + ["API", "api", "API", "API"], + ["APIController", "api_controller", "API controller", "API Controller"], + ["Nokogiri::HTML", "nokogiri/html", "Nokogiri/HTML", "Nokogiri/HTML"], + ["HTTPAPI", "http_api", "HTTP API", "HTTP API"], + ["HTTP::Get", "http/get", "HTTP/get", "HTTP/Get"], + ["SSLError", "ssl_error", "SSL error", "SSL Error"], + ["RESTful", "restful", "RESTful", "RESTful"], + ["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"], + ["IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"], + ["PhDRequired", "phd_required", "PhD required", "PhD Required"], + ["IRoRU", "i_ror_u", "I RoR u", "I RoR U"], + ["RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"], + + # misdirection + ["Capistrano", "capistrano", "Capistrano", "Capistrano"], + ["CapiController", "capi_controller", "Capi controller", "Capi Controller"], + ["HttpsApis", "https_apis", "Https apis", "Https Apis"], + ["Html5", "html5", "Html5", "Html5"], + ["Restfully", "restfully", "Restfully", "Restfully"], + ["RoRails", "ro_rails", "Ro rails", "Ro Rails"] + ].each do |camel, under, human, title| + assert_equal(camel, ActiveSupport::Inflector.camelize(under)) + assert_equal(camel, ActiveSupport::Inflector.camelize(camel)) + assert_equal(under, ActiveSupport::Inflector.underscore(under)) + assert_equal(under, ActiveSupport::Inflector.underscore(camel)) + assert_equal(title, ActiveSupport::Inflector.titleize(under)) + assert_equal(title, ActiveSupport::Inflector.titleize(camel)) + assert_equal(human, ActiveSupport::Inflector.humanize(under)) + end + end + + def test_acronym_override + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("LegacyApi") + end + + assert_equal("LegacyApi", ActiveSupport::Inflector.camelize("legacyapi")) + assert_equal("LegacyAPI", ActiveSupport::Inflector.camelize("legacy_api")) + assert_equal("SomeLegacyApi", ActiveSupport::Inflector.camelize("some_legacyapi")) + assert_equal("Nonlegacyapi", ActiveSupport::Inflector.camelize("nonlegacyapi")) + end + + def test_acronyms_camelize_lower + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + end + + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("html_api", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("htmlAPI", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("HTMLAPI", false)) + end + + def test_underscore_acronym_sequence + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML5") + inflect.acronym("HTML") + end + + assert_equal("html5_html_api", ActiveSupport::Inflector.underscore("HTML5HTMLAPI")) + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) @@ -148,8 +230,8 @@ class InflectorTest < Test::Unit::TestCase end def test_parameterize_with_custom_separator - StringToParameterized.each do |some_string, parameterized_string| - assert_equal(parameterized_string.gsub('-', '_'), ActiveSupport::Inflector.parameterize(some_string, '_')) + StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index ec9d92794c..0cb1f70657 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -168,6 +168,7 @@ module InflectorTestCases StringToParameterizeWithNoSeparator = { "Donald E. Knuth" => "donaldeknuth", + "With-some-dashes" => "with-some-dashes", "Random text with *(bad)* characters" => "randomtextwithbadcharacters", "Trailing bad characters!@#" => "trailingbadcharacters", "!@#Leading bad characters" => "leadingbadcharacters", @@ -179,6 +180,8 @@ module InflectorTestCases StringToParameterizeWithUnderscore = { "Donald E. Knuth" => "donald_e_knuth", "Random text with *(bad)* characters" => "random_text_with_bad_characters", + "With-some-dashes" => "with-some-dashes", + "Retain_underscore" => "retain_underscore", "Trailing bad characters!@#" => "trailing_bad_characters", "!@#Leading bad characters" => "leading_bad_characters", "Squeeze separators" => "squeeze_separators", diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb index bceac1315b..e333b9a78c 100644 --- a/activesupport/test/memoizable_test.rb +++ b/activesupport/test/memoizable_test.rb @@ -2,7 +2,9 @@ require 'abstract_unit' class MemoizableTest < ActiveSupport::TestCase class Person - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls @@ -65,7 +67,9 @@ class MemoizableTest < ActiveSupport::TestCase end module Rates - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end attr_reader :sales_tax_calls def sales_tax(price) @@ -77,7 +81,9 @@ class MemoizableTest < ActiveSupport::TestCase end class Calculator - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end include Rates attr_reader :fib_calls @@ -96,6 +102,15 @@ class MemoizableTest < ActiveSupport::TestCase end memoize :fib + def add_or_subtract(i, j, add) + if add + i + j + else + i - j + end + end + memoize :add_or_subtract + def counter @count ||= 0 @count += 1 @@ -199,9 +214,16 @@ class MemoizableTest < ActiveSupport::TestCase assert_equal 13, @calculator.fib_calls end + def test_memoization_with_boolean_arg + assert_equal 4, @calculator.add_or_subtract(2, 2, true) + assert_equal 2, @calculator.add_or_subtract(4, 2, false) + end + def test_object_memoization [Company.new, Company.new, Company.new].each do |company| - company.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + company.extend ActiveSupport::Memoizable + end company.memoize :name assert_equal "37signals", company.name @@ -235,11 +257,15 @@ class MemoizableTest < ActiveSupport::TestCase def test_double_memoization assert_raise(RuntimeError) { Person.memoize :name } person = Person.new - person.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + person.extend ActiveSupport::Memoizable + end assert_raise(RuntimeError) { person.memoize :name } company = Company.new - company.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + company.extend ActiveSupport::Memoizable + end company.memoize :name assert_raise(RuntimeError) { company.memoize :name } end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index a4e2acbb32..46bc0d7a34 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -4,6 +4,7 @@ begin rescue LoadError end +require 'active_support/core_ext/string/inflections' require 'yaml' class SafeBufferTest < ActiveSupport::TestCase @@ -45,7 +46,7 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal ActiveSupport::SafeBuffer, new_buffer.class end - def test_to_yaml + test "Should be converted to_yaml" do str = 'hello!' buf = ActiveSupport::SafeBuffer.new str yaml = buf.to_yaml @@ -54,10 +55,57 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal 'hello!', YAML.load(yaml) end - def test_nested + test "Should work in nested to_yaml conversion" do str = 'hello!' data = { 'str' => ActiveSupport::SafeBuffer.new(str) } yaml = YAML.dump data assert_equal({'str' => str}, YAML.load(yaml)) end + + test "Should work with underscore" do + str = "MyTest".html_safe.underscore + assert_equal "my_test", str + end + + test "Should not return safe buffer from gsub" do + altered_buffer = @buffer.gsub('', 'asdf') + assert_equal 'asdf', altered_buffer + assert !altered_buffer.html_safe? + end + + test "Should not return safe buffer from gsub!" do + @buffer.gsub!('', 'asdf') + assert_equal 'asdf', @buffer + assert !@buffer.html_safe? + end + + test "Should escape dirty buffers on add" do + dirty = @buffer + clean = "hello".html_safe + @buffer.gsub!('', '<>') + assert_equal "hello<>", clean + @buffer + end + + test "Should concat as a normal string when dirty" do + dirty = @buffer + clean = "hello".html_safe + @buffer.gsub!('', '<>') + assert_equal "<>hello", @buffer + clean + end + + test "Should preserve dirty? status on copy" do + @buffer.gsub!('', '<>') + assert !@buffer.dup.html_safe? + end + + test "Should raise an error when safe_concat is called on dirty buffers" do + @buffer.gsub!('', '<>') + assert_raise ActiveSupport::SafeBuffer::SafeConcatError do + @buffer.safe_concat "BUSTED" + end + end + + test "should not fail if the returned object is not a string" do + assert_kind_of NilClass, @buffer.slice("chipchop") + end end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index ee5a20c789..5bd995aa32 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -50,7 +50,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def test_expression_is_evaluated_in_the_appropriate_scope silence_warnings do - local_scope = 'foo' + local_scope = local_scope = 'foo' assert_difference('local_scope; @object.num') { @object.increment } end end diff --git a/ci/ci_build.rb b/ci/ci_build.rb index c3af1f0177..50f7f410fa 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -85,8 +85,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with MySQL IM enabled" puts ENV['IM'] = 'true' - 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_IM] = rake 'mysql:rebuild_databases', 'mysql:test' + build_results[:activerecord_mysql_isolated_IM] = rake 'mysql:rebuild_databases', 'mysql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -103,8 +103,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with MySQL2 IM enabled" puts ENV['IM'] = 'true' - build_results[:activerecord_mysql2] = rake 'mysql:rebuild_databases', 'mysql2:test' - build_results[:activerecord_mysql2_isolated] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test' + build_results[:activerecord_mysql2_IM] = rake 'mysql:rebuild_databases', 'mysql2:test' + build_results[:activerecord_mysql2_isolated_IM] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test' end cd "#{root_dir}/activerecord" do @@ -121,8 +121,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with PostgreSQL IM enabled" puts ENV['IM'] = 'true' - 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_IM] = rake 'postgresql:rebuild_databases', 'postgresql:test' + build_results[:activerecord_postgresql8_isolated_IM] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test' end cd "#{root_dir}/activerecord" do @@ -139,8 +139,8 @@ cd "#{root_dir}/activerecord" do puts "[CruiseControl] Building Active Record with SQLite 3 IM enabled" puts ENV['IM'] = 'true' - build_results[:activerecord_sqlite3] = rake 'sqlite3:test' - build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test' + build_results[:activerecord_sqlite3_IM] = rake 'sqlite3:test' + build_results[:activerecord_sqlite3_isolated_IM] = rake 'sqlite3:isolated_test' end cd "#{root_dir}/activerecord" do @@ -179,4 +179,3 @@ else puts "[CruiseControl] Failed components: #{failures.map { |component| component.first }.join(', ')}" exit(-1) end - diff --git a/load_paths.rb b/load_paths.rb index 15345e31e3..590af69330 100644 --- a/load_paths.rb +++ b/load_paths.rb @@ -1,9 +1,8 @@ # bust gem prelude if defined? Gem - Gem.source_index gem 'bundler' else require 'rubygems' end require 'bundler' -Bundler.setup
\ No newline at end of file +Bundler.setup diff --git a/railties/CHANGELOG b/railties/CHANGELOG index c77d03c39f..e8b4a32056 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,25 +1,24 @@ *Rails 3.2.0 (unreleased)* +* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" [José Valim] + * Removed old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command. [Guillermo Iguaran] * Removed old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API. [Guillermo Iguaran] - *Rails 3.1.0 (unreleased)* -* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] +* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim] -* Fixed database tasks for jdbc* adapters #jruby +* The new rake task assets:clean removes precompiled assets. [fxn] - [Rashmi Yadav] - -* Template generation for jdbcpostgresql #jruby +* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] - [Vishnu Atrai] +* Fixed database tasks for jdbc* adapters #jruby [Rashmi Yadav] -* Template generation for jdbcmysql and jdbcsqlite3 #jruby +* Template generation for jdbcpostgresql #jruby [Vishnu Atrai] - [Arun Agrawal] +* Template generation for jdbcmysql and jdbcsqlite3 #jruby [Arun Agrawal] * The -j option of the application generator accepts an arbitrary string. If passed "foo", the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest diff --git a/railties/guides/assets/images/radar.png b/railties/guides/assets/images/radar.png Binary files differnew file mode 100644 index 0000000000..f61e08763f --- /dev/null +++ b/railties/guides/assets/images/radar.png diff --git a/railties/guides/assets/images/vijaydev.jpg b/railties/guides/assets/images/vijaydev.jpg Binary files differnew file mode 100644 index 0000000000..e21d3cabfc --- /dev/null +++ b/railties/guides/assets/images/vijaydev.jpg diff --git a/railties/guides/rails_guides/helpers.rb b/railties/guides/rails_guides/helpers.rb index d466c76c7c..463df8a7a8 100644 --- a/railties/guides/rails_guides/helpers.rb +++ b/railties/guides/rails_guides/helpers.rb @@ -15,7 +15,7 @@ module RailsGuides def author(name, nick, image = 'credits_pic_blank.gif', &block) image = "images/#{image}" - result = content_tag(:img, nil, :src => image, :class => 'left pic', :alt => name) + result = content_tag(:img, nil, :src => image, :class => 'left pic', :alt => name, :width => 91, :height => 91) result << content_tag(:h3, name) result << content_tag(:p, capture(&block)) content_tag(:div, result, :class => 'clearfix', :id => nick) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index b064851312..7703d6c720 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -165,7 +165,7 @@ will produce <em>emphasized</em> <em><b>emph & bold</b></em> <a href="http://rubyonrails.org">A link</a> -<target option="fast" name="compile" \> +<target option="fast" name="compile" /> </html> Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: @@ -211,7 +211,7 @@ xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do end </ruby> -h5. Template caching +h5. Template Caching By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will check the file's modification time and recompile it in development mode. @@ -235,7 +235,7 @@ This will render a file named +_menu.html.erb+ at that point within the view is That code will pull in the partial from +app/views/shared/_menu.html.erb+. -h5. Using Partials to Simplify Views +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: @@ -929,7 +929,7 @@ Creates a scope around a specific model object like form_for, but doesn‘t crea h5. file_field -Returns an file upload input tag tailored for accessing a specified attribute. +Returns a file upload input tag tailored for accessing a specified attribute. <ruby> file_field(:user, :avatar) @@ -1037,7 +1037,7 @@ 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}) </ruby> -If @post.author_id is already 1, this would return: +If <tt>@post.author_id</tt> is 1, this would return: <html> <select name="post[author_id]"> @@ -1080,8 +1080,6 @@ Sample usage: option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3) </ruby> -TODO check above textile output looks right - Possible output: <html> @@ -1132,13 +1130,13 @@ h5. select Create a select tag and a series of contained option tags for the provided object and method. -Example with @post.person_id => 1: +Example: <ruby> select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }) </ruby> -could become: +If <tt>@post.person_id</tt> is 1, this would become: <html> <select name="post[person_id]"> @@ -1189,7 +1187,7 @@ h5. file_field_tag Creates a file upload field. -If you are using file uploads then you will also need to set the multipart option for the form tag: +Prior to Rails 3.1, if you are using file uploads, then you will need to set the multipart option for the form tag. Rails 3.1+ does this automatically. <ruby> <%= form_tag { :action => "post" }, { :multipart => true } do %> @@ -1400,102 +1398,6 @@ number_with_precision(111.2345) # => 111.235 number_with_precision(111.2345, 2) # => 111.23 </ruby> -h5. evaluate_remote_response - -Returns +eval(request.responseText)+ which is the JavaScript function that form_remote_tag can call in +:complete+ to evaluate a multiple update return document using +update_element_function+ calls. - -h5. form_remote_tag - -Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement. Even though it‘s using JavaScript to serialize the form elements, the form submission will work just like a regular submission as viewed by the receiving side. - -For example, this: - -<ruby> -form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") } -</ruby> - -would generate the following: - -<html> -<form action="/some/place" method="post" onsubmit="new Ajax.Request('', - {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;"> -</html> - -h5. link_to_remote - -Returns a link to a remote action that's called in the background using XMLHttpRequest. You can generate a link that uses AJAX in the general case, while degrading gracefully to plain link behavior in the absence of JavaScript. For example: - -<ruby> -link_to_remote "Delete this post", - { :update => "posts", :url => { :action => "destroy", :id => post.id } }, - :href => url_for(:action => "destroy", :id => post.id) -</ruby> - -h5. observe_field - -Observes the field specified and calls a callback when its contents have changed. - -<ruby> -observe_field("my_field", :function => "alert('Field changed')") -</ruby> - -h5. observe_form - -Observes the form specified and calls a callback when its contents have changed. The options for observe_form are the same as the options for observe_field. - -<ruby> -observe_field("my_form", :function => "alert('Form changed')") -</ruby> - -h5. periodically_call_remote - -Periodically calls the specified url as often as specified. Usually used to update a specified div with the results of the remote call. The following example will call update every 20 seconds and update the news_block div: - -<ruby> -periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block') -# => PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20) -</ruby> - -h5. remote_form_for - -Creates a form that will submit using XMLHttpRequest in the background instead of the regular reloading POST arrangement and a scope around a specific resource that is used as a base for questioning about values for the fields. - -<ruby> -<%= remote_form_for(@post) do |f| %> - ... -<% end %> -</ruby> - -h5. remote_function - -Returns the JavaScript needed for a remote function. Takes the same arguments as +link_to_remote+. - -<ruby> -<select id="options" onchange="<%= remote_function(:update => "options", :url => { :action => :update_options }) %>"> - <option value="0">Hello</option> - <option value="1">World</option> -</select> -# => <select id="options" onchange="new Ajax.Updater('options', '/testing/update_options', {asynchronous:true, evalScripts:true})"> -</ruby> - -h5. submit_to_remote - -Returns a button input tag that will submit form using XMLHttpRequest in the background instead of a regular POST request that reloads the page. - -For example, the following: - -<ruby> -submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } -</ruby> - -would generate: - -<html> -<input name="create_btn" onclick="new Ajax.Request('/testing/create', - {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); - return false;" type="button" value="Create" /> -</html> - h3. Localized Views Action View has the ability render different templates depending on the current locale. @@ -1520,6 +1422,7 @@ You can read more about the Rails Internationalization (I18n) API "here":i18n.ht h3. Changelog +* May 29, 2011: Removed references to remote_* helpers - Vijay Dev * April 16, 2011: Added 'Using Action View with Rails', 'Templates' and 'Partials' sections. "Sebastian Martinez":http://wyeworks.com * September 3, 2009: Continuing work by Trevor Turk, leveraging the Action Pack docs and "What's new in Edge Rails":http://ryandaigle.com/articles/2007/8/3/what-s-new-in-edge-rails-partials-get-layouts * April 5, 2009: Starting work by Trevor Turk, leveraging Mike Gunderloy's docs diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile index b7926f3a3b..3e46e7df9f 100644 --- a/railties/guides/source/active_record_basics.textile +++ b/railties/guides/source/active_record_basics.textile @@ -64,8 +64,8 @@ There are also some optional column names that will create additional features t * *created_on* - Automatically gets set to the current date when the record is first created. * *updated_at* - Automatically gets set to the current date and time whenever the record is updated. * *updated_on* - Automatically gets set to the current date whenever the record is updated. -* *lock_version* - Adds "optimistic locking":http://api.rubyonrails.com/classes/ActiveRecord/Locking.html to a model. -* *type* - Specifies that the model uses "Single Table Inheritance":http://api.rubyonrails.com/classes/ActiveRecord/Base.html +* *lock_version* - Adds "optimistic locking":http://api.rubyonrails.org/classes/ActiveRecord/Locking.html to a model. +* *type* - Specifies that the model uses "Single Table Inheritance":http://api.rubyonrails.org/classes/ActiveRecord/Base.html * *(table_name)_count* - Used to cache the number of belonging objects on associations. For example, a +comments_count+ column in a +Post+ class that has many instances of +Comment+ will cache the number of existent comments for each post. NOTE: While these column names are optional they are in fact reserved by Active Record. Steer clear of reserved keywords unless you want the extra functionality. For example, "type" is a reserved keyword used to designate a table using Single Table Inheritance. If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling. diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 579a323d57..e3871a3c34 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -57,6 +57,7 @@ The methods are: * +group+ * +order+ * +reorder+ +* +reverse_order+ * +limit+ * +offset+ * +joins+ @@ -483,16 +484,16 @@ SQL uses the +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You For example: <ruby> -Order.group("date(created_at)").having("created_at > ?", 1.month.ago) +Order.group("date(created_at)").having("created_at < ?", 1.month.ago) </ruby> The SQL that would be executed would be something like this: <sql> -SELECT * FROM orders GROUP BY date(created_at) HAVING created_at > '2009-01-15' +SELECT * FROM orders GROUP BY date(created_at) HAVING created_at < '2011-04-27' </sql> -This will return single order objects for each day, but only for the last month. +This will return single order objects for each day, but only those that are at least one month old. h3. Overriding Conditions @@ -550,6 +551,32 @@ In case the +reorder+ clause is not used, the SQL executed would be: SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC </sql> +h4. +reverse_order+ + +The +reverse_order+ method reverses the ordering clause if specified. + +<ruby> +Client.where("orders_count > 10").order(:name).reverse_order +</ruby> + +The SQL that would be executed: +<sql> +SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC +</sql> + +If no ordering clause is specified in the query, the +reverse_order+ orders by the primary key in reverse order. + +<ruby> +Client.where("orders_count > 10").reverse_order +</ruby> + +The SQL that would be executed: +<sql> +SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC +</sql> + +This method accepts *no* arguments. + h3. Readonly Objects Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. @@ -675,7 +702,7 @@ class Post < ActiveRecord::Base has_many :tags end -class Comments < ActiveRecord::Base +class Comment < ActiveRecord::Base belongs_to :post has_one :guest end @@ -683,6 +710,10 @@ end class Guest < ActiveRecord::Base belongs_to :comment end + +class Tag < ActiveRecord::Base + belongs_to :post +end </ruby> Now all of the following will produce the expected join queries using +INNER JOIN+: @@ -700,6 +731,8 @@ SELECT categories.* FROM categories INNER JOIN posts ON posts.category_id = categories.id </sql> +Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:post).select("distinct(categories.id)"). + h5. Joining Multiple Associations <ruby> @@ -714,18 +747,40 @@ SELECT posts.* FROM posts INNER JOIN comments ON comments.post_id = posts.id </sql> +Or, in English: "return all posts that have a category and at least one comment". Note again that posts with multiple comments will show up multiple times. + h5. Joining Nested Associations (Single Level) <ruby> Post.joins(:comments => :guest) </ruby> +This produces: + +<sql> +SELECT posts.* FROM posts + INNER JOIN comments ON comments.post_id = posts.id + INNER JOIN guests ON guests.comment_id = comments.id +</sql> + +Or, in English: "return all posts that have a comment made by a guest." + h5. Joining Nested Associations (Multiple Level) <ruby> Category.joins(:posts => [{:comments => :guest}, :tags]) </ruby> +This produces: + +<sql> +SELECT categories.* FROM categories + INNER JOIN posts ON posts.category_id = categories.id + INNER JOIN comments ON comments.post_id = posts.id + INNER JOIN guests ON guests.comment_id = comments.id + INNER JOIN tags ON tags.post_id = posts.id +</sql> + h4. Specifying Conditions on the Joined Tables You can specify conditions on the joined tables using the regular "Array":#array-conditions and "String":#pure-string-conditions conditions. "Hash conditions":#hash-conditions provides a special syntax for specifying conditions for the joined tables: diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index 9f59397d7d..50ff1c9ff7 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -82,6 +82,7 @@ The following methods skip validations, and will save the object to the database * +increment!+ * +increment_counter+ * +toggle!+ +* +touch+ * +update_all+ * +update_attribute+ * +update_column+ @@ -181,7 +182,7 @@ It can receive an +:accept+ option, which determines the value that will be cons <ruby> class Person < ActiveRecord::Base - validates :terms_of_service, :acceptance => true, :accept => 'yes' + validates :terms_of_service, :acceptance => { :accept => 'yes' } end </ruby> @@ -337,7 +338,7 @@ WARNING. Note that the regular expression above allows a trailing newline charac <ruby> class Player < ActiveRecord::Base validates :points, :numericality => true - validates :games_played, :numericality => true, :only_integer => true + validates :games_played, :numericality => { :only_integer => true } end </ruby> @@ -392,8 +393,8 @@ There is a +:scope+ option that you can use to specify other attributes that are <ruby> class Holiday < ActiveRecord::Base - validates :name, :uniqueness => true, :scope => :year, - :message => "should happen once per year" + validates :name, :uniqueness => { :scope => :year, + :message => "should happen once per year" } end </ruby> @@ -401,7 +402,7 @@ There is also a +:case_sensitive+ option that you can use to define whether the <ruby> class Person < ActiveRecord::Base - validates :name, :uniqueness => true, :case_sensitive => false + validates :name, :uniqueness => { :case_sensitive => false } end </ruby> @@ -429,7 +430,7 @@ end The +validates_with+ helper takes a class, or a list of classes to use for validation. There is no default error message for +validates_with+. You must manually add errors to the record's errors collection in the validator class. -To implement the validate method, you must have an +record+ parameter defined, which is the record to be validated. +To implement the validate method, you must have a +record+ parameter defined, which is the record to be validated. Like all other validations, +validates_with+ takes the +:if+, +:unless+ and +:on+ options. If you pass any other options, it will send those options to the validator class as +options+: @@ -911,20 +912,20 @@ h4. Creating an Object * +before_validation+ * +after_validation+ * +before_save+ -* +after_save+ * +before_create+ * +around_create+ * +after_create+ +* +after_save+ h4. Updating an Object * +before_validation+ * +after_validation+ * +before_save+ -* +after_save+ * +before_update+ * +around_update+ * +after_update+ +* +after_save+ h4. Destroying an Object @@ -1007,6 +1008,7 @@ Just as with validations, it's also possible to skip callbacks. These methods sh * +increment+ * +increment_counter+ * +toggle+ +* +touch+ * +update_column+ * +update_all+ * +update_counters+ diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 43aa6dfbbb..bbf5af5dcc 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -868,7 +868,7 @@ The macro accepts several methods: delegate :name, :age, :address, :twitter, :to => :profile </ruby> -When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such a expression is evaluated in the context of the receiver: +When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such an expression is evaluated in the context of the receiver: <ruby> # delegates to the Rails constant @@ -947,7 +947,7 @@ h4. Class Attributes h5. +class_attribute+ -The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy: +The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy. <ruby> class A @@ -983,7 +983,7 @@ self.default_params = { }.freeze </ruby> -They can be also accessed and overridden at the instance level: +They can be also accessed and overridden at the instance level. <ruby> A.x = 1 @@ -996,7 +996,7 @@ a1.x # => 1, comes from A a2.x # => 2, overridden in a2 </ruby> -The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to false, as in +The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to +false+. <ruby> module ActiveRecord @@ -1009,8 +1009,20 @@ end A model may find that option useful as a way to prevent mass-assignment from setting the attribute. +The generation of the reader instance method can be prevented by setting the option +:instance_reader+ to +false+. + +<ruby> +class A + class_attribute :x, :instance_reader => false +end + +A.x = 1 # NoMethodError +</ruby> + For convenience +class_attribute+ also defines an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called +x?+. +When +:instance_reader+ is +false+, the instance predicate returns a +NoMethodError+ just like the reader method. + NOTE: Defined in +active_support/core_ext/class/attribute.rb+ h5. +cattr_reader+, +cattr_writer+, and +cattr_accessor+ @@ -1036,18 +1048,24 @@ module ActionView end </ruby> -we can access +field_error_proc+ in views. The generation of the writer instance method can be prevented by setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): +we can access +field_error_proc+ in views. + +The generation of the reader instance method can be prevented by setting +:instance_reader+ to +false+ and the generation of the writer instance method can be prevented by setting +:instance_writer+ to +false+. Generation of both methods can be prevented by setting +:instance_accessor+ to +false+. In all cases, the value must be exactly +false+ and not any false value. <ruby> -module ActiveRecord - class Base - # No pluralize_table_names= instance writer is generated. - cattr_accessor :pluralize_table_names, :instance_writer => false +module A + class B + # No first_name instance reader is generated. + cattr_accessor :first_name, :instance_reader => false + # No last_name= instance writer is generated. + cattr_accessor :last_name, :instance_writer => false + # No surname instance reader or surname= writer is generated. + cattr_accessor :surname, :instance_accessor => false end end </ruby> -A model may find that option useful as a way to prevent mass-assignment from setting the attribute. +A model may find it useful to set +:instance_accessor+ to +false+ as a way to prevent mass-assignment from setting the attribute. NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+. @@ -1146,8 +1164,12 @@ h3. Extensions to +String+ h4. Output Safety +h5. Motivation + 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. +h5. Safe Strings + Active Support has the concept of <i>(html) safe</i> 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 <i>unsafe</i> by default: @@ -1173,8 +1195,6 @@ s # => "<script>...</script>" It is your responsibility 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+/<tt><<</tt>, or with <tt>+</tt>, the result is a safe string. Unsafe arguments are escaped: <ruby> @@ -1215,6 +1235,22 @@ end NOTE: Defined in +active_support/core_ext/string/output_safety.rb+. +h5. Transformation + +As a rule of thumb, except perhaps for concatenation as explained above, any method that may change a string gives you an unsafe string. These are +donwcase+, +gsub+, +strip+, +chomp+, +underscore+, etc. + +In the case of in-place transformations like +gsub!+ the receiver itself becomes unsafe. + +INFO: The safety bit is lost always, no matter whether the transformation actually changed something. + +h5. Conversion and Coercion + +Calling +to_s+ on a safe string returns a safe string, but coercion with +to_str+ returns an unsafe string. + +h5. Copying + +Calling +dup+ or +clone+ on safe strings yields safe strings. + h4. +squish+ The method +squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: @@ -1460,7 +1496,15 @@ end That may be handy to compute method names in a language that follows that convention, for example JavaScript. -INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>. +INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>. To support cases such as this, Active Support allows you to specify acronyms in +config/initializers/inflections.rb+: + +<ruby> +ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym 'SSL' +end + +"SSLError".underscore.camelize #=> "SSLError" +</ruby> +camelize+ is aliased to +camelcase+. @@ -2282,7 +2326,7 @@ NOTE: Defined in +active_support/core_ext/array/grouping.rb+. h5. +in_groups(number, fill_with = nil)+ -The method +in_groups+ splits an array into a certain number of groups. The method returns and array with the groups: +The method +in_groups+ splits an array into a certain number of groups. The method returns an array with the groups: <ruby> %w(1 2 3 4 5 6 7).in_groups(3) @@ -2714,7 +2758,7 @@ Active Support extends the method +Range#step+ so that it can be invoked without (1..10).step(2) # => [1, 3, 5, 7, 9] </ruby> -As the example shows, in that case the method returns and array with the corresponding elements. +As the example shows, in that case the method returns an array with the corresponding elements. NOTE: Defined in +active_support/core_ext/range/blockless_step.rb+. @@ -3024,7 +3068,7 @@ Date.new(2010, 1, 31).change(:month => 2) h5(#date-durations). Durations -Durations can be added and substracted to dates: +Durations can be added to and subtracted from dates: <ruby> d = Date.current @@ -3232,7 +3276,7 @@ DateTime.current.change(:month => 2, :day => 30) h5(#datetime-durations). Durations -Durations can be added and substracted to datetimes: +Durations can be added to and subtracted from datetimes: <ruby> now = DateTime.current @@ -3316,6 +3360,32 @@ Active Support defines +Time.current+ to be today in the current time zone. That When making Time comparisons using methods which honor the user time zone, make sure to use +Time.current+ and not +Time.now+. There are cases where the user time zone might be in the future compared to the system time zone, which +Time.today+ uses by default. This means +Time.now+ may equal +Time.yesterday+. +h5. +all_day+, +all_week+, +all_month+, +all_quarter+ and +all_year+ + +The method +all_day+ returns a range representing the whole day of the current time. + +<ruby> +now = Time.current +# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 +now.all_day +# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Mon, 09 Aug 2010 23:59:59 UTC +00:00 +</ruby> + +Analogously, +all_week+, +all_month+, +all_quarter+ and +all_year+ all serve the purpose of generating time ranges. + +<ruby> +now = Time.current +# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 +now.all_week +# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00 +now.all_month +# => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00 +now.all_quarter +# => Thu, 01 Jul 2010 00:00:00 UTC +00:00..Thu, 30 Sep 2010 23:59:59 UTC +00:00 +now.all_year +# => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00 +</ruby> + h4. Time Constructors Active Support defines +Time.current+ to be +Time.zone.now+ if there's a user time zone defined, with fallback to +Time.now+: @@ -3353,7 +3423,7 @@ If the time to be constructed lies beyond the range supported by +Time+ in the r h5(#time-durations). Durations -Durations can be added and substracted to time objects: +Durations can be added to and subtracted from time objects: <ruby> now = Time.current diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile index 1566c23414..8b72e20c33 100644 --- a/railties/guides/source/ajax_on_rails.textile +++ b/railties/guides/source/ajax_on_rails.textile @@ -72,7 +72,7 @@ link_to_remote "Add to cart", If the server returns 200, the output of the above example is equivalent to our first, simple one. However, in case of error, the element with the DOM id +error+ is updated rather than the +cart+ element. -** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: +** *position* By default (i.e. when not specifying this option, like in the examples before) the response is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: *** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element. *** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element. *** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all. diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile index e046bc5cbb..50e86e05a8 100644 --- a/railties/guides/source/api_documentation_guidelines.textile +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -41,8 +41,8 @@ h3. Example Code Choose meaningful examples that depict and cover the basics as well as interesting points or gotchas. -Use two spaces to indent chunks of code.—that is two spaces with respect to the left margin; the examples -themselves should use "Rails code conventions":http://rails.lighthouseapp.com/projects/8994/source-style. +Use two spaces to indent chunks of code--that is two spaces with respect to the left margin; the examples +themselves should use "Rails coding conventions":contributing_to_ruby_on_rails.html#follow-the-coding-conventions. Short docs do not need an explicit "Examples" label to introduce snippets, they just follow paragraphs: diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile index 9ea1aa9e01..78683b743c 100644 --- a/railties/guides/source/asset_pipeline.textile +++ b/railties/guides/source/asset_pipeline.textile @@ -3,27 +3,104 @@ h2. Asset Pipeline This guide will cover the ideology of the asset pipeline introduced in Rails 3.1. By referring to this guide you will be able to: +* Understand what the asset pipeline is and what it does * Properly organize your application assets -* Understand the benefits of the asset pipline -* Adding a preproccessor to the pipeline -* Package assets with your plugin +* Understand the benefits of the asset pipeline +* Adding a pre-processor to the pipeline +* Package assets with a gem endprologue. h3. What Is The Asset Pipeline? -h4. Why Should I Use it? +With Rails 3.1 comes a new feature known as the asset pipeline. The asset pipeline provides features that have usually been implemented by external gems, such as "Jammit":http://documentcloud.github.com/jammit and "Sprockets.":http://getsprockets.org These gems are popular for being able to serve concatenated or compressed versions of the assets of an application, such as Cascade Style Sheets (CSS) or JavaScript (JS) files so that the number of requests made to the server are reduced, making the page load faster. Rails 3.1 includes the Sprockets gem. h3. How to Use the Asset Pipeline +In previous versions of Rails, all assets lived under the +public+ directory in directories such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory will be served by the Sprockets middleware included in the sprockets gem. + +This is not to say that assets can (or should) no longer be placed in +public+. They still can be, they will just be served by the application or the web server which is running the application and served just like normal files. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served. + +When a scaffold or controller is generated for the application, Rails will also generate a JavaScript file (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and a Cascade Style Sheet file (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller. For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You should put any JavaScript or CSS unique to a controller inside their respective asset files. + h4. Asset Organization -h4. Default Files Loaded +Assets can be placed inside an application in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+. + ++app/assets+ is for assets that are owned by the application, such as custom images, JavaScript files or stylesheets. + ++lib/assets+ is for your own libraries' code that doesn't really fit into the scope of the application or those libraries which are shared across applications. + ++vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins. + +Any subdirectory that exists within these three locations will be added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths will be looked through to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and then served up. + +h4. External Assets + +Assets can also come from external sources such as engines. A good example of this is the +jquery-rails+ gem which comes with Rails as the standard JavaScript library gem. This gem contains an engine class which inherits from +Rails::Engine+. By doing this, Rails is informed that the directory for this gem may contain assets and the +app/assets+, +lib/assets+ and +vendor/assets+ directories of this engine are added to the search path of Sprockets. + +h4. Serving Assets + +To serve assets, we can use the same tags that we are generally familiar with: + +<erb> + <%= image_tag "rails.png" %> +</erb> + +Providing that assets are enabled within our application (+config.assets.enabled+ in your environment is set to +true+), this file will be served by Sprockets unless a file at +public/assets/rails.png+ exists, in which case that file will be served. Otherwise, Sprockets will look through the available paths until it finds a file that matches the name and then will serve it, first looking in the application's assets directories and then falling back to the various engines of the application. + +Sprockets does not add any new methods to require your assets, we still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+. You can use it to include from the normal public directory or the assets directory. + +h4. Manifest Files and Directives + +Sprockets allows some assets to be manifest files. These manifest files require what's known as _directives_, which instruct Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets will load the files specified, process them if necessary, concatenate them into one single file and then compress them (if +Rails.application.config.assets.compress+ is set to +true+). By serving one file rather than many, a page's load time can be greatly reduced. + +For example, in the default Rails application there's a +app/assets/javascripts/application.js+ file which contains the following lines: + +<plain> +//= require jquery +//= require jquery_ujs +//= require_tree . +</plain> + +In JavaScript files, directives begin with +//=+. In this case, the following file is using the +require+ directive and the +require_tree+ directive. The +require+ directive tells Sprockets that we would like to require a file called +jquery.js+ that is available somewhere in the search path for Sprockets. By default, this is located inside the +vendor/assets/javascripts+ directory contained within the +jquery-rails+ gem. An identical event takes place for the +jquery_ujs+ require specified here also. + +The +require_tree .+ directive tells Sprockets to include _all_ JavaScript files in this directory into the output. Only a path relative to the file can be specified. + +There's also a default +app/assets/stylesheets/application.css+ file which contains these lines: + +<plain> +/* ... +*= require_self +*= require_tree . +*/ +</plain> + +The directives that work in the JavaScript files will also work in stylesheets, obviously requiring stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory. + +In this example +require_self+ is used. This will put the CSS contained within the file (if any) at the top of any other CSS in this file unless +require_self+ is specified after another +require+ directive. + +h4. Preprocessing + +Based on the extensions of the assets, Sprockets will do preprocessing on the files. With the default gemset that comes with Rails, when a controller or a scaffold is generated, a CoffeeScript file and a SCSS file will be generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and a +app/assets/stylesheets/projects.css.scss+ file. + +When these files are requested, they will be processed by the processors provided by the +coffee-script+ and +sass-rails+ gems and then sent back to the browser as JavaScript and CSS respectively. + +In addition to this single layer of pre-processing, we can also put on additional extensions to the end of the file in order for them to be processed using other languages first. For example, we could call our stylesheet +app/assets/stylesheets/projects.css.scss.erb+ it would first be processed as ERB, then SCSS and finally served as CSS. We could also do this with our JavaScript file, calling it +app/assets/javascripts/projects.js.coffee.erb+. + +Keep in mind that the order of these pre-processors is important. For example, if we called our JavaScript file +app/assets/javascripts/projects.js.erb.coffee+ then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore we would run into problems. + +h4. Compressing Assets + +The default Gemfile also includes the "uglifier":https://github.com/lautis/uglifier gem. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compress your code by removing white spaces and other magical things like changing your if and else statements to ternary operators when possible. + +Sprockets also turns on "Gzip":http://en.wikipedia.org/wiki/Gzip (.gz) when possible (by checking the user's headers). Gzip is a file compression technique, much like the ever so popular "zip" file, except it's more open source friendly. Gzip should not modify the contents of the file, but simply the size of the file. -h4. Directives +h4. Adding Assets to Your Gems -h4. Stacking Preproccessors +To include your assets inside of a gem, simple package it in +lib/assets+ as you would in +app/assets+. You should append or prepend the name of your gem though, this should help avoid name conflicts with other gems or the user's application. -h3. Packaging Assets with Your Plugin +h4. Making Your Library or Gem a Pre-Processor -h3. More on Sprockets +"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh +Tilt: https://github.com/rtomayko/tilt
\ No newline at end of file diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index 458bfefad8..3c2497e83a 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1120,11 +1120,9 @@ h6(#has_many-collection-find). <tt><em>collection</em>.find(...)</tt> The <tt><em>collection</em>.find</tt> method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+. <ruby> -@open_orders = @customer.orders.all(:conditions => "open = 1") +@open_orders = @customer.orders.where(:open => 1) </ruby> -NOTE: Starting Rails 3, supplying options to +ActiveRecord::Base.find+ method is discouraged. Use <tt><em>collection</em>.where</tt> instead when you need to pass conditions. - h6(#has_many-collection-where). <tt><em>collection</em>.where(...)</tt> The <tt><em>collection</em>.where</tt> method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 91827fd493..252003edd0 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -112,7 +112,7 @@ As an example, if you wanted to show all the orders placed on your website in re <ruby> <% Order.find_recent.each do |o| %> - <%= o.buyer.name %> bought <% o.product.name %> + <%= o.buyer.name %> bought <%= o.product.name %> <% end %> <% cache do %> @@ -162,17 +162,17 @@ class ProductSweeper < ActionController::Caching::Sweeper # If our sweeper detects that a Product was created call this def after_create(product) - expire_cache_for(product) + expire_cache_for(product) end # If our sweeper detects that a Product was updated call this def after_update(product) - expire_cache_for(product) + expire_cache_for(product) end # If our sweeper detects that a Product was deleted call this def after_destroy(product) - expire_cache_for(product) + expire_cache_for(product) end private @@ -345,7 +345,7 @@ ActionController::Base.cache_store = MyCacheStore.new h4. Cache Keys -The keys used in a cache can be any object that responds to either +:cache_key+ or to +:to_param+. You can implement the +:cache_key+ method on your classes if you need to generate custom keys. ActiveRecord will generate keys based on the class name and record id. +The keys used in a cache can be any object that responds to either +:cache_key+ or to +:to_param+. You can implement the +:cache_key+ method on your classes if you need to generate custom keys. Active Record will generate keys based on the class name and record id. You can use Hashes and Arrays of values as cache keys. @@ -382,6 +382,7 @@ class ProductsController < ApplicationController # anything. The default render checks for this using the parameters # used in the previous call to stale? and will automatically send a # :not_modified. So that's it, you're done. + end end </ruby> diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index ad36c6532e..b0edc1017d 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -5,7 +5,7 @@ Rails comes with every command line tool you'll need to * Create a Rails application * Generate models, controllers, database migrations, and unit tests * Start a development server -* Mess with objects through an interactive shell +* Experiment with objects through an interactive shell * Profile and benchmark your new creation endprologue. @@ -51,21 +51,19 @@ $ rails new commandsapp 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. +rails server+ -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. +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 access your application through a web browser. -INFO: WEBrick isn't your only option for serving Rails. We'll get to that in a later section. +INFO: WEBrick isn't your only option for serving Rails. We'll get to that "later":#different-servers. -Without any prodding of any kind, +rails server+ will run our new shiny Rails app: +With no further work, +rails server+ will run our new shiny Rails app: <shell> $ cd commandsapp $ rails server => Booting WEBrick -=> Rails 3.0.0 application starting in development on http://0.0.0.0:3000 +=> Rails 3.1.0 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2010-04-18 03:20:33] INFO WEBrick 1.3.1 @@ -75,13 +73,21 @@ $ rails server 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. +You can also use the alias "s" to start the server: <tt>rails s</tt>. + +The server can be run on a different port using the +-p+ option. The default development environment can be changed using +-e+. + +<shell> +$ rails server -e production -p 4000 +</shell> + h4. +rails generate+ -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: +The +rails generate+ command uses templates to create a whole lot of things. Running +rails generate+ by itself gives a list of available generators: <shell> $ rails generate -Usage: rails generate generator [args] [options] +Usage: rails generate GENERATOR [args] [options] ... ... @@ -97,7 +103,7 @@ Rails: 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*, 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. +Using generators will save you a large amount of time by writing *boilerplate code*, code that is necessary for the app to work. Let's make our own controller with the controller generator. But what command should we use? Let's ask the generator: @@ -146,7 +152,8 @@ $ rails generate controller Greetings hello create test/unit/helpers/greetings_helper_test.rb invoke assets create app/assets/javascripts/greetings.js - create app/assets/stylesheets/greetings.css + invoke css + create app/assets/stylesheets/greetings.css </shell> @@ -169,7 +176,7 @@ Then the view, to display our message (in +app/views/greetings/hello.html.erb+): <p><%= @message %></p> </html> -Deal. Go check it out in your browser. Fire up your server using +rails server+. +Fire up your server using +rails server+. <shell> $ rails server @@ -182,7 +189,7 @@ The URL will be "http://localhost:3000/greetings/hello":http://localhost:3000/gr 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. -Rails comes with a generator for data models too: +Rails comes with a generator for data models too. <shell> $ rails generate model @@ -237,7 +244,7 @@ dependency model create test/unit/high_score_test.rb create test/fixtures/high_scores.yml exists db/migrate - create db/migrate/20081217071914_create_high_scores.rb + create db/migrate/20100209025147_create_high_scores.rb </shell> 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. @@ -267,11 +274,13 @@ 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. +You can also use the alias "c" to invoke the console: <tt>rails c</tt>. + If you wish to test out some code without changing any data, you can do that by invoking +rails console --sandbox+. <shell> $ rails console --sandbox -Loading development environment in sandbox (Rails 3.0.0) +Loading development environment in sandbox (Rails 3.1.0) Any modifications you make will be rolled back on exit irb(main):001:0> </shell> @@ -280,13 +289,11 @@ h4. +rails dbconsole+ +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. +rails plugin+ +You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>. -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? +h4. +rails plugin+ -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. +The +rails plugin+ command simplifies plugin management. Plugins can be installed by name or their repository URLs. You need to have Git installed if you want to install a plugin from a Git repo. The same holds for Subversion too. <shell> $ rails plugin install https://github.com/technoweenie/acts_as_paranoid.git @@ -304,9 +311,15 @@ h4. +rails runner+ $ rails runner "Model.long_running_method" </shell> +You can specify the environment in which the +runner+ command should operate using the +-e+ switch. + +<shell> +$ rails runner -e staging "Model.long_running_method" +</shell> + h4. +rails destroy+ -Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. Believe you-me, the creation of this tutorial used this command many times! +Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. <shell> $ rails generate model Oops @@ -333,9 +346,29 @@ $ rails destroy model Oops notempty app </shell> -h4. +rake about+ +h3. Rake + +Rake is Ruby Make, a standalone Ruby utility that replaces the Unix utility 'make', and uses a 'Rakefile' and +.rake+ files to build up a list of tasks. In Rails, Rake is used for common administration tasks, especially sophisticated ones that build off of each other. -Check it: Version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version! +about+ is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. +You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing +rake --tasks+. Each task has a description, and should help you find the thing you need. + +<shell> +$ rake --tasks +(in /home/foobar/commandsapp) +rake db:abort_if_pending_migrations # Raises an error if there are pending migrations +rake db:charset # Retrieves the charset for the current environment's database +rake db:collation # Retrieves the collation for the current environment's database +rake db:create # Create the database defined in config/database.yml for the current Rails.env +... +... +rake tmp:pids:clear # Clears all files in tmp/pids +rake tmp:sessions:clear # Clears all files in tmp/sessions +rake tmp:sockets:clear # Clears all files in tmp/sockets +</shell> + +h4. +about+ + +<tt>rake about</tt> gives information about version numbers for Ruby, RubyGems, Rails, the Rails subcomponents, your application's folder, the current Rails environment name, your app's database adapter, and schema version. It is useful when you need to ask for help, check if a security patch might affect you, or when you need some stats for an existing Rails installation. <shell> $ rake about @@ -343,17 +376,98 @@ About your application's environment Ruby version 1.8.7 (x86_64-linux) RubyGems version 1.3.6 Rack version 1.1 -Rails version 3.0.0 -Active Record version 3.0.0 -Action Pack version 3.0.0 -Active Resource version 3.0.0 -Action Mailer version 3.0.0 -Active Support version 3.0.0 +Rails version 3.1.0 +Active Record version 3.1.0 +Action Pack version 3.1.0 +Active Resource version 3.1.0 +Action Mailer version 3.1.0 +Active Support version 3.1.0 Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, Rack::Sendfile, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::MethodOverride, ActionDispatch::Head Application root /home/foobar/commandsapp Environment development </shell> +h4. +assets+ + +You can precompile the assets in <tt>app/assets</tt> using <tt>rake assets:precompile</tt> and remove compiled assets using <tt>rake assets:clean</tt>. + +h4. +db+ + +The most common tasks of the +db:+ Rake namespace are +migrate+ and +create+, and it will pay off to try out all of the migration rake tasks (+up+, +down+, +redo+, +reset+). +rake db:version+ is useful when troubleshooting, telling you the current version of the database. + +More information about migrations can be found in the "Migrations":migrations.html guide. + +h4. +doc+ + +The +doc:+ namespace has the tools to generate documentation for your app, API documentation, guides. Documentation can also be stripped which is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform. + +* +rake doc:app+ generates documentation for your application in +doc/app+. +* +rake doc:guides+ generates Rails guides in +doc/guides+. +* +rake doc:rails+ generates API documentation for Rails in +doc/api+. +* +rake doc:plugins+ generates API documentation for all the plugins installed in the application in +doc/plugins+. +* +rake doc:clobber_plugins+ removes the generated documentation for all plugins. + +h4. +notes+ + ++rake notes+ will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is only done in files with extension +.builder+, +.rb+, +.rxml+, +.rhtml+ and +.erb+ for both default and custom annotations. + +<shell> +$ rake notes +(in /home/foobar/commandsapp) +app/controllers/admin/users_controller.rb: + * [ 20] [TODO] any other way to do this? + * [132] [FIXME] high priority for next deploy + +app/model/school.rb: + * [ 13] [OPTIMIZE] refactor this code to make it faster + * [ 17] [FIXME] +</shell> + +If you are looking for a specific annotation, say FIXME, you can use +rake notes:fixme+. Note that you have to lower case the annotation's name. + +<shell> +$ rake notes:fixme +(in /home/foobar/commandsapp) +app/controllers/admin/users_controller.rb: + * [132] high priority for next deploy + +app/model/school.rb: + * [ 17] +</shell> + +You can also use custom annotations in your code and list them using +rake notes:custom+ by specifying the annotation using an environment variable +ANNOTATION+. + +<shell> +$ rake notes:custom ANNOTATION=BUG +(in /home/foobar/commandsapp) +app/model/post.rb: + * [ 23] Have to fix this one before pushing! +</shell> + +NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines. + +h4. +routes+ + ++rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. + +h4. +test+ + +INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html + +Rails comes with a test suite called Test::Unit. It is through the use of tests that Rails itself is so stable, and the slew of people working on Rails can prove that everything works as it should. + +The +test:+ namespace helps in running the different tests you will (hopefully!) write. + +h4. +tmp+ + +The <tt>Rails.root/tmp</tt> directory is, like the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of deletions gone awry. + +h4. Miscellaneous + +* +rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. +* +rake secret+ will give you a pseudo-random key to use for your session secret. +* <tt>rake time:zones:all</tt> lists all the timezones Rails knows about. + h3. The Rails Advanced Command Line More advanced use of the command line is focused around finding useful (even surprising at times) options in the utilities, and fitting those to your needs and specific work flow. Listed here are some tricks up Rails' sleeve. @@ -414,11 +528,13 @@ development: ... </shell> -It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails new+ command to generate the basis of your app. +It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. + +NOTE. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails new+ command to generate the basis of your app. -h4. +server+ with Different Backends +h4(#different-servers). +server+ with Different Backends -Many people have created a large number different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!). +Many people have created a large number of different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!). NOTE: For more details on the Rack integration, see "Rails on Rack":rails_on_rack.html. @@ -437,157 +553,6 @@ Successfully installed mongrel-1.1.5 Installing RDoc documentation for mongrel-1.1.5... $ rails server mongrel => Booting Mongrel (use 'rails server webrick' to force WEBrick) -=> Rails 3.0.0 application starting on http://0.0.0.0:3000 +=> Rails 3.1.0 application starting on http://0.0.0.0:3000 ... </shell> - -h4. The Rails Generation: Generators - -INFO: For a good rundown on generators, see "Understanding Generators":http://wiki.rubyonrails.org/rails/pages/UnderstandingGenerators. A lot of its material is presented here. - -Generators are code that generates code. Let's experiment by building one. Our generator will generate a text file. - -The Rails generator by default looks in these places for available generators, where Rails.root is the root of your Rails application, like /home/foobar/commandsapp: - -* Rails.root/lib/generators -* Rails.root/vendor/generators -* Inside any plugin with a directory like "generators" or "rails_generators" -* ~/.rails/generators -* Inside any Gem you have installed with a name ending in "_generator" -* Inside any Gem installed with a "rails_generators" path, and a file ending in "_generator.rb" -* Finally, the builtin Rails generators (controller, model, mailer, etc.) - -Let's try the fourth option (in our home directory), which will be easy to clean up later: - -<shell> -$ mkdir -p ~/.rails/generators/tutorial_test/templates -$ touch ~/.rails/generators/tutorial_test/templates/tutorial.erb -$ touch ~/.rails/generators/tutorial_test/tutorial_test_generator.rb -</shell> - -We'll fill +tutorial_test_generator.rb+ out with: - -<ruby> -class TutorialTestGenerator < Rails::Generator::Base - def initialize(*runtime_args) - super(*runtime_args) - @tut_args = runtime_args - end - - def manifest - record do |m| - m.directory "public" - m.template "tutorial.erb", File.join("public", "tutorial.txt"), - :assigns => { :args => @tut_args } - end - end -end -</ruby> - -We take whatever args are supplied, save them to an instance variable, and literally copying from the Rails source, implement a +manifest+ method, which calls +record+ with a block, and we: - -* Check there's a *public* directory. You bet there is. -* Run the ERB template called "tutorial.erb". -* Save it into "Rails.root/public/tutorial.txt". -* Pass in the arguments we saved through the +:assigns+ parameter. - -Next we'll build the template: - -<shell> -$ cat ~/.rails/generators/tutorial_test/templates/tutorial.erb -I'm a template! - -I got assigned some args: -<%= require 'pp'; PP.pp(args, "") %> -</shell> - -Then we'll make sure it got included in the list of available generators: - -<shell> -$ rails generate -... -... -Installed Generators - User: tutorial_test -</shell> - -SWEET! Now let's generate some text, yeah! - -<shell> -$ rails generate tutorial_test arg1 arg2 arg3 - exists public - create public/tutorial.txt -</shell> - -And the result: - -<shell> -$ cat public/tutorial.txt -I'm a template! - -I got assigned some args: -[["arg1", "arg2", "arg3"], - {:collision=>:ask, - :quiet=>false, - :generator=>"tutorial_test", - :command=>:create}] -</shell> - -Tada! - -h4. Rake is Ruby Make - -Rake is a standalone Ruby utility that replaces the Unix utility 'make', and uses a 'Rakefile' and +.rake+ files to build up a list of tasks. In Rails, Rake is used for common administration tasks, especially sophisticated ones that build off of each other. - -You can get a list of Rake tasks available to you, which will often depend on your current directory, by typing +rake --tasks+. Each task has a description, and should help you find the thing you need. - -<shell> -$ rake --tasks -(in /home/foobar/commandsapp) -rake db:abort_if_pending_migrations # Raises an error if there are pending migrations -rake db:charset # Retrieves the charset for the current environment's database -rake db:collation # Retrieves the collation for the current environment's database -rake db:create # Create the database defined in config/database.yml for the current Rails.env -... -... -rake tmp:pids:clear # Clears all files in tmp/pids -rake tmp:sessions:clear # Clears all files in tmp/sessions -rake tmp:sockets:clear # Clears all files in tmp/sockets -</shell> - -h5. +db:+ Database - -The most common tasks of the +db:+ Rake namespace are +migrate+ and +create+, and it will pay off to try out all of the migration rake tasks (+up+, +down+, +redo+, +reset+). +rake db:version+ is useful when troubleshooting, telling you the current version of the database. - -h5. +doc:+ Documentation - -If you want to strip out or rebuild any of the Rails documentation (including this guide!), the +doc:+ namespace has the tools. Stripping documentation is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform. - -h5. +notes:+ Code note enumeration - -These tasks will search through your code for commented lines beginning with "FIXME", "OPTIMIZE", "TODO", or any custom annotation (like XXX) and show you them. - -h5. +test:+ Rails tests - -INFO: A good description of unit testing in Rails is given in "A Guide to Testing Rails Applications":testing.html - -Rails comes with a test suite called Test::Unit. It is through the use of tests that Rails itself is so stable, and the slew of people working on Rails can prove that everything works as it should. - -The +test:+ namespace helps in running the different tests you will (hopefully!) write. - -h5. +time:+ Timezones - -You can list all the timezones Rails knows about with +rake time:zones:all+, which is useful just in day-to-day life. - -h5. +tmp:+ Temporary files - -The tmp directory is, like in the *nix /tmp directory, the holding place for temporary files like sessions (if you're using a file store for files), process id files, and cached actions. The +tmp:+ namespace tasks will help you clear them if you need to if they've become overgrown, or create them in case of deletions gone awry. - -h5. Miscellaneous Tasks - - +rake stats+ is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio. - - +rake secret+ will give you a pseudo-random key to use for your session secret. - - +rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with. - diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 135c849ec3..8e6010ff79 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -18,7 +18,7 @@ Rails offers four standard spots to place initialization code: h3. Running Code Before Rails -In the rare event that your application needs to run some code before Rails itself is loaded, put it above the call to +require 'rails/all'+ in your +config/application.rb+. +In the rare event that your application needs to run some code before Rails itself is loaded, put it above the call to +require 'rails/all'+ in +config/application.rb+. h3. Configuring Rails Components @@ -52,12 +52,14 @@ end * +config.asset_host+ sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of +config.action_controller.asset_host+. -* +config.asset_path+ can take a callable, a string, or be +nil+. Default is +nil+. If set, this configuration parameter let's you decorate asset paths. For example, the normal path for +blog.js+ would be +/javascripts/blog.js+, let that absolute path be +path+. If +config.asset_path+ is a callable, Rails calls it when generating asset paths passing +path+ as argument. If +config.asset_path+ is a string, it is expected to be a +sprintf+ format string with a +%s+ where +path+ will get inserted. In either case, Rails outputs the decorated path. *This option is ignored if the asset pipeline is enabled, which is by default*. Shorter version of +config.action_controller.asset_path+. +* +config.asset_path+ lets you decorate asset paths. This can be a callable, a string, or be +nil+ which is the default. For example, the normal path for +blog.js+ would be +/javascripts/blog.js+, let that absolute path be +path+. If +config.asset_path+ is a callable, Rails calls it when generating asset paths passing +path+ as argument. If +config.asset_path+ is a string, it is expected to be a +sprintf+ format string with a +%s+ where +path+ will get inserted. In either case, Rails outputs the decorated path. Shorter version of +config.action_controller.asset_path+. <ruby> config.asset_path = proc { |path| "/blog/public#{path}" } </ruby> +NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is enabled, which is the default. + * +config.autoload_once_paths+ accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if +config.cache_classes+ is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in +autoload_paths+. Default is an empty array. * +config.autoload_paths+ accepts an array of paths from which Rails will autoload constants. Default is all directories under +app+. @@ -84,11 +86,11 @@ config.asset_path = proc { |path| "/blog/public#{path}" } * +config.log_level+ defines the verbosity of the Rails logger. This option defaults to +:debug+ for all modes except production, where it defaults to +:info+. -* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ +Logger+ class. Defaults to an instance of +ActiveSupport::BufferedLogger+, with auto flushing off in production mode. +* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby +Logger+ class. Defaults to an instance of +ActiveSupport::BufferedLogger+, with auto flushing off in production mode. -* +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":configuring-middleware section below. +* +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":#configuring-middleware section below. -* +config.plugins+ accepts the list of plugins to load. If this is set to +nil+, default, all plugins will be loaded. If this is set to +[]+, no plugins will be loaded. Otherwise, plugins will be loaded in the order specified. This option let's you enforce some particular loading order, useful when dependencies between plugins require it. For that use case, put first the plugins you want to be loaded in a certain order, and then the special symbol +:all+ to have the rest loaded without the need to specify them. +* +config.plugins+ accepts the list of plugins to load. The default is +nil+ in which case all plugins will be loaded. If this is set to +[]+, no plugins will be loaded. Otherwise, plugins will be loaded in the order specified. This option lets you enforce some particular loading order, useful when dependencies between plugins require it. For that use case, put first the plugins you want to be loaded in a certain order, and then the special symbol +:all+ to have the rest loaded without the need to specify them. * +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled. @@ -96,9 +98,9 @@ config.asset_path = proc { |path| "/blog/public#{path}" } * +config.secret_token+ used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get +config.secret_token+ initialized to a random key in +config/initializers/secret_token.rb+. -* +config.serve_static_assets+ configures Rails to serve static assets. Defaults to true, but in the production environment is turned off. The server software used to run the application should be used to serve the assets instead. +* +config.serve_static_assets+ configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won´t be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. -* +config.session_store+ is usually set up in +config/initializers/session_store.rb+ and specifies what class to use to store the session. Possible values are +:cookie_store+, default, +:mem_cache_store+, and +:disabled+. The last one tells Rails not to deal with sessions. Custom session stores can also be specified like so: +* +config.session_store+ is usually set up in +config/initializers/session_store.rb+ and specifies what class to use to store the session. Possible values are +:cookie_store+ which is the default, +:mem_cache_store+, and +:disabled+. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: <ruby> config.session_store :my_custom_store @@ -114,52 +116,71 @@ WARNING: Threadsafe operation is incompatible with the normal workings of develo * +config.whiny_nils+ enables or disables warnings when a certain set of methods are invoked on +nil+ and it does not respond to them. Defaults to true in development and test environments. +h4. Configuring Assets + +Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful. + +* +config.assets.css_compressor+ defines the CSS compressor to use. Only supported value at the moment is +:yui+, which uses the +yui-compressor+ gem. + * +config.assets.enabled+ a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in +config/application.rb+. +* +config.assets.js_compressor+ defines the JavaScript compressor to use. Possible values are +:closure+, +:uglifier+ and +:yui+ which require the use of the +closure-compiler+, +uglifier+ or +yui-compressor+ gems respectively. + +* +config.assets.paths+ contains the paths which are used to look for assets. Appending paths to this configuration option will cause those paths to be used in the search for assets. + +* +config.assets.precompile+ allows you to specify additional assets (other than +application.css+ and +application.js+) which are to be precompiled when +rake assets:precompile+ is run. + +* +config.assets.prefix+ defines the prefix where assets are served from. Defaults to +/assets+. + + h4. Configuring Generators Rails 3 allows you to alter what generators are used with the +config.generators+ method. This method takes a block: <ruby> - config.generators do |g| - g.orm :active_record - g.test_framework :test_unit - end +config.generators do |g| + g.orm :active_record + g.test_framework :test_unit +end </ruby> The full set of methods that can be used in this block are as follows: -* +force_plural+ allows pluralized model names. Defaults to _false_. -* +helper+ defines whether or not to generate helpers. Defaults to _true_. -* +orm+ defines which orm to use. Defaults to _nil_, so will use Active Record by default. -* +integration_tool+ defines which integration tool to use. Defaults to _nil_. -* +performance_tool+ defines which performance tool to use. Defaults to _nil_. +* +assets+ allows to create assets on generating a scaffold. Defaults to +true+. +* +force_plural+ allows pluralized model names. Defaults to +false+. +* +helper+ defines whether or not to generate helpers. Defaults to +true+. +* +integration_tool+ defines which integration tool to use. Defaults to +nil+. +* +javascripts+ turns on the hook for javascripts in generators. Used in Rails for when the +scaffold+ generator is ran. Defaults to +true+. +* +javascript_engine+ configures the engine to be used (for eg. coffee) when generating assets. Defaults to +nil+. +* +orm+ defines which orm to use. Defaults to +false+ and will use Active Record by default. +* +performance_tool+ defines which performance tool to use. Defaults to +nil+. * +resource_controller+ defines which generator to use for generating a controller when using +rails generate resource+. Defaults to +:controller+. * +scaffold_controller+ different from +resource_controller+, defines which generator to use for generating a _scaffolded_ controller when using +rails generate scaffold+. Defaults to +:scaffold_controller+. -* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is ran, but this hook can be used in other generates as well. -* +test_framework+ defines which test framework to use. Defaults to _nil_, so will use Test::Unit by default. +* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is ran, but this hook can be used in other generates as well. Defaults to +true+. +* +stylesheet_engine+ configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to +:css+. +* +test_framework+ defines which test framework to use. Defaults to +false+ and will use Test::Unit by default. * +template_engine+ defines which template engine to use, such as ERB or Haml. Defaults to +:erb+. h4. Configuring Middleware Every Rails application comes with a standard set of middleware which it uses in this order in the development environment: -* +Rack::SSL+ Will force every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to _true_. -* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is _true_. -* +Rack::Lock+ Will wrap the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to _false_, which it is by default. +* +Rack::SSL+ Will force every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to +true+. +* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is +true+. +* +Rack::Lock+ Will wrap the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to +false+, which it is by default. * +ActiveSupport::Cache::Strategy::LocalCache+ Serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * +Rack::Runtime+ Sets an +X-Runtime+ header, containing the time (in seconds) taken to execute the request. -* +Rails::Rack::Logger+ Will notify the logs that the request has began. After request is complete, flushes all the logs. -* +ActionDispatch::ShowExceptions+ rescues any exception returned by the application and renders nice exception pages if the request is local or if +config.consider_all_requests_local+ is set to _true_. If +config.action_dispatch.show_exceptions+ is set to _false_, exceptions will be raised regardless. -* +ActionDispatch::RemoteIp+ checks for IP spoofing attacks. Configurable with the +config.action_dispatch.ip_spoofing_check+ and +config.action_dispatch.trusted_proxies+ settings. -* +Rack::Sendfile+ The Sendfile middleware intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with +config.action_dispatch.x_sendfile_header+ +* +Rails::Rack::Logger+ Notifies the logs that the request has began. After request is complete, flushes all the logs. +* +ActionDispatch::ShowExceptions+ Rescues any exception returned by the application and renders nice exception pages if the request is local or if +config.consider_all_requests_local+ is set to +true+. If +config.action_dispatch.show_exceptions+ is set to +false+, exceptions will be raised regardless. +* +ActionDispatch::RemoteIp+ Checks for IP spoofing attacks. Configurable with the +config.action_dispatch.ip_spoofing_check+ and +config.action_dispatch.trusted_proxies+ settings. +* +Rack::Sendfile+ Intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with +config.action_dispatch.x_sendfile_header+. * +ActionDispatch::Callbacks+ Runs the prepare callbacks before serving the request. -* +ActiveRecord::ConnectionAdapters::ConnectionManagement+ cleans active connections after each request, unless the +rack.test+ key in the request environment is set to _true_. -* +ActiveRecord::QueryCache+ caches all +SELECT+ queries generated in a request. If an +INSERT+ or +UPDATE+ takes place then the cache is cleaned. +* +ActiveRecord::ConnectionAdapters::ConnectionManagement+ cleans active connections after each request, unless the +rack.test+ key in the request environment is set to +true+. +* +ActiveRecord::QueryCache+ caches all SELECT queries generated in a request. If any INSERT or UPDATE takes place then the cache is cleaned. * +ActionDispatch::Cookies+ sets cookies for the request. * +ActionDispatch::Session::CookieStore+ is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the +config.action_controller.session_store+ to an alternate value. Additionally, options passed to this can be configured by using +config.action_controller.session_options+. * +ActionDispatch::Flash+ sets up the +flash+ keys. Only available if +config.action_controller.session_store+ is set to a value. -* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+ +* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+. * +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PUT and DELETE HTTP method types. * +ActionDispatch::Head+ converts HEAD requests to GET requests and serves them as so. * +ActionDispatch::BestStandardsSupport+ enables "best standards support" so that IE8 renders some elements correctly. @@ -167,44 +188,44 @@ Every Rails application comes with a standard set of middleware which it uses in Besides these usual middleware, you can add your own by using the +config.middleware.use+ method: <ruby> - config.middleware.use Magical::Unicorns +config.middleware.use Magical::Unicorns </ruby> -This will put the +Magical::Unicorns+ middleware on the end of the stack. If you wish to put this middleware before another use +insert_before+: +This will put the +Magical::Unicorns+ middleware on the end of the stack. You can use +insert_before+ if you wish to add a middleware before another. <ruby> - config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns </ruby> -There's also +insert_after+ which will insert a middleware _after_ another: +There's also +insert_after+ which will insert a middleware after another: <ruby> - config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns +config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns </ruby> Middlewares can also be completely swapped out and replaced with others: <ruby> - config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns +config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns </ruby> They can also be removed from the stack completely: <ruby> - config.middleware.delete ActionDispatch::BestStandardsSupport +config.middleware.delete ActionDispatch::BestStandardsSupport </ruby> h4. Configuring i18n * +config.i18n.default_locale+ sets the default locale of an application used for i18n. Defaults to +:en+. -* +config.i18n.load_path+ sets the path Rails uses to look for locale files. Defaults to +config/locales/*.{yml,rb}+ +* +config.i18n.load_path+ sets the path Rails uses to look for locale files. Defaults to +config/locales/*.{yml,rb}+. h4. Configuring Active Record <tt>config.active_record</tt> includes a variety of configuration options: -* +config.active_record.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8.x Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an Active Record model class or an Active Record model instance. Set to nil to disable logging. +* +config.active_record.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then passed on to any new database connections made. You can retrieve this logger by calling +logger+ on either an Active Record model class or an Active Record model instance. Set to +nil+ to disable logging. * +config.active_record.primary_key_prefix_type+ lets you adjust the naming for primary key columns. By default, Rails assumes that primary key columns are named +id+ (and this configuration option doesn't need to be set.) There are two other choices: ** +:table_name+ would make the primary key for the Customer class +customerid+ @@ -214,21 +235,21 @@ h4. Configuring Active Record * +config.active_record.table_name_suffix+ lets you set a global string to be appended to table names. If you set this to +_northwest+, then the Customer class will look for +customers_northwest+ as its table. The default is an empty string. -* +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to +true+ (the default), then the Customer class will use the +customers+ table. If set to +false+, then the Customers class will use the +customer+ table. +* +config.active_record.pluralize_table_names+ specifies whether Rails will look for singular or plural table names in the database. If set to true (the default), then the Customer class will use the +customers+ table. If set to false, then the Customer class will use the +customer+ table. -* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:utc+ for Rails, although ActiveRecord defaults to +:local+ when used outside of Rails. +* +config.active_record.default_timezone+ determines whether to use +Time.local+ (if set to +:local+) or +Time.utc+ (if set to +:utc+) when pulling dates and times from the database. The default is +:utc+ for Rails, although Active Record defaults to +:local+ when used outside of Rails. * +config.active_record.schema_format+ controls the format for dumping the database schema to a file. The options are +:ruby+ (the default) for a database-independent version that depends on migrations, or +:sql+ for a set of (potentially database-dependent) SQL statements. -* +config.active_record.timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is +true+, to use timestamps, which are preferred if there are multiple developers working on the same application. +* +config.active_record.timestamped_migrations+ controls whether migrations are numbered with serial integers or with timestamps. The default is true, to use timestamps, which are preferred if there are multiple developers working on the same application. -* +config.active_record.lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. +* +config.active_record.lock_optimistically+ controls whether Active Record will use optimistic locking and is true by default. * +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app. The MySQL adapter adds one additional configuration option: -* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether ActiveRecord will consider all +tinyint(1)+ columns in a MySQL database to be booleans. By default this is +true+. +* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether Active Record will consider all +tinyint(1)+ columns in a MySQL database to be booleans and is true by default. The schema dumper adds one additional configuration option: @@ -244,17 +265,17 @@ h4. Configuring Action Controller * +config.action_controller.page_cache_directory+ should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>. For Rails, this directory has already been set to +Rails.public_path+ (which is usually set to <tt>Rails.root + "/public"</tt>). Changing this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your web server to look in the new location for cached files. -* +config.action_controller.page_cache_extension+ configures the extension used for cached pages saved to +page_cache_directory+. Defaults to +.html+ +* +config.action_controller.page_cache_extension+ configures the extension used for cached pages saved to +page_cache_directory+. Defaults to +.html+. -* +config.action_controller.perform_caching+ configures whether the application should perform caching or not. Set to _false_ in development mode, _true_ in production. +* +config.action_controller.perform_caching+ configures whether the application should perform caching or not. Set to false in development mode, true in production. * +config.action_controller.default_charset+ specifies the default character set for all renders. The default is "utf-8". -* +config.action_controller.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Controller. Set to nil to disable logging. +* +config.action_controller.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Controller. Set to +nil+ to disable logging. * +config.action_controller.request_forgery_protection_token+ sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ sets it to +:authenticity_token+ by default. -* +config.action_controller.allow_forgery_protection+ enables or disables CSRF protection. By default this is +false+ in test mode and +true+ in all other modes. +* +config.action_controller.allow_forgery_protection+ enables or disables CSRF protection. By default this is false in test mode and true in all other modes. * +config.action_controller.relative_url_root+ can be used to tell Rails that you are deploying to a subdirectory. The default is +ENV['RAILS_RELATIVE_URL_ROOT']+. @@ -288,45 +309,49 @@ h4. Configuring Action View There are only a few configuration options for Action View, starting with four on +ActionView::Base+: -* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| %Q(%<div class="field_with_errors">#{html_tag}</div>).html_safe }</tt> +* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is + +<ruby> +Proc.new { |html_tag, instance| %Q(<div class="field_with_errors">#{html_tag}</div>).html_safe } +</ruby> * +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. -* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. +* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Mailer. Set to +nil+ to disable logging. * +config.action_view.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information. * +config.action_view.javascript_expansions+ is a hash containing expansions that can be used for the JavaScript include tag. By default, this is defined as: <ruby> - config.action_view.javascript_expansions = { :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] } +config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } </ruby> However, you may add to this by defining others: <ruby> - config.action_view.javascript_expansions[:jquery] = ["jquery", "jquery-ui"] +config.action_view.javascript_expansions[:prototype] = ['prototype', 'effects', 'dragdrop', 'controls'] </ruby> And can reference in the view with the following code: <ruby> - <%= javascript_include_tag :jquery %> +<%= javascript_include_tag :prototype %> </ruby> * +config.action_view.stylesheet_expansions+ works in much the same way as +javascript_expansions+, but has no default key. Keys defined for this hash can be referenced in the view like such: <ruby> - <%= stylesheet_link_tag :special %> +<%= stylesheet_link_tag :special %> </ruby> -* +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running. +* +config.action_view.cache_asset_ids+ With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running. h4. Configuring Action Mailer There are a number of settings available on +config.action_mailer+: -* +config.action_mailer.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Action Mailer. Set to nil to disable logging. +* +config.action_mailer.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Mailer. Set to +nil+ to disable logging. * +config.action_mailer.smtp_settings+ allows detailed configuration for the +:smtp+ delivery method. It accepts a hash of options, which can include any of these options: ** +:address+ - Allows you to use a remote mail server. Just change it from its default "localhost" setting. @@ -340,25 +365,35 @@ There are a number of settings available on +config.action_mailer+: ** +:location+ - The location of the sendmail executable. Defaults to +/usr/sbin/sendmail+. ** +:arguments+ - The command line arguments. Defaults to +-i -t+. -* +config.action_mailer.raise_delivery_errors+ specifies whether to raise an error if email delivery cannot be completed. It defaults to +true+. +* +config.action_mailer.raise_delivery_errors+ specifies whether to raise an error if email delivery cannot be completed. It defaults to true. * +config.action_mailer.delivery_method+ defines the delivery method. The allowed values are +:smtp+ (default), +:sendmail+, and +:test+. -* +config.action_mailer.perform_deliveries+ specifies whether mail will actually be delivered. By default this is +true+; it can be convenient to set it to +false+ for testing. +* +config.action_mailer.perform_deliveries+ specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing. * +config.action_mailer.default+ configures Action Mailer defaults. These default to: <ruby> - :mime_version => "1.0", - :charset => "UTF-8", - :content_type => "text/plain", - :parts_order => [ "text/plain", "text/enriched", "text/html" ] +:mime_version => "1.0", +:charset => "UTF-8", +:content_type => "text/plain", +:parts_order => [ "text/plain", "text/enriched", "text/html" ] +</ruby> + +* +config.action_mailer.observers+ registers observers which will be notified when mail is delivered. +<ruby> +config.action_mailer.observers = ["MailObserver"] +</ruby> + +* +config.action_mailer.interceptors+ registers interceptors which will be called before mail is sent. +<ruby> +config.action_mailer.interceptors = ["MailInterceptor"] </ruby> h4. Configuring Active Resource There is a single configuration setting available on +config.active_resource+: -* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then used to log information from Active Resource. Set to nil to disable logging. +* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Resource. Set to +nil+ to disable logging. h4. Configuring Active Support @@ -398,7 +433,7 @@ Some parts of Rails can also be configured externally by supplying environment v h3. Using Initializer Files -After loading the framework and any gems and plugins in your application, Rails turns to loading initializers. An initializer is any file of Ruby code stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks, plugins and gems are loaded, such as options to configure settings for these parts. +After loading the framework and any gems and plugins in your application, Rails turns to loading initializers. An initializer is any Ruby file stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks, plugins and gems are loaded, such as options to configure settings for these parts. NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down. @@ -406,7 +441,7 @@ TIP: If you have any ordering dependency in your initializers, you can control t h3. Initialization events -Rails has 5 initialization events which can be hooked into (listed in order that they are ran): +Rails has 5 initialization events which can be hooked into (listed in the order that they are ran): * +before_configuration+: This is run as soon as the application constant inherits from +Rails::Application+. The +config+ calls are evaluated before this happens. @@ -437,7 +472,7 @@ Initializers defined using the +initializer+ method will be ran in the order the WARNING: You may put your initializer before or after any other initializer in the chain, as long as it is logical. Say you have 4 initializers called "one" through "four" (defined in that order) and you define "four" to go _before_ "four" but _after_ "three", that just isn't logical and Rails will not be able to determine your initializer order. -The block's argument of the +initialize+ is the instance of the application itself, and so we can access the configuration on it by using the +config+ method as this initializer does. +The block argument of the +initializer+ method is the instance of the application itself, and so we can access the configuration on it by using the +config+ method as done in the example. Because +Rails::Application+ inherits from +Rails::Railtie+ (indirectly), you can use the +initializer+ method in +config/application.rb+ to define initializers for the application. @@ -450,21 +485,21 @@ Serves as a placeholder so that +:load_environment_config+ can be defined to run *+load_active_support+* Requires +active_support/dependencies+ which sets up the basis for Active Support. Optionally requires +active_support/all+ if +config.active_support.bare+ is un-truthful, which is the default. -*+preload_frameworks+* Will load all autoload dependencies of Rails automatically if +config.preload_frameworks+ is +true+ or "truthful". By default this configuration option is disabled. In Rails, when internal classes are referenced for the first time they are autoloaded. +:preload_frameworks+ loads all of this at once on initialization. +*+preload_frameworks+* Loads all autoload dependencies of Rails automatically if +config.preload_frameworks+ is +true+ or "truthful". By default this configuration option is disabled. In Rails, when internal classes are referenced for the first time they are autoloaded. +:preload_frameworks+ loads all of this at once on initialization. -*+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, providing that there's no initializer inserted before this point that has defined +Rails.logger+. +*+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, provided that no initializer inserted before this point has defined +Rails.logger+. -*+initialize_cache+* If +RAILS_CACHE+ isn't yet set, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +RAILS_CACHE+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack. +*+initialize_cache+* If +RAILS_CACHE+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +RAILS_CACHE+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack. *+set_clear_dependencies_hook+* Provides a hook for +active_record.set_dispatch_hooks+ to use, which will run before this initializer. This initializer -- which runs only if +cache_classes+ is set to +false+ -- uses +ActionDispatch::Callbacks.after+ to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. -*+initialize_dependency_mechanism+* If +config.cache_classes+ is set to +true+, configures +ActiveSupport::Dependencies.mechanism+ to +require+ dependencies rather than +load+ them. +*+initialize_dependency_mechanism+* If +config.cache_classes+ is true, configures +ActiveSupport::Dependencies.mechanism+ to +require+ dependencies rather than +load+ them. *+bootstrap_hook+* Runs all configured +before_initialize+ blocks. *+i18n.callbacks+* In the development environment, sets up a +to_prepare+ callback which will call +I18n.reload!+ if any of the locales have changed since the last request. In production mode this callback will only run on the first request. -*+active_support.initialize_whiny_nils+* Will require +active_support/whiny_nil+ if +config.whiny_nils+ is set to +true+. This file will output errors such as: +*+active_support.initialize_whiny_nils+* Requires +active_support/whiny_nil+ if +config.whiny_nils+ is true. This file will output errors such as: <plain> Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id @@ -480,19 +515,19 @@ The error occurred while evaluating nil.each *+active_support.deprecation_behavior+* Sets up deprecation reporting for environments, defaulting to +:log+ for development, +:notify+ for production and +:stderr+ for test. If a value isn't set for +config.active_support.deprecation+ then this initializer will prompt the user to configure this line in the current environment's +config/environments+ file. Can be set to an array of values. -*+active_support.initialize_time_zone+* Sets the default time zone for the application based off the +config.time_zone+ setting, which defaults to "UTC". +*+active_support.initialize_time_zone+* Sets the default time zone for the application based on the +config.time_zone+ setting, which defaults to "UTC". *+action_dispatch.configure+* Configures the +ActionDispatch::Http::URL.tld_length+ to be set to the value of +config.action_dispatch.tld_length+. -*+action_view.cache_asset_ids+* Will set +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ to +false+ when Active Support loads, but only if +config.cache_classes+ is too. +*+action_view.cache_asset_ids+* Sets +ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids+ to +false+ when Active Support loads, but only if +config.cache_classes+ is too. *+action_view.javascript_expansions+* Registers the expansions set up by +config.action_view.javascript_expansions+ and +config.action_view.stylesheet_expansions+ to be recognised by Action View and therefore usable in the views. *+action_view.set_configs+* Sets up Action View by using the settings in +config.action_view+ by +send+'ing the method names as setters to +ActionView::Base+ and passing the values through. -*+action_controller.logger+* Sets +ActionController::Base.logger+ -- if it's not already set -- to +Rails.logger+. +*+action_controller.logger+* Sets +ActionController::Base.logger+ -- if it's not already set -- to +Rails.logger+. -*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +RAILS_CACHE+. +*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +RAILS_CACHE+. *+action_controller.set_configs+* Sets up Action Controller by using the settings in +config.action_controller+ by +send+'ing the method names as setters to +ActionController::Base+ and passing the values through. @@ -506,9 +541,9 @@ The error occurred while evaluating nil.each *+active_record.initialize_database+* Loads the database configuration (by default) from +config/database.yml+ and establishes a connection for the current environment. -*+active_record.log_runtime+* Includes +ActiveRecord::Railties::ControllerRuntime+ which is responsible for reporting the length of time Active Record calls took for the request back to the logger. +*+active_record.log_runtime+* Includes +ActiveRecord::Railties::ControllerRuntime+ which is responsible for reporting the time taken by Active Record calls for the request back to the logger. -*+active_record.set_dispatch_hooks+* If +config.cache_classes+ is set to false, all reloadable connections to the database will be reset. +*+active_record.set_dispatch_hooks+* Resets all reloadable connections to the database if +config.cache_classes+ is set to +false+. *+action_mailer.logger+* Sets +ActionMailer::Base.logger+ -- if it's not already set -- to +Rails.logger+. @@ -536,10 +571,6 @@ The error occurred while evaluating nil.each *+load_config_initializers+* Loads all Ruby files from +config/initializers+ in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks and plugins are loaded. -NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the +initializers+ folder on down. - -TIP: If you have any ordering dependency in your initializers, you can control the load order by naming. For example, +01_critical.rb+ will be loaded before +02_normal.rb+. - *+engines_blank_point+* Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are ran. *+add_generator_templates+* Finds templates for generators at +lib/templates+ for the application, railities and engines and adds these to the +config.generators.templates+ setting, which will make the templates available for all generators to reference. @@ -552,13 +583,13 @@ TIP: If you have any ordering dependency in your initializers, you can control t *+build_middleware_stack+* Builds the middleware stack for the application, returning an object which has a +call+ method which takes a Rack environment object for the request. -*+eager_load!+* If +config.cache_classes+ is +true+, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all the Ruby files from +config.eager_load_paths+. +*+eager_load!+* If +config.cache_classes+ is true, runs the +config.before_eager_load+ hooks and then calls +eager_load!+ which will load all the Ruby files from +config.eager_load_paths+. *+finisher_hook+* Provides a hook for after the initialization of process of the application is complete, as well as running all the +config.after_initialize+ blocks for the application, railties and engines. *+set_routes_reloader+* Configures Action Dispatch to reload the routes file using +ActionDispatch::Callbacks.to_prepare+. -*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to +true+ and +config.dependency_loading+ is set to +false+. +*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to true and +config.dependency_loading+ is set to false. h3. Changelog diff --git a/railties/guides/source/contribute.textile b/railties/guides/source/contribute.textile index 4bd527d4c7..f9bb80861c 100644 --- a/railties/guides/source/contribute.textile +++ b/railties/guides/source/contribute.textile @@ -14,7 +14,7 @@ h3. How to Contribute? * Sample format : "Active Record Associations":https://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile. * Sample output : "Active Record Associations":association_basics.html. * You can build the Guides during testing by running +bundle exec rake generate_guides+ in the +railties+ directory. -* You're encouraged to validate XHTML for the generated guides before commiting your changes by running +bundle exec rake validate_guides+ in the +railties+ directory. +* You're encouraged to validate XHTML for the generated guides before committing your changes by running +bundle exec rake validate_guides+ in the +railties+ directory. * Edge guides "can be consulted online":http://edgeguides.rubyonrails.org/. That website is generated periodically from docrails. h3. What to Contribute? @@ -33,7 +33,7 @@ h3. What to Contribute? h3. How is the process? * The preferred way to contribute is to commit to docrails directly. -* A new guide is only edited by its author until finished though. In that case feedback can be given in its LH ticket. +* A new guide is only edited by its author until finished though. * If you are writing a new guide freely commit to docrails partial work and ping lifo or fxn when done with a first draft. * Guides reviewers will then provide feedback, some of it possibly in form of direct commits to agilize the process. * Eventually the guide will be approved and added to the index. @@ -53,7 +53,7 @@ h3. Rules * If the same guide writer wants to write multiple guides, that's ideally the situation we'd love to be in! However, that guide writer will only receive the cash prize for all the subsequent guides (and not the GitHub or RPM prizes). * Our review team will have the final say on whether the guide is complete and of good enough quality. -All authors should read and follow the "Rails Guides Conventions":https://wiki.github.com/lifo/docrails/rails-guides-conventions and the "Rails API Documentation Conventions":https://wiki.github.com/lifo/docrails/rails-api-documentation-conventions. +All authors should read and follow the "Rails Guides Conventions":ruby_on_rails_guides_guidelines.html and the "Rails API Documentation Conventions":api_documentation_guidelines.html. h3. Translations diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 5eb925d7d2..e6ec061c9a 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -24,7 +24,7 @@ If you've found a problem in Ruby on Rails which is not a security risk do a sea At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. -Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating a issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. +Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. h4. Special Treatment for Security Issues @@ -90,7 +90,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby $ rake test </shell> -You can also run tests for an specific framework, like Action Pack, by going into its directory and executing the same command: +You can also run tests for a specific framework, like Action Pack, by going into its directory and executing the same command: <shell> $ cd actionpack @@ -264,7 +264,7 @@ When working with documentation, please take into account the "API Documentation NOTE: As explained above, ordinary code patches should have proper documentation coverage. docrails is only used for isolated documentation improvements. -WARNING: docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. +WARNING: docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. If you have an idea for a new guide you can refer to the "contribution page":contribute.html for instructions on getting involved. @@ -309,7 +309,7 @@ Rails follows a simple set of coding style conventions. * a = b and not a=b. * Follow the conventions you see used in the source already. -These are some guidelines and please use your best judgement in using them. +These are some guidelines and please use your best judgment in using them. h4. Sanity Check @@ -350,7 +350,7 @@ Navigate to the Rails "GitHub repository":https://github.com/rails/rails and pre Add the new remote to your local repository on your local machine: <shell> -$ git remote add mine https://<your user name>@github.com/<your user name>/rails.git +$ git remote add mine git@github.com:<your user name>/rails.git </shell> Push to your remote: @@ -361,7 +361,7 @@ $ git push mine my_new_branch h4. Issue a Pull Request -Navigate to the Rails repository you just pushed to (e.g. https://github.com/<your user name>/rails) and press "Pull Request" in the upper right hand corner. +Navigate to the Rails repository you just pushed to (e.g. https://github.com/your-user-name/rails) and press "Pull Request" in the upper right hand corner. Write your branch name in branch field (is filled with master by default) and press "Update Commit Range" diff --git a/railties/guides/source/credits.html.erb b/railties/guides/source/credits.html.erb index 65e396be95..da6bd6acdf 100644 --- a/railties/guides/source/credits.html.erb +++ b/railties/guides/source/credits.html.erb @@ -9,18 +9,14 @@ Ruby on Rails Guides: Credits <% end %> -<h3 class="section">Rails Documentation Team</h3> +<h3 class="section">Rails Guides Reviewers</h3> -<%= author('Mike Gunderloy', 'mgunderloy') do %> - Mike Gunderloy is a consultant with <a href="http://www.actionrails.com">ActionRails</a>. He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at <a href="http://afreshcup.com">A Fresh Cup</a> and he <a href="http://twitter.com/MikeG1">twitters</a> too much. -<% end %> - -<%= author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails consultant with <a href="http://www.actionrails.com">ActionRails</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has an active <a href="http://twitter.com/lifo">twitter account</a>. +<%= author('Vijay Dev', 'vijaydev', 'vijaydev.jpg') do %> + Vijayakumar, found as Vijay Dev on the web, is a web applications developer and an open source enthusiast who lives in Chennai, India. He started using Rails in 2009 and began actively contributing to Rails documentation in late 2010. He <a href="https://twitter.com/vijay_dev">tweets</a> a lot and also <a href="http://vijaydev.wordpress.com">blogs</a>. <% end %> <%= author('Xavier Noria', 'fxn', 'fxn.png') do %> - Xavier Noria has been into Ruby on Rails since 2005. He is a Rails committer and enjoys combining his passion for Rails and his past life as a proofreader of math textbooks. Xavier is currently a Ruby on Rails consultant. Oh, he also <a href="http://twitter.com/fxn">tweets</a> and can be found everywhere as "fxn". + Xavier Noria has been into Ruby on Rails since 2005. He is a Rails core team member and enjoys combining his passion for Rails and his past life as a proofreader of math textbooks. Xavier is currently an independent Ruby on Rails consultant. Oh, he also <a href="http://twitter.com/fxn">tweets</a> and can be found everywhere as "fxn". <% end %> <h3 class="section">Rails Guides Designers</h3> @@ -31,6 +27,10 @@ Ruby on Rails Guides: Credits <h3 class="section">Rails Guides Authors</h3> +<%= author('Ryan Bigg', 'radar', 'radar.png') do %> +Ryan Bigg works as a consultant at <a href="http://rubyx.com">RubyX</a> and has been working with Rails since 2006. He's co-authoring a book called <a href="http://manning.com/katz">Rails 3 in Action</a> and he's written many gems which can be seen on <a href="http://github.com/radar">his GitHub page</a> and he also tweets prolifically as <a href="http://twitter.com/ryanbigg">@ryanbigg</a>. +<% end %> + <%= author('Frederick Cheung', 'fcheung') do %> Frederick Cheung is Chief Wizard at Texperts where he has been using Rails since 2006. He is based in Cambridge (UK) and when not consuming fine ales he blogs at <a href="http://www.spacevatican.org">spacevatican.org</a>. <% end %> @@ -43,6 +43,10 @@ Ruby on Rails Guides: Credits Jeff Dean is a software engineer with <a href="http://pivotallabs.com">Pivotal Labs</a>. <% end %> +<%= author('Mike Gunderloy', 'mgunderloy') do %> + Mike Gunderloy is a consultant with <a href="http://www.actionrails.com">ActionRails</a>. He brings 25 years of experience in a variety of languages to bear on his current work with Rails. His near-daily links and other blogging can be found at <a href="http://afreshcup.com">A Fresh Cup</a> and he <a href="http://twitter.com/MikeG1">twitters</a> too much. +<% end %> + <%= author('Mikel Lindsaar', 'raasdnil') do %> Mikel Lindsaar has been working with Rails since 2006 and is the author of the Ruby <a href="https://github.com/mikel/mail">Mail gem</a> and core contributor (he helped re-write Action Mailer's API). Mikel is the founder of <a href="http://rubyx.com/">RubyX</a>, has a <a href="http://lindsaar.net/">blog</a> and <a href="http://twitter.com/raasdnil">tweets</a>. <% end %> @@ -55,6 +59,10 @@ Ruby on Rails Guides: Credits James Miller is a software developer for <a href="http://www.jk-tech.com">JK Tech</a> in San Diego, CA. You can find James on GitHub, Gmail, Twitter, and Freenode as "bensie". <% end %> +<%= author('Pratik Naik', 'lifo') do %> + Pratik Naik is a Ruby on Rails consultant with <a href="http://www.actionrails.com">ActionRails</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has an active <a href="http://twitter.com/lifo">twitter account</a>. +<% 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 <a href="http://eventioz.com">Eventioz</a>. He has been using Rails since 2006 and contributing since early 2008. Can be found at gmail, twitter, freenode, everywhere as "miloops". <% end %> diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 4134c9f8ed..4314d00f2e 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -545,7 +545,7 @@ NOTE: In many cases the built-in date pickers are clumsy as they do not aid the h4. Individual Components -Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component +select_year+, +select_month+, +select_day+, +select_hour+, +select_minute+, +select_second+. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for +select_year+, "month" for +select_month+ etc.) although this can be overriden with the +:field_name+ option. The +:prefix+ option works in the same way that it does for +select_date+ and +select_time+ and has the same default value. +Occasionally you need to display just a single date component such as a year or a month. Rails provides a series of helpers for this, one for each component +select_year+, +select_month+, +select_day+, +select_hour+, +select_minute+, +select_second+. These helpers are fairly straightforward. By default they will generate an input field named after the time component (for example "year" for +select_year+, "month" for +select_month+ etc.) although this can be overridden with the +:field_name+ option. The +:prefix+ option works in the same way that it does for +select_date+ and +select_time+ and has the same default value. The first parameter specifies which value should be selected and can either be an instance of a Date, Time or DateTime, in which case the relevant component will be extracted, or a numerical value. For example @@ -777,7 +777,7 @@ If you need to post some data to an external resource it is still great to build Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an +authenticity_token+ hidden field at all. For doing this just pass +false+ to the +:authenticity_token+ option: <erb> -<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %> +<%= form_tag 'http://farfar.away/form', :authenticity_token => false) do %> Form contents <% end %> </erb> diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile index a3181b9ac5..2fa1d6e21d 100644 --- a/railties/guides/source/generators.textile +++ b/railties/guides/source/generators.textile @@ -46,7 +46,7 @@ class InitializerGenerator < Rails::Generators::Base end </ruby> -NOTE: +create_file+ is a method provided by +Thor::Actions+ and the documentation for it and its brethren can be found at "rdoc.info":http://rdoc.info/github/wycats/thor/master/Thor/Actions. +NOTE: +create_file+ is a method provided by +Thor::Actions+. Documentation for +create_file+ and other Thor methods can be found in "Thor's documentation":http://rdoc.info/github/wycats/thor/master/Thor/Actions.html Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and has one method definition. Each public method in the generator is executed when a generator is invoked. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API. @@ -131,7 +131,7 @@ And let's execute our generator: $ rails generate initializer core_extensions </shell> -We can see that now a initializer named core_extensions was created at +config/initializers/core_extensions.rb+ with the contents of our template. That means that +copy_file+ copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+. +We can see that now an initializer named core_extensions was created at +config/initializers/core_extensions.rb+ with the contents of our template. That means that +copy_file+ copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+. The methods that are available for generators are covered in the "final section":#generator-methods of this guide. @@ -365,7 +365,7 @@ $ rails generate scaffold Comment body:text Fallbacks allow your generators to have a single responsibility, increasing code reuse and reducing the amount of duplication. -h3. Application templates +h3. Application Templates Now that you've seen how generators can be used _inside_ an application, did you know they can also be used to _generate_ applications too? This kind of generator is referred as a "template". diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 8a9086f416..6aca5d3420 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -5,7 +5,7 @@ This guide covers getting up and running with Ruby on Rails. After reading it, y * Installing Rails, creating a new Rails application, and connecting your application to a database * The general layout of a Rails application * The basic principles of MVC (Model, View Controller) and RESTful design -* How to quickly generate the starting pieces of a Rails application. +* How to quickly generate the starting pieces of a Rails application endprologue. @@ -177,14 +177,14 @@ In any case, Rails will create a folder in your working directory called <tt>blo |Gemfile|This file allows you to specify what gem dependencies are needed for your Rails application.| |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.| |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.| +|app/|Contains the controllers, models, views and assets 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 files, stylesheets (CSS), and other static files go.| +|public/|The only folder seen to the world as-is. Contains the static files and compiled assets.| |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| @@ -205,8 +205,8 @@ 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 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 +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 an SQLite3 Database @@ -244,7 +244,7 @@ If your development computer's MySQL installation includes a root user with an e h5. Configuring a PostgreSQL Database -Finally if you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases: +If you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases: <yaml> development: @@ -256,6 +256,41 @@ development: password: </yaml> +h5. Configuring an SQLite3 Database for JRuby Platform + +If you choose to use SQLite3 and using JRuby, your +config/database.yml+ will look a little different. Here's the development section: + +<yaml> +development: + adapter: jdbcsqlite3 + database: db/development.sqlite3 +</yaml> + +h5. Configuring a MySQL Database for JRuby Platform + +If you choose to use MySQL and using JRuby, your +config/database.yml+ will look a little different. Here's the development section: + +<yaml> +development: + adapter: jdbcmysql + database: blog_development + username: root + password: +</yaml> + +h5. Configuring a PostgreSQL Database for JRuby Platform + +Finally if you choose to use PostgreSQL and using JRuby, your +config/database.yml+ will look a little different. Here's the development section: + +<yaml> +development: + adapter: jdbcpostgresql + encoding: unicode + database: blog_development + username: blog + password: +</yaml> + Change the username and password in the +development+ section as appropriate. TIP: You don't have to update the database configurations manually. If you had a look at the options of application generator, you have seen that one of them is named <tt>--database</tt>. It lets you choose an adapter for couple of most used relational databases. You can even run the generator repeatedly: <tt>cd .. && rails new blog --database=mysql</tt>. When you confirm the overwriting of the +config/database.yml+ file, your application will be configured for MySQL instead of SQLite. @@ -290,7 +325,7 @@ This will fire up an instance of the WEBrick web server by default (Rails can al 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. +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 @@ -364,11 +399,11 @@ The scaffold generator will build 15 files in your application, along with some |app/views/posts/new.html.erb |A view to create a new post| |app/views/posts/_form.html.erb |A partial to control the overall look and feel of the form used in edit and new views| |app/helpers/posts_helper.rb |Helper functions to be used from the post views| +|app/assets/stylesheets/scaffold.css.scss |Cascading style sheet to make the scaffolded views look better| |test/unit/post_test.rb |Unit testing harness for the posts model| |test/functional/posts_controller_test.rb |Functional testing harness for the posts controller| |test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper| |config/routes.rb |Edited to include routing information for posts| -|app/assets/stylesheets/scaffold.css.scss |Cascading style sheet to make the scaffolded views look better| h4. Running a Migration @@ -413,10 +448,10 @@ h4. Adding a Link To hook the posts up to the home page you've already created, you can add a link to the home page. Open +app/views/home/index.html.erb+ and modify it as follows: -<code lang="ruby"> +<ruby> <h1>Hello, Rails!</h1> <%= link_to "My Blog", posts_path %> -</code> +</ruby> The +link_to+ method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path for posts. @@ -503,7 +538,7 @@ def index end </ruby> -+Post.all+ calls the +Post+ model to return all of the posts currently in the database. The result of this call is an array of posts that we store in a instance variable called +@posts+. ++Post.all+ calls the +Post+ model to return all of the posts currently in the database. The result of this call is an array of posts that we store in an instance variable called +@posts+. TIP: For more information on finding records with Active Record, see "Active Record Query Interface":active_record_querying.html. diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index 608643b3d3..0c8e4e974d 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -809,7 +809,7 @@ That does not mean you're stuck with these limitations, though. The Ruby I18n ge I18n.backend = Globalize::Backend::Static.new </ruby> -You can also use the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends. For example, you could use the ActiveRecord backend and fall back to the (default) Simple backend: +You can also use the Chain backend to chain multiple backends together. This is useful when you want to use standard translations with a Simple backend but store custom application translations in a database or other backends. For example, you could use the Active Record backend and fall back to the (default) Simple backend: <ruby> I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend) diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 638830cd83..340699419b 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -20,15 +20,15 @@ h4. +bin/rails+ The actual +rails+ command is kept in _bin/rails_ at the and goes like this: <ruby> - #!/usr/bin/env ruby - - begin - require "rails/cli" - rescue LoadError - railties_path = File.expand_path('../../railties/lib', __FILE__) - $:.unshift(railties_path) - require "rails/cli" - end +#!/usr/bin/env ruby + +begin + require "rails/cli" +rescue LoadError + railties_path = File.expand_path('../../railties/lib', __FILE__) + $:.unshift(railties_path) + require "rails/cli" +end </ruby> This file will attempt to load +rails/cli+ and if it cannot find it then add the +railties/lib+ path to the load path (+$:+) and will then try to require it again. @@ -38,22 +38,22 @@ h4. +railites/lib/rails/cli.rb+ This file looks like this: <ruby> - require 'rbconfig' - require 'rails/script_rails_loader' +require 'rbconfig' +require 'rails/script_rails_loader' - # If we are inside a Rails application this method performs an exec and thus - # the rest of this script is not run. - Rails::ScriptRailsLoader.exec_script_rails! +# If we are inside a Rails application this method performs an exec and thus +# the rest of this script is not run. +Rails::ScriptRailsLoader.exec_script_rails! - require 'rails/ruby_version_check' - Signal.trap("INT") { puts; exit } +require 'rails/ruby_version_check' +Signal.trap("INT") { puts; exit } - if ARGV.first == 'plugin' - ARGV.shift - require 'rails/commands/plugin_new' - else - require 'rails/commands/application' - end +if ARGV.first == 'plugin' + ARGV.shift + require 'rails/commands/plugin_new' +else + require 'rails/commands/application' +end </ruby> The +rbconfig+ file here is out of Ruby's standard library and provides us with the +RbConfig+ class which contains useful information dependent on how Ruby was compiled. We'll see this in use in +railties/lib/rails/script_rails_loader+. @@ -76,46 +76,46 @@ The +rails/script_rails_loader+ file uses +RbConfig::Config+ to gather up the +b Back in +rails/cli+, the next line is this: <ruby> - Rails::ScriptRailsLoader.exec_script_rails! +Rails::ScriptRailsLoader.exec_script_rails! </ruby> This method is defined in +rails/script_rails_loader+ like this: <ruby> - def self.exec_script_rails! - cwd = Dir.pwd - return unless in_rails_application? || in_rails_application_subdirectory? - exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? - Dir.chdir("..") do - # Recurse in a chdir block: if the search fails we want to be sure - # the application is generated in the original working directory. - exec_script_rails! unless cwd == Dir.pwd - end - rescue SystemCallError - # could not chdir, no problem just return +def self.exec_script_rails! + cwd = Dir.pwd + return unless in_rails_application? || in_rails_application_subdirectory? + exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? + Dir.chdir("..") do + # Recurse in a chdir block: if the search fails we want to be sure + # the application is generated in the original working directory. + exec_script_rails! unless cwd == Dir.pwd end +rescue SystemCallError + # could not chdir, no problem just return +end </ruby> This method will first check if the current working directory (+cwd+) is a Rails application or is a subdirectory of one. The way to determine this is defined in the +in_rails_application?+ method like this: <ruby> - def self.in_rails_application? - File.exists?(SCRIPT_RAILS) - end +def self.in_rails_application? + File.exists?(SCRIPT_RAILS) +end </ruby> The +SCRIPT_RAILS+ constant defined earlier is used here, with +File.exists?+ checking for its presence in the current directory. If this method returns +false+, then +in_rails_application_subdirectory?+ will be used: <ruby> - def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd)) - File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent) - end +def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd)) + File.exists?(File.join(path, SCRIPT_RAILS)) || !path.root? && in_rails_application_subdirectory?(path.parent) +end </ruby> This climbs the directory tree until it reaches a path which contains a +script/rails+ file. If a directory is reached which contains this file then this line will run: <ruby> - exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? +exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application? </ruby> This is effectively the same as doing +ruby script/rails [arguments]+. Where +[arguments]+ at this point in time is simply "server". @@ -125,9 +125,9 @@ h4. +script/rails+ This file looks like this: <ruby> - APP_PATH = File.expand_path('../../config/application', __FILE__) - require File.expand_path('../../config/boot', __FILE__) - require 'rails/commands' +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' </ruby> The +APP_PATH+ constant here will be used later in +rails/commands+. The +config/boot+ file that +script/rails+ references is the +config/boot.rb+ file in our application which is responsible for loading Bundler and setting it up. @@ -137,19 +137,19 @@ h4. +config/boot.rb+ +config/boot.rb+ contains this: <ruby> - require 'rubygems' +require 'rubygems' - # Set up gems listed in the Gemfile. - gemfile = File.expand_path('../../Gemfile', __FILE__) - begin - ENV['BUNDLE_GEMFILE'] = gemfile - require 'bundler' - Bundler.setup - rescue Bundler::GemNotFound => e - STDERR.puts e.message - STDERR.puts "Try running `bundle install`." - exit! - end if File.exist?(gemfile) +# Set up gems listed in the Gemfile. +gemfile = File.expand_path('../../Gemfile', __FILE__) +begin + ENV['BUNDLE_GEMFILE'] = gemfile + require 'bundler' + Bundler.setup +rescue Bundler::GemNotFound => e + STDERR.puts e.message + STDERR.puts "Try running `bundle install`." + exit! +end if File.exist?(gemfile) </ruby> In a standard Rails application, there's a +Gemfile+ which declares all dependencies of the application. +config/boot.rb+ sets +ENV["BUNDLE_GEMFILE"]+ to the location of this file, then requires Bundler and calls +Bundler.setup+ which adds the dependencies of the application (including all the Rails parts) to the load path, making them available for the application to load. The gems that a Rails 3.1 application depends on are as follows: @@ -186,34 +186,34 @@ h4. +rails/commands.rb+ Once +config/boot.rb+ has finished, the next file that is required is +rails/commands+ which will execute a command based on the arguments passed in. In this case, the +ARGV+ array simply contains +server+ which is extracted into the +command+ variable using these lines: <ruby> - aliases = { - "g" => "generate", - "c" => "console", - "s" => "server", - "db" => "dbconsole" - } +aliases = { + "g" => "generate", + "c" => "console", + "s" => "server", + "db" => "dbconsole" +} - command = ARGV.shift - command = aliases[command] || command +command = ARGV.shift +command = aliases[command] || command </ruby> If we used <tt>s</tt> rather than +server+, Rails will use the +aliases+ defined in the file and match them to their respective commands. With the +server+ command, Rails will run this code: <ruby> - when 'server' - # Change to the application's path if there is no config.ru file in current dir. - # This allows us to run script/rails server from other directories, but still get - # the main config.ru and properly set the tmp directory. - Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) - - require 'rails/commands/server' - Rails::Server.new.tap { |server| - # We need to require application after the server sets environment, - # otherwise the --environment option given to the server won't propagate. - require APP_PATH - Dir.chdir(Rails.application.root) - server.start - } +when 'server' + # Change to the application's path if there is no config.ru file in current dir. + # This allows us to run script/rails server from other directories, but still get + # the main config.ru and properly set the tmp directory. + Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru")) + + require 'rails/commands/server' + Rails::Server.new.tap { |server| + # We need to require application after the server sets environment, + # otherwise the --environment option given to the server won't propagate. + require APP_PATH + Dir.chdir(Rails.application.root) + server.start + } </ruby> This file will change into the root of the directory (a path two directories back from +APP_PATH+ which points at +config/application.rb+), but only if the +config.ru+ file isn't found. This then requires +rails/commands/server+ which requires +action_dispatch+ and sets up the +Rails::Server+ class. @@ -239,7 +239,7 @@ The +methods.rb+ file is responsible for defining methods such as +camelize+, +u In this file there are a lot of lines such as this inside the +ActiveSupport+ module: <ruby> - autoload :Inflector +autoload :Inflector </ruby> Due to the overriding of the +autoload+ method, Ruby will know to look for this file at +activesupport/lib/active_support/inflector.rb+ when the +Inflector+ class is first referenced. @@ -263,10 +263,10 @@ h4. +rails/commands/server.rb+ The +Rails::Server+ class is defined in this file as inheriting from +Rack::Server+. When +Rails::Server.new+ is called, this calls the +initialize+ method in +rails/commands/server.rb+: <ruby> - def initialize(*) - super - set_environment - end +def initialize(*) + super + set_environment +end </ruby> Firstly, +super+ is called which calls the +initialize+ method on +Rack::Server+. @@ -278,10 +278,10 @@ h4. Rack: +lib/rack/server.rb+ The +initialize+ method in +Rack::Server+ simply sets a couple of variables: <ruby> - def initialize(options = nil) - @options = options - @app = options[:app] if options && options[:app] - end +def initialize(options = nil) + @options = options + @app = options[:app] if options && options[:app] +end </ruby> In this case, +options+ will be +nil+ so nothing happens in this method. @@ -289,64 +289,64 @@ In this case, +options+ will be +nil+ so nothing happens in this method. After +super+ has finished in +Rack::Server+, we jump back to +rails/commands/server.rb+. At this point, +set_environment+ is called within the context of the +Rails::Server+ object and this method doesn't appear to do much at first glance: <ruby> - def set_environment - ENV["RAILS_ENV"] ||= options[:environment] - end +def set_environment + ENV["RAILS_ENV"] ||= options[:environment] +end </ruby> In fact, the +options+ method here does quite a lot. This method is defined in +Rack::Server+ like this: <ruby> - def options - @options ||= parse_options(ARGV) - end +def options + @options ||= parse_options(ARGV) +end </ruby> Then +parse_options+ is defined like this: <ruby> - def parse_options(args) - options = default_options +def parse_options(args) + options = default_options - # Don't evaluate CGI ISINDEX parameters. - # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html - args.clear if ENV.include?("REQUEST_METHOD") + # Don't evaluate CGI ISINDEX parameters. + # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html + args.clear if ENV.include?("REQUEST_METHOD") - options.merge! opt_parser.parse! args - options[:config] = ::File.expand_path(options[:config]) - ENV["RACK_ENV"] = options[:environment] - options - end + options.merge! opt_parser.parse! args + options[:config] = ::File.expand_path(options[:config]) + ENV["RACK_ENV"] = options[:environment] + options +end </ruby> With the +default_options+ set to this: <ruby> - def default_options - { - :environment => ENV['RACK_ENV'] || "development", - :pid => nil, - :Port => 9292, - :Host => "0.0.0.0", - :AccessLog => [], - :config => "config.ru" - } - end +def default_options + { + :environment => ENV['RACK_ENV'] || "development", + :pid => nil, + :Port => 9292, + :Host => "0.0.0.0", + :AccessLog => [], + :config => "config.ru" + } +end </ruby> There is no +REQUEST_METHOD+ key in +ENV+ so we can skip over that line. The next line merges in the options from +opt_parser+ which is defined plainly in +Rack::Server+ <ruby> - def opt_parser - Options.new - end +def opt_parser + Options.new +end </ruby> The class *is* defined in +Rack::Server+, but is overwritten in +Rails::Server+ to take different arguments. Its +parse!+ method begins like this: <ruby> - def parse!(args) - args, options = args.dup, {} +def parse!(args) + args, options = args.dup, {} opt_parser = OptionParser.new do |opts| opts.banner = "Usage: rails server [mongrel, thin, etc] [options]" @@ -362,100 +362,101 @@ h4. +Rails::Server#start+ This method is defined like this: <ruby> - def start - puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" - puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}" - puts "=> Call with -d to detach" unless options[:daemonize] - trap(:INT) { exit } - puts "=> Ctrl-C to shutdown server" unless options[:daemonize] - - #Create required tmp directories if not found - %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) - end - - super - ensure - # The '-h' option calls exit before @options is set. - # If we call 'options' with it unset, we get double help banners. - puts 'Exiting' unless @options && options[:daemonize] +def start + puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" + puts "=> Rails #{Rails.version} application starting in #{Rails.env} on http://#{options[:Host]}:#{options[:Port]}" + puts "=> Call with -d to detach" unless options[:daemonize] + trap(:INT) { exit } + puts "=> Ctrl-C to shutdown server" unless options[:daemonize] + + #Create required tmp directories if not found + %w(cache pids sessions sockets).each do |dir_to_make| + FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make)) end + + super +ensure + # The '-h' option calls exit before @options is set. + # If we call 'options' with it unset, we get double help banners. + puts 'Exiting' unless @options && options[:daemonize] +end </ruby> This is where the first output of the Rails initialization happens. This method creates a trap for +INT+ signals, so if you +CTRL+C+ the server, it will exit the process. As we can see from the code here, it will create the +tmp/cache+, +tmp/pids+, +tmp/sessions+ and +tmp/sockets+ directories if they don't already exist prior to calling +super+. The +super+ method will call +Rack::Server.start+ which begins its definition like this: <ruby> - def start - if options[:warn] - $-w = true - end +def start + if options[:warn] + $-w = true + end - if includes = options[:include] - $LOAD_PATH.unshift(*includes) - end + if includes = options[:include] + $LOAD_PATH.unshift(*includes) + end - if library = options[:require] - require library - end + if library = options[:require] + require library + end - if options[:debug] - $DEBUG = true - require 'pp' - p options[:server] - pp wrapped_app - pp app - end + if options[:debug] + $DEBUG = true + require 'pp' + p options[:server] + pp wrapped_app + pp app + end +end </ruby> In a Rails application, these options are not set at all and therefore aren't used at all. The first line of code that's executed in this method is a call to this method: <ruby> - wrapped_app +wrapped_app </ruby> This method calls another method: <ruby> - @wrapped_app ||= build_app app +@wrapped_app ||= build_app app </ruby> Then the +app+ method here is defined like so: <ruby> - def app - @app ||= begin - if !::File.exist? options[:config] - abort "configuration #{options[:config]} not found" - end - - app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) - self.options.merge! options - app +def app + @app ||= begin + if !::File.exist? options[:config] + abort "configuration #{options[:config]} not found" end + + app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) + self.options.merge! options + app end +end </ruby> The +options[:config]+ value defaults to +config.ru+ which contains this: <ruby> - # This file is used by Rack-based servers to start the application. +# This file is used by Rack-based servers to start the application. - require ::File.expand_path('../config/environment', __FILE__) - run YourApp::Application +require ::File.expand_path('../config/environment', __FILE__) +run YourApp::Application </ruby> The +Rack::Builder.parse_file+ method here takes the content from this +config.ru+ file and parses it using this code: <ruby> - app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", +app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", TOPLEVEL_BINDING, config </ruby> The <ruby>initialize</ruby> method will take the block here and execute it within an instance of +Rack::Builder+. This is where the majority of the initialization process of Rails happens. The chain of events that this simple line sets off will be the focus of a large majority of this guide. The +require+ line for +config/environment.rb+ in +config.ru+ is the first to run: <ruby> - require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('../config/environment', __FILE__) </ruby> h4. +config/environment.rb+ @@ -475,7 +476,7 @@ h3. Loading Rails The next line in +config/application.rb+ is: <ruby> - require 'rails/all' +require 'rails/all' </ruby> h4. +railties/lib/rails/all.rb+ @@ -483,20 +484,20 @@ h4. +railties/lib/rails/all.rb+ This file is responsible for requiring all the individual parts of Rails like so: <ruby> - require "rails" +require "rails" - %w( +%w( active_record action_controller action_mailer active_resource rails/test_unit - ).each do |framework| - begin - require "#{framework}/railtie" - rescue LoadError - end +).each do |framework| + begin + require "#{framework}/railtie" + rescue LoadError end +end </ruby> First off the line is the +rails+ require itself. @@ -518,9 +519,9 @@ h4. +active_support/core_ext/kernel/reporting.rb+ This is the first of the many Active Support core extensions that come with Rails. This one in particular defines methods in the +Kernel+ module which is mixed in to the +Object+ class so the methods are available on +main+ and can therefore be called like this: <ruby> - silence_warnings do - # some code - end +silence_warnings do + # some code +end </ruby> These methods can be used to silence STDERR responses and the +silence_stream+ allows you to also silence other streams. Additionally, this mixin allows you to suppress exceptions and capture streams. For more information see the "Silencing Warnings, Streams, and Exceptions":http://guides.rubyonrails.org/active_support_core_extensions.html#silencing-warnings-streams-and-exceptions section from the Active Support Core Extensions Guide. @@ -635,14 +636,14 @@ h4. +railties/lib/rails/rack.rb+ The final file to be loaded by +railties/lib/rails/configuration.rb+ is +rails/rack+ which defines some simple autoloads: <ruby> - module Rails - module Rack - autoload :Debugger, "rails/rack/debugger" - autoload :Logger, "rails/rack/logger" - autoload :LogTailer, "rails/rack/log_tailer" - autoload :Static, "rails/rack/static" - end +module Rails + module Rack + autoload :Debugger, "rails/rack/debugger" + autoload :Logger, "rails/rack/logger" + autoload :LogTailer, "rails/rack/log_tailer" + autoload :Static, "rails/rack/static" end +end </ruby> Once this file is finished loading, then the +Rails::Configuration+ class is initialized. This completes the loading of +railties/lib/rails/configuration.rb+ and now we jump back to the loading of +railties/lib/rails/railtie.rb+, where the next file loaded is +active_support/inflector+. @@ -652,17 +653,17 @@ h4. +activesupport/lib/active_support/inflector.rb+ +active_support/inflector.rb+ requires a series of file which are responsible for setting up the basics for knowing how to pluralize and singularize words. These files are: <ruby> - require 'active_support/inflector/inflections' - require 'active_support/inflector/transliterate' - require 'active_support/inflector/methods' +require 'active_support/inflector/inflections' +require 'active_support/inflector/transliterate' +require 'active_support/inflector/methods' - require 'active_support/inflections' - require 'active_support/core_ext/string/inflections' +require 'active_support/inflections' +require 'active_support/core_ext/string/inflections' </ruby> -The +active_support/inflector/methods+ file has already been required by +active_support/autoload+ and so won't be loaded again here. +The +active_support/inflector/methods+ file has already been required by +active_support/autoload+ and so won't be loaded again here. The +activesupport/lib/active_support/inflector/inflections.rb+ is required by +active_support/inflector/methods+. -h4. +activesupport/lib/active_support/inflector/inflections.rb+ +h4. +active_support/inflections+ This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes". @@ -721,22 +722,22 @@ h4. +activesupport/lib/active_support/i18n_railtie.rb+ This file is the first file that sets up configuration with these lines inside the class: <ruby> - class Railtie < Rails::Railtie - config.i18n = ActiveSupport::OrderedOptions.new - config.i18n.railties_load_path = [] - config.i18n.load_path = [] - config.i18n.fallbacks = ActiveSupport::OrderedOptions.new +class Railtie < Rails::Railtie + config.i18n = ActiveSupport::OrderedOptions.new + config.i18n.railties_load_path = [] + config.i18n.load_path = [] + config.i18n.fallbacks = ActiveSupport::OrderedOptions.new </ruby> By inheriting from +Rails::Railtie+ the +Rails::Railtie#inherited+ method is called: <ruby> - def inherited(base) - unless base.abstract_railtie? - base.send(:include, Railtie::Configurable) - subclasses << base - end +def inherited(base) + unless base.abstract_railtie? + base.send(:include, Railtie::Configurable) + subclasses << base end +end </ruby> This first checks if the Railtie that's inheriting it is a component of Rails itself: @@ -763,15 +764,15 @@ end The +config+ method used at the top of +I18n::Railtie+ is defined on +Rails::Railtie+ and is defined like this: <ruby> - def config - @config ||= Railtie::Configuration.new - end +def config + @config ||= Railtie::Configuration.new +end </ruby> At this point, that +Railtie::Configuration+ constant is automatically loaded which causes the +rails/railties/configuration+ file to be loaded. The line for this is this particular line in +railties/lib/rails/railtie.rb+: <ruby> - autoload :Configuration, "rails/railtie/configuration" +autoload :Configuration, "rails/railtie/configuration" </ruby> h4. +railties/lib/rails/railtie/configuration.rb+ @@ -781,15 +782,15 @@ This file begins with a require out to +rails/configuration+ which has already b This file defines the +Rails::Railtie::Configuration+ class which is responsible for providing a way to easily configure railties and it's the +initialize+ method here which is called by the +config+ method back in the +i18n_railtie.rb+ file. The methods on this object don't exist, and so are rescued by the +method_missing+ defined further down in +configuration.rb+: <ruby> - def method_missing(name, *args, &blk) - if name.to_s =~ /=$/ - @@options[$`.to_sym] = args.first - elsif @@options.key?(name) - @@options[name] - else - super - end +def method_missing(name, *args, &blk) + if name.to_s =~ /=$/ + @@options[$`.to_sym] = args.first + elsif @@options.key?(name) + @@options[name] + else + super end +end </ruby> So therefore when an option is referred to it simply stores the value as the key if it's used in a setter context, or retrieves it if used in a getter context. Nothing fancy going on there. @@ -799,21 +800,21 @@ h4. Back to +activesupport/lib/active_support/i18n_railtie.rb+ After the configuration method the +reloader+ method is defined, and then the first of of Railties' initializers is defined: +i18n.callbacks+. <ruby> - initializer "i18n.callbacks" do - ActionDispatch::Reloader.to_prepare do - I18n::Railtie.reloader.execute_if_updated - end +initializer "i18n.callbacks" do + ActionDispatch::Reloader.to_prepare do + I18n::Railtie.reloader.execute_if_updated end +end </ruby> The +initializer+ method (from the +Rails::Initializable+ module) here doesn't run the block, but rather stores it to be run later on: <ruby> - def initializer(name, opts = {}, &blk) - raise ArgumentError, "A block must be passed when defining an initializer" unless blk - opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] } - initializers << Initializer.new(name, nil, opts, &blk) - end +def initializer(name, opts = {}, &blk) + raise ArgumentError, "A block must be passed when defining an initializer" unless blk + opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] } + initializers << Initializer.new(name, nil, opts, &blk) +end </ruby> An initializer can be configured to run before or after another initializer, which we'll see a couple of times throughout this initialization process. Anything that inherits from +Rails::Railtie+ may also make use of the +initializer+ method, something which is covered in the "Configuration guide":[http://ryanbigg.com/guides/configuring.html#rails-railtie-initializer]. @@ -821,25 +822,25 @@ An initializer can be configured to run before or after another initializer, whi The +Initializer+ class here is defined within the +Rails::Initializable+ module and its +initialize+ method is defined to just set up a couple of variables: <ruby> - def initialize(name, context, options, &block) - @name, @context, @options, @block = name, context, options, block - end +def initialize(name, context, options, &block) + @name, @context, @options, @block = name, context, options, block +end </ruby> Once this +initialize+ method is finished, the object is added to the object the +initializers+ method returns: <ruby> - def initializers - @initializers ||= self.class.initializers_for(self) - end +def initializers + @initializers ||= self.class.initializers_for(self) +end </ruby> If +@initializers+ isn't set (which it won't be at this point), the +intializers_for+ method will be called for this class. <ruby> - def initializers_for(binding) - Collection.new(initializers_chain.map { |i| i.bind(binding) }) - end +def initializers_for(binding) + Collection.new(initializers_chain.map { |i| i.bind(binding) }) +end </ruby> The +Collection+ class in +railties/lib/rails/initializable.rb+ inherits from +Array+ and includes the +TSort+ module which is used to sort out the order of the initializers based on the order they are placed in. @@ -847,57 +848,57 @@ The +Collection+ class in +railties/lib/rails/initializable.rb+ inherits from +A The +initializers_chain+ method referenced in the +initializers_for+ method is defined like this: <rub> - def initializers_chain - initializers = Collection.new - ancestors.reverse_each do | klass | - next unless klass.respond_to?(:initializers) - initializers = initializers + klass.initializers - end - initializers +def initializers_chain + initializers = Collection.new + ancestors.reverse_each do | klass | + next unless klass.respond_to?(:initializers) + initializers = initializers + klass.initializers end + initializers +end </ruby> This method collects the initializers from the ancestors of this class and adds them to a new +Collection+ object using the <tt>+</tt> method which is defined like this for the <tt>Collection</tt> class: <ruby> - def +(other) - Collection.new(to_a + other.to_a) - end +def +(other) + Collection.new(to_a + other.to_a) +end </ruby> -So this <tt>+</tt> method is overriden to return a new collection comprising of the existing collection as an array and then using the <tt>Array#+</tt> method combines these two collections, returning a "super" +Collection+ object. In this case, the only initializer that's going to be in this new +Collection+ object is the +i18n.callbacks+ initializer. +So this <tt>+</tt> method is overridden to return a new collection comprising of the existing collection as an array and then using the <tt>Array#+</tt> method combines these two collections, returning a "super" +Collection+ object. In this case, the only initializer that's going to be in this new +Collection+ object is the +i18n.callbacks+ initializer. The next method to be called after this +initializer+ method is the +after_initialize+ method on the +config+ object, which is defined like this: <ruby> - def after_initialize(&block) - ActiveSupport.on_load(:after_initialize, :yield => true, &block) - end +def after_initialize(&block) + ActiveSupport.on_load(:after_initialize, :yield => true, &block) +end </ruby> The +on_load+ method here is provided by the +active_support/lazy_load_hooks+ file which was required earlier and is defined like this: <ruby> - def self.on_load(name, options = {}, &block) - if base = @loaded[name] - execute_hook(base, options, block) - else - @load_hooks[name] << [block, options] - end +def self.on_load(name, options = {}, &block) + if base = @loaded[name] + execute_hook(base, options, block) + else + @load_hooks[name] << [block, options] end +end </ruby> The +@loaded+ variable here is a hash containing elements representing the different components of Rails that have been loaded at this stage. Currently, this hash is empty. So the +else+ is executed here, using the +@load_hooks+ variable defined in +active_support/lazy_load_hooks+: <ruby> - @load_hooks = Hash.new {|h,k| h[k] = [] } +@load_hooks = Hash.new {|h,k| h[k] = [] } </ruby> This defines a new hash which has keys that default to empty arrays. This saves Rails from having to do something like this instead: <ruby> - @load_hooks[name] = [] - @load_hooks[name] << [block, options] +@load_hooks[name] = [] +@load_hooks[name] << [block, options] </ruby> The value added to this array here consists of the block and options passed to +after_initialize+. @@ -929,11 +930,11 @@ h4. +activesupport/lib/action_dispatch.rb+ This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are. <ruby> - activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) - $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) +activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) +$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) - activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) - $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) </ruby> In effect, these lines only define the +activesupport_path+ and +activemodel_path+ variables and nothing more. @@ -941,23 +942,23 @@ In effect, these lines only define the +activesupport_path+ and +activemodel_pat The next two requires in this file are already done, so they are not run: <ruby> - require 'active_support' - require 'active_support/dependencies/autoload' +require 'active_support' +require 'active_support/dependencies/autoload' </ruby> The following require is to +action_pack+ (+activesupport/lib/action_pack.rb+) which has a 22-line copyright notice at the top of it and ends in a simple require to +action_pack/version+. This file, like other +version.rb+ files before it, defines the +ActionPack::VERSION+ constant: <ruby> - module ActionPack - module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 - TINY = 0 - PRE = "beta" - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end +module ActionPack + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 1 + TINY = 0 + PRE = "beta" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end +end </ruby> Once +action_pack+ is finished, then +active_model+ is required. @@ -967,16 +968,16 @@ h4. +activemodel/lib/active_model.rb+ This file makes a require to +active_model/version+ which defines the version for Active Model: <ruby> - module ActiveModel - module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 - TINY = 0 - PRE = "beta" - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end +module ActiveModel + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 1 + TINY = 0 + PRE = "beta" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end +end </ruby> Once the +version.rb+ file is loaded, the +ActiveModel+ module has its autoloaded constants defined as well as a sub-module called +ActiveModel::Serializers+ which has autoloads of its own. When the +ActiveModel+ module is closed the +active_support/i18n+ file is required. @@ -986,15 +987,15 @@ h4. +activesupport/lib/active_support/i18n.rb+ This is where the +i18n+ gem is required and first configured: <ruby> - begin - require 'i18n' - require 'active_support/lazy_load_hooks' - rescue LoadError => e - $stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install" - raise e - end +begin + require 'i18n' + require 'active_support/lazy_load_hooks' +rescue LoadError => e + $stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end - I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" +I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" </ruby> In effect, the +I18n+ module first defined by +i18n_railtie+ is extended by the +i18n+ gem, rather than the other way around. This has no ill effect. They both work on the same way. @@ -1012,9 +1013,9 @@ h4. Back to +activesupport/lib/action_dispatch.rb+ The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here: <ruby> - autoload_under 'testing' do - autoload :Assertions - ... +autoload_under 'testing' do + autoload :Assertions +... </ruby> The +Assertions+ module is in the +action_dispatch/testing+ folder rather than simply +action_dispatch+. @@ -1046,25 +1047,25 @@ This file begins by detecting if the +lib+ directories of +active_support+ and + The first three requires have already been done by other files and so aren't loaded here, but the 4th require, the one to +arel+ will require the file provided by the Arel gem, which defines the +Arel+ module. <ruby> - require 'active_support' - require 'active_support/i18n' - require 'active_model' - require 'arel' +require 'active_support' +require 'active_support/i18n' +require 'active_model' +require 'arel' </ruby> The 5th require in this file is one to +active_record/version+ which defines the +ActiveRecord::VERSION+ constant: <ruby> - module ActiveRecord - module VERSION #:nodoc: - MAJOR = 3 - MINOR = 1 - TINY = 0 - PRE = "beta" - - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') - end +module ActiveRecord + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 1 + TINY = 0 + PRE = "beta" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') end +end </ruby> Once these requires are finished, the base for the +ActiveRecord+ module is defined along with its autoloads. @@ -1072,9 +1073,9 @@ Once these requires are finished, the base for the +ActiveRecord+ module is defi Near the end of the file, we see this line: <ruby> - ActiveSupport.on_load(:active_record) do - Arel::Table.engine = self - end +ActiveSupport.on_load(:active_record) do + Arel::Table.engine = self +end </ruby> This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+. @@ -1082,7 +1083,7 @@ This will set the engine for +Arel::Table+ to be +ActiveRecord::Base+. The file then finishes with this line: <ruby> - I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' +I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' </ruby> This will add the translations from +activerecord/lib/active_record/locale/en.yml+ to the load path for +I18n+, with this file being parsed when all the translations are loaded. @@ -1092,8 +1093,8 @@ h4. Back to +activerecord/lib/active_record/railtie.rb+ The next two <tt>require</tt>s in this file aren't run because their files are already required, with +rails+ being required by +rails/all+ and +active_model/railtie+ being required from +action_dispatch+. <ruby> - require "rails" - require "active_model/railtie" +require "rails" +require "active_model/railtie" </ruby> The next +require+ in this file is to +action_controller/railtie+. @@ -1103,9 +1104,9 @@ h4. +actionpack/lib/action_controller/railtie.rb+ This file begins with a couple more requires to files that have already been loaded: <ruby> - require "rails" - require "action_controller" - require "action_dispatch/railtie" +require "rails" +require "action_controller" +require "action_dispatch/railtie" </ruby> However the require after these is to a file that hasn't yet been loaded, +action_view/railtie+, which begins by requiring +action_view+. diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index d67668df91..ba45b84242 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -123,7 +123,7 @@ Cache-Control: no-cache $ </shell> -We see there is an empty response (no data after the +Cache-Control+ line), but the request was successful because Rails has set the response to 200 OK. You can set the +:status+ option 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. +We see there is an empty response (no data after the +Cache-Control+ line), but the request was successful because Rails has set the response to 200 OK. You can set the +:status+ option 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 acknowledgment 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. diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index d60b68ec7f..f17f686d47 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -55,6 +55,8 @@ class AddReceiveNewsletterToUsers < ActiveRecord::Migration end </ruby> +NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations. + This migration adds a +receive_newsletter+ column to the +users+ table. We want it to default to +false+ for new users, but existing users are considered to have already opted in, so we use the User model to set the flag to +true+ for existing users. @@ -73,11 +75,9 @@ class CreateProducts < ActiveRecord::Migration end </ruby> -NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations. - h4. Migrations are Classes -A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements two class methods: +up+ (perform the required transformations) and +down+ (revert them). +A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements two methods: +up+ (perform the required transformations) and +down+ (revert them). Active Record provides methods that perform common data definition tasks in a database independent way (you'll read about them in detail later): @@ -115,7 +115,7 @@ h4. Changing Migrations Occasionally you will make a mistake when writing a migration. If you have already run the migration then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run +rake db:migrate+. You must rollback the migration (for example with +rake db:rollback+), edit your migration and then run +rake db:migrate+ to run the corrected version. -In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless. Just use some common sense. +In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or more generally which has not been propagated beyond your development machine) is relatively harmless. h3. Creating a Migration diff --git a/railties/guides/source/nested_model_forms.textile b/railties/guides/source/nested_model_forms.textile index 55694c0eb4..4b1fd2e0ac 100644 --- a/railties/guides/source/nested_model_forms.textile +++ b/railties/guides/source/nested_model_forms.textile @@ -90,7 +90,7 @@ h3. Views h4. Controller code -A nested model form will _only_ be build if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. +A nested model form will _only_ be built if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template: @@ -144,7 +144,7 @@ Now add a nested form for the +address+ association: <%= f.text_field :name %> <%= f.fields_for :address do |af| %> - <%= f.text_field :street %> + <%= af.text_field :street %> <% end %> <% end %> </erb> @@ -159,7 +159,7 @@ This generates: </form> </html> -Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be build by the way it has namespaced the +name+ attribute. +Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be built by the way it has namespaced the +name+ attribute. When this form is posted the Rails parameter parser will construct a hash like the following: @@ -185,7 +185,7 @@ The form code for an association collection is pretty similar to that of a singl <%= f.text_field :name %> <%= f.fields_for :projects do |pf| %> - <%= f.text_field :name %> + <%= pf.text_field :name %> <% end %> <% end %> </erb> @@ -201,7 +201,7 @@ Which generates: </form> </html> -As you can see it has generated 2 +project name+ inputs, one for each new +project+ that’s build in the controllers +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: +As you can see it has generated 2 +project name+ inputs, one for each new +project+ that was built in the controller's +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: <ruby> { @@ -215,7 +215,7 @@ As you can see it has generated 2 +project name+ inputs, one for each new +proje } </ruby> -You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance. +You can basically see the +projects_attributes+ hash as an array of attribute hashes, one for each model instance. NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep. diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile index 2b79237c59..dbe6f97f5c 100644 --- a/railties/guides/source/performance_testing.textile +++ b/railties/guides/source/performance_testing.textile @@ -66,7 +66,7 @@ resources :posts # home_controller.rb class HomeController < ApplicationController def dashboard - @users = User.last_ten(:include => :avatars) + @users = User.last_ten.includes(:avatars) @posts = Post.all_today end end @@ -151,7 +151,7 @@ Performance tests can be run in two modes: Benchmarking and Profiling. h5. Benchmarking -Benchmarking makes it easy to quickly gather a few metrics about each test tun. By default, each test case is run +4 times+ in benchmarking mode. +Benchmarking makes it easy to quickly gather a few metrics about each test tun. By default, each test case is run *4 times* in benchmarking mode. To run performance tests in benchmarking mode: @@ -161,7 +161,7 @@ $ rake test:benchmark h5. Profiling -Profiling allows you to make an in-depth analysis of each of your tests by using an external profiler. Depending on your Ruby interpreter, this profiler can be native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each test case is run +1 time+ in profiling mode. +Profiling allows you to make an in-depth analysis of each of your tests by using an external profiler. Depending on your Ruby interpreter, this profiler can be native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each test case is run *once* in profiling mode. To run performance tests in profiling mode: @@ -276,7 +276,7 @@ measurement,created_at,app,rails,ruby,platform h5(#output-profiling). Profiling -In profiling mode, performance tests can generate multiple types of outputs. The command line output is always presented but support for the others is dependant on the interpreter in use. A brief description of each type and their availability across interpreters is given below. +In profiling mode, performance tests can generate multiple types of outputs. The command line output is always presented but support for the others is dependent on the interpreter in use. A brief description of each type and their availability across interpreters is given below. h6. Command Line @@ -481,7 +481,7 @@ h4. +profiler+ Usage: <shell> -Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS] +Usage: rails profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS] -r, --runs N Number of runs. Default: 1 -o, --output PATH Directory to use when writing the results. @@ -573,7 +573,7 @@ h3. Useful Links h4. Rails Plugins and Gems * "Rails Analyzer":http://rails-analyzer.rubyforge.org -* "Palmist":http://www.flyingmachinestudios.com/projects/ +* "Palmist":http://www.flyingmachinestudios.com/programming/announcing-palmist * "Rails Footnotes":https://github.com/josevalim/rails-footnotes/tree/master * "Query Reviewer":https://github.com/dsboulder/query_reviewer/tree/master diff --git a/railties/guides/source/rails_application_templates.textile b/railties/guides/source/rails_application_templates.textile index 388d8eea3e..3db47a70e8 100644 --- a/railties/guides/source/rails_application_templates.textile +++ b/railties/guides/source/rails_application_templates.textile @@ -1,6 +1,6 @@ h2. Rails Application Templates -Application templates are simple ruby files containing DSL for adding plugins/gems/initializers etc. to your freshly created Rails project or an existing Rails project. +Application templates are simple Ruby files containing DSL for adding plugins/gems/initializers etc. to your freshly created Rails project or an existing Rails project. By referring to this guide, you will be able to: @@ -58,14 +58,12 @@ gem "bj" gem "nokogiri" </ruby> -Please note that this will NOT install the gems for you. So you may want to run the +rake gems:install+ task too: +Please note that this will NOT install the gems for you and you will have to run +bundle install+ to do that. <ruby> -rake "gems:install" +bundle install </ruby> -And let Rails take care of installing the required gems if they’re not already installed. - h4. add_source(source, options = {}) Adds the given source to the generated application's +Gemfile+. @@ -229,7 +227,7 @@ rake("rails:freeze:gems") if yes?("Freeze rails gems ?") no?(question) acts just the opposite. </ruby> -h4. git(:must => "-a love") +h4. git(:command) Rails templates let you run any git command: diff --git a/railties/guides/source/rails_on_rack.textile b/railties/guides/source/rails_on_rack.textile index aa53aa6db6..8d5985dba8 100644 --- a/railties/guides/source/rails_on_rack.textile +++ b/railties/guides/source/rails_on_rack.textile @@ -111,11 +111,11 @@ h5. Adding a Middleware You can add a new middleware to the middleware stack using any of the following methods: -* +config.middleware.use(new_middleware, args)+ - Adds the new middleware at the bottom of the middleware stack. +* <tt>config.middleware.use(new_middleware, args)</tt> - Adds the new middleware at the bottom of the middleware stack. -* +config.middleware.insert_before(existing_middleware, new_middleware, args)+ - Adds the new middleware before the specified existing middleware in the middleware stack. +* <tt>config.middleware.insert_before(existing_middleware, new_middleware, args)</tt> - Adds the new middleware before the specified existing middleware in the middleware stack. -* +config.middleware.insert_after(existing_middleware, new_middleware, args)+ - Adds the new middleware after the specified existing middleware in the middleware stack. +* <tt>config.middleware.insert_after(existing_middleware, new_middleware, args)</tt> - Adds the new middleware after the specified existing middleware in the middleware stack. <ruby> # config/environment.rb @@ -154,20 +154,20 @@ h4. Internal Middleware Stack Much of Action Controller's functionality is implemented as Middlewares. The following table explains the purpose of each of them: |_.Middleware|_.Purpose| -|+Rack::Lock+|Sets +env["rack.multithread"]+ flag to +true+ and wraps the application within a Mutex.| +|+Rack::Lock+|Sets <tt>env["rack.multithread"]</tt> flag to +true+ and wraps the application within a Mutex.| |+ActionController::Failsafe+|Returns HTTP Status +500+ to the client if an exception gets raised while dispatching.| |+ActiveRecord::QueryCache+|Enables the Active Record query cache.| |+ActionController::Session::CookieStore+|Uses the cookie based session store.| |+ActionController::Session::MemCacheStore+|Uses the memcached based session store.| |+ActiveRecord::SessionStore+|Uses the database based session store.| -|+Rack::MethodOverride+|Sets HTTP method based on +_method+ parameter or +env["HTTP_X_HTTP_METHOD_OVERRIDE"]+.| +|+Rack::MethodOverride+|Sets HTTP method based on +_method+ parameter or <tt>env["HTTP_X_HTTP_METHOD_OVERRIDE"]</tt>.| |+Rack::Head+|Discards the response body if the client sends a +HEAD+ request.| TIP: It's possible to use any of the above middlewares in your custom Rack stack. h4. Customizing Internal Middleware Stack -It's possible to replace the entire middleware stack with a custom stack using +ActionController::Dispatcher.middleware=+. +It's possible to replace the entire middleware stack with a custom stack using <tt>ActionController::Dispatcher.middleware=</tt>. Put the following in an initializer: diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 08615bed4e..1cbc5c8f6e 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -288,7 +288,7 @@ When using +magazine_ad_path+, you can pass in instances of +Magazine+ and +Ad+ You can also use +url_for+ with a set of objects, and Rails will automatically determine which route you want: <erb> -<%= link_to "Ad details", url_for(@magazine, @ad) %> +<%= link_to "Ad details", url_for([@magazine, @ad]) %> </erb> In this case, Rails will see that +@magazine+ is a +Magazine+ and +@ad+ is an +Ad+ and will therefore use the +magazine_ad_path+ helper. In helpers like +link_to+, you can specify just the object in place of the full +url_for+ call: diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile index 9ae360a73b..5989191b5c 100644 --- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile +++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile @@ -65,7 +65,7 @@ It is also recommended that you work with +WARNINGS=1+. This detects duplicate I If you want to generate guides in languages other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +GUIDES_LANGUAGE+ environment variable: <plain> -rake generate_guides GUIDES_LANGUAGE=es +bundle exec rake generate_guides GUIDES_LANGUAGE=es </plain> h3. HTML Validation @@ -73,7 +73,7 @@ h3. HTML Validation Please validate the generated HTML with: <plain> -rake validate_guides +bundle exec rake validate_guides </plain> Particularly, titles get an ID generated from their content and this often leads to duplicates. Please set +WARNINGS=1+ when generating guides to detect them. The warning messages suggest a way to fix them. diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 7a93c3a1e6..cc55d1f756 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -929,8 +929,8 @@ class UserControllerTest < ActionController::TestCase end invite_email = ActionMailer::Base.deliveries.first - assert_equal invite_email.subject, "You have been invited by me@example.com" - assert_equal invite_email.to[0], 'friend@example.com' + assert_equal "You have been invited by me@example.com", invite_email.subject + assert_equal 'friend@example.com', invite_email.to[0] assert_match /Hi friend@example.com/, invite_email.body end end @@ -944,7 +944,7 @@ The built-in +test/unit+ based testing is not the only way to test Rails applica * "Factory Girl":https://github.com/thoughtbot/factory_girl/tree/master, a replacement for fixtures. * "Machinist":https://github.com/notahat/machinist/tree/master, another replacement for fixtures. * "Shoulda":http://www.thoughtbot.com/projects/shoulda, an extension to +test/unit+ with additional helpers, macros, and assertions. -* "RSpec":http://rspec.info/, a behavior-driven development framework +* "RSpec":http://relishapp.com/rspec, a behavior-driven development framework h3. Changelog diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index cca0891835..df92934457 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -87,6 +87,29 @@ module Rails RAILS_CACHE end + # Returns all rails groups for loading based on: + # + # * The Rails environment; + # * The environment variable RAILS_GROUPS; + # * The optional hash given as argument with group dependencies; + # + # == Examples + # + # groups :assets => [:development, :test] + # + # # Returns + # # => [:default, :development, :assets] for Rails.env == "development" + # # => [:default, :production] for Rails.env == "production" + # + def groups(hash={}) + env = Rails.env + groups = [:default, env] + groups.concat ENV["RAILS_GROUPS"].to_s.split(",") + groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) } + groups.compact! + groups + end + def version VERSION::STRING end diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 953233d774..fe29668c72 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -78,10 +78,6 @@ module Rails require environment if environment end - def eager_load! #:nodoc: - railties.all(&:eager_load!) - super - end def reload_routes! routes_reloader.reload! @@ -100,22 +96,12 @@ module Rails def load_tasks(app=self) initialize_tasks - railties.all { |r| r.load_tasks(app) } - super - self - end - - def load_generators(app=self) - initialize_generators - railties.all { |r| r.load_generators(app) } - Rails::Generators.configure!(app.config.generators) super self end def load_console(app=self) initialize_console - railties.all { |r| r.load_console(app) } super self end @@ -138,6 +124,10 @@ module Rails @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) end + def to_app + self + end + protected alias :build_middleware_stack :app @@ -195,10 +185,6 @@ module Rails end end - def initialize_generators - require "rails/generators" - end - def initialize_console require "pp" require "rails/console/app" diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb index 4fc5e92837..8f3a3e8bbb 100644 --- a/railties/lib/rails/application/railties.rb +++ b/railties/lib/rails/application/railties.rb @@ -4,7 +4,7 @@ module Rails class Application < Engine class Railties < Rails::Engine::Railties def all(&block) - @all ||= railties + engines + super + @all ||= railties + engines + plugins @all.each(&block) if block @all end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 39627a3094..5e55aeada9 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -23,11 +23,7 @@ when 'generate', 'destroy', 'plugin' require APP_PATH Rails.application.require_environment! - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) - Rails.application.load_generators(engine) - else - Rails.application.load_generators - end + Rails.application.load_generators require "rails/commands/#{command}" end @@ -95,7 +91,7 @@ In addition to those, there are: benchmarker See how fast a piece of code runs profiler Get profile information from a piece of code plugin Install a plugin - runner Run a piece of code in the application environment + runner Run a piece of code in the application environment (short-cut alias: "r") All commands can be run with -h for more information. EOT diff --git a/railties/lib/rails/commands/benchmarker.rb b/railties/lib/rails/commands/benchmarker.rb index b06c915ac3..6c52d0f70f 100644 --- a/railties/lib/rails/commands/benchmarker.rb +++ b/railties/lib/rails/commands/benchmarker.rb @@ -9,7 +9,7 @@ ARGV.pop def options options = {} defaults = ActiveSupport::Testing::Performance::DEFAULTS - + OptionParser.new do |opt| opt.banner = "Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS]" opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } @@ -17,13 +17,13 @@ def options opt.on('-m', '--metrics a,b,c', Array, 'Metrics to use.', "Default: #{defaults[:metrics].join(",")}") { |m| options[:metrics] = m.map(&:to_sym) } opt.parse!(ARGV) end - + options end class BenchmarkerTest < ActionDispatch::PerformanceTest self.profile_options = options - + ARGV.each do |expression| eval <<-RUBY def test_#{expression.parameterize('_')} diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb index b6f9a003d1..1fb2d98834 100644 --- a/railties/lib/rails/commands/generate.rb +++ b/railties/lib/rails/commands/generate.rb @@ -7,4 +7,6 @@ if ARGV.first.in?([nil, "-h", "--help"]) end name = ARGV.shift -Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => Rails.root + +root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root +Rails::Generators.invoke name, ARGV, :behavior => :invoke, :destination_root => root diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb index 048af7cbef..4df849447d 100644 --- a/railties/lib/rails/commands/plugin.rb +++ b/railties/lib/rails/commands/plugin.rb @@ -313,11 +313,11 @@ module Commands o.separator "" o.separator "EXAMPLES" o.separator " Install a plugin from a subversion URL:" - o.separator " #{@script_name} plugin install http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder\n" + o.separator " #{@script_name} plugin install http://example.com/my_svn_plugin\n" o.separator " Install a plugin from a git URL:" o.separator " #{@script_name} plugin install git://github.com/SomeGuy/my_awesome_plugin.git\n" o.separator " Install a plugin and add a svn:externals entry to vendor/plugins" - o.separator " #{@script_name} plugin install -x continuous_builder\n" + o.separator " #{@script_name} plugin install -x my_svn_plugin\n" end end diff --git a/railties/lib/rails/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb index 94cf32d32d..ea6347c918 100644 --- a/railties/lib/rails/commands/profiler.rb +++ b/railties/lib/rails/commands/profiler.rb @@ -6,7 +6,7 @@ require 'active_support/testing/performance' def options options = {} defaults = ActiveSupport::Testing::Performance::DEFAULTS - + OptionParser.new do |opt| opt.banner = "Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS]" opt.on('-r', '--runs N', Numeric, 'Number of runs.', "Default: #{defaults[:runs]}") { |r| options[:runs] = r } @@ -15,13 +15,13 @@ def options opt.on('-f', '--formats x,y,z', Array, 'Formats to output to.', "Default: #{defaults[:formats].join(",")}") { |m| options[:formats] = m.map(&:to_sym) } opt.parse!(ARGV) end - + options end class ProfilerTest < ActionDispatch::PerformanceTest self.profile_options = options - + ARGV.each do |expression| eval <<-RUBY def test_#{expression.parameterize('_')} diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index f8b00e7249..e8cc5d9e3b 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -4,6 +4,10 @@ require 'rbconfig' options = { :environment => (ENV['RAILS_ENV'] || "development").dup } code_or_file = nil +if ARGV.first.nil? + ARGV.push "-h" +end + ARGV.clone.options do |opts| script_name = File.basename($0) opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)" diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 91c87514cf..23392276d5 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -43,7 +43,7 @@ module Rails end def app - @app ||= super.instance + @app ||= super.respond_to?(:to_app) ? super.to_app : super end def opt_parser diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 383be1802f..52c89274e7 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -330,6 +330,14 @@ module Rails autoload :Configuration, "rails/engine/configuration" autoload :Railties, "rails/engine/railties" + def load_generators(app=self) + initialize_generators + railties.all { |r| r.load_generators(app) } + Rails::Generators.configure!(app.config.generators) + super + self + end + class << self attr_accessor :called_from, :isolated alias :isolated? :isolated @@ -387,12 +395,20 @@ module Rails delegate :middleware, :root, :paths, :to => :config delegate :engine_name, :isolated?, :to => "self.class" - def load_tasks(*) + def load_tasks(app=self) + railties.all { |r| r.load_tasks(app) } super paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end - + + def load_console(app=self) + railties.all { |r| r.load_console(app) } + super + end + def eager_load! + railties.all(&:eager_load!) + config.eager_load_paths.each do |load_path| matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ Dir.glob("#{load_path}/**/*.rb").sort.each do |file| @@ -567,6 +583,10 @@ module Rails protected + def initialize_generators + require "rails/generators" + end + def routes? defined?(@routes) end diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb new file mode 100644 index 0000000000..3b0920e213 --- /dev/null +++ b/railties/lib/rails/engine/commands.rb @@ -0,0 +1,38 @@ +require 'active_support/core_ext/object/inclusion' + +ARGV << '--help' if ARGV.empty? + +aliases = { + "g" => "generate" +} + +command = ARGV.shift +command = aliases[command] || command + +require ENGINE_PATH +engine = ::Rails::Engine.find(ENGINE_ROOT) + +case command +when 'generate', 'destroy' + require 'rails/generators' + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + require "rails/commands/#{command}" + +when '--version', '-v' + ARGV.unshift '--version' + require 'rails/commands/application' + +else + puts "Error: Command not recognized" unless command.in?(['-h', '--help']) + puts <<-EOT +Usage: rails COMMAND [ARGS] + +The common rails commands available for engines are: + generate Generate new code (short-cut alias: "g") + destroy Undo code generated with "generate" + +All commands can be run with -h for more information. + EOT + exit(1) +end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 09e505a75b..ea9f023511 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -20,6 +20,8 @@ module Rails autoload :ResourceHelpers, 'rails/generators/resource_helpers' autoload :TestCase, 'rails/generators/test_case' + mattr_accessor :namespace + DEFAULT_ALIASES = { :rails => { :actions => '-a', @@ -297,9 +299,6 @@ module Rails return rescue LoadError => e raise unless e.message =~ /#{Regexp.escape(path)}$/ - rescue NameError => e - raise unless e.message =~ /Rails::Generator([\s(::)]|$)/ - warn "[WARNING] Could not load generator #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}.\n#{e.backtrace.join("\n")}" rescue Exception => e warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index d31a3262e3..433a56dc57 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -114,11 +114,11 @@ module Rails # git :add => "this.file that.rb" # git :add => "onefile.rb", :rm => "badfile.cxx" # - def git(command={}) - if command.is_a?(Symbol) - run "git #{command}" + def git(commands={}) + if commands.is_a?(Symbol) + run "git #{commands}" else - command.each do |command, options| + commands.each do |command, options| run "git #{command} #{options}" end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 7972c72c1e..378eceb4c3 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -10,7 +10,7 @@ module Rails module Generators class AppBase < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql ) + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) DATABASES.concat(JDBC_DATABASES) attr_accessor :rails_template @@ -64,8 +64,8 @@ module Rails def initialize(*args) @original_wd = Dir.pwd - super + convert_database_option_for_jruby end protected @@ -157,14 +157,26 @@ module Rails when "postgresql" then "pg" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" - when "jdbcmysql" then "activerecord-jdbcmysql-adapter" - when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" - when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" + when "jdbcmysql" then "activerecord-jdbcmysql-adapter" + when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" + when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" + when "jdbc" then "activerecord-jdbc-adapter" else options[:database] end end - def gem_for_ruby_debugger + def convert_database_option_for_jruby + if defined?(JRUBY_VERSION) + case options[:database] + when "oracle" then options[:database].replace "jdbc" + when "postgresql" then options[:database].replace "jdbcpostgresql" + when "mysql" then options[:database].replace "jdbcmysql" + when "sqlite3" then options[:database].replace "jdbcsqlite3" + end + end + end + + def ruby_debugger_gemfile_entry if RUBY_VERSION < "1.9" "gem 'ruby-debug'" else @@ -172,7 +184,7 @@ module Rails end end - def gem_for_turn + def turn_gemfile_entry unless RUBY_VERSION < "1.9.2" || options[:skip_test_unit] <<-GEMFILE.strip_heredoc group :test do @@ -183,18 +195,24 @@ module Rails end end - def gem_for_javascript + def javascript_gemfile_entry "gem '#{options[:javascript]}-rails'" unless options[:skip_javascript] end def bundle_command(command) - require 'bundler' - require 'bundler/cli' - say_status :run, "bundle #{command}" - Bundler::CLI.new.send(command) - rescue - say_status :failure, "bundler raised an exception, are you offline?", :red + + # We are going to shell out rather than invoking Bundler::CLI.new(command) + # because `rails new` loads the Thor gem and on the other hand bundler uses + # its own vendored Thor, which could be a different version. Running both + # things in the same process is a recipe for a night with paracetamol. + # + # We use backticks and #print here instead of vanilla #system because it + # is easier to silence stdout in the existing test suite this way. The + # end-user gets the bundler commands called anyway, so no big deal. + # + # Thanks to James Tucker for the Gem tricks involved in this call. + print `"#{Gem.ruby}" -rubygems "#{Gem.bin_path('bundler', 'bundle')}" #{command}` end def run_bundle diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 1f6a7a2f59..b9dc31457a 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -259,9 +259,9 @@ module Rails extra << false unless Object.method(:const_defined?).arity == 1 # Extract the last Module in the nesting - last = nesting.inject(Object) do |last, nest| - break unless last.const_defined?(nest, *extra) - last.const_get(nest) + last = nesting.inject(Object) do |last_module, nest| + break unless last_module.const_defined?(nest, *extra) + last_module.const_get(nest) end if last && last.const_defined?(last_name.camelize, *extra) diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 9450894b05..f9f89c9f1d 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -7,7 +7,7 @@ module Rails attr_accessor :name, :type def initialize(name, type) - raise Thor::Error, "Missing type for attribute '#{name}'.\nExample: '#{name}:string' where string is the type." if type.blank? + type = :string if type.blank? @name, @type = name, type.to_sym end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 7e7f8d2d08..c6c0392f43 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -63,9 +63,7 @@ module Rails end def namespace - @namespace ||= if defined?(Rails) && Rails.application - Rails.application.class.parents.detect { |n| n.respond_to?(:_railtie) } - end + Rails::Generators.namespace end def namespaced? diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ebe38bf8e6..cd674ddbdd 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -5,13 +5,17 @@ source 'http://rubygems.org' <%= database_gemfile_entry -%> <%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%> -# Asset template engines <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> -gem 'sass-rails' -gem 'coffee-script' -gem 'uglifier' -<%= gem_for_javascript %> +# Gems used only for assets and not required +# in production environments by default. +group :assets do + gem 'sass-rails' + gem 'coffee-script' + gem 'uglifier' +end + +<%= javascript_gemfile_entry %> # Use unicorn as the web server # gem 'unicorn' @@ -20,6 +24,6 @@ gem 'uglifier' # gem 'capistrano' # To use debugger -# <%= gem_for_ruby_debugger %> +# <%= ruby_debugger_gemfile_entry %> -<%= gem_for_turn -%> +<%= turn_gemfile_entry -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 37c2fb1263..7f623a7af9 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -11,9 +11,10 @@ require "active_resource/railtie" <%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" <% end -%> -# If you have a Gemfile, require the gems listed there, including any gems -# you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) if defined?(Bundler) +# If you have a Gemfile, require the default gems, the ones in the +# current environment and also include :assets gems if in development +# or test environments. +Bundler.require *Rails.groups(:assets => %w(development test)) if defined?(Bundler) module <%= app_const_base %> class Application < Rails::Application diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml new file mode 100644 index 0000000000..1d2bf08b91 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -0,0 +1,62 @@ +# If you are using mssql, derby, hsqldb, or h2 with one of the +# ActiveRecord JDBC adapters, install the appropriate driver, e.g.,: +# gem install activerecord-jdbcmssql-adapter +# +# Configure using Gemfile: +# gem 'activerecord-jdbcmssql-adapter' +# +#development: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_development +# +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +# +#test: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_test +# +#production: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_production + +# If you are using oracle, db2, sybase, informix or prefer to use the plain +# JDBC adapter, configure your database setting as the example below (requires +# you to download and manually install the database vendor's JDBC driver .jar +# file). See your driver documentation for the apropriate driver class and +# connection string: + +development: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. + +test: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_test + +production: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_production diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index 6bf83e86a5..5a594ac1f3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -9,7 +9,7 @@ # And be sure to use new-style password hashing: # http://dev.mysql.com/doc/refman/5.0/en/old-client.html development: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_development username: root password: @@ -19,14 +19,14 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_test username: root password: host: localhost production: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_production username: root password: diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 0c7f45322b..996ff4baa3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -1,19 +1,10 @@ # PostgreSQL. Versions 7.4 and 8.x are supported. # -# Install the pg driver: -# gem install pg -# On Mac OS X with macports: -# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. -# # Configure Using Gemfile # gem 'activerecord-jdbcpostgresql-adapter' development: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_development username: <%= app_name %> @@ -38,14 +29,14 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_test username: <%= app_name %> password: production: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_production username: <%= app_name %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml index 6d241d57ae..175f3eb3db 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -5,16 +5,16 @@ # gem 'activerecord-jdbcsqlite3-adapter' # development: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/development.sqlite3 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/test.sqlite3 production: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 60e26755fe..06ed890e05 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -14,9 +14,6 @@ # Compress JavaScripts and CSS config.assets.compress = true - # Specify the default JavaScript compressor - config.assets.js_compressor = :uglifier - # Specifies the header that your server uses for sending files # (comment out if your front-end server doesn't support this) config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index e56195da80..fa1548db8b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -1,7 +1,7 @@ # Be sure to restart your server when you modify this file. # -# This file contains the settings for ActionController::ParametersWrapper -# which will be enabled by default in the upcoming version of Ruby on Rails. +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActionController::Base.wrap_parameters <%= key_value :format, "[:json]" %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index d50f536164..ea81748464 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -54,5 +54,5 @@ # This is a legacy wild controller route that's not recommended for RESTful applications. # Note: This route will make all actions in every controller accessible via GET requests. - # match ':controller(/:action(/:id(.:format)))' + # match ':controller(/:action(/:id))(.:format)' end diff --git a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb index 5d1be041a5..3fea27b916 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb @@ -5,7 +5,7 @@ class BrowsingTest < ActionDispatch::PerformanceTest # Refer to the documentation for all available options # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] # :output => 'tmp/performance', :formats => [:flat] } - + def test_homepage get '/' end diff --git a/railties/lib/rails/generators/rails/assets/USAGE b/railties/lib/rails/generators/rails/assets/USAGE index c5375cdc06..d2e5ed4482 100644 --- a/railties/lib/rails/generators/rails/assets/USAGE +++ b/railties/lib/rails/generators/rails/assets/USAGE @@ -7,7 +7,7 @@ Description: This generates a JavaScript stub in app/assets/javascripts and a stylesheet stub in app/assets/stylesheets. - + If CoffeeScript is available, JavaScripts will be generated with the .coffee extension. If Sass 3 is available, stylesheets will be generated with the .scss extension. diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 11867a4cd7..7c0a2b9cf4 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -11,15 +11,15 @@ module Rails def app if mountable? directory "app" - template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt", + template "app/views/layouts/application.html.erb.tt", "app/views/layouts/#{name}/application.html.erb" - empty_directory_with_gitkeep "app/assets/images" + empty_directory_with_gitkeep "app/assets/images/#{name}" elsif full? empty_directory_with_gitkeep "app/models" empty_directory_with_gitkeep "app/controllers" empty_directory_with_gitkeep "app/views" empty_directory_with_gitkeep "app/helpers" - empty_directory_with_gitkeep "app/assets/images" + empty_directory_with_gitkeep "app/assets/images/#{name}" end end @@ -108,9 +108,9 @@ task :default => :test def stylesheets if mountable? copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css", - "app/assets/stylesheets/application.css" + "app/assets/stylesheets/#{name}/application.css" elsif full? - empty_directory_with_gitkeep "app/assets/stylesheets" + empty_directory_with_gitkeep "app/assets/stylesheets/#{name}" end end @@ -119,13 +119,15 @@ task :default => :test if mountable? template "#{app_templates_dir}/app/assets/javascripts/application.js.tt", - "app/assets/javascripts/application.js" + "app/assets/javascripts/#{name}/application.js" elsif full? - empty_directory_with_gitkeep "app/assets/javascripts" + empty_directory_with_gitkeep "app/assets/javascripts/#{name}" end end def script(force = false) + return unless full? + directory "script", :force => force do |content| "#{shebang}\n" + content end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index c28e568711..7e6eb18341 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -7,9 +7,8 @@ source "http://rubygems.org" <% end -%> <% if mountable? -%> -<%= gem_for_javascript -%> +<%= javascript_gemfile_entry -%> <% end -%> -if RUBY_VERSION < '1.9' - gem "ruby-debug", ">= 0.10.3" -end +# To use debugger +# <%= ruby_debugger_gemfile_entry %>
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt new file mode 100644 index 0000000000..01550dec2f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/application.html.erb.tt @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<head> + <title><%= camelized %></title> + <%%= stylesheet_link_tag "<%= name %>/application" %> + <%%= javascript_include_tag "<%= name %>/application" %> + <%%= csrf_meta_tags %> +</head> +<body> + +<%%= yield %> + +</body> +</html> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt index 65d82abf6d..aa87d1b50c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt +++ b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt @@ -1,4 +1,7 @@ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. -ENGINE_PATH = File.expand_path('../..', __FILE__) -load File.expand_path('../../<%= dummy_path %>/script/rails', __FILE__) +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb index e7a06e4a73..dea7e22196 100644 --- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb +++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb @@ -1,8 +1,6 @@ require 'test_helper' class <%= class_name %>Test < ActionDispatch::IntegrationTest - fixtures :all - # test "the truth" do # assert true # end diff --git a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb index 14a878328b..d296b26b16 100644 --- a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb +++ b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb @@ -5,7 +5,7 @@ class <%= class_name %>Test < ActionDispatch::PerformanceTest # Refer to the documentation for all available options # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] # :output => 'tmp/performance', :formats => [:flat] } - + def test_homepage get '/' end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 65c567d72f..8c88b25617 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -1,6 +1,7 @@ require 'rails/initializable' require 'rails/configuration' require 'active_support/inflector' +require 'active_support/core_ext/module/introspection' module Rails # Railtie is the core of the Rails framework and provides several hooks to extend @@ -83,7 +84,7 @@ module Rails # == Loading rake tasks and generators # # If your railtie has rake tasks, you can tell Rails to load them through the method - # rake tasks: + # rake_tasks: # # class MyRailtie < Rails::Railtie # rake_tasks do @@ -173,11 +174,11 @@ module Rails def eager_load! end - def load_console(app) + def load_console(app=self) self.class.console.each { |block| block.call(app) } end - def load_tasks(app) + def load_tasks(app=self) extend Rake::DSL if defined? Rake::DSL self.class.rake_tasks.each { |block| block.call(app) } @@ -189,8 +190,12 @@ module Rails end end - def load_generators(app) + def load_generators(app=self) self.class.generators.each { |block| block.call(app) } end + + def railtie_namespace + @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:_railtie) } + end end end diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb index 920ab67ff1..53796c74cf 100644 --- a/railties/lib/rails/railtie/configurable.rb +++ b/railties/lib/rails/railtie/configurable.rb @@ -1,3 +1,5 @@ +require 'active_support/concern' + module Rails class Railtie module Configurable diff --git a/railties/lib/rails/tasks/assets.rake b/railties/lib/rails/tasks/assets.rake index 5f87edc9e2..0236350576 100644 --- a/railties/lib/rails/tasks/assets.rake +++ b/railties/lib/rails/tasks/assets.rake @@ -1,18 +1,26 @@ namespace :assets do desc "Compile all the assets named in config.assets.precompile" - task :precompile => :environment do - # Give assets access to asset_path - Sprockets::Helpers::RailsHelper + task :precompile do + if ENV["RAILS_GROUPS"].to_s.empty? + ENV["RAILS_GROUPS"] = "assets" + Kernel.exec $0, *ARGV + else + Rake::Task["environment"].invoke + Sprockets::Helpers::RailsHelper - assets = Rails.application.config.assets.precompile - Rails.application.assets.precompile(*assets) + assets = Rails.application.config.assets.precompile + Rails.application.assets.precompile(*assets) + end end desc "Remove compiled assets" task :clean => :environment do assets = Rails.application.config.assets public_asset_path = Rails.public_path + assets.prefix - file_list = FileList.new("#{public_asset_path}/*.js", "#{public_asset_path}/*.css") - file_list.each{ |file| rm file } + file_list = FileList.new("#{public_asset_path}/**/*") + file_list.each do |file| + rm_rf file + rm_rf "#{file}.gz" + end end end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index f1c92bd5d4..9c535f4f90 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |s| s.add_dependency('rake', '>= 0.8.7') s.add_dependency('thor', '~> 0.14.6') s.add_dependency('rack-ssl', '~> 1.3.2') + s.add_dependency('rdoc', '~> 3.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 8b38081667..1c3f8a701a 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -10,7 +10,6 @@ require 'active_support/core_ext/logger' require 'action_controller' require 'rails/all' -# TODO: Remove these hacks module TestApp class Application < Rails::Application config.root = File.dirname(__FILE__) diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 98a702f134..b76dae8e18 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -1,8 +1,9 @@ require 'isolation/abstract_unit' +require 'active_support/core_ext/kernel/reporting' require 'rack/test' module ApplicationTests - class RoutingTest < Test::Unit::TestCase + class AssetsTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation include Rack::Test::Methods @@ -11,6 +12,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + def app @app ||= Rails.application end @@ -30,6 +35,31 @@ module ApplicationTests assert_match "alert()", last_response.body end + test "assets are compiled properly" do + app_file "app/assets/javascripts/application.js", "alert();" + + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:precompile` } + end + + file = Dir["#{app_path}/public/assets/application-*.js"][0] + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();\n", File.read(file) + end + + test "assets are cleaned up properly" do + app_file "public/assets/application.js", "alert();" + app_file "public/assets/application.css", "a { color: green; }" + app_file "public/assets/subdir/broken.png", "not really an image file" + + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:clean` } + end + + files = Dir["#{app_path}/public/assets/**/*"] + assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}" + end + test "does not stream session cookies back" do app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 0e27c9606d..2547863d12 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -35,9 +35,25 @@ module ApplicationTests end def teardown + teardown_app FileUtils.rm_rf(new_app) if File.directory?(new_app) end + test "Rails.groups returns available groups" do + require "rails" + + Rails.env = "development" + assert_equal [:default, "development"], Rails.groups + assert_equal [:default, "development", :assets], Rails.groups(:assets => [:development]) + assert_equal [:default, "development", :assets], Rails.groups(:assets => %w(development)) + + Rails.env = "test" + assert_equal [:default, "test"], Rails.groups(:assets => [:development]) + + ENV["RAILS_GROUPS"] = "javascripts,stylesheets" + assert_equal [:default, "test", "javascripts", "stylesheets"], Rails.groups + end + test "Rails.application is nil until app is initialized" do require 'rails' assert_nil Rails.application diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index db8f1f2ac6..1528d5dd87 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -1,13 +1,17 @@ require 'isolation/abstract_unit' class ConsoleTest < Test::Unit::TestCase - include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::Isolation def setup build_app boot_rails end + def teardown + teardown_app + end + def load_environment(sandbox = false) require "#{rails_root}/config/environment" Rails.application.sandbox = sandbox diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 1ca9515335..4365d00b1f 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -9,6 +9,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + def app_const @app_const ||= Class.new(Rails::Application) end diff --git a/railties/test/application/initializers/boot_test.rb b/railties/test/application/initializers/boot_test.rb index 5ec562f12f..b1e01dc13f 100644 --- a/railties/test/application/initializers/boot_test.rb +++ b/railties/test/application/initializers/boot_test.rb @@ -9,8 +9,12 @@ module ApplicationTests # boot_rails end + def teardown + # teardown_app + end + test "booting rails sets the load paths correctly" do # This test is pending reworking the boot process end end -end
\ No newline at end of file +end diff --git a/railties/test/application/initializers/check_ruby_version_test.rb b/railties/test/application/initializers/check_ruby_version_test.rb index 5b6196307d..df7e9696a9 100644 --- a/railties/test/application/initializers/check_ruby_version_test.rb +++ b/railties/test/application/initializers/check_ruby_version_test.rb @@ -9,6 +9,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + test "rails initializes with ruby 1.8.7 or later, except for 1.9.1" do if RUBY_VERSION < '1.8.7' assert_rails_does_not_boot diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 196d121c14..446c85d65a 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -10,6 +10,10 @@ module ApplicationTests FileUtils.rm_rf "#{app_path}/config/environments" end + def teardown + teardown_app + end + # AC & AM test "set load paths set only if action controller or action mailer are in use" do assert_nothing_raised NameError do diff --git a/railties/test/application/initializers/hooks_test.rb b/railties/test/application/initializers/hooks_test.rb index 198abbe861..8c7726339c 100644 --- a/railties/test/application/initializers/hooks_test.rb +++ b/railties/test/application/initializers/hooks_test.rb @@ -10,6 +10,10 @@ module ApplicationTests FileUtils.rm_rf "#{app_path}/config/environments" end + def teardown + teardown_app + end + test "load initializers" do app_file "config/initializers/foo.rb", "$foo = true" require "#{app_path}/config/environment" diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb index 390f65ab5c..8c2c079fb8 100644 --- a/railties/test/application/initializers/i18n_test.rb +++ b/railties/test/application/initializers/i18n_test.rb @@ -11,6 +11,10 @@ module ApplicationTests require "rails/all" end + def teardown + teardown_app + end + def load_app require "#{app_path}/config/environment" end @@ -48,7 +52,7 @@ module ApplicationTests end test "locale files should be added to the load path" do - app_file "config/another_locale.yml", "" + app_file "config/another_locale.yml", "en:\nfoo: ~" add_to_config <<-RUBY config.i18n.load_path << config.root.join("config/another_locale.yml").to_s @@ -127,7 +131,7 @@ en: # Fallbacks test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do - I18n.backend = Class.new { include I18n::Backend::Base }.new + I18n.backend = Class.new(I18n::Backend::Simple).new load_app assert_no_fallbacks end @@ -141,7 +145,7 @@ en: test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do I18n::Railtie.config.i18n.fallbacks = true - I18n::Railtie.config.i18n.backend = Class.new { include I18n::Backend::Base }.new + I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new load_app assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) assert_fallbacks :de => [:de, :en] diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb index 714d62311d..644b8208a9 100644 --- a/railties/test/application/initializers/load_path_test.rb +++ b/railties/test/application/initializers/load_path_test.rb @@ -10,6 +10,10 @@ module ApplicationTests FileUtils.rm_rf "#{app_path}/config/environments" end + def teardown + teardown_app + end + test "initializing an application adds the application paths to the load path" do add_to_config <<-RUBY config.root = "#{app_path}" diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb index c87433db0d..b72c14eaf0 100644 --- a/railties/test/application/initializers/notifications_test.rb +++ b/railties/test/application/initializers/notifications_test.rb @@ -9,6 +9,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + def instrument(*args, &block) ActiveSupport::Notifications.instrument(*args, &block) end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index c340465e87..47c6fd5c6e 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -8,6 +8,10 @@ class LoadingTest < Test::Unit::TestCase boot_rails end + def teardown + teardown_app + end + def app @app ||= Rails.application end diff --git a/railties/test/application/middleware/best_practices_test.rb b/railties/test/application/middleware/best_practices_test.rb index 5b722e7510..1c88b9bf06 100644 --- a/railties/test/application/middleware/best_practices_test.rb +++ b/railties/test/application/middleware/best_practices_test.rb @@ -12,6 +12,10 @@ module ApplicationTests simple_controller end + def teardown + teardown_app + end + test "simple controller in production mode returns best standards" do get '/foo' assert_equal "IE=Edge,chrome=1", last_response.headers["X-UA-Compatible"] diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb index a8033d2b23..e656ada3c0 100644 --- a/railties/test/application/middleware/cache_test.rb +++ b/railties/test/application/middleware/cache_test.rb @@ -11,6 +11,10 @@ module ApplicationTests extend Rack::Test::Methods end + def teardown + teardown_app + end + def simple_controller controller :expires, <<-RUBY class ExpiresController < ApplicationController diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index f28302d70a..da291f061c 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -10,6 +10,10 @@ module ApplicationTests FileUtils.rm_rf "#{app_path}/config/environments" end + def teardown + teardown_app + end + def app @app ||= Rails.application end diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb index 0128261cd4..c7a1c573f9 100644 --- a/railties/test/application/middleware/sendfile_test.rb +++ b/railties/test/application/middleware/sendfile_test.rb @@ -10,6 +10,10 @@ module ApplicationTests FileUtils.rm_rf "#{app_path}/config/environments" end + def teardown + teardown_app + end + def app @app ||= Rails.application end diff --git a/railties/test/application/middleware/show_exceptions_test.rb b/railties/test/application/middleware/show_exceptions_test.rb index 5487e41e0a..e3f27f63c3 100644 --- a/railties/test/application/middleware/show_exceptions_test.rb +++ b/railties/test/application/middleware/show_exceptions_test.rb @@ -10,6 +10,10 @@ module ApplicationTests FileUtils.rm_rf "#{app_path}/config/environments" end + def teardown + teardown_app + end + def app @app ||= Rails.application end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 715798ca08..6a0a272073 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -11,6 +11,10 @@ module ApplicationTests FileUtils.rm_rf "#{app_path}/config/environments" end + def teardown + teardown_app + end + def app @app ||= Rails.application end @@ -200,4 +204,4 @@ module ApplicationTests AppTemplate::Application.middleware.map(&:klass).map(&:name) end end -end
\ No newline at end of file +end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index 03e0247556..0d22d8c19a 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -20,6 +20,10 @@ module ApplicationTests @paths = Rails.application.config.paths end + def teardown + teardown_app + end + def root(*path) app_path(*path).to_s end diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb index 715af5c642..8b2b2f1802 100644 --- a/railties/test/application/rack/logger_test.rb +++ b/railties/test/application/rack/logger_test.rb @@ -14,6 +14,10 @@ module ApplicationTests super end + def teardown + teardown_app + end + def logs @logs ||= @logger.logged(:info) end diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb index b0a9925890..ff9cdcadc7 100644 --- a/railties/test/application/rackup_test.rb +++ b/railties/test/application/rackup_test.rb @@ -15,6 +15,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + test "rails app is present" do assert File.exist?(app_path("config")) end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index ab36ace5e5..44ac6615de 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -10,6 +10,10 @@ module ApplicationTests FileUtils.rm_rf("#{app_path}/config/environments") end + def teardown + teardown_app + end + def test_gems_tasks_are_loaded_first_than_application_ones app_file "lib/tasks/app.rake", <<-RUBY $task_loaded = Rake::Task.task_defined?("db:create:all") @@ -113,7 +117,7 @@ module ApplicationTests end require "#{rails_root}/config/environment" - + # loading a specific fixture errormsg = Dir.chdir(app_path) { `rake db:fixtures:load FIXTURES=products` } assert $?.success?, errormsg diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index e3a7f8a63c..3adf0ccd3e 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -11,6 +11,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + test "rails/info/properties in development" do app("development") get "/rails/info/properties" diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 292d1e247f..4468fa295e 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -18,6 +18,14 @@ module ApplicationTests MODEL end + def teardown + teardown_app + end + + def test_should_include_runner_in_shebang_line_in_help_without_option + assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner` } + end + def test_should_include_runner_in_shebang_line_in_help assert_match "/rails runner", Dir.chdir(app_path) { `bundle exec rails runner --help` } end diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index f96319f472..27a7959e84 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -9,6 +9,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + test "truth" do app_file 'test/unit/foo_test.rb', <<-RUBY require 'test_helper' diff --git a/railties/test/fixtures/lib/generators/wrong_generator.rb b/railties/test/fixtures/lib/generators/wrong_generator.rb deleted file mode 100644 index 6aa7cb052e..0000000000 --- a/railties/test/fixtures/lib/generators/wrong_generator.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Old generator version -class WrongGenerator < Rails::Generator::NamedBase -end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 6926027bed..86217f4ab9 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -135,7 +135,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_jdbcmysql_database run_generator([destination_root, "-d", "jdbcmysql"]) - assert_file "config/database.yml", /jdbcmysql/ + assert_file "config/database.yml", /mysql/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ # TODO: When the JRuby guys merge jruby-openssl in # jruby this will be removed @@ -144,16 +144,31 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_jdbcsqlite3_database run_generator([destination_root, "-d", "jdbcsqlite3"]) - assert_file "config/database.yml", /jdbcsqlite3/ + assert_file "config/database.yml", /sqlite3/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ end def test_config_jdbcpostgresql_database run_generator([destination_root, "-d", "jdbcpostgresql"]) - assert_file "config/database.yml", /jdbcpostgresql/ + assert_file "config/database.yml", /postgresql/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/ end + def test_config_jdbc_database + run_generator([destination_root, "-d", "jdbc"]) + assert_file "config/database.yml", /jdbc/ + assert_file "config/database.yml", /mssql/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbc-adapter["']$/ + end + + def test_config_jdbc_database_when_no_option_given + if defined?(JRUBY_VERSION) + run_generator([destination_root]) + assert_file "config/database.yml", /sqlite3/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ + end + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index 0d2e624f44..c9f8ab0a7b 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -113,15 +113,8 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end end - def test_nil_type_raises_exception - assert_raise Thor::Error do - create_generated_attribute(nil, 'title') - end - end - - def test_missing_type_raises_exception - assert_raise Thor::Error do - create_generated_attribute('', 'title') - end + def test_blank_type_defaults_to_string_raises_exception + assert_equal :string, create_generated_attribute(nil, 'title').type + assert_equal :string, create_generated_attribute("", 'title').type end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 8c5ba9926b..1b0cb425c6 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -12,9 +12,15 @@ class ModelGeneratorTest < Rails::Generators::TestCase end def test_model_with_missing_attribute_type - content = capture(:stderr) { run_generator ["post", "title:string", "body"] } - assert_match(/Missing type for attribute 'body'/, content) - assert_match(/Example: 'body:string' where string is the type/, content) + run_generator ["post", "title", "body:text", "author"] + + assert_migration "db/migrate/create_posts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :title/, up) + assert_match(/t\.text :body/, up) + assert_match(/t\.string :author/, up) + end + end end def test_invokes_default_orm diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 17cbac0912..dd1e4bdac1 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -7,15 +7,7 @@ require 'rails/generators/rails/scaffold/scaffold_generator' class NamespacedGeneratorTestCase < Rails::Generators::TestCase def setup - TestApp::Application.isolate_namespace(TestApp) - end - - def teardown - if TestApp.respond_to?(:_railtie) - TestApp.singleton_class.send(:undef_method, :_railtie) - TestApp.singleton_class.send(:undef_method, :table_name_prefix) - TestApp::Application.isolated = false - end + Rails::Generators.namespace = TestApp end end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index b272638026..be72391e58 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -12,7 +12,6 @@ DEFAULT_PLUGIN_FILES = %w( lib lib/bukkits.rb lib/tasks/bukkits_tasks.rake - script/rails test/bukkits_test.rb test/test_helper.rb test/dummy @@ -67,7 +66,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_file "test" end - def test_database_entry_is_assed_by_default_in_full_mode + def test_database_entry_is_generated_for_sqlite3_by_default_in_full_mode run_generator([destination_root, "--full"]) assert_file "test/dummy/config/database.yml", /sqlite/ assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ @@ -102,19 +101,19 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_skipping_javascripts_without_mountable_option run_generator - assert_no_file "app/assets/javascripts/application.js" + assert_no_file "app/assets/javascripts/bukkits/application.js" assert_no_file "vendor/assets/javascripts/jquery.js" assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_javascripts_generation run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts/application.js" + assert_file "app/assets/javascripts/bukkits/application.js" end def test_jquery_is_the_default_javascript_library run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts/application.js" do |contents| + assert_file "app/assets/javascripts/bukkits/application.js" do |contents| assert_match %r{^//= require jquery}, contents assert_match %r{^//= require jquery_ujs}, contents end @@ -125,7 +124,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_other_javascript_libraries run_generator [destination_root, "--mountable", '-j', 'prototype'] - assert_file "app/assets/javascripts/application.js" do |contents| + assert_file "app/assets/javascripts/bukkits/application.js" do |contents| assert_match %r{^//= require prototype}, contents assert_match %r{^//= require prototype_ujs}, contents end @@ -136,7 +135,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_skip_javascripts run_generator [destination_root, "--skip-javascript", "--mountable"] - assert_no_file "app/assets/javascripts/application.js" + assert_no_file "app/assets/javascripts/bukkits/application.js" assert_no_file "vendor/assets/javascripts/jquery.js" assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end @@ -162,9 +161,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_creating_engine_in_full_mode run_generator [destination_root, "--full"] - assert_file "app/assets/javascripts" - assert_file "app/assets/stylesheets" - assert_file "app/assets/images" + assert_file "app/assets/javascripts/bukkits" + assert_file "app/assets/stylesheets/bukkits" + assert_file "app/assets/images/bukkits" assert_file "app/models" assert_file "app/controllers" assert_file "app/views" @@ -172,6 +171,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < ::Rails::Engine\n end\nend/ assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ + assert_file "script/rails" end def test_being_quiet_while_creating_dummy_application @@ -180,15 +180,19 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_create_mountable_application_with_mountable_option run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts" - assert_file "app/assets/stylesheets" - assert_file "app/assets/images" + assert_file "app/assets/javascripts/bukkits" + assert_file "app/assets/stylesheets/bukkits" + assert_file "app/assets/images/bukkits" assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActionController::Base/ assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/ - assert_file "app/views/layouts/bukkits/application.html.erb", /<title>Bukkits<\/title>/ + assert_file "app/views/layouts/bukkits/application.html.erb" do |contents| + assert_match "<title>Bukkits</title>", contents + assert_match /stylesheet_link_tag\s+['"]bukkits\/application['"]/, contents + assert_match /javascript_include_tag\s+['"]bukkits\/application['"]/, contents + end end def test_creating_gemspec @@ -199,8 +203,16 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "bukkits.gemspec", /s.version = "0.0.1"/ end + def test_usage_of_engine_commands + run_generator [destination_root, "--full"] + assert_file "script/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/ + assert_file "script/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ + assert_file "script/rails", /require 'rails\/all'/ + assert_file "script/rails", /require 'rails\/engine\/commands'/ + end + def test_shebang - run_generator + run_generator [destination_root, "--full"] assert_file "script/rails", /#!\/usr\/bin\/env ruby/ end @@ -254,7 +266,6 @@ class CustomPluginGeneratorTest < Rails::Generators::TestCase assert_file 'spec/dummy' assert_file 'Rakefile', /task :default => :spec/ assert_file 'Rakefile', /# spec tasks in rakefile/ - assert_file 'script/rails', %r{spec/dummy} end protected diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 2135ffac81..c3c6015291 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -272,8 +272,14 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end def test_scaffold_generator_outputs_error_message_on_missing_attribute_type - content = capture(:stderr) { run_generator ["post", "title:string", "body"]} - assert_match(/Missing type for attribute 'body'/, content) - assert_match(/Example: 'body:string' where string is the type/, content) + run_generator ["post", "title", "body:text", "author"] + + assert_migration "db/migrate/create_posts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :title/, up) + assert_match(/t\.text :body/, up) + assert_match(/t\.string :author/, up) + end + end end end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index be9aef8a41..d3074afd91 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -67,12 +67,12 @@ module SharedGeneratorTests end def test_shebang_is_added_to_rails_file - run_generator [destination_root, "--ruby", "foo/bar/baz"] + run_generator [destination_root, "--ruby", "foo/bar/baz", "--full"] assert_file "script/rails", /#!foo\/bar\/baz/ end def test_shebang_when_is_the_same_as_default_use_env - run_generator [destination_root, "--ruby", Thor::Util.ruby_command] + run_generator [destination_root, "--ruby", Thor::Util.ruby_command, "--full"] assert_file "script/rails", /#!\/usr\/bin\/env/ end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 301ae80bcf..56329f3183 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -88,12 +88,6 @@ class GeneratorsTest < Rails::Generators::TestCase assert Rails::Generators.find_by_namespace(:model) end - def test_find_by_namespace_show_warning_if_generator_cant_be_loaded - output = capture(:stderr) { Rails::Generators.find_by_namespace(:wrong) } - assert_match(/\[WARNING\] Could not load generator/, output) - assert_match(/Rails 2\.x generator/, output) - end - def test_invoke_with_nested_namespaces model_generator = mock('ModelGenerator') do expects(:start).with(["Account"], {}) diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 69208ce4c3..685d1b154b 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -91,6 +91,9 @@ module TestHelpers module Generation # Build an application by invoking the generator and going through the whole stack. def build_app(options = {}) + @prev_rails_env = ENV['RAILS_ENV'] + ENV['RAILS_ENV'] = 'development' + FileUtils.rm_rf(app_path) FileUtils.cp_r(tmp_path('app_template'), app_path) @@ -115,6 +118,10 @@ module TestHelpers add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"; config.active_support.deprecation = :log' end + def teardown_app + ENV['RAILS_ENV'] = @prev_rails_env if @prev_rails_env + end + # Make a very basic app, without creating the whole directory structure. # This is faster and simpler than the method above. def make_basic_app @@ -231,6 +238,10 @@ module TestHelpers end end + def remove_file(path) + FileUtils.rm_rf "#{app_path}/#{path}" + end + def controller(name, contents) app_file("app/controllers/#{name}_controller.rb", contents) end @@ -280,4 +291,4 @@ Module.new do end f.puts "require 'rails/all'" end -end +end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index b5b21f9ebe..0ff1e0f180 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -15,7 +15,7 @@ module RailtiesTest @plugin = engine "bukkits" do |plugin| plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine railtie_name "bukkits" end @@ -25,6 +25,10 @@ module RailtiesTest end end + def teardown + teardown_app + end + test "Rails::Engine itself does not respond to config" do boot_rails assert !Rails::Engine.respond_to?(:config) @@ -32,7 +36,7 @@ module RailtiesTest test "initializers are executed after application configuration initializers" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine initializer "dummy_initializer" do end @@ -73,7 +77,7 @@ module RailtiesTest add_to_config("config.action_dispatch.show_exceptions = false") @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, ['Hello World']] } config.middleware.use ::RailtiesTest::EngineTest::Upcaser @@ -123,7 +127,7 @@ module RailtiesTest test "it provides routes as default endpoint" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -149,7 +153,7 @@ module RailtiesTest test "engine can load its own plugins" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -166,7 +170,7 @@ module RailtiesTest test "engine does not load plugins that already exists in application" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -189,7 +193,7 @@ module RailtiesTest test "it loads its environment file" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine end end @@ -208,7 +212,7 @@ module RailtiesTest test "it passes router in env" do @plugin.write "lib/bukkits.rb", <<-RUBY - class Bukkits + module Bukkits class Engine < ::Rails::Engine endpoint lambda { |env| [200, {'Content-Type' => 'text/html'}, 'hello'] } end diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb new file mode 100644 index 0000000000..fe59dcd52b --- /dev/null +++ b/railties/test/railties/generators_test.rb @@ -0,0 +1,115 @@ +RAILS_ISOLATED_ENGINE = true +require "isolation/abstract_unit" + +require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/rails/generators/test_case" + +module RailtiesTests + class GeneratorTest < Rails::Generators::TestCase + include ActiveSupport::Testing::Isolation + + TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp])) + self.destination_root = File.join(TMP_PATH, "foo_bar") + + def tmp_path(*args) + File.join(TMP_PATH, *args) + end + + def engine_path + tmp_path('foo_bar') + end + + def bundled_rails(cmd) + `bundle exec rails #{cmd}` + end + + def rails(cmd) + environment = File.expand_path('../../../../load_paths', __FILE__) + if File.exist?("#{environment}.rb") + require_environment = "-r #{environment}" + end + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/bin/rails #{cmd}` + end + + def build_engine(is_mountable=false) + FileUtils.mkdir_p(engine_path) + FileUtils.rm_r(engine_path) + + mountable = is_mountable ? "--mountable" : "" + + rails("plugin new #{engine_path} --full #{mountable}") + + Dir.chdir(engine_path) do + File.open("Gemfile", "w") do |f| + f.write <<-GEMFILE.gsub(/^ {12}/, '') + source "http://rubygems.org" + + gem 'rails', :path => '#{RAILS_FRAMEWORK_ROOT}' + gem 'sqlite3' + + if RUBY_VERSION < '1.9' + gem "ruby-debug", ">= 0.10.3" + end + GEMFILE + end + end + end + + def build_mountable_engine + build_engine(true) + end + + def test_controllers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/foo_bar/topics_controller.rb", /module FooBar\n class TopicsController/ + assert_no_file "app/controllers/topics_controller.rb" + end + end + + def test_models_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/foo_bar/topic.rb", /module FooBar\n class Topic/ + assert_no_file "app/models/topic.rb" + end + end + + def test_helpers_are_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/foo_bar/topics_helper.rb", /module FooBar\n module TopicsHelper/ + assert_no_file "app/helpers/topics_helper.rb" + end + end + + def test_controllers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g controller topics") + assert_file "app/controllers/topics_controller.rb", /class TopicsController/ + assert_no_file "app/controllers/foo_bar/topics_controller.rb" + end + end + + def test_models_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g model topic") + assert_file "app/models/topic.rb", /class Topic/ + assert_no_file "app/models/foo_bar/topic.rb" + end + end + + def test_helpers_are_not_namespaced_when_engine_is_not_mountable + build_engine + Dir.chdir(engine_path) do + bundled_rails("g helper topics") + assert_file "app/helpers/topics_helper.rb", /module TopicsHelper/ + assert_no_file "app/helpers/foo_bar/topics_helper.rb" + end + end + end +end diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 47a4753e78..b793a7401f 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -106,6 +106,10 @@ module ApplicationTests boot_rails end + def teardown + teardown_app + end + def app @app ||= begin require "#{app_path}/config/environment" @@ -116,7 +120,7 @@ module ApplicationTests def reset_script_name! Rails.application.routes.default_url_options = {} end - + def script_name(script_name) Rails.application.routes.default_url_options = {:script_name => script_name} end @@ -171,4 +175,3 @@ module ApplicationTests end end end - diff --git a/railties/test/railties/plugin_ordering_test.rb b/railties/test/railties/plugin_ordering_test.rb index f6ca493fdf..1cfaf557e9 100644 --- a/railties/test/railties/plugin_ordering_test.rb +++ b/railties/test/railties/plugin_ordering_test.rb @@ -12,6 +12,10 @@ module RailtiesTest plugin "c_plugin", "$arr << :c" end + def teardown + teardown_app + end + def boot_rails super require "#{app_path}/config/environment" @@ -69,4 +73,4 @@ module RailtiesTest assert $bar end end -end
\ No newline at end of file +end diff --git a/railties/test/railties/plugin_test.rb b/railties/test/railties/plugin_test.rb index c15ac05103..f307d53cf7 100644 --- a/railties/test/railties/plugin_test.rb +++ b/railties/test/railties/plugin_test.rb @@ -15,6 +15,10 @@ module RailtiesTest end end + def teardown + teardown_app + end + test "Rails::Plugin itself does not respond to config" do boot_rails assert !Rails::Plugin.respond_to?(:config) diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 18fdf59fe3..55f85c7202 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -11,6 +11,10 @@ module RailtiesTest require "rails/all" end + def teardown + teardown_app + end + def app @app ||= Rails.application end diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index 659551d08a..8cbc76db71 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -53,8 +53,11 @@ module RailtiesTest add_to_config "ActiveRecord::Base.timestamped_migrations = false" + boot_rails + railties = Rails.application.railties.all.map(&:railtie_name) + Dir.chdir(app_path) do - output = `rake bukkits:install:migrations` + output = `bundle exec rake bukkits:install:migrations` assert File.exists?("#{app_path}/db/migrate/2_create_users.rb") assert File.exists?("#{app_path}/db/migrate/3_add_last_name_to_users.rb") @@ -63,15 +66,19 @@ module RailtiesTest assert_match /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length - output = `rake railties:install:migrations` + output = `bundle exec rake railties:install:migrations`.split("\n") assert File.exists?("#{app_path}/db/migrate/4_create_yaffles.rb") - assert_match /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output - assert_match /Copied migration 4_create_yaffles.rb from acts_as_yaffle/, output - assert_no_match /2_create_users/, output + assert_no_match /2_create_users/, output.join("\n") + + yaffle_migration_order = output.index(output.detect{|o| /Copied migration 4_create_yaffles.rb from acts_as_yaffle/ =~ o }) + bukkits_migration_order = output.index(output.detect{|o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o }) + assert_not_nil yaffle_migration_order, "Expected migration to be copied" + assert_not_nil bukkits_migration_order, "Expected migration to be skipped" + assert_equal (railties.index('acts_as_yaffle') > railties.index('bukkits')) , (yaffle_migration_order > bukkits_migration_order) migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length - output = `rake railties:install:migrations` + output = `bundle exec rake railties:install:migrations` assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length end |