diff options
190 files changed, 2814 insertions, 1540 deletions
@@ -8,8 +8,7 @@ else gem 'arel' end -gem 'minitest', '~> 3.1.0' -gem 'mocha', '>= 0.11.2' +gem 'mocha', '>= 0.11.2', :require => false gem 'rack-test', github: "brynary/rack-test" gem 'bcrypt-ruby', '~> 3.0.0' gem 'jquery-rails' diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 4d8f739403..96cfb43e0b 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Allow to set default Action Mailer options via `config.action_mailer.default_options=` *Robert Pankowecki* + * Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu* * Asynchronously send messages via the Rails Queue *Brian Cardarella* diff --git a/actionmailer/lib/action_mailer/async.rb b/actionmailer/lib/action_mailer/async.rb index 86a5b0d035..a364342745 100644 --- a/actionmailer/lib/action_mailer/async.rb +++ b/actionmailer/lib/action_mailer/async.rb @@ -1,39 +1,41 @@ require 'delegate' -module ActionMailer::Async - def method_missing(method_name, *args) - if action_methods.include?(method_name.to_s) - QueuedMessage.new(queue, self, method_name, *args) - else - super +module ActionMailer + module Async + def method_missing(method_name, *args) + if action_methods.include?(method_name.to_s) + QueuedMessage.new(queue, self, method_name, *args) + else + super + end end - end - def queue - Rails.queue - end + def queue + Rails.queue + end - class QueuedMessage < ::Delegator - attr_reader :queue + class QueuedMessage < ::Delegator + attr_reader :queue - def initialize(queue, mailer_class, method_name, *args) - @queue = queue - @mailer_class = mailer_class - @method_name = method_name - @args = args - end + def initialize(queue, mailer_class, method_name, *args) + @queue = queue + @mailer_class = mailer_class + @method_name = method_name + @args = args + end - def __getobj__ - @actual_message ||= @mailer_class.send(:new, @method_name, *@args).message - end + def __getobj__ + @actual_message ||= @mailer_class.send(:new, @method_name, *@args).message + end - def run - __getobj__.deliver - end + def run + __getobj__.deliver + end - # Will push the message onto the Queue to be processed - def deliver - @queue << self + # Will push the message onto the Queue to be processed + def deliver + @queue << self + end end end -end +end
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 89e0adc04d..150d435140 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -278,6 +278,11 @@ module ActionMailer #:nodoc: # set something in the defaults using a proc, and then set the same thing inside of your # mailer method, it will get over written by the mailer method. # + # It is also possible to set these default options that will be used in all mailers through + # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>: + # + # config.action_mailer.default_options = { from: "no-reply@example.org" } + # # = Callbacks # # You can specify callbacks using before_filter and after_filter for configuring your messages. @@ -421,6 +426,10 @@ module ActionMailer #:nodoc: self.default_params = default_params.merge(value).freeze if value default_params end + # Allows to set defaults through app configuration: + # + # config.action_mailer.default_options = { from: "no-reply@example.org" } + alias :default_options= :default # Receives a raw email, parses it into an email object, decodes it, # instantiates a new mailer, and passes the email object to the mailer @@ -457,10 +466,11 @@ module ActionMailer #:nodoc: end # Will force ActionMailer to push new messages to the queue defined - # in the ActionMailer class when set to true + # in the ActionMailer class when set to true. # # class WelcomeMailer < ActionMailer::Base # self.async = true + # end def async=(truth) if truth require 'action_mailer/async' @@ -786,4 +796,3 @@ module ActionMailer #:nodoc: ActiveSupport.run_load_hooks(:action_mailer, self) end end - diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index 17b22aea2a..b8d1db9558 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -15,7 +15,7 @@ module ActionMailer #:nodoc: def any(*args, &block) options = args.extract_options! - raise "You have to supply at least one format" if args.empty? + raise ArgumentError, "You have to supply at least one format" if args.empty? args.each { |type| send(type, options.dup, &block) } end alias :all :any diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 144a6bfe39..4ed332d13d 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -653,6 +653,19 @@ class BaseTest < ActiveSupport::TestCase assert_equal "Anonymous mailer body", mailer.welcome.body.encoded.strip end + test "default_from can be set" do + class DefaultFromMailer < ActionMailer::Base + default :to => 'system@test.lindsaar.net' + self.default_options = {from: "robert.pankowecki@gmail.com"} + + def welcome + mail(subject: "subject", body: "hello world") + end + end + + assert_equal ["robert.pankowecki@gmail.com"], DefaultFromMailer.welcome.from + end + protected # Execute the block setting the given values and restoring old values after diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 345b5aa330..be793d905f 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,19 @@ ## Rails 4.0.0 (unreleased) ## +* Show routes in exception page while debugging a `RoutingError` in development. *Richard Schneeman and Mattt Thompson* + +* Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.: + + class ApplicationController + add_flash_types :error, :warning + end + + If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, :error => 'message'` in a controller. + + *kennyj* + +* Remove Active Model dependency from Action Pack. *Guillermo Iguaran* + * Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping: get Rack::Utils.escape('こんにちは') => 'home#index' @@ -12,9 +26,9 @@ * Return proper format on exceptions. *Santiago Pastorino* -* Allow to use mounted_helpers (helpers for accessing mounted engines) in ActionView::TestCase. *Piotr Sarnacki* +* Allow to use `mounted_helpers` (helpers for accessing mounted engines) in `ActionView::TestCase`. *Piotr Sarnacki* -* Include mounted_helpers (helpers for accessing mounted engines) in ActionDispatch::IntegrationTest by default. *Piotr Sarnacki* +* Include `mounted_helpers` (helpers for accessing mounted engines) in `ActionDispatch::IntegrationTest` by default. *Piotr Sarnacki* * Extracted redirect logic from `ActionController::ForceSSL::ClassMethods.force_ssl` into `ActionController::ForceSSL#force_ssl_redirect` @@ -47,7 +61,7 @@ *Piotr Sarnacki* -* `truncate` now always returns an escaped HTMl-safe string. The option `:escape` can be used as +* `truncate` now always returns an escaped HTML-safe string. The option `:escape` can be used as false to not escape the result. *Li Ellis Gallardo + Rafael Mendonça França* diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 938f3d6dc2..6075e2f02b 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -18,7 +18,6 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency('activesupport', version) - s.add_dependency('activemodel', version) s.add_dependency('rack-cache', '~> 1.2') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('rack', '~> 1.4.1') @@ -26,5 +25,6 @@ Gem::Specification.new do |s| s.add_dependency('journey', '~> 1.0.1') s.add_dependency('erubis', '~> 2.7.0') + s.add_development_dependency('activemodel', version) s.add_development_dependency('tzinfo', '~> 0.3.33') end diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 492329c401..09b9e7ddf0 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -16,6 +16,10 @@ module AbstractController generate_method_for_mime(mime) end + Mime::Type.register_callback do |mime| + generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym) + end + protected def method_missing(symbol, &block) diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 7d73c6af8d..3da2834af0 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -49,9 +49,19 @@ module AbstractController module ClassMethods def view_context_class @view_context_class ||= begin - routes = _routes if respond_to?(:_routes) - helpers = _helpers if respond_to?(:_helpers) - ActionView::Base.prepare(routes, helpers) + routes = respond_to?(:_routes) && _routes + helpers = respond_to?(:_helpers) && _helpers + + Class.new(ActionView::Base) do + if routes + include routes.url_helpers + include routes.mounted_helpers + end + + if helpers + include helpers + end + end end end end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index bd768b634e..b078beb675 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -3,19 +3,34 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - delegate :flash, :to => :request - delegate :alert, :notice, :to => "request.flash" - helper_method :alert, :notice + class_attribute :_flash_types, instance_accessor: false + self._flash_types = [] + + delegate :flash, to: :request + add_flash_types(:alert, :notice) end - protected - def redirect_to(options = {}, response_status_and_flash = {}) #:doc: - if alert = response_status_and_flash.delete(:alert) - flash[:alert] = alert + module ClassMethods + def add_flash_types(*types) + types.each do |type| + next if _flash_types.include?(type) + + define_method(type) do + request.flash[type] + end + helper_method type + + _flash_types << type end + end + end - if notice = response_status_and_flash.delete(:notice) - flash[:notice] = notice + protected + def redirect_to(options = {}, response_status_and_flash = {}) #:doc: + self.class._flash_types.each do |flash_type| + if type = response_status_and_flash.delete(flash_type) + flash[flash_type] = type + end end if other_flashes = response_status_and_flash.delete(:flash) diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 544b4989c7..bdf6e88699 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -8,9 +8,8 @@ module ActionController delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :to => "@_response" - def dispatch(action, request, response = ActionDispatch::Response.new) - @_response ||= response - @_response.request ||= request + def dispatch(action, request) + set_response!(request) super(action, request) end @@ -22,5 +21,12 @@ module ActionController def reset_session @_request.reset_session end + + private + + def set_response!(request) + @_response = ActionDispatch::Response.new + @_response.request = request + end end end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index eeb37db2e7..9f3c997024 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -139,9 +139,6 @@ module ActionController #:nodoc: # session or flash after the template starts rendering will not propagate # to the client. # - # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt> - # will be raised, showing those objects are closed for modification. - # # == Middlewares # # Middlewares that need to manipulate the body won't work with streaming. diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index d1813ee745..0377b8c4cf 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -4,30 +4,25 @@ module ActionController include RackDelegation - def recycle! - @_url_options = nil - end - - - # TODO: Clean this up - def process_with_new_base_test(request, response) - @_request = request - @_response = response - @_response.request = request - ret = process(request.parameters[:action]) - if cookies = @_request.env['action_dispatch.cookies'] - cookies.write(@_response) - end - @_response.prepare! - ret - end - # TODO : Rewrite tests using controller.headers= to use Rack env def headers=(new_headers) @_response ||= ActionDispatch::Response.new @_response.headers.replace(new_headers) end + # Behavior specific to functional tests + module Functional # :nodoc: + def set_response!(request) + end + + def recycle! + @_url_options = nil + self.response_body = nil + self.formats = nil + self.params = nil + end + end + module ClassMethods def before_filters _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name} diff --git a/actionpack/lib/action_controller/model_naming.rb b/actionpack/lib/action_controller/model_naming.rb new file mode 100644 index 0000000000..785221dc3d --- /dev/null +++ b/actionpack/lib/action_controller/model_naming.rb @@ -0,0 +1,12 @@ +module ActionController + module ModelNaming + # Converts the given object to an ActiveModel compliant one. + def convert_to_model(object) + object.respond_to?(:to_model) ? object.to_model : object + end + + def model_name_from_record_or_class(record_or_class) + (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name + end + end +end diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 16a5decc62..d3ac406618 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module' +require 'action_controller/model_naming' module ActionController # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or @@ -27,6 +28,8 @@ module ActionController module RecordIdentifier extend self + include ModelNaming + JOIN = '_'.freeze NEW = 'new'.freeze @@ -40,7 +43,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.param_key(record_or_class) + singular = model_name_from_record_or_class(record_or_class).param_key prefix ? "#{prefix}#{JOIN}#{singular}" : singular end @@ -73,8 +76,7 @@ module ActionController # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to # make sure yourself that your dom ids are valid, in case you overwrite this method. def record_key_for_dom_id(record) - record = record.to_model if record.respond_to?(:to_model) - key = record.to_key + key = convert_to_model(record).to_key key ? key.join('_') : key end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index a1f29ea1bc..5f50bf5de6 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -143,6 +143,9 @@ module ActionController end class TestRequest < ActionDispatch::TestRequest #:nodoc: + DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup + DEFAULT_ENV.delete 'PATH_INFO' + def initialize(env = {}) super @@ -150,10 +153,6 @@ module ActionController self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16)) end - class Result < ::Array #:nodoc: - def to_s() join '/' end - end - def assign_parameters(routes, controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) extra_keys = routes.extra_keys(parameters) @@ -171,7 +170,7 @@ module ActionController non_path_parameters[key] = value else if value.is_a?(Array) - value = Result.new(value.map(&:to_param)) + value = value.map(&:to_param) else value = value.to_param end @@ -211,6 +210,12 @@ module ActionController cookie_jar.update(@set_cookies) cookie_jar.recycle! end + + private + + def default_env + DEFAULT_ENV + end end class TestResponse < ActionDispatch::TestResponse @@ -353,7 +358,7 @@ module ActionController # Use AS::TestCase for the base class when describing a model register_spec_type(self) do |desc| - desc < ActionController::Base + Class === desc && desc < ActionController::Base end module Behavior @@ -430,8 +435,13 @@ module ActionController end # Executes a request simulating HEAD HTTP method and set/volley the response - def head(action, parameters = nil, session = nil, flash = nil) - process(action, "HEAD", parameters, session, flash) + def head(action, *args) + process(action, "HEAD", *args) + end + + # Executes a request simulating OPTIONS HTTP method and set/volley the response + def options(action, *args) + process(action, "OPTIONS", *args) end def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @@ -471,13 +481,17 @@ module ActionController # proper params, as is the case when engaging rack. parameters = paramify_values(parameters) if html_format?(parameters) + @html_document = nil + + unless @controller.respond_to?(:recycle!) + @controller.extend(Testing::Functional) + @controller.class.class_eval { include Testing } + end + @request.recycle! @response.recycle! - @controller.response_body = nil - @controller.formats = nil - @controller.params = nil + @controller.recycle! - @html_document = nil @request.env['REQUEST_METHOD'] = http_method parameters ||= {} @@ -490,26 +504,34 @@ module ActionController @request.session.update(session) if session @request.session["flash"] = @request.flash.update(flash || {}) - @controller.request = @request + @controller.request = @request + @controller.response = @response + build_request_uri(action, parameters) - @controller.class.class_eval { include Testing } - @controller.recycle! - @controller.process_with_new_base_test(@request, @response) + + name = @request.parameters[:action] + + @controller.process(name) + + if cookies = @request.env['action_dispatch.cookies'] + cookies.write(@response) + end + @response.prepare! + @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {} @request.session.delete('flash') if @request.session['flash'].blank? @response end def setup_controller_request_and_response - @request = TestRequest.new - @response = TestResponse.new + @request = TestRequest.new + @response = TestResponse.new + @response.request = @request if klass = self.class.controller_class @controller ||= klass.new rescue nil end - @request.env.delete('PATH_INFO') - if defined?(@controller) && @controller @controller.request = @request @controller.params = {} @@ -523,7 +545,7 @@ module ActionController setup :setup_controller_request_and_response end - private + private def check_required_ivars # Sanity check for required instance variables so we can give an # understandable error message. @@ -564,8 +586,7 @@ module ActionController def html_format?(parameters) return true unless parameters.is_a?(Hash) - format = Mime[parameters[:format]] - format.nil? || format.html? + Mime.fetch(parameters[:format]) { Mime['html'] }.html? end end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index ee1913dbf9..fe39c220a5 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -29,6 +29,11 @@ module Mime Type.lookup_by_extension(type.to_s) end + def self.fetch(type) + return type if type.is_a?(Type) + EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } + end + # Encapsulates the notion of a mime type. Can be used at render time, for example, with: # # class PostsController < ActionController::Base @@ -53,6 +58,8 @@ module Mime cattr_reader :browser_generated_types attr_reader :symbol + @register_callbacks = [] + # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: attr_accessor :order, :name, :q @@ -84,6 +91,10 @@ module Mime TRAILING_STAR_REGEXP = /(text|application)\/\*/ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ + def register_callback(&block) + @register_callbacks << block + end + def lookup(string) LOOKUP[string] end @@ -101,10 +112,15 @@ module Mime def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms)) - SET << Mime.const_get(symbol.upcase) + new_mime = Mime.const_get(symbol.upcase) + SET << new_mime ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last } + + @register_callbacks.each do |callback| + callback.call(new_mime) + end end def parse(accept_header) diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index b903f98761..0f0589a844 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -1,5 +1,7 @@ require 'action_dispatch/http/request' require 'action_dispatch/middleware/exception_wrapper' +require 'action_dispatch/routing/inspector' + module ActionDispatch # This middleware is responsible for logging exceptions and @@ -7,8 +9,9 @@ module ActionDispatch class DebugExceptions RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates') - def initialize(app) - @app = app + def initialize(app, routes_app = nil) + @app = app + @routes_app = routes_app end def call(env) @@ -39,7 +42,8 @@ module ActionDispatch :exception => wrapper.exception, :application_trace => wrapper.application_trace, :framework_trace => wrapper.framework_trace, - :full_trace => wrapper.full_trace + :full_trace => wrapper.full_trace, + :routes => formatted_routes(exception) ) file = "rescues/#{wrapper.rescue_template}" @@ -78,5 +82,13 @@ module ActionDispatch def stderr_logger @stderr_logger ||= ActiveSupport::Logger.new($stderr) end + + def formatted_routes(exception) + return false unless @routes_app.respond_to?(:routes) + if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error) + inspector = ActionDispatch::Routing::RoutesInspector.new + inspector.format(@routes_app.routes.routes).join("\n") + end + end end end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb index 177d383e94..8c594c1523 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -10,8 +10,14 @@ </ol> </p> <% end %> +<%= render :template => "rescues/_trace" %> + +<h2> + Routes +</h2> + <p> - Try running <code>rake routes</code> for more information on available routes. + Routes match in priority from top to bottom </p> -<%= render :template => "rescues/_trace" %> +<p><pre><%= @routes %></pre></p> diff --git a/railties/lib/rails/application/route_inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 942c4f4789..bc7229b6a1 100644 --- a/railties/lib/rails/application/route_inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,7 +1,7 @@ require 'delegate' -module Rails - class Application +module ActionDispatch + module Routing class RouteWrapper < SimpleDelegator def endpoint rack_app ? rack_app.inspect : "#{controller}##{action}" @@ -62,7 +62,7 @@ module Rails ## # This class is just used for displaying route information when someone # executes `rake routes`. People should not use this class. - class RouteInspector # :nodoc: + class RoutesInspector # :nodoc: def initialize @engines = Hash.new end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 94242ad962..53a4afecb3 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1316,22 +1316,6 @@ module ActionDispatch parent_resource.instance_of?(Resource) && @scope[:shallow] end - def draw(name) - path = @draw_paths.find do |_path| - File.exists? "#{_path}/#{name}.rb" - end - - unless path - msg = "Your router tried to #draw the external file #{name}.rb,\n" \ - "but the file was not found in:\n\n" - msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n") - raise ArgumentError, msg - end - - route_path = "#{path}/#{name}.rb" - instance_eval(File.read(route_path), route_path.to_s) - end - # match 'path' => 'controller#action' # match 'path', to: 'controller#action' # match 'path', 'otherpath', on: :member, via: :get @@ -1581,7 +1565,6 @@ module ActionDispatch def initialize(set) #:nodoc: @set = set - @draw_paths = set.draw_paths @scope = { :path_names => @set.resources_path_names } end diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb index 86ce7a83b9..3d7b8878b8 100644 --- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb +++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb @@ -1,3 +1,5 @@ +require 'action_controller/model_naming' + module ActionDispatch module Routing # Polymorphic URL helpers are methods for smart resolution to a named route call when @@ -53,6 +55,8 @@ module ActionDispatch # form_for([blog, @post]) # => "/blog/posts/1" # module PolymorphicRoutes + include ActionController::ModelNaming + # Constructs a call to a named RESTful route for the given record and returns the # resulting URL string. For example: # @@ -154,10 +158,6 @@ module ActionDispatch options[:action] ? "#{options[:action]}_" : '' end - def convert_to_model(object) - object.respond_to?(:to_model) ? object.to_model : object - end - def routing_type(options) options[:routing_type] || :url end @@ -169,7 +169,7 @@ module ActionDispatch if parent.is_a?(Symbol) || parent.is_a?(String) parent else - ActiveModel::Naming.singular_route_key(parent) + model_name_from_record_or_class(parent).singular_route_key end end else @@ -181,9 +181,9 @@ module ActionDispatch route << record elsif record if inflection == :singular - route << ActiveModel::Naming.singular_route_key(record) + route << model_name_from_record_or_class(record).singular_route_key else - route << ActiveModel::Naming.route_key(record) + route << model_name_from_record_or_class(record).route_key end else raise ArgumentError, "Nil location provided. Can't build URI." diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 64b1d58ae9..1d6ca0c78d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -237,7 +237,6 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class, :valid_conditions - attr_accessor :draw_paths alias :routes :set @@ -249,7 +248,6 @@ module ActionDispatch self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.default_url_options = {} - self.draw_paths = [] self.request_class = request_class @valid_conditions = { :controller => true, :action => true } diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index a86b510719..639ae6f398 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -12,7 +12,7 @@ module ActionDispatch def initialize(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application - super(DEFAULT_ENV.merge(env)) + super(default_env.merge(env)) self.host = 'test.host' self.remote_addr = '0.0.0.0' @@ -69,5 +69,11 @@ module ActionDispatch def cookies @cookies ||= {}.with_indifferent_access end + + private + + def default_env + DEFAULT_ENV + end end end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index f98648d930..7bfbc1f0aa 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -148,7 +148,6 @@ module ActionView #:nodoc: cattr_accessor :prefix_partial_path_with_controller_namespace @@prefix_partial_path_with_controller_namespace = true - class_attribute :helpers class_attribute :_routes class_attribute :logger @@ -166,22 +165,6 @@ module ActionView #:nodoc: def xss_safe? #:nodoc: true end - - # This method receives routes and helpers from the controller - # and return a subclass ready to be used as view context. - def prepare(routes, helpers) #:nodoc: - Class.new(self) do - if routes - include routes.url_helpers - include routes.mounted_helpers - end - - if helpers - include helpers - self.helpers = helpers - end - end - end end attr_accessor :view_renderer diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 02c1250c76..68b0195700 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -95,7 +95,7 @@ module ActionView # have SSL certificates for each of the asset hosts this technique allows you # to avoid warnings in the client about mixed media. # - # ActionController::Base.asset_host = Proc.new { |source, request| + # config.action_controller.asset_host = Proc.new { |source, request| # if request.ssl? # "#{request.protocol}#{request.host_with_port}" # else diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index ba2a26fd09..b34f6c8650 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -12,6 +12,7 @@ require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' require 'active_support/deprecation' require 'active_support/core_ext/string/inflections' +require 'action_controller/model_naming' module ActionView # = Action View Form Helpers @@ -117,11 +118,7 @@ module ActionView include FormTagHelper include UrlHelper - - # Converts the given object to an ActiveModel compliant one. - def convert_to_model(object) - object.respond_to?(:to_model) ? object.to_model : object - end + include ActionController::ModelNaming # Creates a form that allows the user to create or update the attributes # of a specific model object. @@ -411,7 +408,7 @@ module ActionView object = nil else object = record.is_a?(Array) ? record.last : record - object_name = options[:as] || ActiveModel::Naming.param_key(object) + object_name = options[:as] || model_name_from_record_or_class(object).param_key apply_form_for_options!(record, object, options) end @@ -1128,7 +1125,7 @@ module ActionView object_name = record_name else object = record_name - object_name = ActiveModel::Naming.param_key(object) + object_name = model_name_from_record_or_class(object).param_key end builder = options[:builder] || default_form_builder @@ -1142,9 +1139,11 @@ module ActionView end class FormBuilder + include ActionController::ModelNaming + # The methods which wrap a form helper call. class_attribute :field_helpers - self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model] + self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model, :model_name_from_record_or_class] attr_accessor :object_name, :object, :options @@ -1214,7 +1213,7 @@ module ActionView end else record_object = record_name.is_a?(Array) ? record_name.last : record_name - record_name = ActiveModel::Naming.param_key(record_object) + record_name = model_name_from_record_or_class(record_object).param_key end index = if options.has_key?(:index) @@ -1396,10 +1395,6 @@ module ActionView @nested_child_index[name] ||= -1 @nested_child_index[name] += 1 end - - def convert_to_model(object) - object.respond_to?(:to_model) ? object.to_model : object - end end end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index e4b34125ad..8340aab4d2 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -90,6 +90,10 @@ class FlashTest < ActionController::TestCase def redirect_with_other_flashes redirect_to '/wonderland', :flash => { :joyride => "Horses!" } end + + def redirect_with_foo_flash + redirect_to "/wonderland", :foo => 'for great justice' + end end tests TestController @@ -203,6 +207,12 @@ class FlashTest < ActionController::TestCase get :redirect_with_other_flashes assert_equal "Horses!", @controller.send(:flash)[:joyride] end + + def test_redirect_to_with_adding_flash_types + @controller.class.add_flash_types :foo + get :redirect_with_foo_flash + assert_equal "for great justice", @controller.send(:flash)[:foo] + end end class FlashIntegrationTest < ActionDispatch::IntegrationTest @@ -210,9 +220,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' class TestController < ActionController::Base - def dont_set_flash - head :ok - end + add_flash_types :bar def set_flash flash["that"] = "hello" @@ -227,6 +235,11 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest def use_flash render :inline => "flash: #{flash["that"]}" end + + def set_bar + flash[:bar] = "for great justice" + head :ok + end end def test_flash @@ -266,6 +279,14 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end + def test_added_flash_types_method + with_test_route_set do + get '/set_bar' + assert_response :success + assert_equal 'for great justice', @controller.bar + end + end + private # Overwrite get to send SessionSecret in env hash diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index bdcd5561a8..c8e036b116 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -505,7 +505,7 @@ class RespondToControllerTest < ActionController::TestCase end class RespondWithController < ActionController::Base - respond_to :html, :json + respond_to :html, :json, :touch respond_to :xml, :except => :using_resource_with_block respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ] @@ -623,12 +623,14 @@ class RespondWithControllerTest < ActionController::TestCase super @request.host = "www.example.com" Mime::Type.register_alias('text/html', :iphone) + Mime::Type.register_alias('text/html', :touch) Mime::Type.register('text/x-mobile', :mobile) end def teardown super Mime::Type.unregister(:iphone) + Mime::Type.unregister(:touch) Mime::Type.unregister(:mobile) end diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb index aa44e0b282..475bf9d3c9 100644 --- a/actionpack/test/controller/new_base/render_action_test.rb +++ b/actionpack/test/controller/new_base/render_action_test.rb @@ -86,8 +86,6 @@ module RenderAction def setup end - describe "Both <controller_path>.html.erb and application.html.erb are missing" - test "rendering with layout => true" do assert_raise(ArgumentError) do get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false @@ -154,8 +152,6 @@ module RenderActionWithApplicationLayout end class LayoutTest < Rack::TestCase - describe "Only application.html.erb is present and <controller_path>.html.erb is missing" - test "rendering implicit application.html.erb as layout" do get "/render_action_with_application_layout/basic/hello_world" @@ -232,8 +228,6 @@ module RenderActionWithControllerLayout end class ControllerLayoutTest < Rack::TestCase - describe "Only <controller_path>.html.erb is present and application.html.erb is missing" - test "render hello_world and implicitly use <controller_path>.html.erb as a layout." do get "/render_action_with_controller_layout/basic/hello_world" @@ -290,8 +284,6 @@ module RenderActionWithBothLayouts end class ControllerLayoutTest < Rack::TestCase - describe "Both <controller_path>.html.erb and application.html.erb are present" - test "rendering implicitly use <controller_path>.html.erb over application.html.erb as a layout" do get "/render_action_with_both_layouts/basic/hello_world" diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index 00c7df2af8..156d87c321 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -160,8 +160,6 @@ module RenderTemplate end class TestWithLayout < Rack::TestCase - describe "Rendering with :template using implicit or explicit layout" - test "rendering with implicit layout" do with_routing do |set| set.draw { get ':controller', :action => :index } diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb index f8d02e8b6c..d6c3926a4d 100644 --- a/actionpack/test/controller/new_base/render_text_test.rb +++ b/actionpack/test/controller/new_base/render_text_test.rb @@ -63,8 +63,6 @@ module RenderText end class RenderTextTest < Rack::TestCase - describe "Rendering text using render :text" - test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| set.draw { get ':controller', :action => 'index' } diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 49137946fe..8990fc34d6 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -197,6 +197,11 @@ XML assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 } end + def test_options + options :test_params + assert_equal 200, @response.status + end + def test_process_without_flash process :set_flash assert_equal '><', flash['test'] @@ -635,7 +640,7 @@ XML get :test_params, :path => ['hello', 'world'] assert_equal ['hello', 'world'], @request.path_parameters['path'] - assert_equal 'hello/world', @request.path_parameters['path'].to_s + assert_equal 'hello/world', @request.path_parameters['path'].to_param end end @@ -913,4 +918,4 @@ class AnonymousControllerTest < ActionController::TestCase get :index assert_equal 'anonymous', @response.body end -end
\ No newline at end of file +end diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index bd078d2b21..8070bdec8a 100644 --- a/actionpack/test/dispatch/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -4,12 +4,11 @@ module ActionDispatch module Routing class MapperTest < ActiveSupport::TestCase class FakeSet - attr_reader :routes, :draw_paths + attr_reader :routes alias :set :routes def initialize @routes = [] - @draw_paths = [] end def resources_path_names diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 9d77c3acc5..ed012093a7 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -118,6 +118,20 @@ class MimeTypeTest < ActiveSupport::TestCase end end + test "register callbacks" do + begin + registered_mimes = [] + Mime::Type.register_callback do |mime| + registered_mimes << mime + end + + Mime::Type.register("text/foo", :foo) + assert_equal registered_mimes, [Mime::FOO] + ensure + Mime::Type.unregister(:FOO) + end + end + test "custom type with extension aliases" do begin Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"] diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb new file mode 100644 index 0000000000..97fc4f3e4b --- /dev/null +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -0,0 +1,170 @@ +require 'minitest/autorun' +require 'action_controller' +require 'rails/engine' +require 'action_dispatch/routing/inspector' + +module ActionDispatch + module Routing + class RoutesInspectorTest < ActiveSupport::TestCase + def setup + @set = ActionDispatch::Routing::RouteSet.new + @inspector = ActionDispatch::Routing::RoutesInspector.new + app = ActiveSupport::OrderedOptions.new + app.config = ActiveSupport::OrderedOptions.new + app.config.assets = ActiveSupport::OrderedOptions.new + app.config.assets.prefix = '/sprockets' + Rails.stubs(:application).returns(app) + Rails.stubs(:env).returns("development") + end + + def draw(&block) + @set.draw(&block) + @inspector.format(@set.routes) + end + + def test_displaying_routes_for_engines + engine = Class.new(Rails::Engine) do + def self.to_s + "Blog::Engine" + end + end + engine.routes.draw do + get '/cart', :to => 'cart#show' + end + + output = draw do + get '/custom/assets', :to => 'custom_assets#show' + mount engine => "/blog", :as => "blog" + end + + expected = [ + "custom_assets GET /custom/assets(.:format) custom_assets#show", + " blog /blog Blog::Engine", + "\nRoutes for Blog::Engine:", + "cart GET /cart(.:format) cart#show" + ] + assert_equal expected, output + end + + def test_cart_inspect + output = draw do + get '/cart', :to => 'cart#show' + end + assert_equal ["cart GET /cart(.:format) cart#show"], output + end + + def test_inspect_shows_custom_assets + output = draw do + get '/custom/assets', :to => 'custom_assets#show' + end + assert_equal ["custom_assets GET /custom/assets(.:format) custom_assets#show"], output + end + + def test_inspect_routes_shows_resources_route + output = draw do + resources :articles + end + expected = [ + " articles GET /articles(.:format) articles#index", + " POST /articles(.:format) articles#create", + " new_article GET /articles/new(.:format) articles#new", + "edit_article GET /articles/:id/edit(.:format) articles#edit", + " article GET /articles/:id(.:format) articles#show", + " PATCH /articles/:id(.:format) articles#update", + " PUT /articles/:id(.:format) articles#update", + " DELETE /articles/:id(.:format) articles#destroy" ] + assert_equal expected, output + end + + def test_inspect_routes_shows_root_route + output = draw do + root :to => 'pages#main' + end + assert_equal ["root GET / pages#main"], output + end + + def test_inspect_routes_shows_dynamic_action_route + output = draw do + get 'api/:action' => 'api' + end + assert_equal [" GET /api/:action(.:format) api#:action"], output + end + + def test_inspect_routes_shows_controller_and_action_only_route + output = draw do + get ':controller/:action' + end + assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output + end + + def test_inspect_routes_shows_controller_and_action_route_with_constraints + output = draw do + get ':controller(/:action(/:id))', :id => /\d+/ + end + assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output + end + + def test_rake_routes_shows_route_with_defaults + output = draw do + get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'} + end + assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output + end + + def test_rake_routes_shows_route_with_constraints + output = draw do + get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ + end + assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output + end + + class RackApp + def self.call(env) + end + end + + def test_rake_routes_shows_route_with_rack_app + output = draw do + get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/ + end + assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output + end + + def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints + constraint = Class.new do + def to_s + "( my custom constraint )" + end + end + + output = draw do + scope :constraint => constraint.new do + mount RackApp => '/foo' + end + end + + assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output + end + + def test_rake_routes_dont_show_app_mounted_in_assets_prefix + output = draw do + get '/sprockets' => RackApp + end + assert_no_match(/RackApp/, output.first) + assert_no_match(/\/sprockets/, output.first) + end + + def test_redirect + output = draw do + get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" } + get "/bar" => redirect(path: "/foo/bar", status: 307) + get "/foobar" => redirect{ "/foo/bar" } + end + + assert_equal " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", output[0] + assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1] + assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2] + end + end + end +end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index f15dd7214b..fed26d89f8 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2324,55 +2324,6 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest end end -class TestDrawExternalFile < ActionDispatch::IntegrationTest - class ExternalController < ActionController::Base - def index - render :text => "external#index" - end - end - - DRAW_PATH = File.expand_path('../../fixtures/routes', __FILE__) - - DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app| - app.draw_paths << DRAW_PATH - end - - def app - DefaultScopeRoutes - end - - def test_draw_external_file - DefaultScopeRoutes.draw do - scope :module => 'test_draw_external_file' do - draw :external - end - end - - get '/external' - assert_equal "external#index", @response.body - end - - def test_draw_nonexistent_file - exception = assert_raise ArgumentError do - DefaultScopeRoutes.draw do - draw :nonexistent - end - end - assert_match 'Your router tried to #draw the external file nonexistent.rb', exception.message - assert_match DRAW_PATH.to_s, exception.message - end - - def test_draw_bogus_file - exception = assert_raise NoMethodError do - DefaultScopeRoutes.draw do - draw :bogus - end - end - assert_match "undefined method `wrong'", exception.message - assert_match 'test/fixtures/routes/bogus.rb:1', exception.backtrace.first - end -end - class TestDefaultScope < ActionDispatch::IntegrationTest module ::Blog class PostsController < ActionController::Base diff --git a/actionpack/test/fixtures/routes/bogus.rb b/actionpack/test/fixtures/routes/bogus.rb deleted file mode 100644 index 41fbf0cd64..0000000000 --- a/actionpack/test/fixtures/routes/bogus.rb +++ /dev/null @@ -1 +0,0 @@ -wrong :route diff --git a/actionpack/test/fixtures/routes/external.rb b/actionpack/test/fixtures/routes/external.rb deleted file mode 100644 index d103c39f53..0000000000 --- a/actionpack/test/fixtures/routes/external.rb +++ /dev/null @@ -1 +0,0 @@ -get '/external' => 'external#index' diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb index 1724d6432d..84e328d8be 100644 --- a/actionpack/test/template/erb/tag_helper_test.rb +++ b/actionpack/test/template/erb/tag_helper_test.rb @@ -3,9 +3,6 @@ require "template/erb/helper" module ERBTest class TagHelperTest < BlockTestCase - - extend ActiveSupport::Testing::Declarative - test "percent equals works for content_tag and does not require parenthesis on method call" do assert_equal "<div>Hello world</div>", render_content("content_tag :div", "Hello world") end diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index c005f040eb..387aafebd4 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -68,14 +68,14 @@ module ActionView assert_nil self.class.determine_default_helper_class("String") end - test "delegates notice to request.flash" do - view.request.flash.expects(:notice).with("this message") - view.notice("this message") + test "delegates notice to request.flash[:notice]" do + view.request.flash.expects(:[]).with(:notice) + view.notice end - test "delegates alert to request.flash" do - view.request.flash.expects(:alert).with("this message") - view.alert("this message") + test "delegates alert to request.flash[:alert]" do + view.request.flash.expects(:[]).with(:alert) + view.alert end test "uses controller lookup context" do diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index d7f30f0920..57b1bc2ada 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -18,17 +18,23 @@ module ActiveModel # end # # cm = ContactMessage.new - # cm.to_model == self # => true - # cm.to_key # => nil - # cm.to_param # => nil - # cm.to_partial_path # => "contact_messages/contact_message" - # + # cm.to_model == cm # => true + # cm.to_key # => nil + # cm.to_param # => nil + # cm.to_partial_path # => "contact_messages/contact_message" module Conversion extend ActiveSupport::Concern # If your object is already designed to implement all of the Active Model # you can use the default <tt>:to_model</tt> implementation, which simply - # returns self. + # returns +self+. + # + # class Person + # include ActiveModel::Conversion + # end + # + # person = Person.new + # person.to_model == person # => true # # If your model does not act like an Active Model object, then you should # define <tt>:to_model</tt> yourself returning a proxy object that wraps @@ -37,21 +43,40 @@ module ActiveModel self end - # Returns an Enumerable of all key attributes if any is set, regardless - # if the object is persisted or not. + # Returns an Enumerable of all key attributes if any is set, regardless if + # the object is persisted or not. If there no key attributes, returns +nil+. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.create + # person.to_key # => [1] def to_key key = respond_to?(:id) && id key ? [key] : nil end - # Returns a string representing the object's key suitable for use in URLs, - # or nil if <tt>persisted?</tt> is false. + # Returns a +string+ representing the object's key suitable for use in URLs, + # or +nil+ if <tt>persisted?</tt> is +false+. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.create + # person.to_param # => "1" def to_param persisted? ? to_key.join('-') : nil end - # Returns a string identifying the path associated with the object. + # Returns a +string+ identifying the path associated with the object. # ActionPack uses this to find a suitable partial to represent the object. + # + # class Person + # include ActiveModel::Conversion + # end + # + # person = Person.new + # person.to_partial_path # => "people/person" def to_partial_path self.class._to_partial_path end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 7014d8114f..9bd5f903cd 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -80,8 +80,8 @@ module ActiveModel # person.changes # => {"name" => ["Bill", "Bob"]} # # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt> - # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to - # in-place attributes. + # to mark that the attribute is changing. Otherwise ActiveModel can't track + # changes to in-place attributes. # # person.name_will_change! # person.name_change # => ["Bill", "Bill"] @@ -115,7 +115,7 @@ module ActiveModel end # Returns a hash of changed attributes indicating their original - # and new values like <tt>attr => [original value, new value]</tt>. + # and new values like <tt>attr => [original value, new value]</tt>. # # person.changes # => {} # person.name = 'bob' diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 567677bbb9..4ed3462e7e 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -54,8 +54,8 @@ module ActiveModel # The above allows you to do: # # p = Person.new - # p.validate! # => ["can not be nil"] - # p.errors.full_messages # => ["name can not be nil"] + # person.validate! # => ["can not be nil"] + # person.errors.full_messages # => ["name can not be nil"] # # etc.. class Errors include Enumerable @@ -81,28 +81,50 @@ module ActiveModel super end - # Clear the messages + # Clear the error messages. + # + # person.errors.full_messages # => ["name can not be nil"] + # person.errors.clear + # person.errors.full_messages # => [] def clear messages.clear end - # Do the error messages include an error with key +error+? + # Returns +true+ if the error messages include an error for the given key + # +attribute+, +false+ otherwise. + # + # person.errors.messages # => { :name => ["can not be nil"] } + # person.errors.include?(:name) # => true + # person.errors.include?(:age) # => false def include?(attribute) (v = messages[attribute]) && v.any? end + # aliases include? alias :has_key? :include? - # Get messages for +key+ + # Get messages for +key+. + # + # person.errors.messages # => { :name => ["can not be nil"] } + # person.errors.get(:name) # => ["can not be nil"] + # person.errors.get(:age) # => nil def get(key) messages[key] end - # Set messages for +key+ to +value+ + # Set messages for +key+ to +value+. + # + # person.errors.get(:name) # => ["can not be nil"] + # person.errors.set(:name, ["can't be nil"]) + # person.errors.get(:name) # => ["can't be nil"] def set(key, value) messages[key] = value end - # Delete messages for +key+ + # Delete messages for +key+. Returns the deleted messages. + # + # person.errors.get(:name) # => ["can not be nil"] + # person.errors.delete(:name) # => ["can not be nil"] + # person.errors.get(:name) # => nil def delete(key) messages.delete(key) end @@ -110,16 +132,16 @@ module ActiveModel # When passed a symbol or a name of a method, returns an array of errors # for the method. # - # p.errors[:name] # => ["can not be nil"] - # p.errors['name'] # => ["can not be nil"] + # person.errors[:name] # => ["can not be nil"] + # person.errors['name'] # => ["can not be nil"] def [](attribute) get(attribute.to_sym) || set(attribute.to_sym, []) end # Adds to the supplied attribute the supplied error message. # - # p.errors[:name] = "must be set" - # p.errors[:name] # => ['must be set'] + # person.errors[:name] = "must be set" + # person.errors[:name] # => ['must be set'] def []=(attribute, error) self[attribute] << error end @@ -128,13 +150,13 @@ module ActiveModel # Yields the attribute and the error for that attribute. If the attribute # has more than one error message, yields once for each error message. # - # p.errors.add(:name, "can't be blank") - # p.errors.each do |attribute, error| + # person.errors.add(:name, "can't be blank") + # person.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # end # - # p.errors.add(:name, "must be specified") - # p.errors.each do |attribute, error| + # person.errors.add(:name, "must be specified") + # person.errors.each do |attribute, error| # # Will yield :name and "can't be blank" # # then yield :name and "must be specified" # end @@ -146,54 +168,65 @@ module ActiveModel # Returns the number of error messages. # - # p.errors.add(:name, "can't be blank") - # p.errors.size # => 1 - # p.errors.add(:name, "must be specified") - # p.errors.size # => 2 + # person.errors.add(:name, "can't be blank") + # person.errors.size # => 1 + # person.errors.add(:name, "must be specified") + # person.errors.size # => 2 def size values.flatten.size end - # Returns all message values + # Returns all message values. + # + # person.errors.messages # => { :name => ["can not be nil", "must be specified"] } + # person.errors.values # => [["can not be nil", "must be specified"]] def values messages.values end - # Returns all message keys + # Returns all message keys. + # + # person.errors.messages # => { :name => ["can not be nil", "must be specified"] } + # person.errors.keys # => [:name] def keys messages.keys end - # Returns an array of error messages, with the attribute name included + # Returns an array of error messages, with the attribute name included. # - # p.errors.add(:name, "can't be blank") - # p.errors.add(:name, "must be specified") - # p.errors.to_a # => ["name can't be blank", "name must be specified"] + # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, "must be specified") + # person.errors.to_a # => ["name can't be blank", "name must be specified"] def to_a full_messages end # Returns the number of error messages. - # p.errors.add(:name, "can't be blank") - # p.errors.count # => 1 - # p.errors.add(:name, "must be specified") - # p.errors.count # => 2 + # + # person.errors.add(:name, "can't be blank") + # person.errors.count # => 1 + # person.errors.add(:name, "must be specified") + # person.errors.count # => 2 def count to_a.size end - # Returns true if no errors are found, false otherwise. + # Returns +true+ if no errors are found, +false+ otherwise. # If the error message is a string it can be empty. + # + # person.errors.full_messages # => ["name can not be nil"] + # person.errors.empty? # => false def empty? all? { |k, v| v && v.empty? && !v.is_a?(String) } end + # aliases empty? alias_method :blank?, :empty? # Returns an xml formatted representation of the Errors hash. # - # p.errors.add(:name, "can't be blank") - # p.errors.add(:name, "must be specified") - # p.errors.to_xml + # person.errors.add(:name, "can't be blank") + # person.errors.add(:name, "must be specified") + # person.errors.to_xml # # => # # <?xml version=\"1.0\" encoding=\"UTF-8\"?> # # <errors> @@ -204,14 +237,21 @@ module ActiveModel to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options)) end - # Returns an Hash that can be used as the JSON representation for this object. - # Options: - # * <tt>:full_messages</tt> - determines if json object should contain - # full messages or not. Default: <tt>false</tt>. + # Returns a Hash that can be used as the JSON representation for this + # object. You can pass the <tt>:full_messages</tt> option. This determines + # if the json object should contain full messages or not (false by default). + # + # person.as_json # => { :name => ["can not be nil"] } + # person.as_json(full_messages: true) # => { :name => ["name can not be nil"] } def as_json(options=nil) to_hash(options && options[:full_messages]) end + # Returns a Hash of attributes with their error messages. If +full_messages+ + # is +true+, it will contain full messages (see +full_message+). + # + # person.to_hash # => { :name => ["can not be nil"] } + # person.to_hash(true) # => { :name => ["name can not be nil"] } def to_hash(full_messages = false) if full_messages messages = {} @@ -224,12 +264,31 @@ module ActiveModel end end - # Adds +message+ to the error messages on +attribute+. More than one error can be added to the same - # +attribute+. - # If no +message+ is supplied, <tt>:invalid</tt> is assumed. + # Adds +message+ to the error messages on +attribute+. More than one error + # can be added to the same +attribute+. If no +message+ is supplied, + # <tt>:invalid</tt> is assumed. # - # If +message+ is a symbol, it will be translated using the appropriate scope (see +generate_message+). - # If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error. + # person.errors.add(:name) + # # => ["is invalid"] + # person.errors.add(:name, 'must be implemented') + # # => ["is invalid", "must be implemented"] + # + # person.errors.messages + # # => { :name => ["must be implemented", "is invalid"] } + # + # If +message+ is a symbol, it will be translated using the appropriate + # scope (see +generate_message+). + # + # If +message+ is a proc, it will be called, allowing for things like + # <tt>Time.now</tt> to be used within an error. + # + # If the <tt>:strict</tt> option is set to true will raise + # ActiveModel::StrictValidationFailed instead of adding the error. + # + # person.errors.add(:name, nil, strict: true) + # # => ActiveModel::StrictValidationFailed: name is invalid + # + # person.errors.messages # => {} def add(attribute, message = nil, options = {}) message = normalize_message(attribute, message, options) if options[:strict] @@ -239,7 +298,12 @@ module ActiveModel self[attribute] << message end - # Will add an error message to each of the attributes in +attributes+ that is empty. + # Will add an error message to each of the attributes in +attributes+ + # that is empty. + # + # person.errors.add_on_empty(:name) + # person.errors.messages + # # => { :name => ["can't be empty"] } def add_on_empty(attributes, options = {}) [attributes].flatten.each do |attribute| value = @base.send(:read_attribute_for_validation, attribute) @@ -248,7 +312,12 @@ module ActiveModel end end - # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?). + # Will add an error message to each of the attributes in +attributes+ that + # is blank (using Object#blank?). + # + # person.errors.add_on_blank(:name) + # person.errors.messages + # # => { :name => ["can't be blank"] } def add_on_blank(attributes, options = {}) [attributes].flatten.each do |attribute| value = @base.send(:read_attribute_for_validation, attribute) @@ -256,10 +325,11 @@ module ActiveModel end end - # Returns true if an error on the attribute with the given message is present, false otherwise. - # +message+ is treated the same as for +add+. - # p.errors.add :name, :blank - # p.errors.added? :name, :blank # => true + # Returns +true+ if an error on the attribute with the given message is + # present, +false+ otherwise. +message+ is treated the same as for +add+. + # + # person.errors.add :name, :blank + # person.errors.added? :name, :blank # => true def added?(attribute, message = nil, options = {}) message = normalize_message(attribute, message, options) self[attribute].include? message @@ -267,22 +337,21 @@ module ActiveModel # Returns all the full error messages in an array. # - # class Company + # class Person # validates_presence_of :name, :address, :email - # validates_length_of :name, :in => 5..30 + # validates_length_of :name, in: 5..30 # end # - # company = Company.create(:address => '123 First St.') - # company.errors.full_messages # => - # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"] + # person = Person.create(address: '123 First St.') + # person.errors.full_messages + # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"] def full_messages map { |attribute, message| full_message(attribute, message) } end # Returns a full message for a given attribute. # - # company.errors.full_message(:name, "is invalid") # => - # "Name is invalid" + # person.errors.full_message(:name, 'is invalid') # => "Name is invalid" def full_message(attribute, message) return message if attribute == :base attr_name = attribute.to_s.tr('.', '_').humanize @@ -298,10 +367,11 @@ module ActiveModel # (<tt>activemodel.errors.messages</tt>). # # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, - # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not - # there also, it returns the translation of the default message - # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name, - # translated attribute name and the value are available for interpolation. + # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if + # that is not there also, it returns the translation of the default message + # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model + # name, translated attribute name and the value are available for + # interpolation. # # When using inheritance in your models, it will check all the inherited # models too, but only if the model itself hasn't been found. Say you have @@ -317,7 +387,6 @@ module ActiveModel # * <tt>activemodel.errors.messages.blank</tt> # * <tt>errors.attributes.title.blank</tt> # * <tt>errors.messages.blank</tt> - # def generate_message(attribute, type = :invalid, options = {}) type = options.delete(:message) if options[:message].is_a?(Symbol) @@ -366,6 +435,8 @@ module ActiveModel end end + # Raised when a validation cannot be corrected by end users and are considered + # exceptional. class StrictValidationFailed < StandardError end end diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 88b730626c..550fa474ea 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -12,19 +12,20 @@ module ActiveModel # you want all features out of the box. # # These tests do not attempt to determine the semantic correctness of the - # returned values. For instance, you could implement valid? to always - # return true, and the tests would pass. It is up to you to ensure that - # the values are semantically meaningful. + # returned values. For instance, you could implement <tt>valid?</tt> to + # always return true, and the tests would pass. It is up to you to ensure + # that the values are semantically meaningful. # - # Objects you pass in are expected to return a compliant object from a - # call to to_model. It is perfectly fine for to_model to return self. + # Objects you pass in are expected to return a compliant object from a call + # to <tt>to_model</tt>. It is perfectly fine for <tt>to_model</tt> to return + # +self+. module Tests # == Responds to <tt>to_key</tt> # # Returns an Enumerable of all (primary) key attributes - # or nil if model.persisted? is false. This is used by - # dom_id to generate unique ids for the object. + # or nil if <tt>model.persisted?</tt> is false. This is used by + # <tt>dom_id</tt> to generate unique ids for the object. def test_to_key assert model.respond_to?(:to_key), "The model should respond to to_key" def model.persisted?() false end @@ -34,13 +35,14 @@ module ActiveModel # == Responds to <tt>to_param</tt> # # Returns a string representing the object's key suitable for use in URLs - # or nil if model.persisted? is false. + # or +nil+ if <tt>model.persisted?</tt> is +false+. # - # Implementers can decide to either raise an exception or provide a default - # in case the record uses a composite primary key. There are no tests for this - # behavior in lint because it doesn't make sense to force any of the possible - # implementation strategies on the implementer. However, if the resource is - # not persisted?, then to_param should always return nil. + # Implementers can decide to either raise an exception or provide a + # default in case the record uses a composite primary key. There are no + # tests for this behavior in lint because it doesn't make sense to force + # any of the possible implementation strategies on the implementer. + # However, if the resource is not persisted?, then <tt>to_param</tt> + # should always return +nil+. def test_to_param assert model.respond_to?(:to_param), "The model should respond to to_param" def model.to_key() [1] end @@ -50,9 +52,8 @@ module ActiveModel # == Responds to <tt>to_partial_path</tt> # - # Returns a string giving a relative path. This is used for looking up + # Returns a string giving a relative path. This is used for looking up # partials. For example, a BlogPost model might return "blog_posts/blog_post" - # def test_to_partial_path assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path" assert_kind_of String, model.to_partial_path @@ -60,11 +61,11 @@ module ActiveModel # == Responds to <tt>persisted?</tt> # - # Returns a boolean that specifies whether the object has been persisted yet. - # This is used when calculating the URL for an object. If the object is - # not persisted, a form for that object, for instance, will route to the - # create action. If it is persisted, a form for the object will routes to - # the update action. + # Returns a boolean that specifies whether the object has been persisted + # yet. This is used when calculating the URL for an object. If the object + # is not persisted, a form for that object, for instance, will route to + # the create action. If it is persisted, a form for the object will routes + # to the update action. def test_persisted? assert model.respond_to?(:persisted?), "The model should respond to persisted?" assert_boolean model.persisted?, "persisted?" @@ -73,8 +74,8 @@ module ActiveModel # == Naming # # Model.model_name must return a string with some convenience methods: - # :human, :singular, and :plural. Check ActiveModel::Naming for more information. - # + # <tt>:human</tt>, <tt>:singular</tt> and <tt>:plural</tt>. Check + # ActiveModel::Naming for more information. def test_model_naming assert model.class.respond_to?(:model_name), "The model should respond to model_name" model_name = model.class.model_name diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 8f2c0bf00a..e57fcf1610 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -141,8 +141,10 @@ module ActiveModel # # attr_accessor :name, :credit_rating # - # attr_accessible :name - # attr_accessible :name, :credit_rating, :as => :admin + # # Both admin and default user can change name of a customer + # attr_accessible :name, :as => [:admin, :default] + # # Only admin can change credit rating of a customer + # attr_accessible :credit_rating, :as => :admin # # def assign_attributes(values, options = {}) # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index cb0a051ca5..33a530e6bd 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -2,11 +2,11 @@ module ActiveModel # == Active Model Basic Model # - # Includes the required interface for an object to interact with <tt>ActionPack</tt>, - # using different <tt>ActiveModel</tt> modules. It includes model name introspections, - # conversions, translations and validations. Besides that, it allows you to - # initialize the object with a hash of attributes, pretty much like - # <tt>ActiveRecord</tt> does. + # Includes the required interface for an object to interact with + # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules. + # It includes model name introspections, conversions, translations and + # validations. Besides that, it allows you to initialize the object with a + # hash of attributes, pretty much like <tt>ActiveRecord</tt> does. # # A minimal implementation could be: # @@ -15,13 +15,13 @@ module ActiveModel # attr_accessor :name, :age # end # - # person = Person.new(:name => 'bob', :age => '18') + # person = Person.new(name: 'bob', age: '18') # person.name # => 'bob' - # person.age # => 18 + # person.age # => 18 # - # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> to - # return <tt>false</tt>, which is the most common case. You may want to override it - # in your class to simulate a different scenario: + # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> + # to return +false+, which is the most common case. You may want to override + # it in your class to simulate a different scenario: # # class Person # include ActiveModel::Model @@ -32,11 +32,12 @@ module ActiveModel # end # end # - # person = Person.new(:id => 1, :name => 'bob') + # person = Person.new(id: 1, name: 'bob') # person.persisted? # => true # - # Also, if for some reason you need to run code on <tt>initialize</tt>, make sure you - # call super if you want the attributes hash initialization to happen. + # Also, if for some reason you need to run code on <tt>initialize</tt>, make + # sure you call +super+ if you want the attributes hash initialization to + # happen. # # class Person # include ActiveModel::Model @@ -48,11 +49,12 @@ module ActiveModel # end # end # - # person = Person.new(:id => 1, :name => 'bob') + # person = Person.new(id: 1, name: 'bob') # person.omg # => true # - # For more detailed information on other functionalities available, please refer - # to the specific modules included in <tt>ActiveModel::Model</tt> (see below). + # For more detailed information on other functionalities available, please + # refer to the specific modules included in <tt>ActiveModel::Model</tt> + # (see below). module Model def self.included(base) #:nodoc: base.class_eval do @@ -63,12 +65,31 @@ module ActiveModel end end + # Initializes a new model with the given +params+. + # + # class Person + # include ActiveModel::Model + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => 18 def initialize(params={}) params.each do |attr, value| self.public_send("#{attr}=", value) end if params end + # Indicates if the model is persisted. Default is +false+. + # + # class Person + # include ActiveModel::Model + # attr_accessor :id, :name + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.persisted? # => false def persisted? false end diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 2b5fc57a3a..ea4f9341c6 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -14,9 +14,137 @@ module ActiveModel alias_method :cache_key, :collection + ## + # :method: == + # + # :call-seq: + # ==(other) + # + # Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and + # +other+ are equal, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name == 'BlogPost' # => true + # BlogPost.model_name == 'Blog Post' # => false + + ## + # :method: === + # + # :call-seq: + # ===(other) + # + # Equivalent to <tt>#==</tt>. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name === 'BlogPost' # => true + # BlogPost.model_name === 'Blog Post' # => false + + ## + # :method: <=> + # + # :call-seq: + # ==(other) + # + # Equivalent to <tt>String#<=></tt>. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name <=> 'BlogPost' # => 0 + # BlogPost.model_name <=> 'Blog' # => 1 + # BlogPost.model_name <=> 'BlogPosts' # => -1 + + ## + # :method: =~ + # + # :call-seq: + # =~(regexp) + # + # Equivalent to <tt>String#=~</tt>. Match the class name against the given + # regexp. Returns the position where the match starts or +nil+ if there is + # no match. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name =~ /Post/ # => 4 + # BlogPost.model_name =~ /\d/ # => nil + + ## + # :method: !~ + # + # :call-seq: + # !~(regexp) + # + # Equivalent to <tt>String#!~</tt>. Match the class name against the given + # regexp. Returns +true+ if there is no match, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name !~ /Post/ # => false + # BlogPost.model_name !~ /\d/ # => true + + ## + # :method: eql? + # + # :call-seq: + # eql?(other) + # + # Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and + # +other+ have the same length and content, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.eql?('BlogPost') # => true + # BlogPost.model_name.eql?('Blog Post') # => false + + ## + # :method: to_s + # + # :call-seq: + # to_s() + # + # Returns the class name. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.to_s # => "BlogPost" + + ## + # :method: to_str + # + # :call-seq: + # to_str() + # + # Equivalent to +to_s+. delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s, :to_str, :to => :name + # Returns a new ActiveModel::Name instance. By default, the +namespace+ + # and +name+ option will take the namespace and name of the given class + # respectively. + # + # module Foo + # class Bar + # end + # end + # + # ActiveModel::Name.new(Foo::Bar).to_s + # # => "Foo::Bar" def initialize(klass, namespace = nil, name = nil) @name = name || klass.name @@ -38,7 +166,11 @@ module ActiveModel end # Transform the model name into a more humane format, using I18n. By default, - # it will underscore then humanize the class name + # it will underscore then humanize the class name. + # + # class BlogPost + # extend ActiveModel::Naming + # end # # BlogPost.model_name.human # => "Blog post" # @@ -82,11 +214,12 @@ module ActiveModel # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover" # # Providing the functionality that ActiveModel::Naming provides in your object - # is required to pass the Active Model Lint test. So either extending the provided - # method below, or rolling your own is required. + # is required to pass the Active Model Lint test. So either extending the + # provided method below, or rolling your own is required. module Naming # Returns an ActiveModel::Name object for module. It can be - # used to retrieve all kinds of naming-related information. + # used to retrieve all kinds of naming-related information + # (See ActiveModel::Name for more information). def model_name @_model_name ||= begin namespace = self.parents.detect do |n| @@ -96,7 +229,7 @@ module ActiveModel end end - # Returns the plural class name of a record or class. Examples: + # Returns the plural class name of a record or class. # # ActiveModel::Naming.plural(post) # => "posts" # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people" @@ -104,7 +237,7 @@ module ActiveModel model_name_from_record_or_class(record_or_class).plural end - # Returns the singular class name of a record or class. Examples: + # Returns the singular class name of a record or class. # # ActiveModel::Naming.singular(post) # => "post" # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person" @@ -112,10 +245,10 @@ module ActiveModel model_name_from_record_or_class(record_or_class).singular end - # Identifies whether the class name of a record or class is uncountable. Examples: + # Identifies whether the class name of a record or class is uncountable. # # ActiveModel::Naming.uncountable?(Sheep) # => true - # ActiveModel::Naming.uncountable?(Post) => false + # ActiveModel::Naming.uncountable?(Post) # => false def self.uncountable?(record_or_class) plural(record_or_class) == singular(record_or_class) end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index f5ea285ccb..ca206ee9aa 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -238,8 +238,7 @@ module ActiveModel # Send observed_method(object) if the method exists and # the observer is enabled for the given object's class. def update(observed_method, object, *extra_args, &block) #:nodoc: - return unless respond_to?(observed_method) - return if disabled_for?(object) + return if !respond_to?(observed_method) || disabled_for?(object) send(observed_method, object, *extra_args, &block) end diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 6d8fd21814..8a63014ffb 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -25,17 +25,16 @@ module ActiveModel # person.name = "Bob" # person.serializable_hash # => {"name"=>"Bob"} # - # You need to declare an attributes hash which contains the attributes - # you want to serialize. Attributes must be strings, not symbols. - # When called, serializable hash will use - # instance methods that match the name of the attributes hash's keys. - # In order to override this behavior, take a look at the private - # method +read_attribute_for_serialization+. + # You need to declare an attributes hash which contains the attributes you + # want to serialize. Attributes must be strings, not symbols. When called, + # serializable hash will use instance methods that match the name of the + # attributes hash's keys. In order to override this behavior, take a look at + # the private method +read_attribute_for_serialization+. # # Most of the time though, you will want to include the JSON or XML # serializations. Both of these modules automatically include the - # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly - # include it. + # <tt>ActiveModel::Serialization</tt> module, so there is no need to + # explicitly include it. # # A minimal implementation including XML and JSON would be: # @@ -64,13 +63,37 @@ module ActiveModel # person.to_json # => "{\"name\":\"Bob\"}" # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person... # - # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and <tt>:include</tt>. - # The following are all valid examples: + # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and + # <tt>:include</tt>. The following are all valid examples: # - # person.serializable_hash(:only => 'name') - # person.serializable_hash(:include => :address) - # person.serializable_hash(:include => { :address => { :only => 'city' }}) + # person.serializable_hash(only: 'name') + # person.serializable_hash(include: :address) + # person.serializable_hash(include: { address: { only: 'city' }}) module Serialization + # Returns a serialized hash of your object. + # + # class Person + # include ActiveModel::Serialization + # + # attr_accessor :name, :age + # + # def attributes + # {'name' => nil, 'age' => nil} + # end + # + # def capitalized_name + # name.capitalize + # end + # end + # + # person = Person.new + # person.name = 'bob' + # person.age = 22 + # person.serializable_hash # => {"name"=>"bob", "age"=>22} + # person.serializable_hash(only: :name) # => {"name"=>"bob"} + # person.serializable_hash(except: :name) # => {"age"=>22} + # person.serializable_hash(methods: :capitalized_name) + # # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"} def serializable_hash(options = nil) options ||= {} @@ -115,7 +138,6 @@ module ActiveModel # @data[key] # end # end - # alias :read_attribute_for_serialization :send # Add associations specified via the <tt>:include</tt> option. diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index b4baf3a946..e4c7553cb8 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -87,8 +87,12 @@ module ActiveModel # # { "comments" => [ { "body" => "Don't think too hard" } ], # # "title" => "So I was thinking" } ] } def as_json(options = nil) - root = include_root_in_json - root = options[:root] if options.try(:key?, :root) + root = if options && options.key?(:root) + options[:root] + else + include_root_in_json + end + if root root = self.class.model_name.element if root == true { root => serializable_hash(options) } diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 43651094cf..8d5ebf527f 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -27,7 +27,7 @@ module ActiveModel # # class Person < ActiveRecord::Base # validates_acceptance_of :terms_of_service - # validates_acceptance_of :eula, :message => "must be abided" + # validates_acceptance_of :eula, message: "must be abided" # end # # If the database column does not exist, the +terms_of_service+ attribute @@ -37,29 +37,17 @@ module ActiveModel # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "must be # accepted"). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default - # is true). + # is +true+). # * <tt>:accept</tt> - Specifies value that is considered accepted. # The default value is a string "1", which makes it easy to relate to # an HTML checkbox. This should be set to +true+ if you are validating # a database column, since the attribute is typecast from "1" to +true+ # before validation. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine - # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, - # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The - # method, proc or string should return or evaluate to a true or false - # value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to - # determine if the validation should not occur (for example, - # <tt>:unless => :skip_validation</tt>, or - # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). - # The method, proc or string should return or evaluate to a true or - # false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_acceptance_of(*attr_names) validates_with AcceptanceValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb index b632a2bd6b..676457ec0f 100644 --- a/activemodel/lib/active_model/validations/clusivity.rb +++ b/activemodel/lib/active_model/validations/clusivity.rb @@ -2,7 +2,7 @@ require 'active_support/core_ext/range.rb' module ActiveModel module Validations - module Clusivity + module Clusivity #:nodoc: ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " << "and must be supplied as the :in option of the configuration hash" diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index b6cf82fb19..baa034eca6 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -25,7 +25,7 @@ module ActiveModel # class Person < ActiveRecord::Base # validates_confirmation_of :user_name, :password # validates_confirmation_of :email_address, - # :message => "should match confirmation" + # message: 'should match confirmation' # end # # View: @@ -38,29 +38,18 @@ module ActiveModel # attribute. # # NOTE: This check is performed only if +password_confirmation+ is not - # +nil+. To require confirmation, make sure - # to add a presence check for the confirmation attribute: + # +nil+. To require confirmation, make sure to add a presence check for + # the confirmation attribute: # - # validates_presence_of :password_confirmation, :if => :password_changed? + # validates_presence_of :password_confirmation, if: :password_changed? # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "doesn't match # confirmation"). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine - # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, - # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The - # method, proc or string should return or evaluate to a true or false - # value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to - # determine if the validation should not occur (e.g. - # <tt>:unless => :skip_validation</tt>, or - # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The - # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_confirmation_of(*attr_names) validates_with ConfirmationValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index c8d7057606..3331162bc0 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -19,37 +19,28 @@ module ActiveModel # particular enumerable object. # # class Person < ActiveRecord::Base - # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here" - # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60" - # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed" - # validates_exclusion_of :password, :in => lambda { |p| [p.username, p.first_name] }, - # :message => "should not be the same as your username or first name" + # validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here" + # validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60' + # validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed" + # validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] }, + # message: 'should not be the same as your username or first name' # end # # Configuration options: - # * <tt>:in</tt> - An enumerable object of items that the value shouldn't be - # part of. This can be supplied as a proc or lambda which returns an + # * <tt>:in</tt> - An enumerable object of items that the value shouldn't + # be part of. This can be supplied as a proc or lambda which returns an # enumerable. If the enumerable is a range the test is performed with # <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. # * <tt>:message</tt> - Specifies a custom error message (default is: "is # reserved"). - # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute - # is +nil+ (default is +false+). + # * <tt>:allow_nil</tt> - If set to true, skips this validation if the + # attribute is +nil+ (default is +false+). # * <tt>:allow_blank</tt> - If set to true, skips this validation if the # attribute is blank(default is +false+). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if - # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or - # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc - # or string should return or evaluate to a true or false value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine - # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, - # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The - # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_exclusion_of(*attr_names) validates_with ExclusionValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index d48987c482..80150229a0 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -57,14 +57,14 @@ module ActiveModel # attribute matches the regular expression: # # class Person < ActiveRecord::Base - # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :on => :create + # validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create # end # # Alternatively, you can require that the specified attribute does _not_ # match the regular expression: # # class Person < ActiveRecord::Base - # validates_format_of :email, :without => /NOSPAM/ + # validates_format_of :email, without: /NOSPAM/ # end # # You can also provide a proc or lambda which will determine the regular @@ -73,15 +73,16 @@ module ActiveModel # class Person < ActiveRecord::Base # # Admin can have number as a first letter in their screen name # validates_format_of :screen_name, - # :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i } + # with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i } # end # # Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the # string, <tt>^</tt> and <tt>$</tt> match the start/end of a line. # - # Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass the - # :multiline => true option in case you use any of these two anchors in the provided - # regular expression. In most cases, you should be using <tt>\A</tt> and <tt>\z</tt>. + # Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass + # the <tt>multiline: true</tt> option in case you use any of these two + # anchors in the provided regular expression. In most cases, you should be + # using <tt>\A</tt> and <tt>\z</tt>. # # You must pass either <tt>:with</tt> or <tt>:without</tt> as an option. # In addition, both must be a regular expression or a proc or lambda, or @@ -89,32 +90,24 @@ module ActiveModel # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "is invalid"). - # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute - # is +nil+ (default is +false+). + # * <tt>:allow_nil</tt> - If set to true, skips this validation if the + # attribute is +nil+ (default is +false+). # * <tt>:allow_blank</tt> - If set to true, skips this validation if the # attribute is blank (default is +false+). # * <tt>:with</tt> - Regular expression that if the attribute matches will - # result in a successful validation. This can be provided as a proc or lambda - # returning regular expression which will be called at runtime. - # * <tt>:without</tt> - Regular expression that if the attribute does not match - # will result in a successful validation. This can be provided as a proc or + # result in a successful validation. This can be provided as a proc or # lambda returning regular expression which will be called at runtime. - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine - # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or - # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc - # or string should return or evaluate to a true or false value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine - # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, - # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The - # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # * <tt>:without</tt> - Regular expression that if the attribute does not + # match will result in a successful validation. This can be provided as + # a proc or lambda returning regular expression which will be called at + # runtime. # * <tt>:multiline</tt> - Set to true if your regular expression contains # anchors that match the beginning or end of lines as opposed to the # beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_format_of(*attr_names) validates_with FormatValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 154db5aedc..c995e28d64 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -19,36 +19,27 @@ module ActiveModel # particular enumerable object. # # class Person < ActiveRecord::Base - # validates_inclusion_of :gender, :in => %w( m f ) - # validates_inclusion_of :age, :in => 0..99 - # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list" - # validates_inclusion_of :states, :in => lambda{ |person| STATES[person.country] } + # validates_inclusion_of :gender, in: %w( m f ) + # validates_inclusion_of :age, in: 0..99 + # validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list" + # validates_inclusion_of :states, in: ->(person) { STATES[person.country] } # end # # Configuration options: # * <tt>:in</tt> - An enumerable object of available items. This can be - # supplied as a proc or lambda which returns an enumerable. If the enumerable - # is a range the test is performed with <tt>Range#cover?</tt>, otherwise with - # <tt>include?</tt>. - # * <tt>:message</tt> - Specifies a custom error message (default is: "is not - # included in the list"). - # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute - # is +nil+ (default is +false+). - # * <tt>:allow_blank</tt> - If set to true, skips this validation if the + # supplied as a proc or lambda which returns an enumerable. If the + # enumerable is a range the test is performed with <tt>Range#cover?</tt>, + # otherwise with <tt>include?</tt>. + # * <tt>:message</tt> - Specifies a custom error message (default is: "is + # not included in the list"). + # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the + # attribute is +nil+ (default is +false+). + # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the # attribute is blank (default is +false+). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if - # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or - # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc - # or string should return or evaluate to a true or false value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine - # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, - # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The - # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_inclusion_of(*attr_names) validates_with InclusionValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 40ebe0cd2e..aa72ea41c7 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -62,56 +62,48 @@ module ActiveModel module HelperMethods - # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time: + # 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 :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 :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: ->(str) { str.scan(/\w+/) } # end # # Configuration options: # * <tt>:minimum</tt> - The minimum size of the attribute. # * <tt>:maximum</tt> - The maximum size of the attribute. # * <tt>:is</tt> - The exact size of the attribute. - # * <tt>:within</tt> - A range specifying the minimum and maximum size of the - # attribute. - # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>. + # * <tt>:within</tt> - A range specifying the minimum and maximum size of + # the attribute. + # * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>. # * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation. # * <tt>:allow_blank</tt> - Attribute may be blank; skip validation. # * <tt>:too_long</tt> - The error message if the attribute goes over the # maximum (default is: "is too long (maximum is %{count} characters)"). # * <tt>:too_short</tt> - The error message if the attribute goes under the # minimum (default is: "is too short (min is %{count} characters)"). - # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method - # and the attribute is the wrong size (default is: "is the wrong length - # (should be %{count} characters)"). + # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> + # method and the attribute is the wrong size (default is: "is the wrong + # length (should be %{count} characters)"). # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, # <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate # <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message. - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if - # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or - # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc - # or string should return or evaluate to a true or false value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine - # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, - # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, - # proc or string should return or evaluate to a true or false value. # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. - # (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to count words - # as in above example). Defaults to <tt>lambda{ |value| value.split(//) }</tt> + # (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words + # as in above example). Defaults to <tt>->(value) { value.split(//) }</tt> # which counts individual characters. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_length_of(*attr_names) validates_with LengthValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 1069ed3906..edebca94a8 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -79,48 +79,40 @@ module ActiveModel end module HelperMethods - # Validates whether the value of the specified attribute is numeric by trying - # to convert it to a float with Kernel.Float (if <tt>only_integer</tt> is false) - # or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt> (if - # <tt>only_integer</tt> is set to true). + # Validates whether the value of the specified attribute is numeric by + # trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt> + # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt> + # (if <tt>only_integer</tt> is set to +true+). # # class Person < ActiveRecord::Base - # validates_numericality_of :value, :on => :create + # validates_numericality_of :value, on: :create # end # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "is not a number"). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. - # * <tt>:only_integer</tt> - Specifies whether the value has to be an integer, - # e.g. an integral value (default is +false+). + # * <tt>:only_integer</tt> - Specifies whether the value has to be an + # integer, e.g. an integral value (default is +false+). # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is # +false+). Notice that for fixnum and float columns empty strings are # converted to +nil+. # * <tt>:greater_than</tt> - Specifies the value must be greater than the # supplied value. - # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater - # than or equal the supplied value. - # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value. - # * <tt>:less_than</tt> - Specifies the value must be less than the supplied - # value. - # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or - # equal the supplied value. - # * <tt>:other_than</tt> - Specifies the value must be other than the supplied + # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be + # greater than or equal the supplied value. + # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied # value. + # * <tt>:less_than</tt> - Specifies the value must be less than the + # supplied value. + # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less + # than or equal the supplied value. + # * <tt>:other_than</tt> - Specifies the value must be other than the + # supplied value. # * <tt>:odd</tt> - Specifies the value must be an odd number. # * <tt>:even</tt> - Specifies the value must be an even number. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if - # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or - # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc - # or string should return or evaluate to a true or false value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine - # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, - # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The - # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+ . + # See <tt>ActiveModel::Validation#validates</tt> for more information # # The following checks can also be supplied with a proc or a symbol which # corresponds to a method: @@ -134,8 +126,8 @@ module ActiveModel # For example: # # class Person < ActiveRecord::Base - # validates_numericality_of :width, :less_than => Proc.new { |person| person.height } - # validates_numericality_of :width, :greater_than => :minimum_weight + # validates_numericality_of :width, less_than: Proc.new { |person| person.height } + # validates_numericality_of :width, greater_than: :minimum_weight # end def validates_numericality_of(*attr_names) validates_with NumericalityValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index a7dcdbba3d..4592a1deb0 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -20,28 +20,19 @@ module ActiveModel # # The first_name attribute must be in the object and it cannot be blank. # - # If you want to validate the presence of a boolean field (where the real values - # are true and false), you will want to use - # <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>. + # If you want to validate the presence of a boolean field (where the real + # values are +true+ and +false+), you will want to use + # <tt>validates_inclusion_of :field_name, in: [true, false]</tt>. # # This is due to the way Object#blank? handles boolean values: # <tt>false.blank? # => true</tt>. # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "can't be blank"). - # * <tt>:on</tt> - Specifies when this validation is active. Runs in all - # validation contexts by default (+nil+), other options are <tt>:create</tt> - # and <tt>:update</tt>. - # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if - # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or - # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc - # or string should return or evaluate to a true or false value. - # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine - # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, - # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, - # proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. - # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 6c13d2b4a2..ecda9dffcf 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -69,14 +69,31 @@ module ActiveModel # validator's initializer as +options[:in]+ while other types including # regular expressions and strings are passed as +options[:with]+ # + # There is also a list of options that could be used along with validators: + # * <tt>:on</tt> - Specifies when this validation is active. Runs in all + # validation contexts by default (+nil+), other options are <tt>:create</tt> + # and <tt>:update</tt>. + # * <tt>:if</tt> - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. <tt>if: :allow_validation</tt>, + # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine + # if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>, + # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The + # method, proc or string should return or evaluate to a +true+ or + # +false+ value. + # * <tt>:strict</tt> - Specifies whether validation should be strict. + # See <tt>ActiveModel::Validation#validates!</tt> for more information. + # + # Example: + # + # validates :password, :presence => true, :confirmation => true, :if => :password_required? + # # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+ # can be given to one specific validator, as a hash: # # validates :password, :presence => { :if => :password_required? }, :confirmation => true # - # Or to all at the same time: - # - # validates :password, :presence => true, :confirmation => true, :if => :password_required? # def validates(*attributes) defaults = attributes.extract_options!.dup diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 2953126c3c..d5d0798704 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -129,7 +129,7 @@ module ActiveModel #:nodoc: # record, attribute and value. # # All Active Model validations are built on top of this validator. - class EachValidator < Validator + class EachValidator < Validator #:nodoc: attr_reader :attributes # Returns a new validator instance. All options will be available via the @@ -168,7 +168,7 @@ module ActiveModel #:nodoc: # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization # and call this block for each attribute being validated. +validates_each+ uses this validator. - class BlockValidator < EachValidator + class BlockValidator < EachValidator #:nodoc: def initialize(options, &block) @block = block super diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 1822ba8e0e..36d94fb38b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,53 @@ ## Rails 4.0.0 (unreleased) ## +* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to` + and `remove_belongs_to` are acceptable. References are reversible. + Examples: + + # Create a user_id column + add_reference(:products, :user) + # Create a supplier_id, supplier_type columns and appropriate index + add_reference(:products, :supplier, polymorphic: true, index: true) + # Remove polymorphic reference + remove_reference(:products, :supplier, polymorphic: true) + + *Aleksey Magusev* + +* Add `:default` and `:null` options to `column_exists?`. + + column_exists?(:testings, :taggable_id, :integer, null: false) + column_exists?(:testings, :taggable_type, :string, default: 'Photo') + + *Aleksey Magusev* + +* `ActiveRecord::Relation#inspect` now makes it clear that you are + dealing with a `Relation` object rather than an array:. + + User.where(:age => 30).inspect + # => <ActiveRecord::Relation [#<User ...>, #<User ...>, ...]> + + User.where(:age => 30).to_a.inspect + # => [#<User ...>, #<User ...>] + + The number of records displayed will be limited to 10. + + *Brian Cardarella, Jon Leighton & Damien Mathieu* + +* Add `collation` and `ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later. + Example: + + development: + adapter: postgresql + host: localhost + database: rails_development + username: foo + password: bar + encoding: UTF8 + collation: ja_JP.UTF8 + ctype: ja_JP.UTF8 + + *kennyj* + * `FinderMethods#exists?` now returns `false` with the `false` argument. *Egor Lynko* @@ -16,7 +64,7 @@ *Tony Schneider* -* Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an +* Allow `ActiveRecord::Relation#pluck` to accept multiple columns. Returns an array of arrays containing the typecasted values: Person.pluck(:id, :name) @@ -57,7 +105,7 @@ *Andrew White* -* Move HABTM validity checks to ActiveRecord::Reflection. One side effect of +* Move HABTM validity checks to `ActiveRecord::Reflection`. One side effect of this is to move when the exceptions are raised from the point of declaration to when the association is built. This is consistant with other association validity checks. @@ -65,7 +113,7 @@ *Andrew White* * Added `stored_attributes` hash which contains the attributes stored using - ActiveRecord::Store. This allows you to retrieve the list of attributes + `ActiveRecord::Store`. This allows you to retrieve the list of attributes you've defined. class User < ActiveRecord::Base @@ -821,7 +869,7 @@ * LRU cache in mysql and sqlite are now per-process caches. - * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache keys are per process id. + * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache keys are per process id. * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto *Aaron Patterson* diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 68f8bbeb1c..a62fce4756 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1167,6 +1167,8 @@ module ActiveRecord # If true, always save the associated objects or destroy them if marked for destruction, # when saving the parent object. If false, never save or destroy the associated objects. # By default, only save associated objects that are new records. + # + # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>. # [:inverse_of] # Specifies the name of the <tt>belongs_to</tt> association on the associated object # that is the inverse of this <tt>has_many</tt> association. Does not work in combination @@ -1288,6 +1290,8 @@ module ActiveRecord # If true, always save the associated object or destroy it if marked for destruction, # when saving the parent object. If false, never save or destroy the associated object. # By default, only save the associated object if it's a new record. + # + # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>. # [:inverse_of] # Specifies the name of the <tt>belongs_to</tt> association on the associated object # that is the inverse of this <tt>has_one</tt> association. Does not work in combination @@ -1404,6 +1408,8 @@ module ActiveRecord # saving the parent object. # If false, never save or destroy the associated object. # By default, only save the associated object if it's a new record. + # + # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>. # [:touch] # If true, the associated object will be touched (the updated_at/on attributes set to now) # when this record is either saved or destroyed. If you specify a symbol, that attribute @@ -1589,6 +1595,8 @@ module ActiveRecord # If false, never save or destroy the associated objects. # By default, only save associated objects that are new records. # + # Note that <tt>accepts_nested_attributes_for</tt> sets <tt>:autosave</tt> to <tt>true</tt>. + # # Option examples: # has_and_belongs_to_many :projects # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ] diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index ab6d253ef8..5b41f72e52 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -158,11 +158,12 @@ module ActiveRecord begin send(name + "=", read_value_from_parameter(name, values_with_empty_parameters)) rescue => ex - errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name) + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) end end unless errors.empty? - raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes" + error_descriptions = errors.map { |ex| ex.message }.join(",") + raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" end end @@ -180,15 +181,27 @@ module ActiveRecord end def read_time_parameter_value(name, values_hash_from_param) - # If Date bits were not provided, error - raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)} - max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6) - # If Date bits were provided but blank, then return nil - return nil if (1..3).any? {|position| values_hash_from_param[position].blank?} + # If column is a :time (and not :date or :timestamp) there is no need to validate if + # there are year/month/day fields + if column_for_attribute(name).type == :time + # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil + {1 => 1970, 2 => 1, 3 => 1}.each do |key,value| + values_hash_from_param[key] ||= value + end + else + # else column is a timestamp, so if Date bits were not provided, error + if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) } + raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)") + end + + # If Date bits were provided but blank, then return nil + return nil if (1..3).any? { |position| values_hash_from_param[position].blank? } + end - set_values = (1..max_position).collect{|position| values_hash_from_param[position] } + max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6) + set_values = (1..max_position).collect{ |position| values_hash_from_param[position] } # If Time bits are not there, then default to 0 - (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]} + (3..5).each { |i| set_values[i] = set_values[i].blank? ? 0 : set_values[i] } instantiate_time_object(name, set_values) end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 4af4d28b74..49ab3ab808 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -6,7 +6,7 @@ module ActiveRecord included do # 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 + class_attribute :serialized_attributes, instance_writer: false self.serialized_attributes = {} end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 3b4537aab4..be6fda95b4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -56,7 +56,7 @@ module ActiveRecord end def select_all(arel, name = nil, binds = []) - if @query_cache_enabled + if @query_cache_enabled && !locked?(arel) sql = to_sql(arel, binds) cache_sql(sql, binds) { super(sql, name, binds) } else @@ -65,6 +65,7 @@ module ActiveRecord end private + def cache_sql(sql, binds) result = if @query_cache[sql].key?(binds) @@ -83,6 +84,10 @@ module ActiveRecord result.collect { |row| row.dup } end end + + def locked?(arel) + arel.respond_to?(:locked) && arel.locked + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 6f9f0399db..60a9eee7c7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -31,7 +31,7 @@ module ActiveRecord # BigDecimals need to be put in a non-normalized form and quoted. when nil then "NULL" when BigDecimal then value.to_s('F') - when Numeric then value.to_s + when Numeric, ActiveSupport::Duration then value.to_s when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value.to_s}'" 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 df78ba6c5a..ef17dfbbc5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -259,7 +259,7 @@ module ActiveRecord end # end EOV end - + # Adds index options to the indexes hash, keyed by column name # This is primarily used to track indexes that need to be created after the table # @@ -282,7 +282,7 @@ module ActiveRecord index_options = options.delete(:index) args.each do |col| column("#{col}_id", :integer, options) - column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil? + column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options end end @@ -441,17 +441,13 @@ module ActiveRecord # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. # - # t.references(:goat) - # t.references(:goat, :polymorphic => true) - # t.belongs_to(:goat) + # t.references(:user) + # t.belongs_to(:supplier, polymorphic: true) + # def references(*args) options = args.extract_options! - polymorphic = options.delete(:polymorphic) - index_options = options.delete(:index) - args.each do |col| - @base.add_column(@table_name, "#{col}_id", :integer, options) - @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil? - @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options + args.each do |ref_name| + @base.add_reference(@table_name, ref_name, options) end end alias :belongs_to :references @@ -459,18 +455,16 @@ module ActiveRecord # Removes a reference. Optionally removes a +type+ column. # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable. # - # t.remove_references(:goat) - # t.remove_references(:goat, :polymorphic => true) - # t.remove_belongs_to(:goat) + # t.remove_references(:user) + # t.remove_belongs_to(:supplier, polymorphic: true) + # def remove_references(*args) options = args.extract_options! - polymorphic = options.delete(:polymorphic) - args.each do |col| - @base.remove_column(@table_name, "#{col}_id") - @base.remove_column(@table_name, "#{col}_type") unless polymorphic.nil? + args.each do |ref_name| + @base.remove_reference(@table_name, ref_name, options) end end - alias :remove_belongs_to :remove_references + alias :remove_belongs_to :remove_references # Adds a column or columns of a specified type # 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 f5794a4e54..3c3f01223c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -57,7 +57,6 @@ module ActiveRecord # Checks to see if a column exists in a given table. # - # === Examples # # Check a column exists # column_exists?(:suppliers, :name) # @@ -65,13 +64,18 @@ module ActiveRecord # column_exists?(:suppliers, :name, :string) # # # Check a column exists with a specific definition - # column_exists?(:suppliers, :name, :string, :limit => 100) + # column_exists?(:suppliers, :name, :string, limit: 100) + # column_exists?(:suppliers, :name, :string, default: 'default') + # column_exists?(:suppliers, :name, :string, null: false) + # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2) def column_exists?(table_name, column_name, type = nil, options = {}) columns(table_name).any?{ |c| c.name == column_name.to_s && - (!type || c.type == type) && - (!options[:limit] || c.limit == options[:limit]) && - (!options[:precision] || c.precision == options[:precision]) && - (!options[:scale] || c.scale == options[:scale]) } + (!type || c.type == type) && + (!options.key?(:limit) || c.limit == options[:limit]) && + (!options.key?(:precision) || c.precision == options[:precision]) && + (!options.key?(:scale) || c.scale == options[:scale]) && + (!options.key?(:default) || c.default == options[:default]) && + (!options.key?(:null) || c.null == options[:null]) } end # Creates a new table with the name +table_name+. +table_name+ may either @@ -439,6 +443,42 @@ module ActiveRecord indexes(table_name).detect { |i| i.name == index_name } end + # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. + # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable. + # + # ====== Create a user_id column + # add_reference(:products, :user) + # + # ====== Create a supplier_id and supplier_type columns + # add_belongs_to(:products, :supplier, polymorphic: true) + # + # ====== Create a supplier_id, supplier_type columns and appropriate index + # add_reference(:products, :supplier, polymorphic: true, index: true) + # + def add_reference(table_name, ref_name, options = {}) + polymorphic = options.delete(:polymorphic) + index_options = options.delete(:index) + add_column(table_name, "#{ref_name}_id", :integer, options) + add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic + add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options + end + alias :add_belongs_to :add_reference + + # Removes the reference(s). Also removes a +type+ column if one exists. + # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable. + # + # ====== Remove the reference + # remove_reference(:products, :user, index: true) + # + # ====== Remove polymorphic reference + # remove_reference(:products, :supplier, polymorphic: true) + # + def remove_reference(table_name, ref_name, options = {}) + remove_column(table_name, "#{ref_name}_id") + remove_column(table_name, "#{ref_name}_type") if options[:polymorphic] + end + alias :remove_belongs_to :remove_reference + # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the # entire structure of the database. def structure_dump diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 921278d145..df4a9d5afc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -72,6 +72,8 @@ module ActiveRecord when /^mediumint/i; 3 when /^smallint/i; 2 when /^tinyint/i; 1 + when /^enum\((.+)\)/i + $1.split(',').map{|enum| enum.strip.length - 2}.max else super end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 8491d42b86..dd40351a38 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -1,3 +1,5 @@ +require 'uri' + module ActiveRecord module ConnectionAdapters class ConnectionSpecification #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 98bcbfff6f..a57a532ba2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -916,7 +916,8 @@ module ActiveRecord end # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>, - # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses + # <tt>:encoding</tt>, <tt>:collation</tt>, <tt>:ctype</tt>, + # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>). # # Example: @@ -933,6 +934,10 @@ module ActiveRecord " TEMPLATE = \"#{value}\"" when :encoding " ENCODING = '#{value}'" + when :collation + " LC_COLLATE = '#{value}'" + when :ctype + " LC_CTYPE = '#{value}'" when :tablespace " TABLESPACE = \"#{value}\"" when :connection_limit @@ -1059,6 +1064,20 @@ module ActiveRecord end_sql end + # Returns the current database collation. + def collation + query(<<-end_sql, 'SCHEMA')[0][0] + SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' + end_sql + end + + # Returns the current database ctype. + def ctype + query(<<-end_sql, 'SCHEMA')[0][0] + SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' + end_sql + end + # Returns an array of schema names. def schema_names query(<<-SQL, 'SCHEMA').flatten diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 858b667e22..5f157fde6d 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -106,13 +106,11 @@ module ActiveRecord attr_reader :record, :attempted_action def initialize(record, attempted_action) + super("Attempted to #{attempted_action} a stale object: #{record.class.name}") @record = record @attempted_action = attempted_action end - def message - "Attempted to #{attempted_action} a stale object: #{record.class.name}" - end end # Raised when association is being configured improperly or @@ -168,9 +166,9 @@ module ActiveRecord class AttributeAssignmentError < ActiveRecordError attr_reader :exception, :attribute def initialize(message, exception, attribute) + super(message) @exception = exception @attribute = attribute - @message = message end end @@ -189,12 +187,10 @@ module ActiveRecord attr_reader :model def initialize(model) + super("Unknown primary key for table #{model.table_name} in model #{model}.") @model = model end - def message - "Unknown primary key for table #{model.table_name} in model #{model}." - end end class ImmutableRelation < ActiveRecordError diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 96b62fdd61..95f4360578 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -51,13 +51,15 @@ module ActiveRecord super || delegate.respond_to?(*args) end - [:create_table, :create_join_table, :change_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| + [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default, :add_reference, :remove_reference].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method}(*args) # def create_table(*args) record(:"#{method}", args) # record(:create_table, args) end # end EOV end + alias :add_belongs_to :add_reference + alias :remove_belongs_to :remove_reference private @@ -102,6 +104,16 @@ module ActiveRecord [:remove_timestamps, args] end + def invert_add_reference(args) + [:remove_reference, args] + end + alias :invert_add_belongs_to :invert_add_reference + + def invert_remove_reference(args) + [:add_reference, args] + end + alias :invert_remove_belongs_to :invert_remove_reference + # Forwards any missing method call to the \target. def method_missing(method, *args, &block) @delegate.send(method, *args, &block) diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb index 7b3d926d91..0015e3a567 100644 --- a/activerecord/lib/active_record/model.rb +++ b/activerecord/lib/active_record/model.rb @@ -103,7 +103,9 @@ module ActiveRecord def abstract_class? false end - + + # Defines the name of the table column which will store the class name on single-table + # inheritance situations. def inheritance_column 'type' end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 841681e542..7febb5539f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -351,7 +351,7 @@ module ActiveRecord if respond_to?(method) 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?" + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" end end end @@ -373,7 +373,7 @@ module ActiveRecord # }) # # Will update the name of the Person with ID 1, build a new associated - # person with the name `John', and mark the associated Person with ID 2 + # person with the name 'John', and mark the associated Person with ID 2 # for destruction. # # Also accepts an Array of attribute hashes: diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 1dcd0a5a32..78ecb1cdc5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -148,13 +148,10 @@ db_namespace = namespace :db do # desc "Retrieves the collation for the current environment's database" task :collation => [:environment, :load_config] do - config = ActiveRecord::Base.configurations[Rails.env || 'development'] - case config['adapter'] - when /mysql/ - ActiveRecord::Base.establish_connection(config) - puts ActiveRecord::Base.connection.collation - else - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + begin + puts ActiveRecord::Tasks::DatabaseTasks.collation_current + rescue NoMethodError + $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch' end end @@ -274,7 +271,7 @@ db_namespace = namespace :db do ENV['ISC_USER'] = config['username'].to_s if config['username'] ENV['ISC_PASSWORD'] = config['password'].to_s if config['password'] end - + def firebird_db_string(config) FireRuby::Database.db_string_for(config.symbolize_keys) end @@ -313,7 +310,7 @@ db_namespace = namespace :db do filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") case abcs[env]['adapter'] when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[Rails.env], filename) + ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[env], filename) when 'sqlserver' `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}` when 'oci', 'oracle' @@ -435,7 +432,7 @@ namespace :railties do task :migrations => :'db:load_config' do to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } railties = {} - Rails.application.railties.all do |railtie| + Rails.application.railties.each do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fe3aa00a74..dd1f77e925 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -492,10 +492,6 @@ module ActiveRecord end end - def inspect - to_a.inspect - end - def pretty_print(q) q.pp(self.to_a) end @@ -518,6 +514,13 @@ module ActiveRecord @values.dup end + def inspect + entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect) + entries[10] = '...' if entries.size == 11 + + "#<#{self.class.name} [#{entries.join(', ')}]>" + end + private def references_eager_loaded_tables? diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index e6a67b76fe..974cd326ef 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -146,8 +146,8 @@ module ActiveRecord to_a end - # Returns true if a record exists in the table that matches the +id+ or - # conditions given, or false otherwise. The argument can take five forms: + # Returns +true+ if a record exists in the table that matches the +id+ or + # conditions given, or +false+ otherwise. The argument can take six forms: # # * Integer - Finds the record with this primary key. # * String - Finds the record with a primary key corresponding to this @@ -155,8 +155,9 @@ module ActiveRecord # * Array - Finds the record that matches these +find+-style conditions # (such as <tt>['color = ?', 'red']</tt>). # * Hash - Finds the record that matches these +find+-style conditions - # (such as <tt>{:color => 'red'}</tt>). - # * No args - Returns false if the table is empty, true otherwise. + # (such as <tt>{color: 'red'}</tt>). + # * +false+ - Returns always +false+. + # * No args - Returns +false+ if the table is empty, +true+ otherwise. # # For more information about specifying conditions as a Hash or Array, # see the Conditions section in the introduction to ActiveRecord::Base. @@ -168,7 +169,8 @@ module ActiveRecord # Person.exists?(5) # Person.exists?('5') # Person.exists?(['name LIKE ?', "%#{query}%"]) - # Person.exists?(:name => "David") + # Person.exists?(name: 'David') + # Person.exists?(false) # Person.exists? def exists?(conditions = :none) conditions = conditions.id if ActiveRecord::Model === conditions diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d61df216bc..e401ed37b0 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -41,6 +41,17 @@ module ActiveRecord alias extensions extending_values + # Specify relationships to be included in the result set. For + # example: + # + # users = User.includes(:address) + # users.each do |user| + # user.address.city + # end + # + # allows you to access the +address+ attribute of the +User+ model without + # firing an additional query. This will often result in a + # performance improvement over a simple +join+ def includes(*args) args.empty? ? self : spawn.includes!(*args) end @@ -131,6 +142,18 @@ module ActiveRecord self end + # Allows to specify a group attribute: + # + # User.group(:name) + # => SELECT "users".* FROM "users" GROUP BY name + # + # Returns an array with distinct records based on the `group` attribute: + # + # User.select([:id, :name]) + # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo"> + # + # User.group(:name) + # => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>] def group(*args) args.blank? ? self : spawn.group!(*args) end @@ -142,6 +165,16 @@ module ActiveRecord self end + # Allows to specify an order attribute: + # + # User.order('name') + # => SELECT "users".* FROM "users" ORDER BY name + # + # User.order('name DESC') + # => SELECT "users".* FROM "users" ORDER BY name DESC + # + # User.order('name DESC, email') + # => SELECT "users".* FROM "users" ORDER BY name DESC, email def order(*args) args.blank? ? self : spawn.order!(*args) end @@ -415,10 +448,10 @@ module ActiveRecord # # Can accept other relation objects. For example: # - # Topic.select('title').from(Topics.approved) + # Topic.select('title').from(Topic.approved) # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery # - # Topics.select('a.title').from(Topics.approved, :a) + # Topic.select('a.title').from(Topic.approved, :a) # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a # def from(value, subquery_name = nil) diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index af51c803a7..b35fec7920 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -31,14 +31,14 @@ module ActiveRecord # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } # - # It is recommended that you use the block form of unscoped because chaining - # unscoped with <tt>scope</tt> does not work. Assuming that + # It is recommended that you use the block form of unscoped because + # chaining unscoped with <tt>scope</tt> does not work. Assuming that # <tt>published</tt> is a <tt>scope</tt>, the following two statements - # are equal: the default_scope is applied on both. + # are equal: the <tt>default_scope</tt> is applied on both. # # Post.unscoped.published # Post.published - def unscoped #:nodoc: + def unscoped block_given? ? relation.scoping { yield } : relation end diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index d13491502e..d836acf18f 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -43,7 +43,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :stored_attributes + class_attribute :stored_attributes, instance_writer: false self.stored_attributes = {} end @@ -58,8 +58,11 @@ module ActiveRecord keys.each do |key| define_method("#{key}=") do |value| initialize_store_attribute(store_attribute) - send(store_attribute)[key] = value - send :"#{store_attribute}_will_change!" + attribute = send(store_attribute) + if value != attribute[key] + attribute[key] = value + send :"#{store_attribute}_will_change!" + end end define_method(key) do diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 999b2ebc85..fb3dfc2730 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -3,13 +3,17 @@ module ActiveRecord module DatabaseTasks # :nodoc: extend self - TASKS_PATTERNS = { - /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks, - /postgresql/ => ActiveRecord::Tasks::PostgreSQLDatabaseTasks, - /sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks - } LOCAL_HOSTS = ['127.0.0.1', 'localhost'] + def register_task(pattern, task) + @tasks ||= {} + @tasks[pattern] = task + end + + register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks) + register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks) + register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks) + def create(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).create @@ -56,6 +60,15 @@ module ActiveRecord class_for_adapter(configuration['adapter']).new(*arguments).charset end + def collation_current(environment = Rails.env) + collation ActiveRecord::Base.configurations[environment] + end + + def collation(*arguments) + configuration = arguments.first + class_for_adapter(configuration['adapter']).new(*arguments).collation + end + def purge(configuration) class_for_adapter(configuration['adapter']).new(configuration).purge end @@ -75,8 +88,8 @@ module ActiveRecord private def class_for_adapter(adapter) - key = TASKS_PATTERNS.keys.detect { |pattern| adapter[pattern] } - TASKS_PATTERNS[key] + key = @tasks.keys.detect { |pattern| adapter[pattern] } + @tasks[key] end def each_current_configuration(environment) diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index b39cd2282f..bf62dfd5b5 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -44,6 +44,10 @@ module ActiveRecord connection.charset end + def collation + connection.collation + end + def structure_dump(filename) establish_connection configuration File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index a210392e53..ea5cb888fb 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -29,6 +29,10 @@ module ActiveRecord connection.encoding end + def collation + connection.collation + end + def purge clear_active_connections! drop diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index b1a0f83b5f..34eaffcb0f 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -2,30 +2,42 @@ class <%= migration_class_name %> < ActiveRecord::Migration <%- if migration_action == 'add' -%> def change <% attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> + <%- end -%> <%- end -%> end <%- else -%> def up <% attributes.each do |attribute| -%> - <%- if migration_action -%> - <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %> +<%- if migration_action -%> + <%- if attribute.reference? -%> + remove_reference :<%= table_name %>, :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %> + <%- else -%> + remove_column :<%= table_name %>, :<%= attribute.name %> <%- end -%> <%- end -%> +<%- end -%> end def down <% attributes.reverse.each do |attribute| -%> - <%- if migration_action -%> +<%- if migration_action -%> + <%- if attribute.reference? -%> + add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> <%- if attribute.has_index? -%> add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> <%- end -%> <%- end -%> <%- end -%> +<%- end -%> end <%- end -%> -end +end
\ No newline at end of file diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb index d56f9f57a4..2cca17b94f 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb @@ -1,7 +1,7 @@ <% module_namespacing do -%> class <%= class_name %> < <%= parent_class_name.classify %> <% attributes.select {|attr| attr.reference? }.each do |attribute| -%> - belongs_to :<%= attribute.name %> + belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %> <% end -%> <% if !accessible_attributes.empty? -%> attr_accessible <%= accessible_attributes.map {|a| ":#{a.name}" }.sort.join(', ') %> diff --git a/activerecord/lib/rails/generators/active_record/model/templates/module.rb b/activerecord/lib/rails/generators/active_record/model/templates/module.rb index fca2908080..a3bf1c37b6 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/module.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/module.rb @@ -1,7 +1,7 @@ <% module_namespacing do -%> module <%= class_path.map(&:camelize).join('::') %> def self.table_name_prefix - '<%= class_path.join('_') %>_' + '<%= namespaced? ? namespaced_class_path.join('_') : class_path.join('_') %>_' end end <% end -%> diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb new file mode 100644 index 0000000000..40af317ad1 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/enum_test.rb @@ -0,0 +1,10 @@ +require "cases/helper" + +class MysqlEnumTest < ActiveRecord::TestCase + class EnumTest < ActiveRecord::Base + end + + def test_enum_limit + assert_equal 5, EnumTest.columns.first.limit + end +end diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb new file mode 100644 index 0000000000..f3a05e48ad --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb @@ -0,0 +1,10 @@ +require "cases/helper" + +class Mysql2EnumTest < ActiveRecord::TestCase + class EnumTest < ActiveRecord::Base + end + + def test_enum_limit + assert_equal 5, EnumTest.columns.first.limit + end +end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 447d729e52..113c27b194 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -21,6 +21,10 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) end + def test_create_database_with_collation_and_ctype + assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'UTF8' LC_COLLATE = 'ja_JP.UTF8' LC_CTYPE = 'ja_JP.UTF8'), create_database(:aimonetti, :encoding => :"UTF8", :collation => :"ja_JP.UTF8", :ctype => :"ja_JP.UTF8") + end + def test_add_index # add_index calls index_name_exists? which can't work since execute is stubbed ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*| diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index f6e168caf2..f823ce33d8 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -21,6 +21,14 @@ module ActiveRecord assert_not_nil @connection.encoding end + def test_collation + assert_not_nil @connection.collation + end + + def test_ctype + assert_not_nil @connection.ctype + end + def test_default_client_min_messages assert_equal "warning", @connection.client_min_messages end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index b8481175b2..3ea6201d60 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -193,7 +193,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_no_sql_should_be_fired_if_association_already_loaded Car.create(:name => 'honda') bulbs = Car.first.bulbs - bulbs.inspect # to load all instances of bulbs + bulbs.to_a # to load all instances of bulbs assert_no_queries do bulbs.first() diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 1093fedea1..fe385feb4a 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -792,6 +792,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end private + def cached_columns Topic.columns.find_all { |column| !Topic.serialized_attributes.include? column.name diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 232a542158..e34f505a02 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -872,24 +872,39 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on end - def test_multiparameter_assignment_of_aggregation_with_missing_values - ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do - customer = Customer.new - address = Address.new("The Street", "The City", "The Country") - attributes = { "address(2)" => address.city, "address(3)" => address.country } - customer.attributes = attributes - end - assert_equal("address", ex.errors[0].attribute) - end - - def test_multiparameter_assignment_of_aggregation_with_large_index - ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do - customer = Customer.new - address = Address.new("The Street", "The City", "The Country") - attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country } - customer.attributes = attributes + def test_multiparameter_attributes_setting_time_attribute + return skip "Oracle does not have TIME data type" if current_adapter? :OracleAdapter + + topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" ) + assert_equal 1, topic.bonus_time.hour + assert_equal 5, topic.bonus_time.min + end + + def test_multiparameter_attributes_setting_date_attribute + topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" ) + assert_equal 1952, topic.written_on.year + assert_equal 3, topic.written_on.month + assert_equal 11, topic.written_on.day + end + + def test_multiparameter_attributes_setting_date_and_time_attribute + topic = Topic.new( + "written_on(1i)" => "1952", + "written_on(2i)" => "3", + "written_on(3i)" => "11", + "written_on(4i)" => "13", + "written_on(5i)" => "55") + assert_equal 1952, topic.written_on.year + assert_equal 3, topic.written_on.month + assert_equal 11, topic.written_on.day + assert_equal 13, topic.written_on.hour + assert_equal 55, topic.written_on.min + end + + def test_multiparameter_attributes_setting_time_but_not_date_on_date_field + assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do + Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" ) end - assert_equal("address", ex.errors[0].attribute) end def test_attributes_on_dummy_time diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb index 4111a5f808..a7b63d15c9 100644 --- a/activerecord/test/cases/column_test.rb +++ b/activerecord/test/cases/column_test.rb @@ -76,6 +76,12 @@ module ActiveRecord date_string = Time.now.utc.strftime("%F") assert_equal date_string, column.type_cast(date_string).strftime("%F") end + + def test_type_cast_duration_to_integer + column = Column.new("field", nil, "integer") + assert_equal 1800, column.type_cast(30.minutes) + assert_equal 7200, column.type_cast(2.hours) + end end end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index afff020561..44d08a8ee4 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -5,7 +5,6 @@ require 'config' gem 'minitest' require 'minitest/autorun' require 'stringio' -require 'mocha' require 'cases/test_case' require 'active_record' diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb index 8726ba5e51..9b9c09d2d8 100644 --- a/activerecord/test/cases/inclusion_test.rb +++ b/activerecord/test/cases/inclusion_test.rb @@ -4,7 +4,7 @@ require 'models/teapot' class BasicInclusionModelTest < ActiveRecord::TestCase def test_basic_model Teapot.create!(:name => "Ronnie Kemper") - assert_equal "Ronnie Kemper", Teapot.find(1).name + assert_equal "Ronnie Kemper", Teapot.first.name end def test_initialization diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index ab61a4dcef..ce9be66069 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -291,14 +291,20 @@ module ActiveRecord def test_column_exists_with_definition connection.create_table :testings do |t| - t.column :foo, :string, :limit => 100 - t.column :bar, :decimal, :precision => 8, :scale => 2 + t.column :foo, :string, limit: 100 + t.column :bar, :decimal, precision: 8, scale: 2 + t.column :taggable_id, :integer, null: false + t.column :taggable_type, :string, default: 'Photo' end - assert connection.column_exists?(:testings, :foo, :string, :limit => 100) - refute connection.column_exists?(:testings, :foo, :string, :limit => 50) - assert connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2) - refute connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2) + assert connection.column_exists?(:testings, :foo, :string, limit: 100) + refute connection.column_exists?(:testings, :foo, :string, limit: nil) + assert connection.column_exists?(:testings, :bar, :decimal, precision: 8, scale: 2) + refute connection.column_exists?(:testings, :bar, :decimal, precision: nil, scale: nil) + assert connection.column_exists?(:testings, :taggable_id, :integer, null: false) + refute connection.column_exists?(:testings, :taggable_id, :integer, null: true) + assert connection.column_exists?(:testings, :taggable_type, :string, default: 'Photo') + refute connection.column_exists?(:testings, :taggable_type, :string, default: nil) end def test_column_exists_on_table_with_no_options_parameter_supplied diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 063209389f..4614be9650 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -30,61 +30,57 @@ module ActiveRecord def test_references_column_type_adds_id with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}] + @connection.expect :add_reference, nil, [:delete_me, :customer, {}] t.references :customer end end def test_remove_references_column_type_removes_id with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'customer_id'] + @connection.expect :remove_reference, nil, [:delete_me, :customer, {}] t.remove_references :customer end end def test_add_belongs_to_works_like_add_references with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}] + @connection.expect :add_reference, nil, [:delete_me, :customer, {}] t.belongs_to :customer end end def test_remove_belongs_to_works_like_remove_references with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'customer_id'] + @connection.expect :remove_reference, nil, [:delete_me, :customer, {}] t.remove_belongs_to :customer end end def test_references_column_type_with_polymorphic_adds_type with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}] - @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}] - t.references :taggable, :polymorphic => true + @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true] + t.references :taggable, polymorphic: true end end def test_remove_references_column_type_with_polymorphic_removes_type with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'taggable_id'] - @connection.expect :remove_column, nil, [:delete_me, 'taggable_type'] - t.remove_references :taggable, :polymorphic => true + @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true] + t.remove_references :taggable, polymorphic: true end end def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}] - @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}] - t.references :taggable, :polymorphic => true, :null => false + @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false] + t.references :taggable, polymorphic: true, null: false end end def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'taggable_id'] - @connection.expect :remove_column, nil, [:delete_me, 'taggable_type'] - t.remove_references :taggable, :polymorphic => true, :null => false + @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false] + t.remove_references :taggable, polymorphic: true, null: false end end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 7d026961be..f2213ee6aa 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -110,9 +110,9 @@ module ActiveRecord end def test_invert_add_index_with_name - @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}] - remove = @recorder.inverse.first - assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove + @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}] + remove = @recorder.inverse.first + assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove end def test_invert_add_index_with_no_options @@ -138,6 +138,30 @@ module ActiveRecord add = @recorder.inverse.first assert_equal [:add_timestamps, [:table]], add end + + def test_invert_add_reference + @recorder.record :add_reference, [:table, :taggable, { polymorphic: true }] + remove = @recorder.inverse.first + assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }]], remove + end + + def test_invert_add_belongs_to_alias + @recorder.record :add_belongs_to, [:table, :user] + remove = @recorder.inverse.first + assert_equal [:remove_reference, [:table, :user]], remove + end + + def test_invert_remove_reference + @recorder.record :remove_reference, [:table, :taggable, { polymorphic: true }] + add = @recorder.inverse.first + assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }]], add + end + + def test_invert_remove_belongs_to_alias + @recorder.record :remove_belongs_to, [:table, :user] + add = @recorder.inverse.first + assert_equal [:add_reference, [:table, :user]], add + end end end end diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb index 0428d9ba76..8b91b3bc92 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -10,60 +10,53 @@ module ActiveRecord @connection = ActiveRecord::Base.connection end + def teardown + super + %w(artists_musics musics_videos catalog).each do |table_name| + connection.drop_table table_name if connection.tables.include?(table_name) + end + end + def test_create_join_table connection.create_join_table :artists, :musics assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort - ensure - connection.drop_table :artists_musics end def test_create_join_table_set_not_null_by_default connection.create_join_table :artists, :musics assert_equal [false, false], connection.columns(:artists_musics).map(&:null) - ensure - connection.drop_table :artists_musics end def test_create_join_table_with_strings connection.create_join_table 'artists', 'musics' assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort - ensure - connection.drop_table :artists_musics end def test_create_join_table_with_the_proper_order connection.create_join_table :videos, :musics assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort - ensure - connection.drop_table :musics_videos end def test_create_join_table_with_the_table_name connection.create_join_table :artists, :musics, :table_name => :catalog assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort - ensure - connection.drop_table :catalog end def test_create_join_table_with_the_table_name_as_string connection.create_join_table :artists, :musics, :table_name => 'catalog' assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort - ensure - connection.drop_table :catalog end def test_create_join_table_with_column_options connection.create_join_table :artists, :musics, :column_options => {:null => true} assert_equal [true, true], connection.columns(:artists_musics).map(&:null) - ensure - connection.drop_table :artists_musics end end end diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb index fe53510ba2..768ebc5861 100644 --- a/activerecord/test/cases/migration/helper.rb +++ b/activerecord/test/cases/migration/helper.rb @@ -14,8 +14,10 @@ module ActiveRecord module TestHelper attr_reader :connection, :table_name + CONNECTION_METHODS = %w[add_column remove_column rename_column add_index change_column rename_table column_exists? index_exists? add_reference add_belongs_to remove_reference remove_references remove_belongs_to] + class TestModel < ActiveRecord::Base - self.table_name = 'test_models' + self.table_name = :test_models end def setup @@ -36,29 +38,8 @@ module ActiveRecord end private - def add_column(*args) - connection.add_column(*args) - end - - def remove_column(*args) - connection.remove_column(*args) - end - - def rename_column(*args) - connection.rename_column(*args) - end - def add_index(*args) - connection.add_index(*args) - end - - def change_column(*args) - connection.change_column(*args) - end - - def rename_table(*args) - connection.rename_table(*args) - end + delegate(*CONNECTION_METHODS, to: :connection) end end end diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb new file mode 100644 index 0000000000..144302bd4a --- /dev/null +++ b/activerecord/test/cases/migration/references_statements_test.rb @@ -0,0 +1,111 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class ReferencesStatementsTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + def setup + super + @table_name = :test_models + + add_column table_name, :supplier_id, :integer + add_index table_name, :supplier_id + end + + def test_creates_reference_id_column + add_reference table_name, :user + assert column_exists?(table_name, :user_id, :integer) + end + + def test_does_not_create_reference_type_column + add_reference table_name, :taggable + refute column_exists?(table_name, :taggable_type, :string) + end + + def test_creates_reference_type_column + add_reference table_name, :taggable, polymorphic: true + assert column_exists?(table_name, :taggable_type, :string) + end + + def test_creates_reference_id_index + add_reference table_name, :user, index: true + assert index_exists?(table_name, :user_id) + end + + def test_does_not_create_reference_id_index + add_reference table_name, :user + refute index_exists?(table_name, :user_id) + end + + def test_creates_polymorphic_index + add_reference table_name, :taggable, polymorphic: true, index: true + assert index_exists?(table_name, [:taggable_id, :taggable_type]) + end + + def test_creates_reference_type_column_with_default + add_reference table_name, :taggable, polymorphic: { default: 'Photo' }, index: true + assert column_exists?(table_name, :taggable_type, :string, default: 'Photo') + end + + def test_creates_named_index + add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id' } + assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id') + end + + def test_deletes_reference_id_column + remove_reference table_name, :supplier + refute column_exists?(table_name, :supplier_id, :integer) + end + + def test_deletes_reference_id_index + remove_reference table_name, :supplier + refute index_exists?(table_name, :supplier_id) + end + + def test_does_not_delete_reference_type_column + with_polymorphic_column do + remove_reference table_name, :supplier + + refute column_exists?(table_name, :supplier_id, :integer) + assert column_exists?(table_name, :supplier_type, :string) + end + end + + def test_deletes_reference_type_column + with_polymorphic_column do + remove_reference table_name, :supplier, polymorphic: true + refute column_exists?(table_name, :supplier_type, :string) + end + end + + def test_deletes_polymorphic_index + with_polymorphic_column do + remove_reference table_name, :supplier, polymorphic: true + refute index_exists?(table_name, [:supplier_id, :supplier_type]) + end + end + + def test_add_belongs_to_alias + add_belongs_to table_name, :user + assert column_exists?(table_name, :user_id, :integer) + end + + def test_remove_belongs_to_alias + remove_belongs_to table_name, :supplier + refute column_exists?(table_name, :supplier_id, :integer) + end + + private + + def with_polymorphic_column + add_column table_name, :supplier_type, :string + add_index table_name, [:supplier_id, :supplier_type] + + yield + end + end + end +end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 3daa033ed0..3a234f0cc1 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -196,7 +196,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to - assert_raise_with_message ArgumentError, "Cannot build association looter. Are you trying to build a polymorphic one-to-one association?" do + assert_raise_with_message ArgumentError, "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?" do Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"}) end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 08f655d7fa..0153e74604 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -164,6 +164,14 @@ class QueryCacheTest < ActiveRecord::TestCase end end end + + def test_cache_is_ignored_for_locked_relations + task = Task.find 1 + + Task.cache do + assert_queries(2) { task.lock!; task.lock! } + end + end end class QueryCacheExpiryTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 80ee74e41e..3dd11ae89d 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -216,6 +216,14 @@ module ActiveRecord def test_string_with_crazy_column assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo)) end + + def test_quote_duration + assert_equal "1800", @quoter.quote(30.minutes) + end + + def test_quote_duration_int_column + assert_equal "7200", @quoter.quote(2.hours, FakeColumn.new(:integer)) + end end end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 8544d36aa8..8713b8d5e4 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1311,4 +1311,25 @@ class RelationTest < ActiveRecord::TestCase relation.merge! where: 'foo' end end + + test "relations show the records in #inspect" do + relation = Post.limit(2) + assert_equal "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>", relation.inspect + end + + test "relations limit the records in #inspect at 10" do + relation = Post.limit(11) + assert_equal "#<ActiveRecord::Relation [#{Post.limit(10).map(&:inspect).join(', ')}, ...]>", relation.inspect + end + + test "already-loaded relations don't perform a new query in #inspect" do + relation = Post.limit(2) + relation.to_a + + expected = "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>" + + assert_no_queries do + assert_equal expected, relation.inspect + end + end end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index a4c065e667..ce167509c1 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -51,4 +51,11 @@ class SerializationTest < ActiveRecord::TestCase assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}" end end + + def test_serialized_attributes_are_class_level_settings + assert_raise NoMethodError do + topic = Topic.new + topic.serialized_attributes = [] + end + end end diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index e401dd4b12..3e60b62fd5 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -34,6 +34,11 @@ class StoreTest < ActiveRecord::TestCase assert @john.settings_changed? end + test "updating the store won't mark it as changed if an attribute isn't changed" do + @john.color = @john.color + assert !@john.settings_changed? + end + test "object initialization with not nullable column" do assert_equal true, @john.remember_login end @@ -115,4 +120,11 @@ class StoreTest < ActiveRecord::TestCase test "stored attributes are returned" do assert_equal [:color, :homepage], Admin::User.stored_attributes[:settings] end + + test "stores_attributes are class level settings" do + assert_raise NoMethodError do + @john.stored_attributes = {} + end + end + end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 5f36b2c841..4f3489b7a5 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -16,6 +16,22 @@ module ActiveRecord :postgresql => :postgresql_tasks, :sqlite3 => :sqlite_tasks } + + class DatabaseTasksRegisterTask < ActiveRecord::TestCase + def test_register_task + klazz = Class.new do + def initialize(*arguments); end + def structure_dump(filename); end + end + instance = klazz.new + + klazz.stubs(:new).returns instance + instance.expects(:structure_dump).with("awesome-file.sql") + + ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz) + ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :foo}, "awesome-file.sql") + end + end class DatabaseTasksCreateTest < ActiveRecord::TestCase include DatabaseTasksSetupper @@ -251,6 +267,17 @@ module ActiveRecord end end + class DatabaseTasksCollationTest < ActiveRecord::TestCase + include DatabaseTasksSetupper + + ADAPTERS_TASKS.each do |k, v| + define_method("test_#{k}_collation") do + eval("@#{v}").expects(:collation) + ActiveRecord::Tasks::DatabaseTasks.collation 'adapter' => k + end + end + end + class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase include DatabaseTasksSetupper diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 42a11b0171..9a0eb423bd 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -195,6 +195,24 @@ module ActiveRecord end end + class MysqlDBCollationTest < ActiveRecord::TestCase + def setup + @connection = stub(:create_database => true) + @configuration = { + 'adapter' => 'mysql', + 'database' => 'my-app-db' + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_collation + @connection.expects(:collation) + ActiveRecord::Tasks::DatabaseTasks.collation @configuration + end + end + class MySQLStructureDumpTest < ActiveRecord::TestCase def setup @connection = stub(:structure_dump => true) diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index e8769bd4df..62acd53003 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -38,6 +38,14 @@ module ActiveRecord merge('encoding' => 'latin') end + def test_creates_database_with_given_collation_and_ctype + @connection.expects(:create_database). + with('my-app-db', @configuration.merge('encoding' => 'utf8', 'collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8')) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration. + merge('collation' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8') + end + def test_establishes_connection_to_new_database ActiveRecord::Base.expects(:establish_connection).with(@configuration) @@ -151,6 +159,24 @@ module ActiveRecord end end + class PostgreSQLDBCollationTest < ActiveRecord::TestCase + def setup + @connection = stub(:create_database => true) + @configuration = { + 'adapter' => 'postgresql', + 'database' => 'my-app-db' + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_collation + @connection.expects(:collation) + ActiveRecord::Tasks::DatabaseTasks.collation @configuration + end + end + class PostgreSQLStructureDumpTest < ActiveRecord::TestCase def setup @connection = stub(:structure_dump => true) diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb index b5557fc953..06a1d0ffc2 100644 --- a/activerecord/test/cases/tasks/sqlite_rake_test.rb +++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb @@ -124,6 +124,27 @@ module ActiveRecord end end + class SqliteDBCollationTest < ActiveRecord::TestCase + def setup + @database = 'db_create.sqlite3' + @connection = stub :connection + @configuration = { + 'adapter' => 'sqlite3', + 'database' => @database + } + + File.stubs(:exist?).returns(false) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_collation + assert_raise NoMethodError do + ActiveRecord::Tasks::DatabaseTasks.collation @configuration, '/rails/root' + end + end + end + class SqliteStructureDumpTest < ActiveRecord::TestCase def setup @database = "db_create.sqlite3" diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 9fefa9ad3a..43cde4ab73 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -64,7 +64,6 @@ class AuditLog < ActiveRecord::Base belongs_to :unvalidated_developer, :class_name => 'Developer' end -DeveloperSalary = Struct.new(:amount) class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base self.table_name = 'developers' has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id' diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 65b6f9f227..24a43d7ece 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -32,4 +32,13 @@ CREATE TABLE collation_tests ( ) CHARACTER SET utf8 COLLATE utf8_general_ci SQL + ActiveRecord::Base.connection.execute <<-SQL +DROP TABLE IF EXISTS enum_tests; +SQL + + ActiveRecord::Base.connection.execute <<-SQL +CREATE TABLE enum_tests ( + enum_column ENUM('true','false') +) +SQL end diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb index 7d324f98c4..802c08b819 100644 --- a/activerecord/test/schema/mysql_specific_schema.rb +++ b/activerecord/test/schema/mysql_specific_schema.rb @@ -43,4 +43,14 @@ CREATE TABLE collation_tests ( ) CHARACTER SET utf8 COLLATE utf8_general_ci SQL + ActiveRecord::Base.connection.execute <<-SQL +DROP TABLE IF EXISTS enum_tests; +SQL + + ActiveRecord::Base.connection.execute <<-SQL +CREATE TABLE enum_tests ( + enum_column ENUM('true','false') +) +SQL + end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 87b956e3fc..df144dd00b 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,8 +1,10 @@ ## Rails 4.0.0 (unreleased) ## -* AS::Callbacks: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev* +* `Time#change` now works with time values with offsets other than UTC or the local time zone. *Andrew White* -* Add `Time#prev_quarter' and 'Time#next_quarter' short-hands for months_ago(3) and months_since(3). *SungHee Kang* +* `ActiveSupport::Callbacks`: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev* + +* Add `Time#prev_quarter` and `Time#next_quarter` short-hands for `months_ago(3)` and `months_since(3)`. *SungHee Kang* * Remove obsolete and unused `require_association` method from dependencies. *fxn* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index fa38d5c1e3..836bc2f9cf 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -22,4 +22,5 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.6') s.add_dependency('multi_json', '~> 1.3') s.add_dependency('tzinfo', '~> 0.3.33') + s.add_dependency('minitest', '~> 3.2') end diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 13d659f52a..7c3a5eaace 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -4,10 +4,6 @@ require 'active_support/core_ext/date_time/calculations' require 'active_support/values/time_zone' class DateTime - # Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows - # DateTimes outside the range of what can be created with Time. - remove_method :to_time - # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. # # This method is aliased to <tt>to_s</tt>. @@ -57,16 +53,6 @@ class DateTime alias_method :default_inspect, :inspect alias_method :inspect, :readable_inspect - # Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class. - # If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time. - def to_time - if offset == 0 - ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000) - else - self - end - end - # Returns DateTime with local offset for given year if format is local else offset is zero # # DateTime.civil_from_format :local, 2012 @@ -94,8 +80,11 @@ class DateTime private + def offset_in_seconds + (offset * 86400).to_i + end + def seconds_since_unix_epoch - seconds_per_day = 86_400 - (self - ::DateTime.civil(1970)) * seconds_per_day + (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight end end diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 5a61906222..c82da3c6c2 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -4,13 +4,6 @@ class Hash # # @person.update_attributes(params[:person].except(:admin)) # - # If the receiver responds to +convert_key+, the method is called on each of the - # arguments. This allows +except+ to play nice with hashes with indifferent access - # for instance: - # - # {:a => 1}.with_indifferent_access.except(:a) # => {} - # {:a => 1}.with_indifferent_access.except('a') # => {} - # def except(*keys) dup.except!(*keys) end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 8e728691c6..e753e36124 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -7,7 +7,7 @@ class Hash # # => { "NAME" => "Rob", "AGE" => "28" } def transform_keys result = {} - keys.each do |key| + each_key do |key| result[yield(key)] = self[key] end result diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index e8623a9c54..0a71fc117c 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -86,16 +86,21 @@ class Time # (hour, min, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and # minute is passed, then sec and usec is set to 0. def change(options) - ::Time.send( - utc? ? :utc_time : :local_time, - options.fetch(:year, year), - options.fetch(:month, month), - options.fetch(:day, day), - options.fetch(:hour, hour), - options.fetch(:min, options[:hour] ? 0 : min), - options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec), - options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) - ) + new_year = options.fetch(:year, year) + new_month = options.fetch(:month, month) + new_day = options.fetch(:day, day) + new_hour = options.fetch(:hour, hour) + new_min = options.fetch(:min, options[:hour] ? 0 : min) + new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + + if utc? + ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + elsif zone + ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) + else + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) + end end # Uses Date to provide precise Time calculations for years, months, and days. diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 48c39d9370..1cc852a3e6 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -101,7 +101,19 @@ module ActiveSupport end def updated_at(paths) - @updated_at || paths.map { |path| File.mtime(path) }.max || Time.at(0) + @updated_at || max_mtime(paths) || Time.at(0) + end + + # This method returns the maximum mtime of the files in +paths+, or +nil+ + # if the array is empty. + # + # Files with a mtime in the future are ignored. Such abnormal situation + # can happen for example if the user changes the clock by hand. It is + # healthy to consider this edge case because with mtimes in the future + # reloading is not triggered. + def max_mtime(paths) + time_now = Time.now + paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max end def compile_glob(hash) diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index c04c2ed15b..ca2d8cb270 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,3 +1,5 @@ +require 'active_support/inflector/inflections' + module ActiveSupport Inflector.inflections do |inflect| inflect.plural(/$/, 's') diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 600e353812..c9e50a9462 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -2,6 +2,8 @@ require 'active_support/core_ext/array/prepend_and_append' module ActiveSupport module Inflector + extend self + # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional # inflection rules. # diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 2acc6ddee5..c14a43de0d 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,6 +1,7 @@ # encoding: utf-8 require 'active_support/inflector/inflections' +require 'active_support/inflections' module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index e2b46a235a..d2b8e602f9 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -1,24 +1,21 @@ +gem 'minitest' # make sure we get the gem, not stdlib require 'minitest/spec' require 'active_support/testing/setup_and_teardown' require 'active_support/testing/assertions' require 'active_support/testing/deprecation' -require 'active_support/testing/declarative' require 'active_support/testing/isolation' -require 'active_support/testing/mochaing' +require 'active_support/testing/mocha_module' require 'active_support/core_ext/kernel/reporting' +require 'active_support/deprecation' module ActiveSupport class TestCase < ::MiniTest::Spec - if MiniTest::Unit::VERSION < '2.6.1' - class << self - alias :name :to_s - end - end + include ActiveSupport::Testing::MochaModule # Use AS::TestCase for the base class when describing a model register_spec_type(self) do |desc| - desc < ActiveRecord::Model + Class === desc && desc < ActiveRecord::Model end Assertion = MiniTest::Assertion @@ -38,7 +35,24 @@ module ActiveSupport include ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation - extend ActiveSupport::Testing::Declarative + + def self.describe(text) + if block_given? + super + else + ActiveSupport::Deprecation.warn("`describe` without a block is deprecated, please switch to: `def self.name; #{text.inspect}; end`\n") + + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def self.name + "#{text}" + end + RUBY_EVAL + end + end + + class << self + alias :test :it + end # test/unit backwards compatibility methods alias :assert_raise :assert_raises diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb deleted file mode 100644 index 508e37254a..0000000000 --- a/activesupport/lib/active_support/testing/declarative.rb +++ /dev/null @@ -1,40 +0,0 @@ -module ActiveSupport - module Testing - module Declarative - - def self.extended(klass) #:nodoc: - klass.class_eval do - - unless method_defined?(:describe) - def self.describe(text) - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def self.name - "#{text}" - end - RUBY_EVAL - end - end - - end - end - - unless defined?(Spec) - # test "verify something" do - # ... - # end - def test(name, &block) - test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym - defined = instance_method(test_name) rescue false - raise "#{test_name} is already defined in #{self}" if defined - if block_given? - define_method(test_name, &block) - else - define_method(test_name) do - flunk "No implementation provided for #{name}" - end - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb new file mode 100644 index 0000000000..ed2942d23a --- /dev/null +++ b/activesupport/lib/active_support/testing/mocha_module.rb @@ -0,0 +1,22 @@ +module ActiveSupport + module Testing + module MochaModule + begin + require 'mocha_standalone' + include Mocha::API + + def before_setup + mocha_setup + super + end + + def after_teardown + super + mocha_verify + mocha_teardown + end + rescue LoadError + end + end + end +end diff --git a/activesupport/lib/active_support/testing/mochaing.rb b/activesupport/lib/active_support/testing/mochaing.rb deleted file mode 100644 index 4ad75a6681..0000000000 --- a/activesupport/lib/active_support/testing/mochaing.rb +++ /dev/null @@ -1,7 +0,0 @@ -begin - silence_warnings { require 'mocha' } -rescue LoadError - # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh. - Object.const_set :Mocha, Module.new - Mocha.const_set :ExpectationError, Class.new(StandardError) -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 527fa555b7..a65148cf1f 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -4,20 +4,11 @@ require 'active_support/callbacks' module ActiveSupport module Testing module SetupAndTeardown - - PASSTHROUGH_EXCEPTIONS = [ - NoMemoryError, - SignalException, - Interrupt, - SystemExit - ] - extend ActiveSupport::Concern included do include ActiveSupport::Callbacks define_callbacks :setup, :teardown - end module ClassMethods @@ -30,28 +21,15 @@ module ActiveSupport end end - def run(runner) - result = '.' - begin - run_callbacks :setup do - result = super - end - rescue *PASSTHROUGH_EXCEPTIONS - raise - rescue Exception => e - result = runner.puke(self.class, method_name, e) - ensure - begin - run_callbacks :teardown - rescue *PASSTHROUGH_EXCEPTIONS - raise - rescue Exception => e - result = runner.puke(self.class, method_name, e) - end - end - result + def before_setup + super + run_callbacks :setup end + def after_teardown + run_callbacks :teardown + super + end end end end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 28bc06f103..cc3e6a4bf3 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -254,8 +254,7 @@ module ActiveSupport # Time.utc(2000).to_f # => 946684800.0 # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 def at(secs) - utc = Time.at(secs).utc rescue DateTime.civil(1970).since(secs) - utc.in_time_zone(self) + Time.at(secs).utc.in_time_zone(self) end # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 57ed4a6b60..25ed962c23 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -18,8 +18,6 @@ end require 'minitest/autorun' require 'empty_bool' -silence_warnings { require 'mocha' } - ENV['NO_RELOAD'] = '1' require 'active_support' diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 183d58482d..21b7efdc73 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -427,6 +427,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase def test_to_i assert_equal 946684800, DateTime.civil(2000).to_i + assert_equal 946684800, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_i end protected diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index d6f285598e..412aef9301 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -459,6 +459,15 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45) end + def test_offset_change + assert_equal Time.new(2006,2,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2006) + assert_equal Time.new(2005,6,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:month => 6) + assert_equal Time.new(2012,9,22,15,15,10,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:year => 2012, :month => 9) + assert_equal Time.new(2005,2,22,16,0,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16) + assert_equal Time.new(2005,2,22,16,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:hour => 16, :min => 45) + assert_equal Time.new(2005,2,22,15,45,0,"-08:00"), Time.new(2005,2,22,15,15,10,"-08:00").change(:min => 45) + end + def test_advance assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1) assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4) @@ -503,6 +512,28 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) end + def test_offset_advance + assert_equal Time.new(2006,2,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 1) + assert_equal Time.new(2005,6,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:months => 4) + assert_equal Time.new(2005,3,21,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3) + assert_equal Time.new(2005,3,25,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.5) + assert_in_delta Time.new(2005,3,26,12,51,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:weeks => 3.7), 1 + assert_equal Time.new(2005,3,5,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5) + assert_equal Time.new(2005,3,6,3,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.5) + assert_in_delta Time.new(2005,3,6,8,3,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:days => 5.7), 1 + assert_equal Time.new(2012,9,22,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 7) + assert_equal Time.new(2013,10,3,15,15,10,'-08:00'), Time.new(2005,2,22,15,15,10,'-08:00').advance(:years => 7, :months => 19, :days => 11) + assert_equal Time.new(2013,10,17,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5) + assert_equal Time.new(2001,12,27,15,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => -3, :months => -2, :days => -1) + assert_equal Time.new(2005,2,28,15,15,10,'-08:00'), Time.new(2004,2,29,15,15,10,'-08:00').advance(:years => 1) #leap day plus one year + assert_equal Time.new(2005,2,28,20,15,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5) + assert_equal Time.new(2005,2,28,15,22,10,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:minutes => 7) + assert_equal Time.new(2005,2,28,15,15,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:seconds => 9) + assert_equal Time.new(2005,2,28,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => 5, :minutes => 7, :seconds => 9) + assert_equal Time.new(2005,2,28,10,8,1,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:hours => -5, :minutes => -7, :seconds => -9) + assert_equal Time.new(2013,10,17,20,22,19,'-08:00'), Time.new(2005,2,28,15,15,10,'-08:00').advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9) + end + def test_advance_with_nsec t = Time.at(0, Rational(108635108, 1000)) assert_equal t, t.advance(:months => 0) diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb index 8adff5de8d..bd1df0f858 100644 --- a/activesupport/test/file_update_checker_test.rb +++ b/activesupport/test/file_update_checker_test.rb @@ -48,6 +48,21 @@ class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase assert_equal 1, i end + def test_should_be_robust_to_handle_files_with_wrong_modified_time + i = 0 + now = Time.now + time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future + File.utime time, time, FILES[2] + + checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } + + sleep(1) + FileUtils.touch(FILES[0..1]) + + assert checker.execute_if_updated + assert_equal 1, i + end + def test_should_cache_updated_result_until_execute i = 0 checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } diff --git a/guides/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb index 8bf1192ffe..3daca18a71 100644 --- a/guides/code/getting_started/test/test_helper.rb +++ b/guides/code/getting_started/test/test_helper.rb @@ -3,7 +3,7 @@ require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting diff --git a/guides/source/2_2_release_notes.textile b/guides/source/2_2_release_notes.textile index 3a0f2efbaf..eb4b32329b 100644 --- a/guides/source/2_2_release_notes.textile +++ b/guides/source/2_2_release_notes.textile @@ -118,9 +118,9 @@ h4. Transactional Migrations Historically, multiple-step Rails migrations have been a source of trouble. If something went wrong during a migration, everything before the error changed the database and everything after the error wasn't applied. Also, the migration version was stored as having been executed, which means that it couldn't be simply rerun by +rake db:migrate:redo+ after you fix the problem. Transactional migrations change this by wrapping migration steps in a DDL transaction, so that if any of them fail, the entire migration is undone. In Rails 2.2, transactional migrations are supported on PostgreSQL out of the box. The code is extensible to other database types in the future - and IBM has already extended it to support the DB2 adapter. -* Lead Contributor: "Adam Wiggins":http://adam.blog.heroku.com/ +* Lead Contributor: "Adam Wiggins":http://adam.heroku.com/ * More information: -** "DDL Transactions":http://adam.blog.heroku.com/past/2008/9/3/ddl_transactions/ +** "DDL Transactions":http://adam.heroku.com/past/2008/9/3/ddl_transactions/ ** "A major milestone for DB2 on Rails":http://db2onrails.com/2008/11/08/a-major-milestone-for-db2-on-rails/ h4. Connection Pooling diff --git a/guides/source/2_3_release_notes.textile b/guides/source/2_3_release_notes.textile index 15abba66ab..36f425574b 100644 --- a/guides/source/2_3_release_notes.textile +++ b/guides/source/2_3_release_notes.textile @@ -561,7 +561,7 @@ This will layer the changes from the template on top of whatever code the projec h4. Quieter Backtraces -Building on Thoughtbot's "Quiet Backtrace":http://www.thoughtbot.com/projects/quietbacktrace plugin, which allows you to selectively remove lines from +Test::Unit+ backtraces, Rails 2.3 implements +ActiveSupport::BacktraceCleaner+ and +Rails::BacktraceCleaner+ in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a +config/backtrace_silencers.rb+ file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace. +Building on Thoughtbot's "Quiet Backtrace":https://github.com/thoughtbot/quietbacktrace plugin, which allows you to selectively remove lines from +Test::Unit+ backtraces, Rails 2.3 implements +ActiveSupport::BacktraceCleaner+ and +Rails::BacktraceCleaner+ in core. This supports both filters (to perform regex-based substitutions on backtrace lines) and silencers (to remove backtrace lines entirely). Rails automatically adds silencers to get rid of the most common noise in a new application, and builds a +config/backtrace_silencers.rb+ file to hold your own additions. This feature also enables prettier printing from any gem in the backtrace. h4. Faster Boot Time in Development Mode with Lazy Loading/Autoload diff --git a/guides/source/4_0_release_notes.textile b/guides/source/4_0_release_notes.textile index 23c5220c24..895372ba63 100644 --- a/guides/source/4_0_release_notes.textile +++ b/guides/source/4_0_release_notes.textile @@ -64,6 +64,16 @@ h3. Documentation h3. Railties +* Allow scaffold/model/migration generators to accept a <tt>polymorphic</tt> modifier for <tt>references</tt>/<tt>belongs_to</tt>, for instance + +<shell> +rails g model Product supplier:references{polymorphic} +</shell> + +will generate the model with <tt>belongs_to :supplier, polymorphic: true</tt> association and appropriate migration. + +* Set <tt>config.active_record.migration_error</tt> to <tt>:page_load</tt> for development. + * Add runner to <tt>Rails::Railtie</tt> as a hook called just after runner starts. * Add <tt>/rails/info/routes</tt> path which displays the same information as +rake routes+. @@ -102,12 +112,42 @@ h4(#railties_deprecations). Deprecations h3. Action Mailer -* No changes. +* Allow to set default Action Mailer options via <tt>config.action_mailer.default_options=</tt>. + +* Raise an <tt>ActionView::MissingTemplate</tt> exception when no implicit template could be found. + +* Asynchronously send messages via the Rails Queue. h3. Action Pack h4. Action Controller +* Add <tt>ActionController::Flash.add_flash_types</tt> method to allow people to register their own flash types. e.g.: + +<ruby> +class ApplicationController + add_flash_types :error, :warning +end +</ruby> + +If you add the above code, you can use <tt><%= error %></tt> in an erb, and <tt>redirect_to /foo, :error => 'message'</tt> in a controller. + +* Remove Active Model dependency from Action Pack. + +* Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping: + +<ruby> +get Rack::Utils.escape('こんにちは') => 'home#index' +</ruby> + +You just have to write the unicode route: + +<ruby> +get 'こんにちは' => 'home#index' +</ruby> + +* Return proper format on exceptions. + * Extracted redirect logic from <tt>ActionController::ForceSSL::ClassMethods.force_ssl</tt> into <tt>ActionController::ForceSSL#force_ssl_redirect</tt>. * URL path parameters with invalid encoding now raise <tt>ActionController::BadRequest</tt>. @@ -156,6 +196,10 @@ h5(#actioncontroller_deprecations). Deprecations h4. Action Dispatch +* Show routes in exception page while debugging a <tt>RoutingError</tt> in development. + +* Include <tt>mounted_helpers</tt> (helpers for accessing mounted engines) in <tt>ActionDispatch::IntegrationTest</tt> by default. + * Added <tt>ActionDispatch::SSL</tt> middleware that when included force all the requests to be under HTTPS protocol. * Copy literal route constraints to defaults so that url generation know about them. The copied constraints are <tt>:protocol</tt>, <tt>:subdomain</tt>, <tt>:domain</tt>, <tt>:host</tt> and <tt>:port</tt>. @@ -179,12 +223,14 @@ If <tt>:patch</tt> is the default verb for updates, edits are tunneled as <tt>PA * Turn off verbose mode of <tt>rack-cache</tt>, we still have <tt>X-Rack-Cache</tt> to check that info. -* Include mounted_helpers (helpers for accessing mounted engines) in <tt>ActionDispatch::IntegrationTest</tt> by default. - h5(#actiondispatch_deprecations). Deprecations h4. Action View +* Remove Active Model dependency from Action Pack. + +* Allow to use <tt>mounted_helpers</tt> (helpers for accessing mounted engines) in <tt>ActionView::TestCase</tt>. + * Make current object and counter (when it applies) variables accessible when rendering templates with <tt>:object</tt> or <tt>:collection</tt>. * Allow to lazy load +default_form_builder+ by passing a string instead of a constant. @@ -205,8 +251,6 @@ h4. Action View * Removed old +text_helper+ apis for +highlight+, +excerpt+ and +word_wrap+. -* Allow to use mounted_helpers (helpers for accessing mounted engines) in <tt>ActionView::TestCase</tt>. - * Remove the leading \n added by textarea on +assert_select+. * Changed default value for <tt>config.action_view.embed_authenticity_token_in_remote_forms</tt> to false. This change breaks remote forms that need to work also without JavaScript, so if you need such behavior, you can either set it to true or explicitly pass <tt>:authenticity_token => true</tt> in form options. @@ -299,6 +343,146 @@ Moved into a separate gem <tt>sprockets-rails</tt>. h3. Active Record +* Add <tt>add_reference</tt> and <tt>remove_reference</tt> schema statements. Aliases, <tt>add_belongs_to</tt> and <tt>remove_belongs_to</tt> are acceptable. References are reversible. + +<ruby> +# Create a user_id column +add_reference(:products, :user) + +# Create a supplier_id, supplier_type columns and appropriate index +add_reference(:products, :supplier, polymorphic: true, index: true) + +# Remove polymorphic reference +remove_reference(:products, :supplier, polymorphic: true) +</ruby> + + +* Add <tt>:default</tt> and <tt>:null</tt> options to <tt>column_exists?</tt>. + +<ruby> +column_exists?(:testings, :taggable_id, :integer, null: false) +column_exists?(:testings, :taggable_type, :string, default: 'Photo') +</ruby> + +* <tt>ActiveRecord::Relation#inspect</tt> now makes it clear that you are dealing with a <tt>Relation</tt> object rather than an array: + +<ruby> +User.where(:age => 30).inspect +# => <ActiveRecord::Relation [#<User ...>, #<User ...>]> + +User.where(:age => 30).to_a.inspect +# => [#<User ...>, #<User ...>] +</ruby> + +if more than 10 items are returned by the relation, inspect will only show the first 10 followed by ellipsis. + +* Add <tt>:collation</tt> and <tt>:ctype</tt> support to PostgreSQL. These are available for PostgreSQL 8.4 or later. + +<yaml> +development: + adapter: postgresql + host: localhost + database: rails_development + username: foo + password: bar + encoding: UTF8 + collation: ja_JP.UTF8 + ctype: ja_JP.UTF8 +</yaml> + +* <tt>FinderMethods#exists?</tt> now returns <tt>false</tt> with the <tt>false</tt> argument. + +* Added support for specifying the precision of a timestamp in the postgresql adapter. So, instead of having to incorrectly specify the precision using the <tt>:limit</tt> option, you may use <tt>:precision</tt>, as intended. For example, in a migration: + +<ruby> +def change + create_table :foobars do |t| + t.timestamps :precision => 0 + end +end +</ruby> + +* Allow <tt>ActiveRecord::Relation#pluck</tt> to accept multiple columns. Returns an array of arrays containing the typecasted values: + +<ruby> +Person.pluck(:id, :name) +# SELECT people.id, people.name FROM people +# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] +</ruby> + +* Improve the derivation of HABTM join table name to take account of nesting. It now takes the table names of the two models, sorts them lexically and then joins them, stripping any common prefix from the second table name. Some examples: + +<plain> +Top level models (Category <=> Product) +Old: categories_products +New: categories_products + +Top level models with a global table_name_prefix (Category <=> Product) +Old: site_categories_products +New: site_categories_products + +Nested models in a module without a table_name_prefix method (Admin::Category <=> Admin::Product) +Old: categories_products +New: categories_products + +Nested models in a module with a table_name_prefix method (Admin::Category <=> Admin::Product) +Old: categories_products +New: admin_categories_products + +Nested models in a parent model (Catalog::Category <=> Catalog::Product) +Old: categories_products +New: catalog_categories_products + +Nested models in different parent models (Catalog::Category <=> Content::Page) +Old: categories_pages +New: catalog_categories_content_pages +</plain> + +* Move HABTM validity checks to <tt>ActiveRecord::Reflection</tt>. One side effect of this is to move when the exceptions are raised from the point of declaration to when the association is built. This is consistant with other association validity checks. + +* Added <tt>stored_attributes</tt> hash which contains the attributes stored using <tt>ActiveRecord::Store</tt>. This allows you to retrieve the list of attributes you've defined. + +<ruby> +class User < ActiveRecord::Base + store :settings, accessors: [:color, :homepage] +end + +User.stored_attributes[:settings] # [:color, :homepage] +</ruby> + +* <tt>composed_of</tt> was removed. You'll have to write your own accessor and mutator methods if you'd like to use value objects to represent some portion of your models. So, instead of: + +<ruby> +class Person < ActiveRecord::Base + composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] +end +</ruby> + +you could write something like this: + +<ruby> +def address + @address ||= Address.new(address_street, address_city) +end + +def address=(address) + self[:address_street] = @address.street + self[:address_city] = @address.city + + @address = address +end +</ruby> + +* PostgreSQL default log level is now 'warning', to bypass the noisy notice messages. You can change the log level using the <tt>min_messages</tt> option available in your <tt>config/database.yml</tt>. + +* Add uuid datatype support to PostgreSQL adapter. + +* <tt>update_attribute</tt> has been removed. Use <tt>update_column</tt> if you want to bypass mass-assignment protection, validations, callbacks, and touching of updated_at. Otherwise please use <tt>update_attributes</tt>. + +* Added <tt>ActiveRecord::Migration.check_pending!</tt> that raises an error if migrations are pending. + +* Added <tt>#destroy!</tt> which acts like <tt>#destroy</tt> but will raise an <tt>ActiveRecord::RecordNotDestroyed</tt> exception instead of returning <tt>false</tt>. + * Allow blocks for count with <tt>ActiveRecord::Relation</tt>, to work similar as <tt>Array#count</tt>: <tt>Person.where("age > 26").count { |person| person.gender == 'female' }</tt> * Added support to <tt>CollectionAssociation#delete</tt> for passing fixnum or string values as record ids. This finds the records responding to the ids and deletes them. @@ -355,7 +539,7 @@ Post.find_by! name: 'Spartacus' * Added <tt>ActiveRecord::Base#slice</tt> to return a hash of the given methods with their names as keys and returned values as values. -* Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this commit: https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed. +* Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this "commit":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed. * Added a feature to dump/load internal state of +SchemaCache+ instance because we want to boot more quickly when we have many models. @@ -475,27 +659,37 @@ The code to implement the deprecated features has been moved out to the +active_ Don't use this: - scope :red, where(color: 'red') - default_scope where(color: 'red') +<ruby> +scope :red, where(color: 'red') +default_scope where(color: 'red') +</ruby> Use this: - scope :red, -> { where(color: 'red') } - default_scope { where(color: 'red') } +<ruby> +scope :red, -> { where(color: 'red') } +default_scope { where(color: 'red') } +</ruby> The former has numerous issues. It is a common newbie gotcha to do the following: - scope :recent, where(published_at: Time.now - 2.weeks) +<ruby> +scope :recent, where(published_at: Time.now - 2.weeks) +</ruby> Or a more subtle variant: - scope :recent, -> { where(published_at: Time.now - 2.weeks) } - scope :recent_red, recent.where(color: 'red') +<ruby> +scope :recent, -> { where(published_at: Time.now - 2.weeks) } +scope :recent_red, recent.where(color: 'red') +</ruby> Eager scopes are also very complex to implement within Active Record, and there are still bugs. For example, the following does not do what you expect: - scope :remove_conditions, except(:where) - where(...).remove_conditions # => still has conditions +<ruby> +scope :remove_conditions, except(:where) +where(...).remove_conditions # => still has conditions +</ruby> * Added deprecation for the :dependent => :restrict association option. @@ -507,6 +701,32 @@ The code to implement the deprecated features has been moved out to the +active_ h3. Active Model +* Changed <tt>AM::Serializers::JSON.include_root_in_json</tt> default value to false. Now, AM Serializers and AR objects have the same default behaviour. + +<ruby> +class User < ActiveRecord::Base; end + +class Person + include ActiveModel::Model + include ActiveModel::AttributeMethods + include ActiveModel::Serializers::JSON + + attr_accessor :name, :age + + def attributes + instance_values + end +end + +user.as_json +=> {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true} +# root is not included + +person.as_json +=> {"name"=>"Francesco", "age"=>22} +# root is not included +</ruby> + * Passing false hash values to +validates+ will no longer enable the corresponding validators. * +ConfirmationValidator+ error messages will attach to <tt>:#{attribute}_confirmation</tt> instead of +attribute+. @@ -521,10 +741,28 @@ h4(#activemodel_deprecations). Deprecations h3. Active Resource -* Active Resource is removed from Rails 4.0 and is now a separate gem. TODO: put a link to the gem here. +* Active Resource is removed from Rails 4.0 and is now a separate "gem":https://github.com/rails/activeresource. h3. Active Support +* <tt>Time#change</tt> now works with time values with offsets other than UTC or the local time zone. + +* Add <tt>Time#prev_quarter</tt> and <tt>Time#next_quarter</tt> short-hands for <tt>months_ago(3)</tt> and <tt>months_since(3)</tt>. + +* Remove obsolete and unused <tt>require_association</tt> method from dependencies. + +* Add <tt>:instance_accessor</tt> option for <tt>config_accessor</tt>. + +<ruby> +class User + include ActiveSupport::Configurable + config_accessor :allowed_access, instance_accessor: false +end + +User.new.allowed_access = true # => NoMethodError +User.new.allowed_access # => NoMethodError +</ruby> + * <tt>ActionView::Helpers::NumberHelper</tt> methods have been moved to <tt>ActiveSupport::NumberHelper</tt> and are now available via <tt>Numeric#to_s</tt>. * <tt>Numeric#to_s</tt> now accepts the formatting options :phone, :currency, :percentage, :delimited, :rounded, :human, and :human_size. @@ -575,6 +813,8 @@ h3. Active Support h4(#activesupport_deprecations). Deprecations +* <tt>ActiveSupport::Callbacks</tt>: deprecate usage of filter object with <tt>#before</tt> and <tt>#after</tt> methods as <tt>around</tt> callback. + * <tt>BufferedLogger</tt> is deprecated. Use <tt>ActiveSupport::Logger</tt> or the +logger+ from Ruby stdlib. * Deprecates the compatibility method <tt>Module#local_constant_names</tt> and use <tt>Module#local_constants</tt> instead (which returns symbols). diff --git a/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile index ebe774fbef..7c61cc4a8d 100644 --- a/guides/source/action_mailer_basics.textile +++ b/guides/source/action_mailer_basics.textile @@ -457,6 +457,7 @@ The following configuration options are best made in one of the environment file |+delivery_method+|Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:file</tt> and <tt>:test</tt>.| |+perform_deliveries+|Determines whether deliveries are actually carried out when the +deliver+ method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| |+deliveries+|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| +|+async+|Setting this flag will turn on asynchronous message sending, message rendering and delivery will be pushed to <tt>Rails.queue</tt> for processing.| h4. Example Action Mailer Configuration @@ -514,3 +515,33 @@ end </ruby> In the test we send the email and store the returned object in the +email+ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. + +h3. Asynchronous + +You can turn on application-wide asynchronous message sending by adding to your <tt>config/application.rb</tt> file: + +<ruby> +config.action_mailer.async = true +</ruby> + +Alternatively you can turn on async within specific mailers: + +<ruby> +class WelcomeMailer < ActionMailer::Base + self.async = true +end +</ruby> + +h4. Custom Queues + +If you need a different queue than <tt>Rails.queue</tt> for your mailer you can override <tt>ActionMailer::Base#queue</tt>: + +<ruby> +class WelcomeMailer < ActionMailer::Base + def queue + MyQueue.new + end +end +</ruby> + +Your custom queue should expect a job that responds to <tt>#run</tt>. diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile index 101988c59e..9863d16fda 100644 --- a/guides/source/active_record_querying.textile +++ b/guides/source/active_record_querying.textile @@ -12,8 +12,6 @@ This guide covers different ways to retrieve data from the database using Active endprologue. -WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in other versions of Rails. - If you're used to using raw SQL to find database records, then you will generally find that there are better ways to carry out the same operations in Rails. Active Record insulates you from the need to use SQL in most cases. Code examples throughout this guide will refer to one or more of the following models: @@ -53,20 +51,26 @@ h3. Retrieving Objects from the Database To retrieve objects from the database, Active Record provides several finder methods. Each finder method allows you to pass arguments into it to perform certain queries on your database without writing raw SQL. The methods are: -* +where+ -* +select+ +* +bind+ +* +create_with+ +* +extending+ +* +from+ * +group+ -* +order+ -* +reorder+ -* +reverse_order+ -* +limit+ -* +offset+ -* +joins+ +* +having+ * +includes+ +* +joins+ +* +limit+ * +lock+ +* +offset+ +* +order+ +* +none+ * +readonly+ -* +from+ -* +having+ +* +references+ +* +reorder+ +* +reverse_order+ +* +select+ +* +uniq+ +* +where+ All of the above methods return an instance of <tt>ActiveRecord::Relation</tt>. diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile index af46538bf5..cd9aab4892 100644 --- a/guides/source/configuring.textile +++ b/guides/source/configuring.textile @@ -424,7 +424,7 @@ There are a number of settings available on +config.action_mailer+: * +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: +* +config.action_mailer.default_options+ configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to: <ruby> :mime_version => "1.0", :charset => "UTF-8", diff --git a/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile index 1dadce2083..fd5e2b28c0 100644 --- a/guides/source/contributing_to_ruby_on_rails.textile +++ b/guides/source/contributing_to_ruby_on_rails.textile @@ -66,12 +66,26 @@ Install first libxml2 and libxslt together with their development files for Noko $ sudo apt-get install libxml2 libxml2-dev libxslt1-dev </shell> +If you are on Fedora or CentOS, you can run + +<shell> +$ sudo yum install libxml2 libxml2-devel libxslt libxslt-devel +</shell> + +If you have any problems with these libraries, you should install them manually compiling the source code. Just follow the instructions "here":http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos . + Also, SQLite3 and its development files for the +sqlite3-ruby+ gem -- in Ubuntu you're done with just <shell> $ sudo apt-get install sqlite3 libsqlite3-dev </shell> +And if you are on Fedora or CentOS, you're done with + +<shell> +$ sudo yum install sqlite3 sqlite3-devel +</shell> + Get a recent version of "Bundler":http://gembundler.com/: <shell> @@ -150,6 +164,13 @@ $ sudo apt-get install mysql-server libmysqlclient15-dev $ sudo apt-get install postgresql postgresql-client postgresql-contrib libpq-dev </shell> +On Fedora or CentOS, just run: + +<shell> +$ sudo yum install mysql-server mysql-devel +$ sudo yum install postgresql-server postgresql-devel +</shell> + After that run: <shell> @@ -172,7 +193,7 @@ and create the test databases: <shell> $ cd activerecord -$ rake mysql:build_databases +$ bundle exec rake mysql:build_databases </shell> PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account @@ -185,7 +206,7 @@ and then create the test databases with <shell> $ cd activerecord -$ rake postgresql:build_databases +$ bundle exec rake postgresql:build_databases </shell> NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation. diff --git a/guides/source/debugging_rails_applications.textile b/guides/source/debugging_rails_applications.textile index 0802a2db26..667f2d2140 100644 --- a/guides/source/debugging_rails_applications.textile +++ b/guides/source/debugging_rails_applications.textile @@ -102,7 +102,7 @@ It can also be useful to save information to log files at runtime. Rails maintai h4. What is the Logger? -Rails makes use of Ruby's standard +logger+ to write log information. You can also substitute another logger such as +Log4r+ if you wish. +Rails makes use of the +ActiveSupport::BufferedLogger+ class to write log information. You can also substitute another logger such as +Log4r+ if you wish. You can specify an alternative logger in your +environment.rb+ or any environment file: @@ -626,7 +626,7 @@ In this section, you will learn how to find and fix such leaks by using tools su h4. BleakHouse -"BleakHouse":https://github.com/fauna/bleak_house/tree/master is a library for finding memory leaks. +"BleakHouse":https://github.com/evan/bleak_house/ is a library for finding memory leaks. If a Ruby object does not go out of scope, the Ruby Garbage Collector won't sweep it since it is referenced somewhere. Leaks like this can grow slowly and your application will consume more and more memory, gradually affecting the overall system performance. This tool will help you find leaks on the Ruby heap. @@ -675,7 +675,7 @@ To analyze it, just run the listed command. The top 20 leakiest lines will be li This way you can find where your application is leaking memory and fix it. -If "BleakHouse":https://github.com/fauna/bleak_house/tree/master doesn't report any heap growth but you still have memory growth, you might have a broken C extension, or real leak in the interpreter. In that case, try using Valgrind to investigate further. +If "BleakHouse":https://github.com/evan/bleak_house/ doesn't report any heap growth but you still have memory growth, you might have a broken C extension, or real leak in the interpreter. In that case, try using Valgrind to investigate further. h4. Valgrind @@ -708,4 +708,4 @@ h3. References * "Debugging with ruby-debug":http://bashdb.sourceforge.net/ruby-debug.html * "ruby-debug cheat sheet":http://cheat.errtheblog.com/s/rdebug/ * "Ruby on Rails Wiki: How to Configure Logging":http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging -* "Bleak House Documentation":http://blog.evanweaver.com/files/doc/fauna/bleak_house/files/README.html +* "Bleak House Documentation":http://blog.evanweaver.com/files/doc/fauna/bleak_house/ diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile index 897327a888..07419d11b4 100644 --- a/guides/source/getting_started.textile +++ b/guides/source/getting_started.textile @@ -900,7 +900,7 @@ end </ruby> The new method, +update_attributes+, is used when you want to update a record -that already exists, and it accepts an hash containing the attributes +that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the post we want to show the form back to the user. @@ -1185,7 +1185,7 @@ delete "posts/:id" => "posts#destroy" That's a lot to type for covering a single *resource*. Fortunately, Rails provides a +resources+ method which can be used to declare a -standard REST resource. Here's how +config/routes/rb+ looks after the +standard REST resource. Here's how +config/routes.rb+ looks after the cleanup: <ruby> diff --git a/guides/source/i18n.textile b/guides/source/i18n.textile index ee7176a6c8..8ad6ee4b73 100644 --- a/guides/source/i18n.textile +++ b/guides/source/i18n.textile @@ -220,7 +220,7 @@ Every helper method dependent on +url_for+ (e.g. helpers for named routes like + You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this. -You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the Dutch locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+path_prefix+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way: +You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the Dutch locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+scoping+":http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html option in this way: <ruby> # config/routes.rb diff --git a/guides/source/migrations.textile b/guides/source/migrations.textile index 342b5a4d57..06e85e5914 100644 --- a/guides/source/migrations.textile +++ b/guides/source/migrations.textile @@ -111,6 +111,7 @@ Active Record provides methods that perform common data definition tasks in a database independent way (you'll read about them in detail later): * +add_column+ +* +add_reference+ * +add_index+ * +change_column+ * +change_table+ @@ -120,6 +121,7 @@ database independent way (you'll read about them in detail later): * +remove_column+ * +remove_index+ * +rename_column+ +* +remove_reference+ If you need to perform tasks specific to your database (for example create a "foreign key":#active-record-and-referential-integrity constraint) then the @@ -332,6 +334,51 @@ NOTE: The generated migration file for destructive migrations will still be old-style using the +up+ and +down+ methods. This is because Rails needs to know the original data types defined when you made the original changes. +Also the generator accepts column type as +references+(also available as +belongs_to+), for instance + +<shell> +$ rails generate migration AddUserRefToProducts user:references +</shell> + +generates + +<ruby> +class AddUserRefToProducts < ActiveRecord::Migration + def change + add_reference :products, :user, :index => true + end +end +</ruby> + +This migration will create a user_id column and appropriate index. + +h4. Supported type modifiers + +You can also specify some options just after the field type between curly braces. You can use the +following modifiers: + +* +limit+ Sets the maximum size of the +string/text/binary/integer+ fields +* +precision+ Defines the precision for the +decimal+ fields +* +scale+ Defines the scale for the +decimal+ fields +* +polymorphic+ Adds a +type+ column for +belongs_to+ associations + +For instance running + +<shell> +$ rails generate migration AddDetailsToProducts price:decimal{5,2} supplier:references{polymorphic} +</shell> + +will produce a migration that looks like this + +<ruby> +class AddDetailsToProducts < ActiveRecord::Migration + def change + add_column :products, :price, :precision => 5, :scale => 2 + add_reference :products, :user, :polymorphic => true, :index => true + end +end +</ruby> + h3. Writing a Migration Once you have created your migration using one of the generators it's time to diff --git a/guides/source/performance_testing.textile b/guides/source/performance_testing.textile index 958b13cd9e..982fd1b070 100644 --- a/guides/source/performance_testing.textile +++ b/guides/source/performance_testing.textile @@ -524,11 +524,11 @@ Please refer to the "API docs":http://api.rubyonrails.org/classes/ActiveRecord/B h4. Controller -Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715 +Similarly, you could use this helper method inside "controllers":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html <ruby> def process_projects - self.class.benchmark("Processing projects") do + benchmark("Processing projects") do Project.process(params[:project_ids]) Project.update_cached_projects end @@ -539,7 +539,7 @@ NOTE: +benchmark+ is a class method inside controllers h4. View -And in "views":http://api.rubyonrails.org/classes/ActionController/Benchmarking/ClassMethods.html#M000715: +And in "views":http://api.rubyonrails.org/classes/ActiveSupport/Benchmarkable.html: <erb> <% benchmark("Showing projects partial") do %> diff --git a/guides/source/plugins.textile b/guides/source/plugins.textile index 95e38db483..9001857a5f 100644 --- a/guides/source/plugins.textile +++ b/guides/source/plugins.textile @@ -427,4 +427,4 @@ h4. References * "Developing a RubyGem using Bundler":https://github.com/radar/guides/blob/master/gem-development.md * "Using Gemspecs As Intended":http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/ * "Gemspec Reference":http://docs.rubygems.org/read/chapter/20 -* "GemPlugins":http://www.mbleigh.com/2008/06/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins +* "GemPlugins":http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins diff --git a/guides/source/routing.textile b/guides/source/routing.textile index dae25853cd..cffbf9bec4 100644 --- a/guides/source/routing.textile +++ b/guides/source/routing.textile @@ -32,7 +32,13 @@ the request is dispatched to the +patients+ controller's +show+ action with <tt> h4. Generating Paths and URLs from Code -You can also generate paths and URLs. If your application contains this code: +You can also generate paths and URLs. If the route above is modified to be + +<ruby> +get "/patients/:id" => "patients#show", :as => "patient" +</ruby> + +If your application contains this code: <ruby> @patient = Patient.find(17) @@ -845,24 +851,6 @@ end This will create routing helpers such as +magazine_periodical_ads_url+ and +edit_magazine_periodical_ad_path+. -h3. Breaking Up a Large Route File - -If you have a large route file that you would like to break up into multiple files, you can use the +#draw+ method in your router: - -<ruby> -draw :admin -</ruby> - -Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method. You can then use the normal routing DSL inside that file: - -<ruby> -# in config/routes/admin.rb - -namespace :admin do - resources :posts -end -</ruby> - h3. Inspecting and Testing Routes Rails offers facilities for inspecting and testing your routes. diff --git a/guides/source/security.textile b/guides/source/security.textile index 626d6fa508..8879122b66 100644 --- a/guides/source/security.textile +++ b/guides/source/security.textile @@ -851,7 +851,7 @@ Network traffic is mostly based on the limited Western alphabet, so new characte &#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;> </html> -This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":http://www.businessinfo.co.uk/labs/hackvertor/hackvertor.php. Rails' sanitize() method does a good job to fend off encoding attacks. +This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":https://hackvertor.co.uk/public. Rails' sanitize() method does a good job to fend off encoding attacks. h5. Examples from the Underground diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 65f4b06699..5165d9401f 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,26 @@ ## Rails 4.0.0 (unreleased) ## +* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance + + rails g migration AddReferencesToProducts user:references supplier:references{polymorphic} + + will generate the migration with: + + add_reference :products, :user, index: true + add_reference :products, :supplier, polymorphic: true, index: true + + *Aleksey Magusev* + +* Allow scaffold/model/migration generators to accept a `polymorphic` modifier + for `references`/`belongs_to`, for instance + + rails g model Product supplier:references{polymorphic} + + will generate the model with `belongs_to :supplier, polymorphic: true` + association and appropriate migration. + + *Aleksey Magusev* + * Set `config.active_record.migration_error` to `:page_load` for development *Richard Schneeman* * Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj* diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 32797ee657..3afbf0a03e 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -53,7 +53,6 @@ module Rails autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configuration, 'rails/application/configuration' autoload :Finisher, 'rails/application/finisher' - autoload :Railties, 'rails/application/railties' autoload :RoutesReloader, 'rails/application/routes_reloader' class << self @@ -80,9 +79,42 @@ module Rails @routes_reloader = nil @env_config = nil @ordered_railties = nil + @railties = nil @queue = nil end + # Returns true if the application is initialized. + def initialized? + @initialized + end + + # Implements call according to the Rack API. It simples + # dispatch the request to the underlying middleware stack. + def call(env) + env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) + super(env) + end + + # Reload application routes regardless if they changed or not. + def reload_routes! + routes_reloader.reload! + end + + # Stores some of the Rails initial environment parameters which + # will be used by middlewares and engines to configure themselves. + def env_config + @env_config ||= super.merge({ + "action_dispatch.parameter_filter" => config.filter_parameters, + "action_dispatch.secret_token" => config.secret_token, + "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, + "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, + "action_dispatch.logger" => Rails.logger, + "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner + }) + end + + ## Rails internal API + # This method is called just after an application inherits from Rails::Application, # allowing the developer to load classes in lib and use them during application # configuration. @@ -106,18 +138,14 @@ module Rails require environment if environment end - # Reload application routes regardless if they changed or not. - def reload_routes! - routes_reloader.reload! - end - def routes_reloader #:nodoc: @routes_reloader ||= RoutesReloader.new end - # Returns an array of file paths appended with a hash of directories-extensions - # suitable for ActiveSupport::FileUpdateChecker API. - def watchable_args + # Returns an array of file paths appended with a hash of + # directories-extensions suitable for ActiveSupport::FileUpdateChecker + # API. + def watchable_args #:nodoc: files, dirs = config.watchable_files.dup, config.watchable_dirs.dup ActiveSupport::Dependencies.autoload_paths.each do |path| @@ -138,45 +166,64 @@ module Rails self end - def initialized? - @initialized + def initializers #:nodoc: + Bootstrap.initializers_for(self) + + railties_initializers(super) + + Finisher.initializers_for(self) end - # Load the application and its railties tasks and invoke the registered hooks. - # Check <tt>Rails::Railtie.rake_tasks</tt> for more info. - def load_tasks(app=self) - initialize_tasks - super + def config #:nodoc: + @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) + end + + def queue #:nodoc: + @queue ||= build_queue + end + + def build_queue #:nodoc: + config.queue.new + end + + def to_app #:nodoc: self end - # Load the application console and invoke the registered hooks. - # Check <tt>Rails::Railtie.console</tt> for more info. - def load_console(app=self) - initialize_console + def helpers_paths #:nodoc: + config.helpers_paths + end + + def railties #:nodoc: + @railties ||= Rails::Railtie.subclasses.map(&:instance) + + Rails::Engine.subclasses.map(&:instance) + end + + protected + + alias :build_middleware_stack :app + + def run_tasks_blocks(app) #:nodoc: + railties.each { |r| r.run_tasks_blocks(app) } + super + require "rails/tasks" + task :environment do + $rails_rake_task = true + require_environment! + end + end + + def run_generators_blocks(app) #:nodoc: + railties.each { |r| r.run_generators_blocks(app) } super - self end - # Load the application runner and invoke the registered hooks. - # Check <tt>Rails::Railtie.runner</tt> for more info. - def load_runner(app=self) - initialize_runner + def run_runner_blocks(app) #:nodoc: + railties.each { |r| r.run_runner_blocks(app) } super - self end - # Stores some of the Rails initial environment parameters which - # will be used by middlewares and engines to configure themselves. - def env_config - @env_config ||= super.merge({ - "action_dispatch.parameter_filter" => config.filter_parameters, - "action_dispatch.secret_token" => config.secret_token, - "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, - "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, - "action_dispatch.logger" => Rails.logger, - "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner - }) + def run_console_blocks(app) #:nodoc: + railties.each { |r| r.run_console_blocks(app) } + super end # Returns the ordered railties for this application considering railties_order. @@ -192,7 +239,7 @@ module Rails end end - all = (railties.all - order) + all = (railties - order) all.push(self) unless (all + order).include?(self) order.push(:all) unless order.include?(:all) @@ -202,47 +249,25 @@ module Rails end end - def initializers #:nodoc: - Bootstrap.initializers_for(self) + - super + - Finisher.initializers_for(self) - end - - def config #:nodoc: - @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) - end - - def queue #:nodoc: - @queue ||= build_queue - end - - def build_queue # :nodoc: - config.queue.new - end - - def to_app - self - end - - def helpers_paths #:nodoc: - config.helpers_paths - end - - def call(env) - env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) - super(env) + def railties_initializers(current) #:nodoc: + initializers = [] + ordered_railties.each do |r| + if r == self + initializers += current + else + initializers += r.initializers + end + end + initializers end - protected - - alias :build_middleware_stack :app - - def reload_dependencies? + def reload_dependencies? #:nodoc: config.reload_classes_only_on_change != true || reloaders.map(&:updated?).any? end - def default_middleware_stack + def default_middleware_stack #:nodoc: ActionDispatch::MiddlewareStack.new.tap do |middleware| + app = self if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache @@ -266,11 +291,10 @@ module Rails middleware.use ::ActionDispatch::RequestId middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods middleware.use ::ActionDispatch::ShowExceptions, config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) - middleware.use ::ActionDispatch::DebugExceptions + middleware.use ::ActionDispatch::DebugExceptions, app middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies unless config.cache_classes - app = self middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? } end @@ -296,26 +320,7 @@ module Rails end end - def initialize_tasks #:nodoc: - self.class.rake_tasks do - require "rails/tasks" - task :environment do - $rails_rake_task = true - require_environment! - end - end - end - - def initialize_console #:nodoc: - require "pp" - require "rails/console/app" - require "rails/console/helpers" - end - - def initialize_runner #:nodoc: - end - - def build_original_fullpath(env) + def build_original_fullpath(env) #:nodoc: path_info = env["PATH_INFO"] query_string = env["QUERY_STRING"] script_name = env["SCRIPT_NAME"] diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb deleted file mode 100644 index f20a9689de..0000000000 --- a/railties/lib/rails/application/railties.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'rails/engine/railties' - -module Rails - class Application < Engine - class Railties < Rails::Engine::Railties - def all(&block) - @all ||= railties + engines - @all.each(&block) if block - @all - end - end - end -end diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index 19f616ec50..6f9a200aa9 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -3,13 +3,12 @@ require "active_support/core_ext/module/delegation" module Rails class Application class RoutesReloader - attr_reader :route_sets, :paths, :external_routes + attr_reader :route_sets, :paths delegate :execute_if_updated, :execute, :updated?, :to => :updater def initialize - @paths = [] - @route_sets = [] - @external_routes = [] + @paths = [] + @route_sets = [] end def reload! @@ -24,11 +23,7 @@ module Rails def updater @updater ||= begin - dirs = @external_routes.each_with_object({}) do |dir, hash| - hash[dir.to_s] = %w(rb) - end - - updater = ActiveSupport::FileUpdateChecker.new(paths, dirs) { reload! } + updater = ActiveSupport::FileUpdateChecker.new(paths) { reload! } updater.execute updater end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 806b553b81..f469c334a7 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -2,7 +2,6 @@ require 'rails/railtie' require 'active_support/core_ext/module/delegation' require 'pathname' require 'rbconfig' -require 'rails/engine/railties' module Rails # <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of @@ -177,8 +176,7 @@ module Rails # # * routes: when you mount an Engine with <tt>mount(MyEngine::Engine => '/my_engine')</tt>, # it's used as default :as option - # * some of the rake tasks are based on engine name, e.g. <tt>my_engine:install:migrations</tt>, - # <tt>my_engine:install:assets</tt> + # * rake task for installing migrations <tt>my_engine:install:migrations</tt> # # Engine name is set by default based on class name. For <tt>MyEngine::Engine</tt> it will be # <tt>my_engine_engine</tt>. You can change it manually using the <tt>engine_name</tt> method: @@ -338,27 +336,6 @@ module Rails # config.railties_order = [Blog::Engine, :main_app, :all] class Engine < Railtie autoload :Configuration, "rails/engine/configuration" - autoload :Railties, "rails/engine/railties" - - def initialize - @_all_autoload_paths = nil - @_all_load_paths = nil - @app = nil - @config = nil - @env_config = nil - @helpers = nil - @railties = nil - @routes = nil - super - end - - 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 @@ -408,7 +385,7 @@ module Rails end unless mod.respond_to?(:railtie_routes_url_helpers) - define_method(:railtie_routes_url_helpers) { railtie.routes_url_helpers } + define_method(:railtie_routes_url_helpers) { railtie.routes.url_helpers } end end end @@ -417,34 +394,65 @@ module Rails # Finds engine with given path def find(path) expanded_path = File.expand_path path - Rails::Engine::Railties.engines.find { |engine| - File.expand_path(engine.root) == expanded_path - } + Rails::Engine.subclasses.each do |klass| + engine = klass.instance + return engine if File.expand_path(engine.root) == expanded_path + end + nil end end delegate :middleware, :root, :paths, :to => :config delegate :engine_name, :isolated?, :to => "self.class" - def load_tasks(app=self) - railties.all { |r| r.load_tasks(app) } + def initialize + @_all_autoload_paths = nil + @_all_load_paths = nil + @app = nil + @config = nil + @env_config = nil + @helpers = nil + @routes = nil super - paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end + # Load console and invoke the registered hooks. + # Check <tt>Rails::Railtie.console</tt> for more info. def load_console(app=self) - railties.all { |r| r.load_console(app) } - super + require "pp" + require "rails/console/app" + require "rails/console/helpers" + run_console_blocks(app) + self end + # Load Rails runner and invoke the registered hooks. + # Check <tt>Rails::Railtie.runner</tt> for more info. def load_runner(app=self) - railties.all { |r| r.load_runner(app) } - super + run_runner_blocks(app) + self end - def eager_load! - railties.all(&:eager_load!) + # Load Rake, railties tasks and invoke the registered hooks. + # Check <tt>Rails::Railtie.rake_tasks</tt> for more info. + def load_tasks(app=self) + require "rake" + run_tasks_blocks(app) + self + end + # Load rails generators and invoke the registered hooks. + # Check <tt>Rails::Railtie.generators</tt> for more info. + def load_generators(app=self) + require "rails/generators" + run_generators_blocks(app) + Rails::Generators.configure!(app.config.generators) + self + end + + # Eager load the application by loading all ruby + # files inside eager_load paths. + def 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| @@ -453,10 +461,7 @@ module Rails end end - def railties - @railties ||= self.class::Railties.new(config) - end - + # Returns a module with all the helpers defined for the engine. def helpers @helpers ||= begin helpers = Module.new @@ -468,14 +473,12 @@ module Rails end end + # Returns all registered helpers paths. def helpers_paths paths["app/helpers"].existent end - def routes_url_helpers - routes.url_helpers - end - + # Returns the underlying rack application for this engine. def app @app ||= begin config.middleware = config.middleware.merge_into(default_middleware_stack) @@ -483,45 +486,33 @@ module Rails end end + # Returns the endpoint for this engine. If none is registered, + # defaults to an ActionDispatch::Routing::RouteSet. def endpoint self.class.endpoint || routes end + # Define the Rack API for this engine. def call(env) app.call(env.merge!(env_config)) end + # Defines additional Rack env configuration that is added on each call. def env_config @env_config ||= { 'action_dispatch.routes' => routes } end + # Defines the routes for this engine. If a block is given to + # routes, it is appended to the engine. def routes - @routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes| - routes.draw_paths.concat paths["config/routes"].paths - end - + @routes ||= ActionDispatch::Routing::RouteSet.new @routes.append(&Proc.new) if block_given? @routes end - def ordered_railties - railties.all + [self] - end - - def initializers - initializers = [] - ordered_railties.each do |r| - if r == self - initializers += super - else - initializers += r.initializers - end - end - initializers - end - + # Define the configuration object for the engine. def config @config ||= Engine::Configuration.new(find_root_with_flag("lib")) end @@ -560,12 +551,10 @@ module Rails initializer :add_routing_paths do |app| paths = self.paths["config/routes.rb"].existent - external_paths = self.paths["config/routes"].paths if routes? || paths.any? app.routes_reloader.paths.unshift(*paths) app.routes_reloader.route_sets << routes - app.routes_reloader.external_routes.unshift(*external_paths) end end @@ -626,7 +615,6 @@ module Rails else Rake::Task["app:railties:install:migrations"].invoke end - end end end @@ -634,19 +622,20 @@ module Rails protected - def initialize_generators - require "rails/generators" + def run_tasks_blocks(*) #:nodoc: + super + paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end - def routes? + def routes? #:nodoc: @routes end - def has_migrations? + def has_migrations? #:nodoc: paths["db/migrate"].existent.any? end - def find_root_with_flag(flag, default=nil) + def find_root_with_flag(flag, default=nil) #:nodoc: root_path = self.class.called_from while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") @@ -660,19 +649,19 @@ module Rails Pathname.new File.realpath root end - def default_middleware_stack + def default_middleware_stack #:nodoc: ActionDispatch::MiddlewareStack.new end - def _all_autoload_once_paths + def _all_autoload_once_paths #:nodoc: config.autoload_once_paths end - def _all_autoload_paths + def _all_autoload_paths #:nodoc: @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq end - def _all_load_paths + def _all_load_paths #:nodoc: @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq end end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index e31df807a6..6b18b1e249 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -53,7 +53,6 @@ module Rails paths.add "config/initializers", :glob => "**/*.rb" paths.add "config/locales", :glob => "*.{rb,yml}" paths.add "config/routes.rb" - paths.add "config/routes", :glob => "**/*.rb" paths.add "db" paths.add "db/migrate" paths.add "db/seeds.rb" diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb deleted file mode 100644 index 033d9c4180..0000000000 --- a/railties/lib/rails/engine/railties.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Rails - class Engine < Railtie - class Railties - # TODO Write tests for this behavior extracted from Application - def initialize(config) - @config = config - end - - def all(&block) - @all ||= [] - @all.each(&block) if block - @all - end - - def self.railties - @railties ||= ::Rails::Railtie.subclasses.map(&:instance) - end - - def self.engines - @engines ||= ::Rails::Engine.subclasses.map(&:instance) - end - - delegate :railties, :engines, :to => "self.class" - end - end -end diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 25d0161e4c..d480fc12b5 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -35,7 +35,7 @@ module Rails private - # parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals + # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to # when declaring options curly brackets should be used def parse_type_and_options(type) case type @@ -43,6 +43,8 @@ module Rails return $1, :limit => $2.to_i when /decimal\{(\d+)[,.-](\d+)\}/ return :decimal, :precision => $1.to_i, :scale => $2.to_i + when /(references|belongs_to)\{polymorphic\}/ + return $1, :polymorphic => true else return type, {} end @@ -92,13 +94,21 @@ module Rails end def index_name - reference? ? "#{name}_id" : name + if reference? + polymorphic? ? %w(id type).map { |t| "#{name}_#{t}" } : "#{name}_id" + else + name + end end def reference? self.class.reference?(type) end + def polymorphic? + self.attr_options.has_key?(:polymorphic) + end + def has_index? @has_index end diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb index 0090293200..9afda2d0df 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb @@ -6,7 +6,7 @@ class ActiveSupport::TestCase <% unless options[:skip_active_record] -%> ActiveRecord::Migration.check_pending! - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE index 67f76aad01..c46c86076e 100644 --- a/railties/lib/rails/generators/rails/model/USAGE +++ b/railties/lib/rails/generators/rails/model/USAGE @@ -19,6 +19,55 @@ Description: then the generator will create a module with a table_name_prefix method to prefix the model's table name with the module name (e.g. admin_account) +Available field types: + + Just after the field name you can specify a type like text or boolean. + It will generate the column with the associated SQL type. For instance: + + `rails generate model post title:string body:text` + + will generate a title column with a varchar type and a body column with a text + type. You can use the following types: + + integer + primary_key + decimal + float + boolean + binary + string + text + date + time + datetime + timestamp + + You can also consider `references` as a kind of type. For instance, if you run: + + `rails generate model photo title:string album:references` + + It will generate an album_id column. You should generate this kind of fields when + you will use a `belongs_to` association for instance. `references` also support + the polymorphism, you could enable the polymorphism like this: + + `rails generate model product supplier:references{polymorphic}` + + You can also specify some options just after the field type. You can use the + following options: + + limit Set the maximum size of the field giving a number between curly braces + default Set a default value for the field + precision Defines the precision for the decimal fields + scale Defines the scale for the decimal fields + uniq Defines the field values as unique + index Will add an index on the field + + Examples: + + `rails generate model user pseudo:string{30}` + `rails generate model user pseudo:string:uniq` + + Examples: `rails generate model account` diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore index 458b2c662e..086d87818a 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore @@ -1,8 +1,10 @@ .bundle/ log/*.log pkg/ +<% unless options[:skip_test_unit] && options[:dummy_path] == 'test/dummy' -%> <%= dummy_path %>/db/*.sqlite3 <%= dummy_path %>/db/*.sqlite3-journal <%= dummy_path %>/log/*.log <%= dummy_path %>/tmp/ <%= dummy_path %>/.sass-cache +<% end -%>
\ No newline at end of file diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 5081074395..512803aeac 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -1,4 +1,4 @@ -require 'rails/application/route_inspector' +require 'action_dispatch/routing/inspector' class Rails::InfoController < ActionController::Base self.view_paths = File.join(File.dirname(__FILE__), 'templates') @@ -15,7 +15,7 @@ class Rails::InfoController < ActionController::Base end def routes - inspector = Rails::Application::RouteInspector.new + inspector = ActionDispatch::Routing::RoutesInspector.new @info = inspector.format(_routes.routes).join("\n") end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 6cd9c7bc95..3c2210aaf9 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -51,7 +51,8 @@ module Rails end def []=(path, value) - add(path, :with => value) + glob = self[path] ? self[path].glob : nil + add(path, :with => value, :glob => glob) end def add(path, options={}) @@ -114,7 +115,7 @@ module Rails class Path include Enumerable - attr_reader :path, :root + attr_reader :path attr_accessor :glob def initialize(root, current, paths, options = {}) @@ -180,14 +181,6 @@ module Rails @paths end - def paths - raise "You need to set a path root" unless @root.path - - map do |p| - File.join @root.path, p - end - end - # Expands all paths against the root and return all unique values. def expanded raise "You need to set a path root" unless @root.path diff --git a/railties/lib/rails/queueing.rb b/railties/lib/rails/queueing.rb index b4bc7fcd18..8a76914548 100644 --- a/railties/lib/rails/queueing.rb +++ b/railties/lib/rails/queueing.rb @@ -22,6 +22,13 @@ module Rails @que.dup end + # Marshal and unmarshal job before pushing it onto the queue. This will + # raise an exception on any attempts in tests to push jobs that can't (or + # shouldn't) be marshalled. + def push(job) + super Marshal.load(Marshal.dump(job)) + end + # Drain the queue, running all jobs in a different thread. This method # may not be available on production queues. def drain diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index a06be59759..1cb99463cc 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -181,33 +181,35 @@ module Rails def eager_load! end - def load_console(app=self) + def railtie_namespace + @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) } + end + + protected + + def run_console_blocks(app) #:nodoc: self.class.console.each { |block| block.call(app) } end - def load_runner(app=self) + def run_generators_blocks(app) #:nodoc: + self.class.generators.each { |block| block.call(app) } + end + + def run_runner_blocks(app) #:nodoc: self.class.runner.each { |block| block.call(app) } end - def load_tasks(app=self) - require 'rake' + def run_tasks_blocks(app) #:nodoc: extend Rake::DSL - self.class.rake_tasks.each { |block| self.instance_exec(app, &block) } + self.class.rake_tasks.each { |block| instance_exec(app, &block) } - # load also tasks from all superclasses + # Load also tasks from all superclasses klass = self.class.superclass + while klass.respond_to?(:rake_tasks) - klass.rake_tasks.each { |t| self.instance_exec(app, &t) } + klass.rake_tasks.each { |t| instance_exec(app, &t) } klass = klass.superclass end end - - 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_namespace) } - end end end diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 5778b22f18..95f47566ef 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,7 +1,7 @@ desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' task :routes => :environment do all_routes = Rails.application.routes.routes - require 'rails/application/route_inspector' - inspector = Rails::Application::RouteInspector.new + require 'action_dispatch/routing/inspector' + inspector = ActionDispatch::Routing::RoutesInspector.new puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n" end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 46bf3bbe48..581ceaf9ce 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -10,7 +10,10 @@ require 'action_dispatch/testing/integration' # Enable turn if it is available begin require 'turn' - MiniTest::Unit.use_natural_language_case_names = true + + Turn.config do |c| + c.natural = true + end rescue LoadError end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 207739c4bc..6d28947e83 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.rdoc_options << '--exclude' << '.' s.add_dependency('rake', '>= 0.8.7') - s.add_dependency('thor', '>= 0.15.3', '< 2.0') + s.add_dependency('thor', '>= 0.15.4', '< 2.0') s.add_dependency('rdoc', '~> 3.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index e0893f53be..4029984ce9 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -50,8 +50,6 @@ module ApplicationTests assert_path @paths["config/locales"], "config/locales/en.yml" assert_path @paths["config/environment"], "config/environment.rb" assert_path @paths["config/environments"], "config/environments/development.rb" - assert_path @paths["config/routes.rb"], "config/routes.rb" - assert_path @paths["config/routes"], "config/routes" assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first end diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb index da8bdeed52..22d6c20404 100644 --- a/railties/test/application/queue_test.rb +++ b/railties/test/application/queue_test.rb @@ -30,46 +30,68 @@ module ApplicationTests assert_kind_of Rails::Queueing::Queue, Rails.queue end - test "in development mode, an enqueued job will be processed in a separate thread" do - app("development") + class ThreadTrackingJob + def initialize + @origin = Thread.current.object_id + end + + def run + @target = Thread.current.object_id + end - job = Struct.new(:origin, :target).new(Thread.current) - def job.run - self.target = Thread.current + def ran_in_different_thread? + @origin != @target end + def ran? + @target + end + end + + test "in development mode, an enqueued job will be processed in a separate thread" do + app("development") + + job = ThreadTrackingJob.new Rails.queue.push job sleep 0.1 - assert job.target, "The job was run" - assert_not_equal job.origin, job.target + assert job.ran?, "Expected job to be run" + assert job.ran_in_different_thread?, "Expected job to run in a different thread" end test "in test mode, explicitly draining the queue will process it in a separate thread" do app("test") - job = Struct.new(:origin, :target).new(Thread.current) - def job.run - self.target = Thread.current + Rails.queue.push ThreadTrackingJob.new + job = Rails.queue.jobs.last + Rails.queue.drain + + assert job.ran?, "Expected job to be run" + assert job.ran_in_different_thread?, "Expected job to run in a different thread" + end + + class IdentifiableJob + def initialize(id) + @id = id end - Rails.queue.push job - Rails.queue.drain + def ==(other) + other.same_id?(@id) + end + + def same_id?(other_id) + other_id == @id + end - assert job.target, "The job was run" - assert_not_equal job.origin, job.target + def run + end end test "in test mode, the queue can be observed" do app("test") - job = Struct.new(:id) do - def run - end - end - jobs = (1..10).map do |id| - job.new(id) + IdentifiableJob.new(id) end jobs.each do |job| @@ -79,6 +101,29 @@ module ApplicationTests assert_equal jobs, Rails.queue.jobs end + test "in test mode, adding an unmarshallable job will raise an exception" do + app("test") + anonymous_class_instance = Struct.new(:run).new + assert_raises TypeError do + Rails.queue.push anonymous_class_instance + end + end + + test "attempting to marshal a queue will raise an exception" do + app("test") + assert_raises TypeError do + Marshal.dump Rails.queue + end + end + + test "attempting to add a reference to itself to the queue will raise an exception" do + app("test") + job = {reference: Rails.queue} + assert_raises TypeError do + Rails.queue.push job + end + end + def setup_custom_queue add_to_env_config "production", <<-RUBY require "my_queue" diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 6bb50fea19..a450f90dbf 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -157,7 +157,7 @@ module ApplicationTests add_to_config "config.active_record.schema_format = :sql" output = Dir.chdir(app_path) do `rails generate scaffold user username:string; - bundle exec rake db:migrate db:test:clone 2>&1 --trace` + bundle exec rake db:migrate db:test:prepare 2>&1 --trace` end assert_match(/Execute db:test:load_structure/, output) end diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb deleted file mode 100644 index 3b8c874b5b..0000000000 --- a/railties/test/application/route_inspect_test.rb +++ /dev/null @@ -1,168 +0,0 @@ -require 'minitest/autorun' -require 'rails/application/route_inspector' -require 'action_controller' -require 'rails/engine' - -module ApplicationTests - class RouteInspectTest < ActiveSupport::TestCase - def setup - @set = ActionDispatch::Routing::RouteSet.new - @inspector = Rails::Application::RouteInspector.new - app = ActiveSupport::OrderedOptions.new - app.config = ActiveSupport::OrderedOptions.new - app.config.assets = ActiveSupport::OrderedOptions.new - app.config.assets.prefix = '/sprockets' - Rails.stubs(:application).returns(app) - Rails.stubs(:env).returns("development") - end - - def draw(&block) - @set.draw(&block) - @inspector.format(@set.routes) - end - - def test_displaying_routes_for_engines - engine = Class.new(Rails::Engine) do - def self.to_s - "Blog::Engine" - end - end - engine.routes.draw do - get '/cart', :to => 'cart#show' - end - - output = draw do - get '/custom/assets', :to => 'custom_assets#show' - mount engine => "/blog", :as => "blog" - end - - expected = [ - "custom_assets GET /custom/assets(.:format) custom_assets#show", - " blog /blog Blog::Engine", - "\nRoutes for Blog::Engine:", - "cart GET /cart(.:format) cart#show" - ] - assert_equal expected, output - end - - def test_cart_inspect - output = draw do - get '/cart', :to => 'cart#show' - end - assert_equal ["cart GET /cart(.:format) cart#show"], output - end - - def test_inspect_shows_custom_assets - output = draw do - get '/custom/assets', :to => 'custom_assets#show' - end - assert_equal ["custom_assets GET /custom/assets(.:format) custom_assets#show"], output - end - - def test_inspect_routes_shows_resources_route - output = draw do - resources :articles - end - expected = [ - " articles GET /articles(.:format) articles#index", - " POST /articles(.:format) articles#create", - " new_article GET /articles/new(.:format) articles#new", - "edit_article GET /articles/:id/edit(.:format) articles#edit", - " article GET /articles/:id(.:format) articles#show", - " PATCH /articles/:id(.:format) articles#update", - " PUT /articles/:id(.:format) articles#update", - " DELETE /articles/:id(.:format) articles#destroy" ] - assert_equal expected, output - end - - def test_inspect_routes_shows_root_route - output = draw do - root :to => 'pages#main' - end - assert_equal ["root GET / pages#main"], output - end - - def test_inspect_routes_shows_dynamic_action_route - output = draw do - get 'api/:action' => 'api' - end - assert_equal [" GET /api/:action(.:format) api#:action"], output - end - - def test_inspect_routes_shows_controller_and_action_only_route - output = draw do - get ':controller/:action' - end - assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output - end - - def test_inspect_routes_shows_controller_and_action_route_with_constraints - output = draw do - get ':controller(/:action(/:id))', :id => /\d+/ - end - assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output - end - - def test_rake_routes_shows_route_with_defaults - output = draw do - get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'} - end - assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output - end - - def test_rake_routes_shows_route_with_constraints - output = draw do - get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ - end - assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output - end - - class RackApp - def self.call(env) - end - end - - def test_rake_routes_shows_route_with_rack_app - output = draw do - get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/ - end - assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output - end - - def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints - constraint = Class.new do - def to_s - "( my custom constraint )" - end - end - - output = draw do - scope :constraint => constraint.new do - mount RackApp => '/foo' - end - end - - assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output - end - - def test_rake_routes_dont_show_app_mounted_in_assets_prefix - output = draw do - get '/sprockets' => RackApp - end - assert_no_match(/RackApp/, output.first) - assert_no_match(/\/sprockets/, output.first) - end - - def test_redirect - output = draw do - get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" } - get "/bar" => redirect(path: "/foo/bar", status: 307) - get "/foobar" => redirect{ "/foo/bar" } - end - - assert_equal " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", output[0] - assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1] - assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2] - end - end -end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index d1373ba202..396b1849d8 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -178,90 +178,7 @@ module ApplicationTests assert_equal 'WIN', last_response.body end - test "routes drawing from config/routes" do - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do - draw :external - end - RUBY - - app_file 'config/routes/external.rb', <<-RUBY - get ':controller/:action' - RUBY - - controller :success, <<-RUBY - class SuccessController < ActionController::Base - def index - render :text => "success!" - end - end - RUBY - - app 'development' - get '/success/index' - assert_equal 'success!', last_response.body - end - {"development" => "baz", "production" => "bar"}.each do |mode, expected| - test "reloads routes when external configuration is changed in #{mode}" do - controller :foo, <<-RUBY - class FooController < ApplicationController - def bar - render :text => "bar" - end - - def baz - render :text => "baz" - end - end - RUBY - - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do - draw :external - end - RUBY - - app_file 'config/routes/external.rb', <<-RUBY - get 'foo', :to => 'foo#bar' - RUBY - - app(mode) - - get '/foo' - assert_equal 'bar', last_response.body - - app_file 'config/routes/external.rb', <<-RUBY - get 'foo', :to => 'foo#baz' - RUBY - - sleep 0.1 - - get '/foo' - assert_equal expected, last_response.body - - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do - draw :external - draw :other_external - end - RUBY - - app_file 'config/routes/other_external.rb', <<-RUBY - get 'win', :to => 'foo#baz' - RUBY - - sleep 0.1 - - get '/win' - - if mode == "development" - assert_equal expected, last_response.body - else - assert_equal 404, last_response.status - end - end - test "reloads routes when configuration is changed in #{mode}" do controller :foo, <<-RUBY class FooController < ApplicationController diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb index 68406dce4c..addf49cdb6 100644 --- a/railties/test/engine_test.rb +++ b/railties/test/engine_test.rb @@ -11,14 +11,4 @@ class EngineTest < ActiveSupport::TestCase assert !engine.routes? end - - it "does not add more paths to routes on each call" do - engine = Class.new(Rails::Engine) - - engine.routes - length = engine.routes.draw_paths.length - - engine.routes - assert_equal length, engine.routes.draw_paths.length - end end diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index 6e3fc84781..6ab1cd58c7 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -102,22 +102,28 @@ class GeneratedAttributeTest < Rails::Generators::TestCase def test_reference_is_true %w(references belongs_to).each do |attribute_type| - assert_equal( - true, - create_generated_attribute(attribute_type).reference? - ) + assert create_generated_attribute(attribute_type).reference? end end def test_reference_is_false %w(foo bar baz).each do |attribute_type| - assert_equal( - false, - create_generated_attribute(attribute_type).reference? - ) + assert !create_generated_attribute(attribute_type).reference? end end + def test_polymorphic_reference_is_true + %w(references belongs_to).each do |attribute_type| + assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? + end + end + + def test_polymorphic_reference_is_false + %w(foo bar baz).each do |attribute_type| + assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? + end + 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 @@ -126,5 +132,6 @@ class GeneratedAttributeTest < Rails::Generators::TestCase def test_handles_index_names_for_references assert_equal "post", create_generated_attribute('string', 'post').index_name assert_equal "post_id", create_generated_attribute('references', 'post').index_name + assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index b320e40654..86e3793289 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -76,6 +76,23 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_remove_migration_with_references_options + migration = "remove_references_from_books" + run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :up, content do |up| + assert_match(/remove_reference :books, :author/, up) + assert_match(/remove_reference :books, :distributor, polymorphic: true/, up) + end + + assert_method :down, content do |down| + assert_match(/add_reference :books, :author, index: true/, down) + assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, down) + end + end + end + def test_add_migration_with_attributes_and_indices migration = "add_title_with_index_and_body_to_posts" run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"] @@ -138,6 +155,18 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end + def test_add_migration_with_references_options + migration = "add_references_to_books" + run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |up| + assert_match(/add_reference :books, :author, index: true/, up) + assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, up) + end + end + end + def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove migration = "create_books" run_generator [migration, "title:string", "content:text"] diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index fd3b8c8a17..ec33bd7c6b 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -166,7 +166,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase end def test_add_migration_with_attributes_index_declaration_and_attribute_options - run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq"] + run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq", "supplier:references{polymorphic}"] assert_migration "db/migrate/create_products.rb" do |content| assert_method :change, content do |up| @@ -174,6 +174,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_match(/t.string :title, limit: 40/, up) assert_match(/t.string :content, limit: 255/, up) assert_match(/t.decimal :price, precision: 5, scale: 2/, up) + assert_match(/t.references :supplier, polymorphic: true/, up) end assert_match(/add_index :products, :title/, content) assert_match(/add_index :products, :price/, content) @@ -193,15 +194,25 @@ class ModelGeneratorTest < Rails::Generators::TestCase end def test_model_with_references_attribute_generates_belongs_to_associations - run_generator ["product", "name:string", "supplier_id:references"] + run_generator ["product", "name:string", "supplier:references"] assert_file "app/models/product.rb", /belongs_to :supplier/ end def test_model_with_belongs_to_attribute_generates_belongs_to_associations - run_generator ["product", "name:string", "supplier_id:belongs_to"] + run_generator ["product", "name:string", "supplier:belongs_to"] assert_file "app/models/product.rb", /belongs_to :supplier/ end + def test_model_with_polymorphic_references_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:references{polymorphic}"] + assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/ + end + + def test_model_with_polymorphic_belongs_to_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:belongs_to{polymorphic}"] + assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/ + end + def test_migration_with_timestamps run_generator assert_migration "db/migrate/create_accounts.rb", /t.timestamps/ diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 2ae9dc61a7..db2b8af217 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -99,7 +99,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase run_generator ["admin/account"] assert_file "app/models/test_app/admin.rb", /module TestApp/, /module Admin/ assert_file "app/models/test_app/admin.rb", /def self\.table_name_prefix/ - assert_file "app/models/test_app/admin.rb", /'admin_'/ + assert_file "app/models/test_app/admin.rb", /'test_app_admin_'/ assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ActiveRecord::Base/ end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 0a235b56d5..6c3e0178f2 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -264,11 +264,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "spec/dummy" assert_file "spec/dummy/config/application.rb" assert_no_file "test" + assert_file '.gitignore' do |contents| + assert_match(/spec\/dummy/, contents) + end end def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path FileUtils.cd(Rails.root) - run_generator([destination_root, "--dummy_path", "spec/dummy" "--skip-test-unit"]) + run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"]) assert_file ".gitignore" do |contents| assert_match(/spec\/dummy/, contents) end @@ -280,6 +283,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "bukkits.gemspec" do |contents| assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents) end + assert_file '.gitignore' do |contents| + assert_no_match(/test\dummy/, contents) + end end def test_skipping_gemspec diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 417d019178..ad2f85a5ff 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -1,7 +1,6 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/model/model_generator' require 'rails/generators/test_unit/model/model_generator' -require 'mocha' class GeneratorsTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 800b1c90f0..6071cd3f39 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -273,24 +273,18 @@ end # Create a scope and build a fixture rails app Module.new do extend TestHelpers::Paths + # Build a rails app - if File.exist?(app_template_path) - FileUtils.rm_rf(app_template_path) - end + FileUtils.rm_rf(app_template_path) FileUtils.mkdir(app_template_path) environment = File.expand_path('../../../../load_paths', __FILE__) - if File.exist?("#{environment}.rb") - require_environment = "-r #{environment}" - end + require_environment = "-r #{environment}" `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path}` + File.open("#{app_template_path}/config/boot.rb", 'w') do |f| - if require_environment - f.puts "Dir.chdir('#{File.dirname(environment)}') do" - f.puts " require '#{environment}'" - f.puts "end" - end + f.puts "require '#{environment}'" f.puts "require 'rails/all'" end end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 5d6b6f9f72..76ff3ec3e4 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -29,7 +29,6 @@ class PathsTest < ActiveSupport::TestCase test "creating a root level path" do @root.add "app" assert_equal ["/foo/bar/app"], @root["app"].to_a - assert_equal ["/foo/bar/app"], @root["app"].paths end test "creating a root level path with options" do @@ -192,7 +191,6 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].glob = "*.rb" assert_equal "*.rb", @root["app"].glob - assert_equal ["/foo/bar/app"], @root["app"].paths end test "it should be possible to override a path's default glob without assignment" do @@ -200,6 +198,13 @@ class PathsTest < ActiveSupport::TestCase assert_equal "*.rb", @root["app"].glob end + test "it should be possible to replace a path and persist the original paths glob" do + @root.add "app", :glob => "*.rb" + @root["app"] = "app2" + assert_equal ["/foo/bar/app2"], @root["app"].to_a + assert_equal "*.rb", @root["app"].glob + end + test "a path can be added to the load path" do @root["app"] = "app" @root["app"].load_path! diff --git a/railties/test/queueing/test_queue_test.rb b/railties/test/queueing/test_queue_test.rb index 78c6c617fe..2f0f507adb 100644 --- a/railties/test/queueing/test_queue_test.rb +++ b/railties/test/queueing/test_queue_test.rb @@ -2,22 +2,18 @@ require 'abstract_unit' require 'rails/queueing' class TestQueueTest < ActiveSupport::TestCase - class Job - def initialize(&block) - @block = block - end + def setup + @queue = Rails::Queueing::TestQueue.new + end + class ExceptionRaisingJob def run - @block.call if @block + raise end end - def setup - @queue = Rails::Queueing::TestQueue.new - end - def test_drain_raises - @queue.push Job.new { raise } + @queue.push ExceptionRaisingJob.new assert_raises(RuntimeError) { @queue.drain } end @@ -27,41 +23,80 @@ class TestQueueTest < ActiveSupport::TestCase assert_equal [1,2], @queue.jobs end + class EquivalentJob + def initialize + @initial_id = self.object_id + end + + def run + end + + def ==(other) + other.same_initial_id?(@initial_id) + end + + def same_initial_id?(other_id) + other_id == @initial_id + end + end + def test_contents assert @queue.empty? - job = Job.new + job = EquivalentJob.new @queue.push job refute @queue.empty? assert_equal job, @queue.pop end - def test_order - processed = [] + class ProcessingJob + def self.clear_processed + @processed = [] + end + + def self.processed + @processed + end + + def initialize(object) + @object = object + end + + def run + self.class.processed << @object + end + end - job1 = Job.new { processed << 1 } - job2 = Job.new { processed << 2 } + def test_order + ProcessingJob.clear_processed + job1 = ProcessingJob.new(1) + job2 = ProcessingJob.new(2) @queue.push job1 @queue.push job2 @queue.drain - assert_equal [1,2], processed + assert_equal [1,2], ProcessingJob.processed end - def test_drain - t = nil - ran = false + class ThreadTrackingJob + attr_reader :thread_id - job = Job.new do - ran = true - t = Thread.current + def run + @thread_id = Thread.current.object_id end - @queue.push job + def ran? + @thread_id + end + end + + def test_drain + @queue.push ThreadTrackingJob.new + job = @queue.jobs.last @queue.drain assert @queue.empty? - assert ran, "The job runs synchronously when the queue is drained" - assert_not_equal t, Thread.current + assert job.ran?, "The job runs synchronously when the queue is drained" + assert_not_equal job.thread_id, Thread.current.object_id end end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 52c7fae6c6..63814f7a04 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -1098,7 +1098,7 @@ YAML assert_equal "// App's bar js\n;", last_response.body.strip # ensure that railties are not added twice - railties = Rails.application.ordered_railties.map(&:class) + railties = Rails.application.send(:ordered_railties).map(&:class) assert_equal railties, railties.uniq end diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb index 1938bfb6c2..c90b795d59 100644 --- a/railties/test/railties/generators_test.rb +++ b/railties/test/railties/generators_test.rb @@ -75,6 +75,18 @@ module RailtiesTests end end + def test_table_name_prefix_is_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g model namespaced/topic") + assert_file "app/models/foo_bar/namespaced.rb", /module FooBar\n module Namespaced/ do |content| + assert_class_method :table_name_prefix, content do |method_content| + assert_match(/'foo_bar_namespaced_'/, method_content) + end + end + end + end + def test_helpers_are_correctly_namespaced_when_engine_is_mountable build_mountable_engine Dir.chdir(engine_path) do |