diff options
270 files changed, 3963 insertions, 1816 deletions
diff --git a/.travis.yml b/.travis.yml index 9e7a449010..16086e2663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +services: memcache script: 'ci/travis.rb' before_install: - travis_retry gem install bundler @@ -11,6 +11,7 @@ gem 'rack-cache', '~> 1.2' gem 'jquery-rails', '~> 3.1.0' gem 'turbolinks' gem 'coffee-rails', '~> 4.0.0' +gem 'sprockets-rails', github: 'rails/sprockets-rails', branch: '2-1-stable' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) @@ -76,7 +76,7 @@ We encourage you to contribute to Ruby on Rails! Please check out the ## Code Status -* [![Build Status](https://travis-ci.org/rails/rails.png?branch=master)](https://travis-ci.org/rails/rails) +* [![Build Status](https://travis-ci.org/rails/rails.svg?branch=master)](https://travis-ci.org/rails/rails) ## License diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc index 9b06d0d162..c6c1c12e87 100644 --- a/RELEASING_RAILS.rdoc +++ b/RELEASING_RAILS.rdoc @@ -158,7 +158,7 @@ commits should be added to the release branch besides regression fixing commits. == Day of release Many of these steps are the same as for the release candidate, so if you need -more explanation on a particular step, so the RC steps. +more explanation on a particular step, see the RC steps. Today, do this stuff in this order: diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index e70e0b5fa7..221aaa338c 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,9 +1,47 @@ +* Make URL escaping more consistent: + + 1. Escape '%' characters in URLs - only unescaped data should be passed to URL helpers + 2. Add an `escape_segment` helper to `Router::Utils` that escapes '/' characters + 3. Use `escape_segment` rather than `escape_fragment` in optimized URL generation + 4. Use `escape_segment` rather than `escape_path` in URL generation + + For point 4 there are two exceptions. Firstly, when a route uses wildcard segments + (e.g. *foo) then we use `escape_path` as the value may contain '/' characters. This + means that wildcard routes can't be optimized. Secondly, if a `:controller` segment + is used in the path then this uses `escape_path` as the controller may be namespaced. + + Fixes #14629, #14636 and #14070. + + *Andrew White*, *Edho Arief* + +* Add alias `ActionDispatch::Http::UploadedFile#to_io` to + `ActionDispatch::Http::UploadedFile#tempfile`. + + *Tim Linquist* + +* Returns null type format when format is not know and controller is using `any` + format block. + + Fixes #14462. + + *Rafael Mendonça França* + +* Improve routing error page with fuzzy matching search. + + *Winston* + +* Only make deeply nested routes shallow when parent is shallow. + + Fixes #14684. + + *Andrew White*, *James Coglan* + * Append link to bad code to backtrace when exception is SyntaxError. *Boris Kuznetsov* - + * Swapped the parameters of assert_equal in `assert_select` so that the - proper values were printed correctly + proper values were printed correctly Fixes #14422. diff --git a/actionpack/RUNNING_UNIT_TESTS.rdoc b/actionpack/RUNNING_UNIT_TESTS.rdoc index ad1448f61b..2f923136d9 100644 --- a/actionpack/RUNNING_UNIT_TESTS.rdoc +++ b/actionpack/RUNNING_UNIT_TESTS.rdoc @@ -8,10 +8,10 @@ Rake can be found at http://rake.rubyforge.org. == Running by hand -To run a single test suite +Run a single test suite: rake test TEST=path/to/test.rb -which can be further narrowed down to one test: +Run one test in a test suite: rake test TEST=path/to/test.rb TESTOPTS="--name=test_something" diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index d6c941832f..69aca308d6 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -178,41 +178,35 @@ module AbstractController # set up before_action, prepend_before_action, skip_before_action, etc. # for each of before, after, and around. [:before, :after, :around].each do |callback| - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - # Append a before, after or around callback. See _insert_callbacks - # for details on the allowed parameters. - def #{callback}_action(*names, &blk) # def before_action(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options) - end # end - end # end - - alias_method :#{callback}_filter, :#{callback}_action - - # Prepend a before, after or around callback. See _insert_callbacks - # for details on the allowed parameters. - def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) - end # end - end # end - - alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action - - # Skip a before, after or around callback. See _insert_callbacks - # for details on the allowed parameters. - def skip_#{callback}_action(*names) # def skip_before_action(*names) - _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options| - skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options) - end # end - end # end - - alias_method :skip_#{callback}_filter, :skip_#{callback}_action - - # *_action is the same as append_*_action - alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action - alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action - RUBY_EVAL + define_method "#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options) + end + end + + alias_method :"#{callback}_filter", :"#{callback}_action" + + define_method "prepend_#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options.merge(:prepend => true)) + end + end + + alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action" + + # Skip a before, after or around callback. See _insert_callbacks + # for details on the allowed parameters. + define_method "skip_#{callback}_action" do |*names| + _insert_callbacks(names) do |name, options| + skip_callback(:process_action, callback, name, options) + end + end + + alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action" + + # *_action is the same as append_*_action + alias_method :"append_#{callback}_action", :"#{callback}_action" # alias_method :append_before_action, :before_action + alias_method :"append_#{callback}_filter", :"#{callback}_action" # alias_method :append_before_filter, :before_action end end end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index b84c9e78c3..0f4cc7a8f5 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -70,7 +70,8 @@ module ActionController # can do the following: # # class HelloController < ActionController::Metal - # include ActionController::Rendering + # include AbstractController::Rendering + # include ActionView::Layouts # append_view_path "#{Rails.root}/app/views" # # def index diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 6c7b4652d4..0443b73953 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -42,8 +42,8 @@ module ActionController nil end - # Hash of available renderers, mapping a renderer name to its proc. - # Default keys are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>. + # A Set containing renderer names that correspond to available renderer procs. + # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>. RENDERERS = Set.new # Adds a new renderer to call within controller actions. diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index e24b56fa91..5096558c67 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -22,7 +22,7 @@ module ActionController #:nodoc: # # 3) if the responder does not <code>respond_to :to_xml</code>, call <code>#to_format</code> on it. # - # === Builtin HTTP verb semantics + # === Built-in HTTP verb semantics # # The default \Rails responder holds semantics for each HTTP verb. Depending on the # content type, verb and the resource status, it will behave differently. diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index df57efaa97..caaebc537a 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -17,8 +17,9 @@ module ActionController @_templates = Hash.new(0) @_layouts = Hash.new(0) @_files = Hash.new(0) + @_subscribers = [] - ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload| + @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload| path = payload[:layout] if path @_layouts[path] += 1 @@ -28,7 +29,7 @@ module ActionController end end - ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| + @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| path = payload[:virtual_path] next unless path partial = path =~ /^.*\/_[^\/]*$/ @@ -41,7 +42,7 @@ module ActionController @_templates[path] += 1 end - ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| + @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload| next if payload[:virtual_path] # files don't have virtual path path = payload[:identifier] @@ -53,8 +54,9 @@ module ActionController end def teardown_subscriptions - ActiveSupport::Notifications.unsubscribe("render_template.action_view") - ActiveSupport::Notifications.unsubscribe("!render_template.action_view") + @_subscribers.each do |subscriber| + ActiveSupport::Notifications.unsubscribe(subscriber) + end end def process(*args) diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 289e204ac8..2b851cc28d 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -6,8 +6,8 @@ module ActionDispatch module Http # Allows you to specify sensitive parameters which will be replaced from # the request log by looking in the query string of the request and all - # subhashes of the params hash to filter. If a block is given, each key and - # value of the params hash and all subhashes is passed to it, the value + # sub-hashes of the params hash to filter. If a block is given, each key and + # value of the params hash and all sub-hashes is passed to it, the value # or key can be replaced using String#replace or similar method. # # env["action_dispatch.parameter_filter"] = [:password] diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index b803ce8b6f..0b2b60d2e4 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -129,7 +129,7 @@ module ActionDispatch end end - order.include?(Mime::ALL) ? formats.first : nil + order.include?(Mime::ALL) ? format : nil end protected diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index a8d2dc3950..45bf751d09 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -18,6 +18,7 @@ module ActionDispatch # A +Tempfile+ object with the actual uploaded file. Note that some of # its interface is available directly. attr_accessor :tempfile + alias :to_io :tempfile # A string with the headers of the multipart request. attr_accessor :headers diff --git a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb index 254c2befc4..94b0a24344 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/simulator.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/simulator.rb @@ -19,6 +19,14 @@ module ActionDispatch end def simulate(string) + ms = memos(string) { return } + MatchData.new(ms) + end + + alias :=~ :simulate + alias :match :simulate + + def memos(string) input = StringScanner.new(string) state = [0] while sym = input.scan(%r([/.?]|[^/.?]+)) @@ -29,15 +37,10 @@ module ActionDispatch tt.accepting? s } - return if acceptance_states.empty? + return yield if acceptance_states.empty? - memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact - - MatchData.new(memos) + acceptance_states.flat_map { |x| tt.memo(x) }.compact end - - alias :=~ :simulate - alias :match :simulate end end end diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb index e6212b1ee2..990d2127ee 100644 --- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb +++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb @@ -40,7 +40,19 @@ module ActionDispatch end def move(t, a) - move_string(t, a).concat(move_regexp(t, a)) + return [] if t.empty? + + regexps = [] + + t.map { |s| + if states = @regexp_states[s] + regexps.concat states.map { |re, v| re === a ? v : nil } + end + + if states = @string_states[s] + states[a] + end + }.compact.concat regexps end def as_json(options = nil) @@ -139,26 +151,6 @@ module ActionDispatch raise ArgumentError, 'unknown symbol: %s' % sym.class end end - - def move_regexp(t, a) - return [] if t.empty? - - t.flat_map { |s| - if states = @regexp_states[s] - states.map { |re, v| re === a ? v : nil } - end - }.compact.uniq - end - - def move_string(t, a) - return [] if t.empty? - - t.map do |s| - if states = @string_states[s] - states[a] - end - end.compact - end end end end diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index c8eb0f6f2d..2b399d3ee3 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -101,6 +101,10 @@ module ActionDispatch end end + def glob? + !path.spec.grep(Nodes::Star).empty? + end + def dispatcher? @dispatcher end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 419e665d12..36561c71a1 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -121,8 +121,7 @@ module ActionDispatch def filter_routes(path) return [] unless ast - data = simulator.match(path) - data ? data.memos : [] + simulator.memos(path) { [] } end def find_routes env diff --git a/actionpack/lib/action_dispatch/journey/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index d1a004af50..ac4ecb1e65 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -1,5 +1,3 @@ -require 'uri' - module ActionDispatch module Journey # :nodoc: class Router # :nodoc: @@ -25,31 +23,67 @@ module ActionDispatch # URI path and fragment escaping # http://tools.ietf.org/html/rfc3986 - module UriEscape # :nodoc: - # Symbol captures can generate multiple path segments, so include /. - reserved_segment = '/' - reserved_fragment = '/?' - reserved_pchar = ':@&=+$,;%' - - safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}" - safe_segment = "#{safe_pchar}#{reserved_segment}" - safe_fragment = "#{safe_pchar}#{reserved_fragment}" - UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze - UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze + class UriEncoder # :nodoc: + ENCODE = "%%%02X".freeze + ENCODING = Encoding::US_ASCII + EMPTY = "".force_encoding(ENCODING).freeze + DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(ENCODING) } + + ALPHA = "a-zA-Z".freeze + DIGIT = "0-9".freeze + UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze + SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze + + ESCAPED = /%[a-zA-Z0-9]{2}/.freeze + + FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze + SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze + PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze + + def escape_fragment(fragment) + escape(fragment, FRAGMENT) + end + + def escape_path(path) + escape(path, PATH) + end + + def escape_segment(segment) + escape(segment, SEGMENT) + end + + def unescape_uri(uri) + uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(uri.encoding) + end + + protected + def escape(component, pattern) + component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(ENCODING) + end + + def percent_encode(unsafe) + safe = EMPTY.dup + unsafe.each_byte { |b| safe << DEC2HEX[b] } + safe + end end - Parser = URI::Parser.new + ENCODER = UriEncoder.new def self.escape_path(path) - Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT) + ENCODER.escape_path(path.to_s) + end + + def self.escape_segment(segment) + ENCODER.escape_segment(segment.to_s) end def self.escape_fragment(fragment) - Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT) + ENCODER.escape_fragment(fragment.to_s) end def self.unescape_uri(uri) - Parser.unescape(uri) + ENCODER.unescape_uri(uri) end end end diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index daade5bb74..d9f634623d 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -114,19 +114,26 @@ module ActionDispatch end private + def escape_path(value) + Router::Utils.escape_path(value) + end + + def escape_segment(value) + Router::Utils.escape_segment(value) + end def visit(node, optional = false) case node.type when :LITERAL, :SLASH, :DOT node.left when :STAR - visit(node.left) + visit_STAR(node.left) when :GROUP visit(node.left, true) when :CAT visit_CAT(node, optional) when :SYMBOL - visit_SYMBOL(node) + visit_SYMBOL(node, node.to_sym) end end @@ -141,9 +148,15 @@ module ActionDispatch end end - def visit_SYMBOL(node) + def visit_STAR(node) if value = options[node.to_sym] - Router::Utils.escape_path(value) + escape_path(value) + end + end + + def visit_SYMBOL(node, name) + if value = options[name] + name == :controller ? escape_path(value) : escape_segment(value) end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 3be0ce1860..0864e7ef2a 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -29,7 +29,7 @@ module ActionDispatch # # Configure your session store in config/initializers/session_store.rb: # - # Myapp::Application.config.session_store :cookie_store, key: '_your_app_session' + # Rails.application.config.session_store :cookie_store, key: '_your_app_session' # # Configure your secret key in config/secrets.yml: # diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 999c022535..0c7caef25d 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -32,11 +32,14 @@ module ActionDispatch private def redirect_to_https(request) - url = URI(request.url) - url.scheme = "https" - url.host = @host if @host - url.port = @port if @port - headers = { 'Content-Type' => 'text/html', 'Location' => url.to_s } + host = @host || request.host + port = @port || request.port + + location = "https://#{host}" + location << ":#{port}" if port != 80 + location << request.fullpath + + headers = { 'Content-Type' => 'text/html', 'Location' => location } [301, headers, []] end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb index f154021ae6..f154021ae6 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb new file mode 100644 index 0000000000..603de54b8b --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb @@ -0,0 +1,9 @@ +<%= @exception.class.to_s %><% + if @request.parameters['controller'] +%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> +<% end %> + +<%= @exception.message %> +<%= render template: "rescues/_source" %> +<%= render template: "rescues/_trace" %> +<%= render template: "rescues/_request_and_response" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index 323873ba4b..cce0d75af4 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -4,21 +4,41 @@ border-collapse: collapse; } - #route_table td { - padding: 0 30px; + #route_table thead tr { + border-bottom: 2px solid #ddd; + } + + #route_table thead tr.bottom { + border-bottom: none; } - #route_table tr.bottom th { - padding-bottom: 10px; + #route_table thead tr.bottom th { + padding: 10px 0; line-height: 15px; } - #route_table .matched_paths { + #route_table tbody tr { + border-bottom: 1px solid #ddd; + } + + #route_table tbody tr:nth-child(odd) { + background: #f2f2f2; + } + + #route_table tbody.exact_matches, + #route_table tbody.fuzzy_matches { background-color: LightGoldenRodYellow; + border-bottom: solid 2px SlateGrey; } - #route_table .matched_paths { - border-bottom: solid 3px SlateGrey; + #route_table tbody.exact_matches tr, + #route_table tbody.fuzzy_matches tr { + background: none; + border-bottom: none; + } + + #route_table td { + padding: 4px 30px; } #path_search { @@ -45,13 +65,15 @@ <th><%# HTTP Verb %> </th> <th><%# Path %> - <%= search_field(:path, nil, id: 'path_search', placeholder: "Path Match") %> + <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %> </th> <th><%# Controller#action %> </th> </tr> </thead> - <tbody class='matched_paths' id='matched_paths'> + <tbody class='exact_matches' id='exact_matches'> + </tbody> + <tbody class='fuzzy_matches' id='fuzzy_matches'> </tbody> <tbody> <%= yield %> @@ -59,6 +81,7 @@ </table> <script type='text/javascript'> + // Iterates each element through a function function each(elems, func) { if (!elems instanceof Array) { elems = [elems]; } for (var i = 0, len = elems.length; i < len; i++) { @@ -66,77 +89,110 @@ } } - function setValOn(elems, val) { - each(elems, function(elem) { - elem.innerHTML = val; - }); + // Sets innerHTML for an element + function setContent(elem, text) { + elem.innerHTML = text; } - function onClick(elems, func) { - each(elems, function(elem) { - elem.onclick = func; - }); - } + // Enables path search functionality + function setupMatchPaths() { + // Check if the user input (sanitized as a path) matches the regexp data attribute + function checkExactMatch(section, elem, value) { + var string = sanitizePath(value), + regexp = elem.getAttribute("data-regexp"); - // Enables functionality to toggle between `_path` and `_url` helper suffixes - function setupRouteToggleHelperLinks() { - var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]'); - onClick(toggleLinks, function(){ - var helperTxt = this.getAttribute("data-route-helper"), - helperElems = document.querySelectorAll('[data-route-name] span.helper'); - setValOn(helperElems, helperTxt); - }); - } + showMatch(string, regexp, section, elem); + } - // takes an array of elements with a data-regexp attribute and - // passes their parent <tr> into the callback function - // if the regexp matches a given path - function eachElemsForPath(elems, path, func) { - each(elems, function(e){ - var reg = e.getAttribute("data-regexp"); - if (path.match(RegExp(reg))) { - func(e.parentNode.cloneNode(true)); - } - }) - } + // Check if the route path data attribute contains the user input + function checkFuzzyMatch(section, elem, value) { + var string = elem.getAttribute("data-route-path"), + regexp = value; - // Ensure path always starts with a slash "/" and remove params or fragments - function sanitizePath(path) { - var path = path.charAt(0) == '/' ? path : "/" + path; - return path.replace(/\#.*|\?.*/, ''); - } + showMatch(string, regexp, section, elem); + } - // Enables path search functionality - function setupMatchPaths() { - var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), - pathElem = document.querySelector('#path_search'), - selectedSection = document.querySelector('#matched_paths'), - noMatchText = '<tr><th colspan="4">None</th></tr>'; + // Display the parent <tr> element in the appropriate section when there's a match + function showMatch(string, regexp, section, elem) { + if(string.match(RegExp(regexp))) { + section.appendChild(elem.parentNode.cloneNode(true)); + } + } + + // Check if there are any matched results in a section + function checkNoMatch(section, defaultText, noMatchText) { + if (section.innerHTML === defaultText) { + setContent(section, defaultText + noMatchText); + } + } + // Ensure path always starts with a slash "/" and remove params or fragments + function sanitizePath(path) { + var path = path.charAt(0) == '/' ? path : "/" + path; + return path.replace(/\#.*|\?.*/, ''); + } - // Remove matches if no path is present - pathElem.onblur = function(e) { - if (pathElem.value === "") selectedSection.innerHTML = ""; + var regexpElems = document.querySelectorAll('#route_table [data-regexp]'), + searchElem = document.querySelector('#search'), + exactMatches = document.querySelector('#exact_matches'), + fuzzyMatches = document.querySelector('#fuzzy_matches'); + + // Remove matches when no search value is present + searchElem.onblur = function(e) { + if (searchElem.value === "") { + setContent(exactMatches, ""); + setContent(fuzzyMatches, ""); + } } // On key press perform a search for matching paths - pathElem.onkeyup = function(e){ - var path = sanitizePath(pathElem.value), - defaultText = '<tr><th colspan="4">Paths Matching (' + path + '):</th></tr>'; + searchElem.onkeyup = function(e){ + var userInput = searchElem.value, + defaultExactMatch = '<tr><th colspan="4">Paths Matching (' + sanitizePath(userInput) +'):</th></tr>', + defaultFuzzyMatch = '<tr><th colspan="4">Paths Containing (' + userInput +'):</th></tr>', + noExactMatch = '<tr><th colspan="4">No Exact Matches Found</th></tr>', + noFuzzyMatch = '<tr><th colspan="4">No Fuzzy Matches Found</th></tr>'; // Clear out results section - selectedSection.innerHTML= defaultText; + setContent(exactMatches, defaultExactMatch); + setContent(fuzzyMatches, defaultFuzzyMatch); + + // Display exact matches and fuzzy matches + each(regexpElems, function(elem) { + checkExactMatch(exactMatches, elem, userInput); + checkFuzzyMatch(fuzzyMatches, elem, userInput); + }) + + // Display 'No Matches' message when no matches are found + checkNoMatch(exactMatches, defaultExactMatch, noExactMatch); + checkNoMatch(fuzzyMatches, defaultFuzzyMatch, noFuzzyMatch); + } + } - // Display matches if they exist - eachElemsForPath(regexpElems, path, function(e){ - selectedSection.appendChild(e); + // Enables functionality to toggle between `_path` and `_url` helper suffixes + function setupRouteToggleHelperLinks() { + + // Sets content for each element + function setValOn(elems, val) { + each(elems, function(elem) { + setContent(elem, val); }); + } - // If no match present, tell the user - if (selectedSection.innerHTML === defaultText) { - selectedSection.innerHTML = selectedSection.innerHTML + noMatchText; - } + // Sets onClick event for each element + function onClick(elems, func) { + each(elems, function(elem) { + elem.onclick = func; + }); } + + var toggleLinks = document.querySelectorAll('#route_table [data-route-helper]'); + onClick(toggleLinks, function(){ + var helperTxt = this.getAttribute("data-route-helper"), + helperElems = document.querySelectorAll('[data-route-name] span.helper'); + + setValOn(helperElems, helperTxt); + }); } setupMatchPaths(); diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index a9ac2bce1d..9cd884daa3 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -11,7 +11,7 @@ module ActionDispatch # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # AppName::Application.routes.draw do + # Rails.application.routes.draw do # Pattern 1 tells some request to go to one place # Pattern 2 tell them to go to another # ... diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 6f0b49cf28..77718a14c1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -995,6 +995,7 @@ module ActionDispatch @as = options[:as] @param = (options[:param] || :id).to_sym @options = options + @shallow = false end def default_actions @@ -1055,6 +1056,13 @@ module ActionDispatch "#{path}/:#{nested_param}" end + def shallow=(value) + @shallow = value + end + + def shallow? + @shallow + end end class SingletonResource < Resource #:nodoc: @@ -1361,7 +1369,7 @@ module ActionDispatch end with_scope_level(:nested) do - if shallow? && nesting_depth > 1 + if shallow? && shallow_nesting_depth > 1 shallow_scope(parent_resource.nested_scope, nested_options) { yield } else scope(parent_resource.nested_scope, nested_options) { yield } @@ -1576,6 +1584,7 @@ module ActionDispatch end def resource_scope(kind, resource) #:nodoc: + resource.shallow = @scope[:shallow] old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource @nesting.push(resource) @@ -1600,6 +1609,10 @@ module ActionDispatch @nesting.size end + def shallow_nesting_depth #:nodoc: + @nesting.select(&:shallow?).size + end + def param_constraint? #:nodoc: @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index a03fb4cee7..1ec6fa674b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -155,7 +155,7 @@ module ActionDispatch end def self.optimize_helper?(route) - route.requirements.except(:controller, :action).empty? + !route.glob? && route.requirements.except(:controller, :action).empty? end class OptimizedUrlHelper < UrlHelper # :nodoc: @@ -194,7 +194,7 @@ module ActionDispatch end def replace_segment(params, segment) - Symbol === segment ? @klass.escape_fragment(params[segment]) : segment + Symbol === segment ? @klass.escape_segment(params[segment]) : segment end def optimize_routes_generation?(t) diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 8a128427bf..12023e6f77 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -267,7 +267,7 @@ module ActionDispatch text.strip! unless NO_STRIP.include?(match.name) text.sub!(/\A\n/, '') if match.name == "textarea" unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text) + content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, text) true end end @@ -276,7 +276,7 @@ module ActionDispatch html = match.children.map(&:to_s).join html.strip! unless NO_STRIP.include?(match.name) unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s) - content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, html) + content_mismatch ||= sprintf("<%s> expected but was\n<%s>", match_with, html) true end end @@ -289,7 +289,7 @@ module ActionDispatch # FIXME: minitest provides messaging when we use assert_operator, # so is this custom message really needed? - message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.) + message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}) if count assert_equal count, matches.size, message else diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 114bbf3c22..580604df3a 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -79,13 +79,13 @@ class AssertSelectTest < ActionController::TestCase def test_assert_select render_html %Q{<div id="1"></div><div id="2"></div>} assert_select "div", 2 - assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" } + assert_failure(/\AExpected at least 1 element matching \"p\", found 0\.$/) { assert_select "p" } end def test_equality_integer render_html %Q{<div id="1"></div><div id="2"></div>} - assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) { assert_select "div", 3 } - assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", 0 } + assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) { assert_select "div", 3 } + assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", 0 } end def test_equality_true_false @@ -100,13 +100,14 @@ class AssertSelectTest < ActionController::TestCase def test_equality_false_message render_html %Q{<div id="1"></div><div id="2"></div>} - assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", false } + assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", false } end def test_equality_string_and_regexp render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", "foo" } assert_raise(Assertion) { assert_select "div", "bar" } + assert_failure(/\A<bar> expected but was\n<foo>\.$/) { assert_select "div", "bar" } assert_nothing_raised { assert_select "div", :text=>"foo" } assert_raise(Assertion) { assert_select "div", :text=>"bar" } assert_nothing_raised { assert_select "div", /(foo|bar)/ } @@ -124,6 +125,7 @@ class AssertSelectTest < ActionController::TestCase assert_raise(Assertion) { assert_select "p", html } assert_nothing_raised { assert_select "p", :html=>html } assert_raise(Assertion) { assert_select "p", :html=>text } + assert_failure(/\A<#{text}> expected but was\n<#{html}>\.$/) { assert_select "p", :html=>text } # No stripping for pre. render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>} text = "\n\"This is not a big problem,\" he said.\n" @@ -144,29 +146,29 @@ class AssertSelectTest < ActionController::TestCase def test_counts render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", 2 } - assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do + assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do assert_select "div", 3 end assert_nothing_raised { assert_select "div", 1..2 } - assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do + assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do assert_select "div", 3..4 end assert_nothing_raised { assert_select "div", :count=>2 } - assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do + assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do assert_select "div", :count=>3 end assert_nothing_raised { assert_select "div", :minimum=>1 } assert_nothing_raised { assert_select "div", :minimum=>2 } - assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do + assert_failure(/\AExpected at least 3 elements matching \"div\", found 2\.$/) do assert_select "div", :minimum=>3 end assert_nothing_raised { assert_select "div", :maximum=>2 } assert_nothing_raised { assert_select "div", :maximum=>3 } - assert_failure(/Expected at most 1 element matching \"div\", found 2/) do + assert_failure(/\AExpected at most 1 element matching \"div\", found 2\.$/) do assert_select "div", :maximum=>1 end assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 } - assert_failure(/Expected between 3 and 4 elements matching \"div\", found 2/) do + assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do assert_select "div", :minimum=>3, :maximum=>4 end end @@ -204,7 +206,7 @@ class AssertSelectTest < ActionController::TestCase end end - assert_failure(/Expected at least 1 element matching \"#4\", found 0\./) do + assert_failure(/\AExpected at least 1 element matching \"#4\", found 0\.$/) do assert_select "div" do assert_select "#4" end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 25a4857eba..3720a920d0 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -210,20 +210,29 @@ class FlashTest < ActionController::TestCase end def test_redirect_to_with_adding_flash_types - @controller.class.add_flash_types :foo + original_controller = @controller + test_controller_with_flash_type_foo = Class.new(TestController) do + add_flash_types :foo + end + @controller = test_controller_with_flash_type_foo.new get :redirect_with_foo_flash assert_equal "for great justice", @controller.send(:flash)[:foo] + ensure + @controller = original_controller end - class SubclassesTestController < TestController; end - def test_add_flash_type_to_subclasses - TestController.add_flash_types :foo - assert SubclassesTestController._flash_types.include?(:foo) + test_controller_with_flash_type_foo = Class.new(TestController) do + add_flash_types :foo + end + subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo) + assert subclass_controller_with_no_flash_type._flash_types.include?(:foo) end - def test_do_not_add_flash_type_to_parent_class - SubclassesTestController.add_flash_types :bar + def test_does_not_add_flash_type_to_parent_class + Class.new(TestController) do + add_flash_types :bar + end assert_not TestController._flash_types.include?(:bar) end end diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 499c62cc35..ce6d135d92 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -492,6 +492,11 @@ class RespondToControllerTest < ActionController::TestCase assert_equal 'Whatever you ask for, I got it', @response.body end + def test_handle_any_any_unkown_format + get :handle_any_any, { format: 'php' } + assert_equal 'Whatever you ask for, I got it', @response.body + end + def test_browser_check_with_any_any @request.accept = "application/json, application/xml" get :json_xml_or_html diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 99229b3baf..5ab5141966 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -138,14 +138,14 @@ module RequestForgeryProtectionTests assert_not_blocked do get :index end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_button_to_with_token_tag assert_not_blocked do get :show_button end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_without_token_tag_if_remote @@ -175,7 +175,7 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' ensure ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original end @@ -185,21 +185,21 @@ module RequestForgeryProtectionTests assert_not_blocked do get :form_for_remote_with_external_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' end def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested assert_not_blocked do get :form_for_remote_with_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_render_form_with_token_tag_with_authenticity_token_requested assert_not_blocked do get :form_for_with_token end - assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token end def test_should_allow_get diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 0dba651139..8660deb634 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -147,9 +147,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/", {}, xhr_request_env assert_response 500 + assert_no_match(/<header>/, body) assert_no_match(/<body>/, body) assert_equal response.content_type, "text/plain" - assert_match(/puke/, body) + assert_match(/RuntimeError\npuke/, body) get "/not_found", {}, xhr_request_env assert_response 404 diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index ab2f0ec8de..b22a56bb27 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1994,6 +1994,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'cards#destroy', @response.body end + def test_shallow_deeply_nested_resources + draw do + resources :blogs do + resources :posts do + resources :comments, shallow: true + end + end + end + + get '/comments/1' + assert_equal 'comments#show', @response.body + + assert_equal '/comments/1', comment_path('1') + assert_equal '/blogs/new', new_blog_path + assert_equal '/blogs/1/posts/new', new_blog_post_path(:blog_id => 1) + assert_equal '/blogs/1/posts/2/comments/new', new_blog_post_comment_path(:blog_id => 1, :post_id => 2) + end + def test_shallow_nested_resources_within_scope draw do scope '/hello' do @@ -3578,8 +3596,8 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest include Routes.url_helpers def app; Routes end - test 'escapes generated path segment' do - assert_equal '/a%20b/c+d', segment_path(:segment => 'a b/c+d') + test 'escapes slash in generated path segment' do + assert_equal '/a%20b%2Fc+d', segment_path(:segment => 'a b/c+d') end test 'unescapes recognized path segment' do @@ -3587,7 +3605,7 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest assert_equal 'a b/c+d', @response.body end - test 'escapes generated path splat' do + test 'does not escape slash in generated path splat' do assert_equal '/a%20b/c+d', splat_path(:splat => 'a b/c+d') end @@ -3772,6 +3790,8 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest get '/post(/:action(/:id))' => ok, as: :posts get '/:foo/:foo_type/bars/:id' => ok, as: :bar get '/projects/:id.:format' => ok, as: :project + get '/pages/:id' => ok, as: :page + get '/wiki/*page' => ok, as: :wiki end end @@ -3804,6 +3824,26 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest assert_equal '/projects/1.json', Routes.url_helpers.project_path(1, :json) assert_equal '/projects/1.json', project_path(1, :json) end + + test 'segments with question marks are escaped' do + assert_equal '/pages/foo%3Fbar', Routes.url_helpers.page_path('foo?bar') + assert_equal '/pages/foo%3Fbar', page_path('foo?bar') + end + + test 'segments with slashes are escaped' do + assert_equal '/pages/foo%2Fbar', Routes.url_helpers.page_path('foo/bar') + assert_equal '/pages/foo%2Fbar', page_path('foo/bar') + end + + test 'glob segments with question marks are escaped' do + assert_equal '/wiki/foo%3Fbar', Routes.url_helpers.wiki_path('foo?bar') + assert_equal '/wiki/foo%3Fbar', wiki_path('foo?bar') + end + + test 'glob segments with slashes are not escaped' do + assert_equal '/wiki/foo/bar', Routes.url_helpers.wiki_path('foo/bar') + assert_equal '/wiki/foo/bar', wiki_path('foo/bar') + end end class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 94969f795a..c3598c5e8e 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -196,6 +196,13 @@ class SSLTest < ActionDispatch::IntegrationTest response.headers['Location'] end + def test_redirect_to_host_with_port + self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org:443") + get "http://example.org/path?key=value" + assert_equal "https://ssl.example.org:443/path?key=value", + response.headers['Location'] + end + def test_redirect_to_secure_host_when_on_subdomain self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org") get "http://ssl.example.org/path?key=value" diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 72f3d1db0d..9f6381f118 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -33,6 +33,12 @@ module ActionDispatch assert_equal 'foo', uf.tempfile end + def test_to_io_returns_the_tempfile + tf = Object.new + uf = Http::UploadedFile.new(:tempfile => tf) + assert_equal tf, uf.to_io + end + def test_delegates_path_to_tempfile tf = Class.new { def path; 'thunderhorse' end } uf = Http::UploadedFile.new(:tempfile => tf.new) diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb index 93348f4647..584fd56a5c 100644 --- a/actionpack/test/journey/router/utils_test.rb +++ b/actionpack/test/journey/router/utils_test.rb @@ -5,11 +5,15 @@ module ActionDispatch class Router class TestUtils < ActiveSupport::TestCase def test_path_escape - assert_equal "a/b%20c+d", Utils.escape_path("a/b c+d") + assert_equal "a/b%20c+d%25", Utils.escape_path("a/b c+d%") + end + + def test_segment_escape + assert_equal "a%2Fb%20c+d%25", Utils.escape_segment("a/b c+d%") end def test_fragment_escape - assert_equal "a/b%20c+d?e", Utils.escape_fragment("a/b c+d?e") + assert_equal "a/b%20c+d%25?e", Utils.escape_fragment("a/b c+d%?e") end def test_uri_unescape diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index a286f77633..e54b64e0f3 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -367,7 +367,18 @@ module ActionDispatch nil, { :controller => "tasks", :action => "a/b c+d", }, {}) - assert_equal '/tasks/a/b%20c+d', path + assert_equal '/tasks/a%2Fb%20c+d', path + end + + def test_generate_escapes_with_namespaced_controller + path = Path::Pattern.new '/:controller(/:action)' + @router.routes.add_route @app, path, {}, {}, {} + + path, _ = @formatter.generate(:path_info, + nil, { :controller => "admin/tasks", + :action => "a/b c+d", + }, {}) + assert_equal '/admin/tasks/a%2Fb%20c+d', path end def test_generate_extra_params diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 8c6db33be7..50ca64d536 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,32 @@ +* Change `favicon_link_tag` default mimetype from `image/vnd.microsoft.icon` to + `image/x-icon`. + + Before: + + #=> favicon_link_tag 'myicon.ico' + <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> + + After: + + #=> favicon_link_tag 'myicon.ico' + <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" /> + + *Geoffroy Lorieux* + +* Remove wrapping div with inline styles for hidden form fields. + + We are dropping HTML 4.01 and XHTML strict compliance since input tags directly + inside a form are valid HTML5, and the absense of inline styles help in validating + for Content Security Policy. + + *Joost Baaij* + +* `collection_check_boxes` respects `:index` option for the hidden filed name. + + Fixes #14147. + + *Vasiliy Ermolovich* + * `date_select` helper with option `with_css_classes: true` does not overwrite other classes. *Izumi Wong-Horiuchi* diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index aa49f1edc1..413532826b 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -149,12 +149,12 @@ module ActionView # ==== Options # # * <tt>:rel</tt> - Specify the relation of this link, defaults to 'shortcut icon' - # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/vnd.microsoft.icon' + # * <tt>:type</tt> - Override the auto-generated mime type, defaults to 'image/x-icon' # # ==== Examples # # favicon_link_tag 'myicon.ico' - # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> + # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" /> # # Mobile Safari looks for a different <link> tag, pointing to an image that # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad. @@ -165,7 +165,7 @@ module ActionView def favicon_link_tag(source='favicon.ico', options={}) tag('link', { :rel => 'shortcut icon', - :type => 'image/vnd.microsoft.icon', + :type => 'image/x-icon', :href => path_to_image(source) }.merge!(options.symbolize_keys)) end diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index af70a4242a..227ad4cdfa 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -10,7 +10,7 @@ module ActionView # Full usage example: # # config/routes.rb: - # Basecamp::Application.routes.draw do + # Rails.application.routes.draw do # resources :posts # root to: "posts#index" # end diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index d1c268ec40..4db8930a26 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -11,7 +11,7 @@ module ActionView # The best way to use this is by doing key-based cache expiration # on top of a cache store like Memcached that'll automatically # kick out old entries. For more on key-based expiration, see: - # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works + # http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works # # When using this method, you list the cache dependency as the name of the cache, like so: # diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 1ff090f244..22bfd87d85 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -746,6 +746,7 @@ module ActionView # label(:post, :terms) do # 'Accept <a href="/terms">Terms</a>.'.html_safe # end + # # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label> def label(object_name, method, content_or_options = nil, options = nil, &block) Tags::Label.new(object_name, method, self, content_or_options, options).render(&block) end diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 0bbe08166b..66c9e20682 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -550,6 +550,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # color_field_tag 'name' + # # => <input id="name" name="name" type="color" /> + # + # color_field_tag 'color', '#DEF726' + # # => <input id="color" name="color" type="color" value="#DEF726" /> + # + # color_field_tag 'color', nil, class: 'special_input' + # # => <input class="special_input" id="color" name="color" type="color" /> + # + # color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" /> def color_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "color")) end @@ -558,6 +571,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # search_field_tag 'name' + # # => <input id="name" name="name" type="search" /> + # + # search_field_tag 'search', 'Enter your search query here' + # # => <input id="search" name="search" type="search" value="Enter your search query here" /> + # + # search_field_tag 'search', nil, class: 'special_input' + # # => <input class="special_input" id="search" name="search" type="search" /> + # + # search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" /> def search_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "search")) end @@ -566,6 +592,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # telephone_field_tag 'name' + # # => <input id="name" name="name" type="tel" /> + # + # telephone_field_tag 'tel', '0123456789' + # # => <input id="tel" name="tel" type="tel" value="0123456789" /> + # + # telephone_field_tag 'tel', nil, class: 'special_input' + # # => <input class="special_input" id="tel" name="tel" type="tel" /> + # + # telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" /> def telephone_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "tel")) end @@ -638,6 +677,19 @@ module ActionView # # ==== Options # * Accepts the same options as text_field_tag. + # + # ==== Examples + # url_field_tag 'name' + # # => <input id="name" name="name" type="url" /> + # + # url_field_tag 'url', 'http://rubyonrails.org' + # # => <input id="url" name="url" type="url" value="http://rubyonrails.org" /> + # + # url_field_tag 'url', nil, class: 'special_input' + # # => <input class="special_input" id="url" name="url" type="url" /> + # + # url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true + # # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" /> def url_field_tag(name, value = nil, options = {}) text_field_tag(name, value, options.stringify_keys.update("type" => "url")) end @@ -726,9 +778,11 @@ module ActionView method_tag(method) + token_tag(authenticity_token) end - enforce_utf8 = html_options.delete("enforce_utf8") { true } - tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag - content_tag(:div, tags, :style => 'display:none') + if html_options.delete("enforce_utf8") { true } + utf8_enforcer_tag + method_tag + else + method_tag + end end def form_tag_html(html_options) diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 8b28e4fc33..6242a2a085 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -28,10 +28,7 @@ module ActionView # Append a hidden field to make sure something will be sent back to the # server if all check boxes are unchecked. if @options.fetch(:include_hidden, true) - hidden_name = @html_options[:name] || "#{tag_name}[]" - hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil) - - rendered_collection + hidden + rendered_collection + hidden_field else rendered_collection end @@ -42,6 +39,18 @@ module ActionView def render_component(builder) builder.check_box + builder.label end + + def hidden_field + hidden_name = @html_options[:name] + + hidden_name ||= if @options.has_key?(:index) + "#{tag_name_with_index(@options[:index])}[]" + else + "#{tag_name}[]" + end + + @template_object.hidden_field_tag(hidden_name, "", id: nil) + end end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 991f32cea2..8050638363 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -44,7 +44,7 @@ module ActionView def default_html_options_for_collection(item, value) #:nodoc: html_options = @html_options.dup - [:checked, :selected, :disabled].each do |option| + [:checked, :selected, :disabled, :readonly].each do |option| current_value = @options[option] next if current_value.nil? diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 89c196e578..894616a449 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -323,7 +323,7 @@ module ActionView inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param) end end - content_tag('form', content_tag('div', inner_tags), form_options) + content_tag('form', inner_tags, form_options) end # Creates a link tag of the given +name+ using a URL created by the set of diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 3145446114..9e8e6f43d5 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -235,7 +235,8 @@ module ActionView :@options, :@test_passed, :@view, - :@view_context_class + :@view_context_class, + :@_subscribers ] def _user_defined_ivars diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb index 0a9628da8d..0a62f49f35 100644 --- a/actionview/test/activerecord/form_helper_activerecord_test.rb +++ b/actionview/test/activerecord/form_helper_activerecord_test.rb @@ -59,12 +59,13 @@ class FormHelperActiveRecordTest < ActionView::TestCase protected def hidden_fields(method = nil) - txt = %{<div style="display:none">} - txt << %{<input name="utf8" type="hidden" value="✓" />} + txt = %{<input name="utf8" type="hidden" value="✓" />} + if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} end - txt << %{</div>} + + txt end def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) @@ -88,4 +89,4 @@ class FormHelperActiveRecordTest < ActionView::TestCase form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" end -end
\ No newline at end of file +end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 3ca71d3376..651978ed80 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -195,9 +195,9 @@ class AssetTagHelperTest < ActionView::TestCase } FaviconLinkToTag = { - %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), - %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />), - %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />), + %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />), + %(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />), + %(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/x-icon" />), %(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />), %(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(<link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />) } diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 73fa3b6b4e..5e991d87ad 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -68,6 +68,23 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_no_select 'input[type=radio][value=false][disabled=disabled]' end + test 'collection radio accepts multiple readonly items' do + collection = [[1, true], [0, false], [2, 'other']] + with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => [true, false] + + assert_select 'input[type=radio][value=true][readonly=readonly]' + assert_select 'input[type=radio][value=false][readonly=readonly]' + assert_no_select 'input[type=radio][value=other][readonly=readonly]' + end + + test 'collection radio accepts single readonly item' do + collection = [[1, true], [0, false]] + with_collection_radio_buttons :user, :active, collection, :last, :first, :readonly => true + + assert_select 'input[type=radio][value=true][readonly=readonly]' + assert_no_select 'input[type=radio][value=false][readonly=readonly]' + end + test 'collection radio accepts html options as input' do collection = [[1, true], [0, false]] with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio' @@ -204,6 +221,13 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1 end + test 'collection check boxes generates a hidden field with index if it was provided' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, { index: 322 } + + assert_select "input[type=hidden][name='user[322][category_ids][]'][value=]", count: 1 + end + test 'collection check boxes does not generate a hidden field if include_hidden option is false' do collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] with_collection_check_boxes :user, :category_ids, collection, :id, :name, include_hidden: false @@ -325,6 +349,33 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_no_select 'input[type=checkbox][value=2][disabled=disabled]' end + test 'collection check boxes accepts multiple readonly items' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => [1, 3] + + assert_select 'input[type=checkbox][value=1][readonly=readonly]' + assert_select 'input[type=checkbox][value=3][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=2][readonly=readonly]' + end + + test 'collection check boxes accepts single readonly item' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => 1 + + assert_select 'input[type=checkbox][value=1][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=3][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=2][readonly=readonly]' + end + + test 'collection check boxes accepts a proc to readonly items' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :readonly => proc { |i| i.first == 1 } + + assert_select 'input[type=checkbox][value=1][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=3][readonly=readonly]' + assert_no_select 'input[type=checkbox][value=2][readonly=readonly]' + end + test 'collection check boxes accepts html options' do collection = [[1, 'Category 1'], [2, 'Category 2']] with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check' diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index b5e9801776..0ad0ae6b4b 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -265,6 +265,13 @@ class FormHelperTest < ActionView::TestCase ) end + def test_label_with_block_and_html + assert_dom_equal( + '<label for="post_terms">Accept <a href="/terms">Terms</a>.</label>', + label(:post, :terms) { 'Accept <a href="/terms">Terms</a>.'.html_safe } + ) + end + def test_label_with_block_and_options assert_dom_equal( '<label for="my_for">The title, please:</label>', @@ -1420,7 +1427,7 @@ class FormHelperTest < ActionView::TestCase expected = whole_form("/posts", "new_post", "new_post") do "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" + "<label for='post_1_tag_ids_1'>Tag 1</label>" + - "<input name='post[tag_ids][]' type='hidden' value='' />" + "<input name='post[1][tag_ids][]' type='hidden' value='' />" end assert_dom_equal expected, output_buffer @@ -3020,12 +3027,13 @@ class FormHelperTest < ActionView::TestCase protected def hidden_fields(method = nil) - txt = %{<div style="display:none">} - txt << %{<input name="utf8" type="hidden" value="✓" />} + txt = %{<input name="utf8" type="hidden" value="✓" />} + if method && !%w(get post).include?(method.to_s) txt << %{<input name="_method" type="hidden" value="#{method}" />} end - txt << %{</div>} + + txt end def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index cf824e2733..18c739674a 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -14,12 +14,15 @@ class FormTagHelperTest < ActionView::TestCase method = options[:method] enforce_utf8 = options.fetch(:enforce_utf8, true) - txt = %{<div style="display:none">} - txt << %{<input name="utf8" type="hidden" value="✓" />} if enforce_utf8 - if method && !%w(get post).include?(method.to_s) - txt << %{<input name="_method" type="hidden" value="#{method}" />} + ''.tap do |txt| + if enforce_utf8 + txt << %{<input name="utf8" type="hidden" value="✓" />} + end + + if method && !%w(get post).include?(method.to_s) + txt << %{<input name="_method" type="hidden" value="#{method}" />} + end end - txt << %{</div>} end def form_text(action = "http://www.example.com", options = {}) diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index 7e978e15d2..35279a4558 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -53,12 +53,12 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_button_to_with_straight_url - assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com") + assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com") end def test_button_to_with_path assert_dom_equal( - %{<form method="post" action="/article/Hello" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="/article/Hello" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", article_path("Hello".html_safe)) ) end @@ -67,7 +67,7 @@ class UrlHelperTest < ActiveSupport::TestCase self.request_forgery = true assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></form>}, button_to("Hello", "http://www.example.com") ) ensure @@ -75,102 +75,102 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_button_to_with_form_class - assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class') end def test_button_to_with_form_class_escapes - assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>') end def test_button_to_with_query - assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2") end def test_button_to_with_html_safe_URL - assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) end def test_button_to_with_query_and_no_name - assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&q2=v2" class="button_to"><div><input type="submit" value="http://www.example.com?q1=v1&q2=v2" /></div></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&q2=v2" class="button_to"><input type="submit" value="http://www.example.com?q1=v1&q2=v2" /></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2") end def test_button_to_with_javascript_confirm assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" }) ) end def test_button_to_with_javascript_disable_with assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", data: { disable_with: "Greeting..." }) ) end def test_button_to_with_remote_and_form_options assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: true, form: { class: "custom-class", "data-type" => "json" }) ) end def test_button_to_with_remote_and_javascript_confirm assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-confirm="Are you sure?" type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: true, data: { confirm: "Are you sure?" }) ) end def test_button_to_with_remote_and_javascript_disable_with assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><input data-disable-with="Greeting..." type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: true, data: { disable_with: "Greeting..." }) ) end def test_button_to_with_remote_false assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", remote: false) ) end def test_button_to_enabled_disabled assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", disabled: false) ) assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input disabled="disabled" type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input disabled="disabled" type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", disabled: true) ) end def test_button_to_with_method_delete assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", method: :delete) ) end def test_button_to_with_method_get assert_dom_equal( - %{<form method="get" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + %{<form method="get" action="http://www.example.com" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com", method: :get) ) end def test_button_to_with_block assert_dom_equal( - %{<form method="post" action="http://www.example.com" class="button_to"><div><button type="submit"><span>Hello</span></button></div></form>}, + %{<form method="post" action="http://www.example.com" class="button_to"><button type="submit"><span>Hello</span></button></form>}, button_to("http://www.example.com") { content_tag(:span, 'Hello') } ) end def test_button_to_with_params assert_dom_equal( - %{<form action="http://www.example.com" class="button_to" method="post"><div><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></div></form>}, + %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="foo" value="bar" /><input type="hidden" name="baz" value="quux" /></form>}, button_to("Hello", "http://www.example.com", params: {foo: :bar, baz: "quux"}) ) end diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 3e49c34182..feb3d9371d 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -48,6 +48,7 @@ module ActiveModel eager_autoload do autoload :Errors + autoload :StrictValidationFailed, 'active_model/errors' end module Serializers diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 7022f9bee5..ff41572105 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -53,7 +53,7 @@ module ActiveModel # # Configuration options: # * <tt>:on</tt> - Specifies when this validation is active - # (<tt>:create</tt> or <tt>:update</tt>. + # (<tt>:create</tt> or <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>). diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3fc6e9e158..9138230b5e 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -7,6 +7,177 @@ *Jan Habermann* +* `@destroyed` should always be set to `false` when an object is duped. + + *Kuldeep Aggarwal* + +* Fixed has_many association to make it support irregular inflections. + + Fixes #8928. + + *arthurnn*, *Javier Goizueta* + +* Fixed a problem where count used with a grouping was not returning a Hash. + + Fixes #14721. + + *Eric Chahin* + +* `sanitize_sql_like` helper method to escape a string for safe use in a SQL + LIKE statement. + + Example: + + class Article + def self.search(term) + where("title LIKE ?", sanitize_sql_like(term)) + end + end + + Article.search("20% _reduction_") + # => Query looks like "... title LIKE '20\% \_reduction\_' ..." + + *Rob Gilson*, *Yves Senn* + +* Do not quote uuid default value on `change_column`. + + Fixes #14604. + + *Eric Chahin* + +* The comparison between `Relation` and `CollectionProxy` should be consistent. + + Example: + + author.posts == Post.where(author_id: author.id) + # => true + Post.where(author_id: author.id) == author.posts + # => true + + Fixes #13506. + + *Lauro Caetano* + +* Calling `delete_all` on an unloaded `CollectionProxy` no longer + generates a SQL statement containing each id of the collection: + + Before: + + DELETE FROM `model` WHERE `model`.`parent_id` = 1 + AND `model`.`id` IN (1, 2, 3...) + + After: + + DELETE FROM `model` WHERE `model`.`parent_id` = 1 + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Fixed error for aggregate methods (`empty?`, `any?`, `count`) with `select` + which created invalid SQL. + + Fixes #13648. + + *Simon Woker* + +* PostgreSQL adapter only warns once for every missing OID per connection. + + Fixes #14275. + + *Matthew Draper*, *Yves Senn* + +* PostgreSQL adapter automatically reloads it's type map when encountering + unknown OIDs. + + Fixes #14678. + + *Matthew Draper*, *Yves Senn* + +* Fix insertion of records via `has_many :through` association with scope. + + Fixes #3548. + + *Ivan Antropov* + +* Auto-generate stable fixture UUIDs on PostgreSQL. + + Fixes: #11524 + + *Roderick van Domburg* + +* Fixed a problem where an enum would overwrite values of another enum + with the same name in an unrelated class. + + Fixes #14607. + + *Evan Whalen* + +* PostgreSQL and SQLite string columns no longer have a default limit of 255. + + Fixes #13435, #9153. + + *Vladimir Sazhin*, *Toms Mikoss*, *Yves Senn* + +* Make possible to have an association called `records`. + + Fixes #11645. + + *prathamesh-sonpatki* + +* `to_sql` on an association now matches the query that is actually executed, where it + could previously have incorrectly accrued additional conditions (e.g. as a result of + a previous query). CollectionProxy now always defers to the association scope's + `arel` method so the (incorrect) inherited one should be entirely concealed. + + Fixes #14003. + + *Jefferson Lai* + +* Block a few default Class methods as scope name. + + For instance, this will raise: + + scope :public, -> { where(status: 1) } + + *arthurnn* + +* Fixed error when using `with_options` with lambda. + + Fixes #9805. + + *Lauro Caetano* + +* Switch `sqlite3:///` URLs (which were temporarily + deprecated in 4.1) from relative to absolute. + + If you still want the previous interpretation, you should replace + `sqlite3:///my/path` with `sqlite3:my/path`. + + *Matthew Draper* + +* Treat blank UUID values as `nil`. + + Example: + + Sample.new(uuid_field: '') #=> <Sample id: nil, uuid_field: nil> + + *Dmitry Lavrov* + +* Enable support for materialized views on PostgreSQL >= 9.3. + + *Dave Lee* + +* The PostgreSQL adapter supports custom domains. Fixes #14305. + + *Yves Senn* + +* PostgreSQL `Column#type` is now determined through the corresponding OID. + The column types stay the same except for enum columns. They no longer have + `nil` as type but `enum`. + + See #7814. + + *Yves Senn* + * Fixed error when specifying a non-empty default value on a PostgreSQL array column. Fixes #10613. diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb index 20516bba0c..5a84792f45 100644 --- a/activerecord/lib/active_record/association_relation.rb +++ b/activerecord/lib/active_record/association_relation.rb @@ -9,6 +9,10 @@ module ActiveRecord @association end + def ==(other) + other == to_a + end + private def exec_queries diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 8272a5584c..1edd4fa3aa 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -31,6 +31,14 @@ module ActiveRecord @updated end + def decrement_counters # :nodoc: + with_cache_name { |name| decrement_counter name } + end + + def increment_counters # :nodoc: + with_cache_name { |name| increment_counter name } + end + private def find_target? @@ -51,13 +59,15 @@ module ActiveRecord end end - def decrement_counters - with_cache_name { |name| decrement_counter name } + def decrement_counter(counter_cache_name) + if foreign_key_present? + klass.decrement_counter(counter_cache_name, target_id) + end end - def decrement_counter counter_cache_name + def increment_counter(counter_cache_name) if foreign_key_present? - klass.decrement_counter(counter_cache_name, target_id) + klass.increment_counter(counter_cache_name, target_id) end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 5ccaa55a32..47cc1f4b34 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -26,28 +26,9 @@ module ActiveRecord::Associations::Builder private def self.add_counter_cache_methods(mixin) - return if mixin.method_defined? :belongs_to_counter_cache_after_create + return if mixin.method_defined? :belongs_to_counter_cache_after_update mixin.class_eval do - def belongs_to_counter_cache_after_create(reflection) - if record = send(reflection.name) - cache_column = reflection.counter_cache_column - record.class.increment_counter(cache_column, record.id) - @_after_create_counter_called = true - end - end - - def belongs_to_counter_cache_before_destroy(reflection) - foreign_key = reflection.foreign_key.to_sym - unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key - record = send reflection.name - if record && !self.destroyed? - cache_column = reflection.counter_cache_column - record.class.decrement_counter(cache_column, record.id) - end - end - end - def belongs_to_counter_cache_after_update(reflection) foreign_key = reflection.foreign_key cache_column = reflection.counter_cache_column @@ -73,14 +54,6 @@ module ActiveRecord::Associations::Builder def self.add_counter_cache_callbacks(model, reflection) cache_column = reflection.counter_cache_column - model.after_create lambda { |record| - record.belongs_to_counter_cache_after_create(reflection) - } - - model.before_destroy lambda { |record| - record.belongs_to_counter_cache_before_destroy(reflection) - } - model.after_update lambda { |record| record.belongs_to_counter_cache_after_update(reflection) } diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 1f314e0677..803e3ab9ab 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -134,11 +134,11 @@ module ActiveRecord end def create(attributes = {}, &block) - create_record(attributes, &block) + _create_record(attributes, &block) end def create!(attributes = {}, &block) - create_record(attributes, true, &block) + _create_record(attributes, true, &block) end # Add +records+ to this association. Returns +self+ so method calls may @@ -182,11 +182,11 @@ module ActiveRecord # # See delete for more info. def delete_all(dependent = nil) - if dependent.present? && ![:nullify, :delete_all].include?(dependent) + if dependent && ![:nullify, :delete_all].include?(dependent) raise ArgumentError, "Valid values are :nullify or :delete_all" end - dependent = if dependent.present? + dependent = if dependent dependent elsif options[:dependent] == :destroy :delete_all @@ -248,7 +248,7 @@ module ActiveRecord dependent = _options[:dependent] || options[:dependent] if records.first == :all - if loaded? || dependent == :destroy + if (loaded? || dependent == :destroy) && dependent != :delete_all delete_or_destroy(load_target, dependent) else delete_records(:all, dependent) @@ -449,13 +449,13 @@ module ActiveRecord persisted + memory end - def create_record(attributes, raise = false, &block) + def _create_record(attributes, raise = false, &block) unless owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end if attributes.is_a?(Array) - attributes.collect { |attr| create_record(attr, raise, &block) } + attributes.collect { |attr| _create_record(attr, raise, &block) } else transaction do add_to_target(build_record(attributes)) do |record| diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index eba688866c..84c8cfe72b 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -357,7 +357,7 @@ module ActiveRecord # Deletes all the records from the collection. For +has_many+ associations, # the deletion is done according to the strategy specified by the <tt>:dependent</tt> - # option. Returns an array with the deleted records. + # option. # # If no <tt>:dependent</tt> option is given, then it will follow the # default strategy. The default strategy is <tt>:nullify</tt>. This @@ -435,11 +435,6 @@ module ActiveRecord # # ] # # person.pets.delete_all - # # => [ - # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>, - # # #<Pet id: 2, name: "Spook", person_id: 1>, - # # #<Pet id: 3, name: "Choo-Choo", person_id: 1> - # # ] # # Pet.find(1, 2, 3) # # => ActiveRecord::RecordNotFound @@ -860,6 +855,10 @@ module ActiveRecord !!@association.include?(record) end + def arel + scope.arel + end + def proxy_association @association end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 3e4b7902c0..aac85a36c8 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -58,7 +58,7 @@ module ActiveRecord # the loaded flag is set to true as well. def count_records count = if has_cached_counter? - owner.send(:read_attribute, cached_counter_attribute_name) + owner.read_attribute cached_counter_attribute_name else scope.count end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 64bc98c642..73baefb8e1 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -18,7 +18,7 @@ module ActiveRecord # SELECT query if you use #length. def size if has_cached_counter? - owner.send(:read_attribute, cached_counter_attribute_name) + owner.read_attribute cached_counter_attribute_name(reflection) elsif loaded? target.size else @@ -83,12 +83,16 @@ module ActiveRecord @through_records[record.object_id] ||= begin ensure_mutable - through_record = through_association.build + through_record = through_association.build through_scope_attributes through_record.send("#{source_reflection.name}=", record) through_record end end + def through_scope_attributes + scope.where_values_hash(through_association.reflection.name.to_s) + end + def save_through_record(record) build_through_record(record).save! ensure diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 399aff378a..747bb5f1d6 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -18,11 +18,11 @@ module ActiveRecord end def create(attributes = {}, &block) - create_record(attributes, &block) + _create_record(attributes, &block) end def create!(attributes = {}, &block) - create_record(attributes, true, &block) + _create_record(attributes, true, &block) end def build(attributes = {}) @@ -52,7 +52,7 @@ module ActiveRecord replace(record) end - def create_record(attributes, raise_error = false) + def _create_record(attributes, raise_error = false) record = build_record(attributes) yield(record) if block_given? saved = record.save diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index ce65948133..f8a85b8a6f 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -16,7 +16,7 @@ module ActiveRecord chain.drop(1).each do |reflection| relation = reflection.klass.all relation.merge!(reflection.scope) if reflection.scope - + scope.merge!( relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load) ) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ea48a13ea8..4b1733619a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -29,6 +29,8 @@ module ActiveRecord end } + BLACKLISTED_CLASS_METHODS = %w(private public protected) + class AttributeMethodCache def initialize @module = Module.new @@ -132,7 +134,7 @@ module ActiveRecord # A class method is 'dangerous' if it is already (re)defined by Active Record, but # not by any ancestors. (So 'puts' is not dangerous but 'new' is.) def dangerous_class_method?(method_name) - class_method_defined_within?(method_name, Base) + BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base) end def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 8a1b199997..99070f127b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -79,11 +79,11 @@ module ActiveRecord end end - def update_record(*) + def _update_record(*) partial_writes? ? super(keys_for_partial_write) : super end - def create_record(*) + def _create_record(*) partial_writes? ? super(keys_for_partial_write) : super end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 67abbbc2a0..c3466153d6 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -30,7 +30,8 @@ module ActiveRecord # ==== Parameters # # * +attr_name+ - The field name that should be serialized. - # * +class_name+ - Optional, class name that the object type should be equal to. + # * +class_name_or_coder+ - Optional, a coder object, which responds to `.load` / `.dump` + # or a class name that the object type should be equal to. # # ==== Example # @@ -38,13 +39,23 @@ module ActiveRecord # class User < ActiveRecord::Base # serialize :preferences # end - def serialize(attr_name, class_name = Object) + # + # # Serialize preferences using JSON as coder. + # class User < ActiveRecord::Base + # serialize :preferences, JSON + # end + # + # # Serialize preferences as Hash using YAML coder. + # class User < ActiveRecord::Base + # serialize :preferences, Hash + # end + def serialize(attr_name, class_name_or_coder = Object) include Behavior - coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) } - class_name + coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } + class_name_or_coder else - Coders::YAMLColumn.new(class_name) + Coders::YAMLColumn.new(class_name_or_coder) end # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index e9622ca0c1..f149d8f127 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -35,7 +35,7 @@ module ActiveRecord # # === One-to-one Example # - # class Post + # class Post < ActiveRecord::Base # has_one :author, autosave: true # end # @@ -76,7 +76,7 @@ module ActiveRecord # # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved: # - # class Post + # class Post < ActiveRecord::Base # has_many :comments # :autosave option is not declared # end # @@ -95,20 +95,23 @@ module ActiveRecord # When <tt>:autosave</tt> is true all children are saved, no matter whether they # are new records or not: # - # class Post + # class Post < ActiveRecord::Base # has_many :comments, autosave: true # end # # post = Post.create(title: 'ruby rocks') # post.comments.create(body: 'hello world') # post.comments[0].body = 'hi everyone' - # post.save # => saves both post and comment, with 'hi everyone' as body + # post.comments.build(body: "good morning.") + # post.title += "!" + # post.save # => saves both post and comments. # # Destroying one of the associated models as part of the parent's save action # is as simple as marking it for destruction: # - # post.comments.last.mark_for_destruction - # post.comments.last.marked_for_destruction? # => true + # post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]> + # post.comments[1].mark_for_destruction + # post.comments[1].marked_for_destruction? # => true # post.comments.length # => 2 # # Note that the model is _not_ yet removed from the database: diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 35f19f0bc0..5955673b42 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -302,11 +302,11 @@ module ActiveRecord run_callbacks(:save) { super } end - def create_record #:nodoc: + def _create_record #:nodoc: run_callbacks(:create) { super } end - def update_record(*) #:nodoc: + def _update_record(*) #:nodoc: run_callbacks(:update) { super } end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index da25e640c1..47f2ad9b10 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -381,7 +381,7 @@ module ActiveRecord end def binds_from_relation(relation, binds) - if relation.is_a?(Relation) && binds.blank? + if relation.is_a?(Relation) && binds.empty? relation, binds = relation.arel, relation.bind_values end [relation, binds] 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 20eea208ec..d2ef83b047 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -187,7 +187,6 @@ module ActiveRecord include Arel::Visitors::BindVisitor end - # FIXME: Make the first parameter more similar for the two adapters def initialize(connection, logger, connection_options, config) super(connection, logger) @connection_options, @config = connection_options, config diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 9a133168f8..b79d1a4458 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -33,9 +33,14 @@ module ActiveRecord def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = URI.parse(url) - @adapter = @uri.scheme + @adapter = @uri.scheme.gsub('-', '_') @adapter = "postgresql" if @adapter == "postgres" - @query = @uri.query || '' + + if @uri.opaque + @uri.opaque, @query = @uri.opaque.split('?', 2) + else + @query = @uri.query + end end # Converts the given URL to a full connection hash. @@ -57,38 +62,46 @@ module ActiveRecord # Converts the query parameters of the URI into a hash. # - # "localhost?pool=5&reap_frequency=2" - # # => { "pool" => "5", "reap_frequency" => "2" } + # "localhost?pool=5&reaping_frequency=2" + # # => { "pool" => "5", "reaping_frequency" => "2" } # # returns empty hash if no query present. # # "localhost" # # => {} def query_hash - Hash[@query.split("&").map { |pair| pair.split("=") }] + Hash[(@query || '').split("&").map { |pair| pair.split("=") }] end def raw_config - query_hash.merge({ - "adapter" => @adapter, - "username" => uri.user, - "password" => uri.password, - "port" => uri.port, - "database" => database, - "host" => uri.host }) + if uri.opaque + query_hash.merge({ + "adapter" => @adapter, + "database" => uri.opaque }) + else + query_hash.merge({ + "adapter" => @adapter, + "username" => uri.user, + "password" => uri.password, + "port" => uri.port, + "database" => database_from_path, + "host" => uri.host }) + end end # Returns name of the database. - # Sqlite3 expects this to be a full path or `:memory:`. - def database + def database_from_path if @adapter == 'sqlite3' - if '/:memory:' == uri.path - ':memory:' - else - uri.path - end + # 'sqlite3:/foo' is absolute, because that makes sense. The + # corresponding relative version, 'sqlite3:foo', is handled + # elsewhere, as an "opaque". + + uri.path else - uri.path.sub(%r{^/},"") + # Only SQLite uses a filename as the "database" name; for + # anything else, a leading slash would be silly. + + uri.path.sub(%r{^/}, "") end end end @@ -124,7 +137,7 @@ module ActiveRecord if config resolve_connection config elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call - resolve_env_connection env.to_sym + resolve_symbol_connection env.to_sym else raise AdapterNotSpecified end @@ -193,42 +206,41 @@ module ActiveRecord # def resolve_connection(spec) case spec - when Symbol, String - resolve_env_connection spec + when Symbol + resolve_symbol_connection spec + when String + resolve_string_connection spec when Hash resolve_hash_connection spec end end + def resolve_string_connection(spec) + # Rails has historically accepted a string to mean either + # an environment key or a URL spec, so we have deprecated + # this ambiguous behaviour and in the future this function + # can be removed in favor of resolve_url_connection. + if configurations.key?(spec) || spec !~ /:/ + ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \ + "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" + resolve_symbol_connection(spec) + else + resolve_url_connection(spec) + end + end + # Takes the environment such as `:production` or `:development`. # This requires that the @configurations was initialized with a key that # matches. # - # - # Resolver.new("production" => {}).resolve_env_connection(:production) + # Resolver.new("production" => {}).resolve_symbol_connection(:production) # # => {} # - # Takes a connection URL. - # - # Resolver.new({}).resolve_env_connection("postgresql://localhost/foo") - # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } - # - def resolve_env_connection(spec) - # Rails has historically accepted a string to mean either - # an environment key or a URL spec, so we have deprecated - # this ambiguous behaviour and in the future this function - # can be removed in favor of resolve_string_connection and - # resolve_symbol_connection. + def resolve_symbol_connection(spec) if config = configurations[spec.to_s] - if spec.is_a?(String) - ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \ - "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" - end resolve_connection(config) - elsif spec.is_a?(String) - resolve_string_connection(spec) else - raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}") + raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") end end @@ -244,7 +256,12 @@ module ActiveRecord spec end - def resolve_string_connection(url) + # Takes a connection URL. + # + # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # + def resolve_url_connection(url) ConnectionUrlResolver.new(url).to_hash end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 2b5049f5a5..5e82fdcbe0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -20,9 +20,9 @@ module ActiveRecord ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config) rescue Mysql2::Error => error if error.message.include?("Unknown database") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 49f0bfbcde..e6aa2ba921 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -36,9 +36,9 @@ module ActiveRecord ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config) rescue Mysql::Error => error if error.message.include?("Unknown database") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 823189934d..2cbcd5fd50 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -151,70 +151,7 @@ module ActiveRecord # Maps PostgreSQL-specific data types to logical Rails types. def simplified_type(field_type) - case field_type - # Numeric and monetary types - when /^(?:real|double precision)$/ - :float - # Monetary types - when 'money' - :decimal - when 'hstore' - :hstore - when 'ltree' - :ltree - # Network address types - when 'inet' - :inet - when 'cidr' - :cidr - when 'macaddr' - :macaddr - # Character types - when /^(?:character varying|bpchar)(?:\(\d+\))?$/ - :string - when /^citext(?:\(\d+\))?$/ - :citext - # Binary data types - when 'bytea' - :binary - # Date/time types - when /^timestamp with(?:out)? time zone$/ - :datetime - when /^interval(?:|\(\d+\))$/ - :string - # Geometric types - when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ - :string - # Bit strings - when /^bit(?: varying)?(?:\(\d+\))?$/ - :string - # XML type - when 'xml' - :xml - # tsvector type - when 'tsvector' - :tsvector - # Arrays - when /^\D+\[\]$/ - :string - # Object identifier types - when 'oid' - :integer - # UUID type - when 'uuid' - :uuid - # JSON type - when 'json' - :json - # Small and big integer types - when /^(?:small|big)int$/ - :integer - when /(num|date|tstz|ts|int4|int8)range$/ - field_type.to_sym - # Pass through all types that are not specific to PostgreSQL. - else - super - end + @oid_type.simplified_type(field_type) || super end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 51ee2829b2..168b08ba75 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -44,10 +44,32 @@ module ActiveRecord end end + def select_value(arel, name = nil, binds = []) + arel, binds = binds_from_relation arel, binds + sql = to_sql(arel, binds) + execute_and_clear(sql, name, binds) do |result| + result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0 + end + end + + def select_values(arel, name = nil) + arel, binds = binds_from_relation arel, [] + sql = to_sql(arel, binds) + execute_and_clear(sql, name, binds) do |result| + if result.nfields > 0 + result.column_values(0) + else + [] + end + end + end + # Executes a SELECT query and returns an array of rows. Each row is an # array of field values. def select_rows(sql, name = nil, binds = []) - exec_query(sql, name, binds).rows + execute_and_clear(sql, name, binds) do |result| + result.values + end end # Executes an INSERT query and returns the new record's ID @@ -134,31 +156,20 @@ module ActiveRecord end def exec_query(sql, name = 'SQL', binds = []) - result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) : - exec_cache(sql, name, binds) - - types = {} - fields = result.fields - fields.each_with_index do |fname, i| - ftype = result.ftype i - fmod = result.fmod i - types[fname] = type_map.fetch(ftype, fmod) { |oid, mod| - warn "unknown OID: #{fname}(#{oid}) (#{sql})" - OID::Identity.new - } + execute_and_clear(sql, name, binds) do |result| + types = {} + fields = result.fields + fields.each_with_index do |fname, i| + ftype = result.ftype i + fmod = result.fmod i + types[fname] = get_oid_type(ftype, fmod, fname) + end + ActiveRecord::Result.new(fields, result.values, types) end - - ret = ActiveRecord::Result.new(fields, result.values, types) - result.clear - return ret end def exec_delete(sql, name = 'SQL', binds = []) - result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) : - exec_cache(sql, name, binds) - affected = result.cmd_tuples - result.clear - affected + execute_and_clear(sql, name, binds) {|result| result.cmd_tuples } end alias :exec_update :exec_delete diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 5d32aaed50..9e898015a6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -6,6 +6,7 @@ module ActiveRecord module OID class Type def type; end + def simplified_type(sql_type); type end def infinity(options = {}) ::Float::INFINITY * (options[:negative] ? -1 : 1) @@ -18,7 +19,9 @@ module ActiveRecord end end - class Text < Type + class String < Type + def type; :string end + def type_cast(value) return if value.nil? @@ -26,9 +29,23 @@ module ActiveRecord end end + class SpecializedString < OID::String + def type; @type end + + def initialize(type) + @type = type + end + end + + class Text < OID::String + def type; :text end + end + class Bit < Type + def type; :string end + def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_bit value else value @@ -37,6 +54,8 @@ module ActiveRecord end class Bytea < Type + def type; :binary end + def type_cast(value) return if value.nil? PGconn.unescape_bytea value @@ -44,9 +63,11 @@ module ActiveRecord end class Money < Type + def type; :decimal end + def type_cast(value) return if value.nil? - return value unless String === value + return value unless ::String === value # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): @@ -88,8 +109,10 @@ module ActiveRecord end class Point < Type + def type; :string end + def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_point value else value @@ -98,13 +121,15 @@ module ActiveRecord end class Array < Type + def type; @subtype.type end + attr_reader :subtype def initialize(subtype) @subtype = subtype end def type_cast(value) - if String === value + if ::String === value ConnectionAdapters::PostgreSQLColumn.string_to_array value, @subtype else value @@ -114,6 +139,8 @@ module ActiveRecord class Range < Type attr_reader :subtype + def simplified_type(sql_type); sql_type.to_sym end + def initialize(subtype) @subtype = subtype end @@ -160,6 +187,8 @@ This is not reliable and will be removed in the future. end class Integer < Type + def type; :integer end + def type_cast(value) return if value.nil? @@ -168,6 +197,8 @@ This is not reliable and will be removed in the future. end class Boolean < Type + def type; :boolean end + def type_cast(value) return if value.nil? @@ -177,6 +208,14 @@ This is not reliable and will be removed in the future. class Timestamp < Type def type; :timestamp; end + def simplified_type(sql_type) + case sql_type + when /^timestamp with(?:out)? time zone$/ + :datetime + else + :timestamp + end + end def type_cast(value) return if value.nil? @@ -188,7 +227,7 @@ This is not reliable and will be removed in the future. end class Date < Type - def type; :datetime; end + def type; :date; end def type_cast(value) return if value.nil? @@ -200,6 +239,8 @@ This is not reliable and will be removed in the future. end class Time < Type + def type; :time end + def type_cast(value) return if value.nil? @@ -210,6 +251,8 @@ This is not reliable and will be removed in the future. end class Float < Type + def type; :float end + def type_cast(value) return if value.nil? @@ -218,6 +261,8 @@ This is not reliable and will be removed in the future. end class Decimal < Type + def type; :decimal end + def type_cast(value) return if value.nil? @@ -230,12 +275,16 @@ This is not reliable and will be removed in the future. end class Enum < Type + def type; :enum end + def type_cast(value) value.to_s end end class Hstore < Type + def type; :hstore end + def type_cast_for_write(value) ConnectionAdapters::PostgreSQLColumn.hstore_to_string value end @@ -252,14 +301,20 @@ This is not reliable and will be removed in the future. end class Cidr < Type + def type; :cidr end def type_cast(value) return if value.nil? ConnectionAdapters::PostgreSQLColumn.string_to_cidr value end end + class Inet < Cidr + def type; :inet end + end class Json < Type + def type; :json end + def type_cast_for_write(value) ConnectionAdapters::PostgreSQLColumn.json_to_string value end @@ -275,6 +330,13 @@ This is not reliable and will be removed in the future. end end + class Uuid < Type + def type; :uuid end + def type_cast(value) + value.presence + end + end + class TypeMap def initialize @mapping = {} @@ -321,7 +383,7 @@ This is not reliable and will be removed in the future. } # Register an OID type named +name+ with a typecasting object in - # +type+. +name+ should correspond to the `typname` column in + # +type+. +name+ should correspond to the `typname` column in # the `pg_type` table. def self.register_type(name, type) NAMES[name] = type @@ -338,48 +400,46 @@ This is not reliable and will be removed in the future. end register_type 'int2', OID::Integer.new - alias_type 'int4', 'int2' - alias_type 'int8', 'int2' - alias_type 'oid', 'int2' - + alias_type 'int4', 'int2' + alias_type 'int8', 'int2' + alias_type 'oid', 'int2' register_type 'numeric', OID::Decimal.new + register_type 'float4', OID::Float.new + alias_type 'float8', 'float4' register_type 'text', OID::Text.new - alias_type 'varchar', 'text' - alias_type 'char', 'text' - alias_type 'bpchar', 'text' - alias_type 'xml', 'text' - - # FIXME: why are we keeping these types as strings? - alias_type 'tsvector', 'text' - alias_type 'interval', 'text' - alias_type 'macaddr', 'text' - alias_type 'uuid', 'text' - - register_type 'money', OID::Money.new - register_type 'bytea', OID::Bytea.new + register_type 'varchar', OID::String.new + alias_type 'char', 'varchar' + alias_type 'bpchar', 'varchar' register_type 'bool', OID::Boolean.new register_type 'bit', OID::Bit.new - register_type 'varbit', OID::Bit.new - - register_type 'float4', OID::Float.new - alias_type 'float8', 'float4' - + alias_type 'varbit', 'bit' register_type 'timestamp', OID::Timestamp.new - register_type 'timestamptz', OID::Timestamp.new + alias_type 'timestamptz', 'timestamp' register_type 'date', OID::Date.new register_type 'time', OID::Time.new - register_type 'path', OID::Text.new + register_type 'money', OID::Money.new + register_type 'bytea', OID::Bytea.new register_type 'point', OID::Point.new - register_type 'polygon', OID::Text.new - register_type 'circle', OID::Text.new register_type 'hstore', OID::Hstore.new register_type 'json', OID::Json.new - register_type 'citext', OID::Text.new - register_type 'ltree', OID::Text.new - register_type 'cidr', OID::Cidr.new - alias_type 'inet', 'cidr' + register_type 'inet', OID::Inet.new + register_type 'uuid', OID::Uuid.new + register_type 'xml', SpecializedString.new(:xml) + register_type 'tsvector', SpecializedString.new(:tsvector) + register_type 'macaddr', SpecializedString.new(:macaddr) + register_type 'citext', SpecializedString.new(:citext) + register_type 'ltree', SpecializedString.new(:ltree) + + # FIXME: why are we keeping these types as strings? + alias_type 'interval', 'varchar' + alias_type 'path', 'varchar' + alias_type 'line', 'varchar' + alias_type 'polygon', 'varchar' + alias_type 'circle', 'varchar' + alias_type 'lseg', 'varchar' + alias_type 'box', 'varchar' end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 210172cf32..403e37fde9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -4,14 +4,14 @@ module ActiveRecord module Quoting # Escapes binary strings for bytea input to the database. def escape_bytea(value) - PGconn.escape_bytea(value) if value + @connection.escape_bytea(value) if value end # Unescapes bytea output from a database to the binary string it represents. # NOTE: This is NOT an inverse of escape_bytea! This is only to be used # on escaped binary output from database drive. def unescape_bytea(value) - PGconn.unescape_bytea(value) if value + @connection.unescape_bytea(value) if value end # Quotes PostgreSQL-specific data types for SQL input. @@ -182,6 +182,15 @@ module ActiveRecord end result end + + # Does not quote function default values for UUID columns + def quote_default_value(value, column) #:nodoc: + if column.type == :uuid && value =~ /\(\)/ + value + else + quote(value) + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index e0afa989cd..1dc7a6f0fd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -104,14 +104,11 @@ module ActiveRecord schema, table = Utils.extract_schema_and_table(name.to_s) return false unless table - binds = [[nil, table]] - binds << [nil, schema] if schema - exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 SELECT COUNT(*) FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind in ('v','r') + WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view AND c.relname = '#{table.gsub(/(^"|"$)/,'')}' AND n.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'} SQL @@ -185,13 +182,15 @@ module ActiveRecord def columns(table_name) # Limit, precision, and scale are all handled by the superclass. column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod| - oid = type_map.fetch(oid.to_i, fmod.to_i) { - OID::Identity.new - } + oid = get_oid_type(oid.to_i, fmod.to_i, column_name) PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f') end end + def column_for(table_name, column_name) #:nodoc: + columns(table_name).detect { |c| c.name == column_name.to_s } + end + # Returns the current database name. def current_database query('select current_database()', 'SCHEMA')[0][0] @@ -409,13 +408,15 @@ module ActiveRecord # Changes the default value of a table column. def change_column_default(table_name, column_name, default) clear_cache! - execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}" + column = column_for(table_name, column_name) + execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column end def change_column_null(table_name, column_name, null, default = nil) clear_cache! unless null || default.nil? - execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + column = column_for(table_name, column_name) + execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column end execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL") end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index c31fe96842..1f1bd342d1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -209,7 +209,7 @@ module ActiveRecord NATIVE_DATABASE_TYPES = { primary_key: "serial primary key", - string: { name: "character varying", limit: 255 }, + string: { name: "character varying" }, text: { name: "text" }, integer: { name: "integer" }, float: { name: "float" }, @@ -454,6 +454,10 @@ module ActiveRecord postgresql_version >= 90200 end + def supports_materialized_views? + postgresql_version >= 90300 + end + def enable_extension(name) exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap { reload_type_map @@ -555,6 +559,17 @@ module ActiveRecord @type_map end + def get_oid_type(oid, fmod, column_name) + if !type_map.key?(oid) + initialize_type_map(type_map, [oid]) + end + + type_map.fetch(oid, fmod) { + warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String." + type_map[oid] = OID::Identity.new + } + end + def reload_type_map type_map.clear initialize_type_map(type_map) @@ -579,25 +594,32 @@ module ActiveRecord type_map end - def initialize_type_map(type_map) + def initialize_type_map(type_map, oids = nil) if supports_ranges? - result = execute(<<-SQL, 'SCHEMA') - SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype + query = <<-SQL + SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype FROM pg_type as t LEFT JOIN pg_range as r ON oid = rngtypid SQL else - result = execute(<<-SQL, 'SCHEMA') - SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput + query = <<-SQL + SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype FROM pg_type as t SQL end - ranges, nodes = result.partition { |row| row['typinput'] == 'range_in' } - leaves, nodes = nodes.partition { |row| row['typelem'] == '0' } + + if oids + query += "WHERE t.oid::integer IN (%s)" % oids.join(", ") + end + + result = execute(query, 'SCHEMA') + ranges, nodes = result.partition { |row| row['typtype'] == 'r' } + enums, nodes = nodes.partition { |row| row['typtype'] == 'e' } + domains, nodes = nodes.partition { |row| row['typtype'] == 'd' } arrays, nodes = nodes.partition { |row| row['typinput'] == 'array_in' } + leaves, nodes = nodes.partition { |row| row['typelem'] == '0' } # populate the enum types - enums, leaves = leaves.partition { |row| row['typinput'] == 'enum_in' } enums.each do |row| type_map[row['oid'].to_i] = OID::Enum.new end @@ -626,10 +648,28 @@ module ActiveRecord range = OID::Range.new subtype type_map[row['oid'].to_i] = range end + + # populate domain types + domains.each do |row| + base_type_oid = row["typbasetype"].to_i + if base_type = type_map[base_type_oid] + type_map[row['oid'].to_i] = base_type + else + warn "unknown base type (OID: #{base_type_oid}) for domain #{row["typname"]}." + end + end end FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: + def execute_and_clear(sql, name, binds) + result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) : + exec_cache(sql, name, binds) + ret = yield result + result.clear + ret + end + def exec_no_cache(sql, name, binds) log(sql, name, binds) { @connection.async_exec(sql) } end @@ -707,9 +747,9 @@ module ActiveRecord configure_connection rescue ::PG::Error => error if error.message.include?("does not exist") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 3c5f7a981e..2d5c47967d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -30,12 +30,12 @@ module ActiveRecord db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] - ConnectionAdapters::SQLite3Adapter.new(db, logger, config) + ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config) rescue Errno::ENOENT => error if error.message.include?("No such file or directory") - raise ActiveRecord::NoDatabaseError.new(error.message) + raise ActiveRecord::NoDatabaseError.new(error.message, error) else - raise error + raise end end end @@ -63,7 +63,7 @@ module ActiveRecord NATIVE_DATABASE_TYPES = { primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL', - string: { name: "varchar", limit: 255 }, + string: { name: "varchar" }, text: { name: "text" }, integer: { name: "integer" }, float: { name: "float" }, @@ -127,7 +127,7 @@ module ActiveRecord include Arel::Visitors::BindVisitor end - def initialize(connection, logger, config) + def initialize(connection, logger, connection_options, config) super(connection, logger) @active = nil diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index bbb866cedf..31e7390bf7 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -58,9 +58,9 @@ module ActiveRecord end class MergeAndResolveDefaultUrlConfig # :nodoc: - def initialize(raw_configurations, url = ENV['DATABASE_URL']) + def initialize(raw_configurations) @raw_config = raw_configurations.dup - @url = url + @env = DEFAULT_ENV.call.to_s end # Returns fully resolved connection hashes. @@ -71,33 +71,10 @@ module ActiveRecord private def config - if @url - raw_merged_into_default - else - @raw_config - end - end - - def raw_merged_into_default - default = default_url_hash - - @raw_config.each do |env, values| - default[env] = values || {} - default[env].merge!("url" => @url) { |h, v1, v2| v1 || v2 } if default[env].is_a?(Hash) - end - default - end - - # When the raw configuration is not present and ENV['DATABASE_URL'] - # is available we return a hash with the connection information in - # the connection URL. This hash responds to any string key with - # resolved connection information. - def default_url_hash - Hash.new do |hash, key| - hash[key] = if key.is_a? String - ActiveRecord::ConnectionAdapters::ConnectionSpecification::ConnectionUrlResolver.new(@url).to_hash - else - nil + @raw_config.dup.tap do |cfg| + if url = ENV['DATABASE_URL'] + cfg[@env] ||= {} + cfg[@env]["url"] ||= url end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 4e53f66005..d6df98a80f 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -267,6 +267,7 @@ module ActiveRecord @attributes_cache = {} @new_record = true + @destroyed = false super end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index dcbdf75627..b7b790322a 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -118,5 +118,54 @@ module ActiveRecord update_counters(id, counter_name => -1) end end + + protected + + def actually_destroyed? + @_actually_destroyed + end + + def clear_destroy_state + @_actually_destroyed = nil + end + + private + + def _create_record(*) + id = super + + each_counter_cached_associations do |association| + if send(association.reflection.name) + association.increment_counters + @_after_create_counter_called = true + end + end + + id + end + + def destroy_row + affected_rows = super + + if affected_rows > 0 + each_counter_cached_associations do |association| + foreign_key = association.reflection.foreign_key.to_sym + unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key + if send(association.reflection.name) + association.decrement_counters + end + end + end + end + + affected_rows + end + + def each_counter_cached_associations + reflections.each do |name, reflection| + yield association(name) if reflection.belongs_to? && reflection.counter_cache_column + end + end + end end diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 4aa323fb00..18f1ca26de 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/object/deep_dup' + module ActiveRecord # Declare an enum attribute where the values map to integers in the database, # but can be queried by name. Example: @@ -65,10 +67,14 @@ module ActiveRecord # # Where conditions on an enum attribute must use the ordinal value of an enum. module Enum - DEFINED_ENUMS = {} # :nodoc: + def self.extended(base) + base.class_attribute(:defined_enums) + base.defined_enums = {} + end - def enum_mapping_for(attr_name) # :nodoc: - DEFINED_ENUMS[attr_name.to_s] + def inherited(base) + base.defined_enums = defined_enums.deep_dup + super end def enum(definitions) @@ -122,9 +128,8 @@ module ActiveRecord klass.send(:detect_enum_conflict!, name, value, true) klass.scope value, -> { klass.where name => i } end - - DEFINED_ENUMS[name.to_s] = enum_values end + defined_enums[name.to_s] = enum_values end end @@ -134,7 +139,7 @@ module ActiveRecord mod = Module.new do private def save_changed_attribute(attr_name, value) - if (mapping = self.class.enum_mapping_for(attr_name)) + if (mapping = self.class.defined_enums[attr_name.to_s]) if attribute_changed?(attr_name) old = changed_attributes[attr_name] diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 7f6228131f..71efbb8f93 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -95,15 +95,7 @@ module ActiveRecord end # Raised when a given database does not exist - class NoDatabaseError < ActiveRecordError - def initialize(message) - super extend_message(message) - end - - # can be over written to add additional error information. - def extend_message(message) - message - end + class NoDatabaseError < StatementInvalid end # Raised on attempt to save stale record. Record is stale when it's being saved in another query after diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 6f134bbef8..47d32fae05 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -2,6 +2,7 @@ require 'erb' require 'yaml' require 'zlib' require 'active_support/dependencies' +require 'active_support/core_ext/securerandom' require 'active_record/fixture_set/file' require 'active_record/errors' @@ -550,9 +551,13 @@ module ActiveRecord end # Returns a consistent, platform-independent identifier for +label+. - # Identifiers are positive integers less than 2^30. - def self.identify(label) - Zlib.crc32(label.to_s) % MAX_ID + # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes. + def self.identify(label, column_type = :integer) + if column_type == :uuid + SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, label.to_s) + else + Zlib.crc32(label.to_s) % MAX_ID + end end # Superclass for the evaluation contexts used by ERB fixtures. @@ -633,7 +638,7 @@ module ActiveRecord # generate a primary key if necessary if has_primary_key_column? && !row.include?(primary_key_name) - row[primary_key_name] = ActiveRecord::FixtureSet.identify(label) + row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type) end # If STI is used, find the correct subclass for association reflection @@ -656,7 +661,8 @@ module ActiveRecord row[association.foreign_type] = $1 end - row[fk_name] = ActiveRecord::FixtureSet.identify(value) + fk_type = association.active_record.columns_hash[association.foreign_key].type + row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) end when :has_many if association.options[:through] @@ -683,6 +689,10 @@ module ActiveRecord def name @association.name end + + def primary_key_type + @association.klass.column_types[@association.klass.primary_key].type + end end class HasManyThroughProxy < ReflectionProxy # :nodoc: @@ -700,17 +710,22 @@ module ActiveRecord @primary_key_name ||= model_class && model_class.primary_key end + def primary_key_type + @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type + end + def add_join_records(rows, row, association) # This is the case when the join table has no fixtures file if (targets = row.delete(association.name.to_s)) - table_name = association.join_table - lhs_key = association.lhs_key - rhs_key = association.rhs_key + table_name = association.join_table + column_type = association.primary_key_type + lhs_key = association.lhs_key + rhs_key = association.rhs_key targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) rows[table_name].concat targets.map { |target| { lhs_key => row[primary_key_name], - rhs_key => ActiveRecord::FixtureSet.identify(target) } + rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) } } end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 6f54729b3c..4d63b04d9f 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -66,7 +66,7 @@ module ActiveRecord send(lock_col + '=', previous_lock_value + 1) end - def update_record(attribute_names = @attributes.keys) #:nodoc: + def _update_record(attribute_names = @attributes.keys) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 9d92e747d4..e6195e48a5 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -485,10 +485,10 @@ module ActiveRecord end # Takes in a limit and checks if the attributes_collection has too many - # records. The method will take limits in the form of symbols, procs, and - # number-like objects (anything that can be compared with an integer). + # records. It accepts limit in the form of symbol, proc, or + # number-like object (anything that can be compared with an integer). # - # Will raise an TooManyRecords error if the attributes_collection is + # Raises TooManyRecords error if the attributes_collection is # larger than the limit. def check_record_limit!(limit, attributes_collection) if limit @@ -519,7 +519,7 @@ module ActiveRecord ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) end - # Determines if a new record should be build by checking for + # Determines if a new record should be rejected by checking # has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this # association and evaluates to +true+. def reject_new_record?(association_name, attributes) diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 5b255c3fe5..05d0c41678 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -43,7 +43,7 @@ module ActiveRecord end def count(*) - 0 + calculate :count, nil end def sum(*) @@ -54,7 +54,7 @@ module ActiveRecord # TODO: Remove _options argument as soon we remove support to # activerecord-deprecated_finders. if operation == :count - 0 + group_values.any? ? Hash.new : 0 else nil end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 1a2581f579..13d7432773 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -484,24 +484,24 @@ module ActiveRecord def create_or_update raise ReadOnlyRecord if readonly? - result = new_record? ? create_record : update_record + result = new_record? ? _create_record : _update_record result != false end # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. - def update_record(attribute_names = @attributes.keys) + def _update_record(attribute_names = @attributes.keys) attributes_values = arel_attributes_with_values_for_update(attribute_names) if attributes_values.empty? 0 else - self.class.unscoped.update_record attributes_values, id, id_was + self.class.unscoped._update_record attributes_values, id, id_was end end # Creates a record with values matching those of the instance attributes # and returns its id. - def create_record(attribute_names = @attributes.keys) + def _create_record(attribute_names = @attributes.keys) attributes_values = arel_attributes_with_values_for_create(attribute_names) new_id = self.class.unscoped.insert attributes_values diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 11b564f8f9..a4ceacbf44 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -116,17 +116,22 @@ module ActiveRecord # and then establishes the connection. initializer "active_record.initialize_database" do |app| ActiveSupport.on_load(:active_record) do + self.configurations = Rails.application.config.database_configuration - class ActiveRecord::NoDatabaseError - remove_possible_method :extend_message - def extend_message(message) - message << "Run `$ bin/rake db:create db:migrate` to create your database" - message - end - end + begin + establish_connection + rescue ActiveRecord::NoDatabaseError + warn <<-end_warning +Oops - You have a database configured, but it doesn't exist yet! - self.configurations = Rails.application.config.database_configuration - establish_connection +Here's how to get started: + + 1. Configure your database in config/database.yml. + 2. Run `bin/rake db:create` to create the database. + 3. Run `bin/rake db:setup` to load your database schema. +end_warning + raise + end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 03b5bdc46c..4fde6677be 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -22,11 +22,11 @@ module ActiveRecord end def self.add_reflection(ar, name, reflection) - ar.reflections = ar.reflections.merge(name => reflection) + ar.reflections = ar.reflections.merge(name.to_s => reflection) end def self.add_aggregate_reflection(ar, name, reflection) - ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection) + ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection) end # \Reflection enables to interrogate Active Record classes and objects @@ -48,7 +48,7 @@ module ActiveRecord # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection # def reflect_on_aggregation(aggregation) - aggregate_reflections[aggregation] + aggregate_reflections[aggregation.to_s] end # Returns an array of AssociationReflection objects for all the @@ -72,7 +72,7 @@ module ActiveRecord # Invoice.reflect_on_association(:line_items).macro # returns :has_many # def reflect_on_association(association) - reflections[association] + reflections[association.to_s] end # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled. @@ -449,9 +449,9 @@ module ActiveRecord end def derive_class_name - class_name = name.to_s.camelize + class_name = name.to_s class_name = class_name.singularize if collection? - class_name + class_name.camelize end def derive_foreign_key @@ -617,7 +617,7 @@ module ActiveRecord # # => [:tag, :tags] # def source_reflection_names - (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq + options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq end def source_reflection_name # :nodoc: diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 9eaba4a655..709edbee88 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -70,7 +70,7 @@ module ActiveRecord binds) end - def update_record(values, id, id_was) # :nodoc: + def _update_record(values, id, id_was) # :nodoc: substitutes, binds = substitute_values values um = @klass.unscoped.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key) @@ -238,7 +238,7 @@ module ActiveRecord # Returns size of the records. def size - loaded? ? @records.length : count + loaded? ? @records.length : count(:all) end # Returns true if there are no records. @@ -248,8 +248,7 @@ module ActiveRecord if limit_value == 0 true else - # FIXME: This count is not compatible with #select('authors.*') or other select narrows - c = count + c = count(:all) c.respond_to?(:zero?) ? c.zero? : c.empty? end end @@ -529,9 +528,9 @@ module ActiveRecord # # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} - def where_values_hash + def where_values_hash(relation_table_name = table_name) equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node| - node.left.relation.name == table_name + node.left.relation.name == relation_table_name } binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] @@ -570,6 +569,8 @@ module ActiveRecord # Compares two relations for equality. def ==(other) case other + when Associations::CollectionProxy, AssociationRelation + self == other.to_a when Relation other.to_sql == to_sql when Array diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 45ffb99868..514ebc2bfe 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -188,7 +188,7 @@ module ActiveRecord private def has_include?(column_name) - eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?)) + eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?)) end def perform_calculation(operation, column_name, options = {}) @@ -231,7 +231,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = reorder(nil) + relation = unscope(:order) column_alias = column_name diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c2b9dc08fe..7af4b29ebc 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -129,9 +129,9 @@ module ActiveRecord # def first(limit = nil) if limit - find_nth_with_limit(offset_value, limit) + find_nth_with_limit(offset_index, limit) else - find_nth(:first, offset_value) + find_nth(0, offset_index) end end @@ -181,7 +181,7 @@ module ActiveRecord # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) # Person.where(["user_name = :u", { u: user_name }]).second def second - find_nth(:second, offset_value ? offset_value + 1 : 1) + find_nth(1, offset_index) end # Same as +second+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -197,7 +197,7 @@ module ActiveRecord # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) # Person.where(["user_name = :u", { u: user_name }]).third def third - find_nth(:third, offset_value ? offset_value + 2 : 2) + find_nth(2, offset_index) end # Same as +third+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -213,7 +213,7 @@ module ActiveRecord # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) # Person.where(["user_name = :u", { u: user_name }]).fourth def fourth - find_nth(:fourth, offset_value ? offset_value + 3 : 3) + find_nth(3, offset_index) end # Same as +fourth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -229,7 +229,7 @@ module ActiveRecord # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) # Person.where(["user_name = :u", { u: user_name }]).fifth def fifth - find_nth(:fifth, offset_value ? offset_value + 4 : 4) + find_nth(4, offset_index) end # Same as +fifth+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -245,7 +245,7 @@ module ActiveRecord # Person.offset(3).forty_two # returns the fifth object from OFFSET 3 (which is OFFSET 44) # Person.where(["user_name = :u", { u: user_name }]).forty_two def forty_two - find_nth(:forty_two, offset_value ? offset_value + 41 : 41) + find_nth(41, offset_index) end # Same as +forty_two+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record @@ -334,6 +334,10 @@ module ActiveRecord private + def offset_index + offset_value || 0 + end + def find_with_associations join_dependency = construct_join_dependency @@ -468,20 +472,24 @@ module ActiveRecord end end - def find_nth(ordinal, offset) + def find_nth(index, offset) if loaded? - @records.send(ordinal) + @records[index] else + offset += index @offsets[offset] ||= find_nth_with_limit(offset, 1).first end end def find_nth_with_limit(offset, limit) - if order_values.empty? && primary_key - order(arel_table[primary_key].asc).limit(limit).offset(offset).to_a - else - limit(limit).offset(offset).to_a - end + relation = if order_values.empty? && primary_key + order(arel_table[primary_key].asc) + else + self + end + + relation = relation.offset(offset) unless offset.zero? + relation.limit(limit).to_a end def find_last diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 182b9ed89c..be44fccad5 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -139,7 +139,6 @@ module ActiveRecord def merge_single_values relation.from_value = values[:from] unless relation.from_value relation.lock_value = values[:lock] unless relation.lock_value - relation.reverse_order_value = values[:reverse_order] unless values[:create_with].blank? relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with]) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0213bca981..4287304945 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -825,7 +825,9 @@ module ActiveRecord end def reverse_order! # :nodoc: - self.reverse_order_value = !reverse_order_value + orders = order_values.uniq + orders.reject!(&:blank?) + self.order_values = reverse_sql_order(orders) self end @@ -871,7 +873,6 @@ module ActiveRecord case scope when :order - self.reverse_order_value = false result = [] else result = [] unless single_val_method @@ -1031,7 +1032,6 @@ module ActiveRecord def build_order(arel) orders = order_values.uniq orders.reject!(&:blank?) - orders = reverse_sql_order(orders) if reverse_order_value arel.order(*orders) unless orders.empty? end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 5a71c13d91..be62e41932 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -107,6 +107,13 @@ module ActiveRecord end.join(', ') end + # Sanitizes a +string+ so that it is safe to use within a sql + # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%" + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } + end + # Accepts an array of conditions. The array has each value # sanitized and interpolated into the SQL statement. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 6ce0495f6f..168b338b97 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -28,7 +28,7 @@ module ActiveRecord # Example usage of +DatabaseTasks+ outside Rails could look as such: # # include ActiveRecord::Tasks - # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml')) + # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml') # DatabaseTasks.db_dir = 'db' # # other settings... # diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 7178bed560..6c30ccab72 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -43,7 +43,7 @@ module ActiveRecord private - def create_record + def _create_record if self.record_timestamps current_time = current_time_from_proper_timezone @@ -57,7 +57,7 @@ module ActiveRecord super end - def update_record(*args) + def _update_record(*args) if should_record_timestamps? current_time = current_time_from_proper_timezone diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 71c71cb4b1..ee080451a9 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -93,7 +93,7 @@ module ActiveRecord end def map_enum_attribute(klass, attribute, value) - mapping = klass.enum_mapping_for(attribute.to_s) + mapping = klass.defined_enums[attribute.to_s] value = mapping[value] if value && mapping value end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index ed4d0d503d..90953ce6cd 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -144,9 +144,9 @@ module ActiveRecord @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" end end - - def test_foreign_key_violations_are_translated_to_specific_exception - unless current_adapter?(:SQLite3Adapter) + + unless current_adapter?(:SQLite3Adapter) + def test_foreign_key_violations_are_translated_to_specific_exception assert_raises(ActiveRecord::InvalidForeignKey) do # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method if @connection.prefetch_primary_key? @@ -157,10 +157,8 @@ module ActiveRecord end end end - end - - def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false - unless current_adapter?(:SQLite3Adapter) + + def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false klass_has_fk = Class.new(ActiveRecord::Base) do self.table_name = 'fk_test_has_fk' end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index d1c644c016..7c0f11b033 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -1,10 +1,10 @@ require "cases/helper" +require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + include ConnectionHelper + def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end @@ -12,8 +12,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase end teardown do - ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + reset_connection end def test_add_index diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 42ffb91095..412efa22ff 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -1,7 +1,9 @@ require "cases/helper" +require 'support/connection_helper' require 'support/ddl_helper' class MysqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper include DdlHelper class Klass < ActiveRecord::Base @@ -160,15 +162,6 @@ class MysqlConnectionTest < ActiveRecord::TestCase private - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end - end - def with_example_table(&block) definition ||= <<-SQL `id` int(11) auto_increment PRIMARY KEY, diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 81c614924f..cefc3e3c7e 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -1,10 +1,10 @@ require "cases/helper" +require 'support/connection_helper' class ActiveSchemaTest < ActiveRecord::TestCase - def setup - @connection = ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + include ConnectionHelper + def setup ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute def execute(sql, name = nil) return sql end @@ -12,8 +12,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase end teardown do - ActiveRecord::Base.remove_connection - ActiveRecord::Base.establish_connection(@connection) + reset_connection end def test_add_index diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 57d8b8dd99..182d9409c7 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -1,6 +1,9 @@ require "cases/helper" +require 'support/connection_helper' class MysqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + def setup super @subscriber = SQLSubscriber.new @@ -103,15 +106,4 @@ class MysqlConnectionTest < ActiveRecord::TestCase assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime) end end - - private - - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end - end end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 36ded66998..18dd4a6de8 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -25,7 +25,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_column assert_equal :string, @column.type - assert_equal "character varying(255)", @column.sql_type + assert_equal "character varying", @column.sql_type assert @column.array assert_not @column.text? assert_not @column.number? diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index c3394d7712..e3478856c8 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -70,6 +70,23 @@ class PostgresqlByteaTest < ActiveRecord::TestCase assert_equal(data, record.payload) end + def test_via_to_sql + data = "'\u001F\\" + ByteaDataType.create(payload: data) + sql = ByteaDataType.where(payload: data).select(:payload).to_sql + result = @connection.query(sql) + assert_equal([[data]], result) + end + + def test_via_to_sql_with_complicating_connection + Thread.new do + other_conn = ActiveRecord::Base.connection + other_conn.execute('SET standard_conforming_strings = off') + end.join + + test_via_to_sql + end + def test_write_binary data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) assert(data.size > 1) diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 514ae20e3c..5f84c893c0 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -1,7 +1,10 @@ require "cases/helper" +require 'support/connection_helper' module ActiveRecord class PostgresqlConnectionTest < ActiveRecord::TestCase + include ConnectionHelper + class NonExistentTable < ActiveRecord::Base end @@ -198,17 +201,5 @@ module ActiveRecord ActiveRecord::Base.establish_connection(orig_connection.deep_merge({:variables => {:debug_print_plan => :default}})) end end - - private - - def run_without_connection - original_connection = ActiveRecord::Base.remove_connection - begin - yield original_connection - ensure - ActiveRecord::Base.establish_connection(original_connection) - end - end - end end diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb new file mode 100644 index 0000000000..5286a847a4 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +require "cases/helper" +require 'support/connection_helper' +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlDomainTest < ActiveRecord::TestCase + include ConnectionHelper + + class PostgresqlDomain < ActiveRecord::Base + self.table_name = "postgresql_domains" + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.transaction do + @connection.execute "CREATE DOMAIN custom_money as numeric(8,2)" + @connection.create_table('postgresql_domains') do |t| + t.column :price, :custom_money + end + end + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_domains' + @connection.execute 'DROP DOMAIN IF EXISTS custom_money' + reset_connection + end + + def test_column + column = PostgresqlDomain.columns_hash["price"] + assert_equal :decimal, column.type + assert_equal "custom_money", column.sql_type + assert column.number? + assert_not column.text? + assert_not column.binary? + assert_not column.array + end + + def test_domain_acts_like_basetype + PostgresqlDomain.create price: "" + record = PostgresqlDomain.first + assert_nil record.price + + record.price = "34.15" + record.save! + + assert_equal BigDecimal.new("34.15"), record.reload.price + end +end diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 381c397922..4146b117f6 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -1,18 +1,16 @@ # -*- coding: utf-8 -*- require "cases/helper" +require 'support/connection_helper' require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' class PostgresqlEnumTest < ActiveRecord::TestCase + include ConnectionHelper + class PostgresqlEnum < ActiveRecord::Base self.table_name = "postgresql_enums" end - teardown do - @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' - @connection.execute 'DROP TYPE IF EXISTS mood' - end - def setup @connection = ActiveRecord::Base.connection @connection.transaction do @@ -23,14 +21,17 @@ class PostgresqlEnumTest < ActiveRecord::TestCase t.column :current_mood, :mood end end - # reload type map after creating the enum type - @connection.send(:reload_type_map) + end + + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' + @connection.execute 'DROP TYPE IF EXISTS mood' + reset_connection end def test_column column = PostgresqlEnum.columns_hash["current_mood"] - # TODO: enum columns should be of type enum or string, not nil. - assert_nil column.type + assert_equal :enum, column.type assert_equal "mood", column.sql_type assert_not column.number? assert_not column.text? diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 49d8ec238d..b7791078db 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -1,11 +1,13 @@ # encoding: utf-8 require "cases/helper" require 'support/ddl_helper' +require 'support/connection_helper' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapterTest < ActiveRecord::TestCase include DdlHelper + include ConnectionHelper def setup @connection = ActiveRecord::Base.connection @@ -357,6 +359,43 @@ module ActiveRecord end end + def test_reload_type_map_for_newly_defined_types + @connection.execute "CREATE TYPE feeling AS ENUM ('good', 'bad')" + result = @connection.select_all "SELECT 'good'::feeling" + assert_instance_of(PostgreSQLAdapter::OID::Enum, + result.column_types["feeling"]) + ensure + @connection.execute "DROP TYPE IF EXISTS feeling" + reset_connection + end + + def test_only_reload_type_map_once_for_every_unknown_type + silence_warnings do + assert_queries 2, ignore_none: true do + @connection.select_all "SELECT NULL::anyelement" + end + assert_queries 1, ignore_none: true do + @connection.select_all "SELECT NULL::anyelement" + end + assert_queries 2, ignore_none: true do + @connection.select_all "SELECT NULL::anyarray" + end + end + ensure + reset_connection + end + + def test_only_warn_on_first_encounter_of_unknown_oid + warning = capture(:stderr) { + @connection.select_all "SELECT NULL::anyelement" + @connection.select_all "SELECT NULL::anyelement" + @connection.select_all "SELECT NULL::anyelement" + } + assert_match(/\Aunknown OID \d+: failed to recognize type of 'anyelement'. It will be treated as String.\n\z/, warning) + ensure + reset_connection + end + private def insert(ctx, data) binds = data.map { |name, value| diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 39f5b9b18a..51846e22d9 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -47,9 +47,9 @@ module ActiveRecord def test_quote_cast_numeric fixnum = 666 - c = PostgreSQLColumn.new(nil, nil, OID::Decimal.new, 'varchar') + c = PostgreSQLColumn.new(nil, nil, OID::String.new, 'varchar') assert_equal "'666'", @conn.quote(fixnum, c) - c = PostgreSQLColumn.new(nil, nil, OID::Decimal.new, 'text') + c = PostgreSQLColumn.new(nil, nil, OID::Text.new, 'text') assert_equal "'666'", @conn.quote(fixnum, c) end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index a9d4312fb3..060b17d071 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'support/connection_helper' require 'active_record/base' require 'active_record/connection_adapters/postgresql_adapter' @@ -8,17 +9,13 @@ if ActiveRecord::Base.connection.supports_ranges? end class PostgresqlRangeTest < ActiveRecord::TestCase - teardown do - @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' - @connection.execute 'DROP TYPE IF EXISTS floatrange' - end + self.use_transactional_fixtures = false + include ConnectionHelper def setup @connection = PostgresqlRange.connection begin @connection.transaction do - @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' - @connection.execute 'DROP TYPE IF EXISTS floatrange' @connection.execute <<_SQL CREATE TYPE floatrange AS RANGE ( subtype = float8, @@ -37,7 +34,6 @@ _SQL @connection.add_column 'postgresql_ranges', 'float_range', 'floatrange' end - @connection.send :reload_type_map PostgresqlRange.reset_column_information rescue ActiveRecord::StatementInvalid skip "do not test on PG without range" @@ -96,6 +92,12 @@ _SQL @empty_range = PostgresqlRange.find(105) end + teardown do + @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' + @connection.execute 'DROP TYPE IF EXISTS floatrange' + reset_connection + end + def test_data_type_of_range_types assert_equal :daterange, @first_range.column_for_attribute(:date_range).type assert_equal :numrange, @first_range.column_for_attribute(:num_range).type diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 5e5f653d17..11ec7599a3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -271,13 +271,13 @@ class SchemaTest < ActiveRecord::TestCase end def test_with_uppercase_index_name - ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} + @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" + assert_nothing_raised { @connection.remove_index! "things", "#{SCHEMA_NAME}.things_Index"} + @connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - ActiveRecord::Base.connection.execute "CREATE INDEX \"things_Index\" ON #{SCHEMA_NAME}.things (name)" - ActiveRecord::Base.connection.schema_search_path = SCHEMA_NAME - assert_nothing_raised { ActiveRecord::Base.connection.remove_index! "things", "things_Index"} - ActiveRecord::Base.connection.schema_search_path = "public" + with_schema_search_path SCHEMA_NAME do + assert_nothing_raised { @connection.remove_index! "things", "things_Index"} + end end def test_primary_key_with_schema_specified @@ -328,18 +328,17 @@ class SchemaTest < ActiveRecord::TestCase end def test_prepared_statements_with_multiple_schemas + [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| + with_schema_search_path schema_name do + Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) + end + end - @connection.schema_search_path = SCHEMA_NAME - Thing5.create(:id => 1, :name => "thing inside #{SCHEMA_NAME}", :email => "thing1@localhost", :moment => Time.now) - - @connection.schema_search_path = SCHEMA2_NAME - Thing5.create(:id => 1, :name => "thing inside #{SCHEMA2_NAME}", :email => "thing1@localhost", :moment => Time.now) - - @connection.schema_search_path = SCHEMA_NAME - assert_equal 1, Thing5.count - - @connection.schema_search_path = SCHEMA2_NAME - assert_equal 1, Thing5.count + [SCHEMA_NAME, SCHEMA2_NAME].each do |schema_name| + with_schema_search_path schema_name do + assert_equal 1, Thing5.count + end + end end def test_schema_exists? diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index f79a7a598b..bdf8e15e3e 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -40,6 +40,19 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase drop_table "uuid_data_type" end + def test_change_column_default + @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()" + UUIDType.reset_column_information + column = UUIDType.columns.find { |c| c.name == 'thingy' } + assert_equal "uuid_generate_v1()", column.default_function + + @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()" + + UUIDType.reset_column_information + column = UUIDType.columns.find { |c| c.name == 'thingy' } + assert_equal "uuid_generate_v4()", column.default_function + end + def test_data_type_of_uuid_types column = UUIDType.columns_hash["guid"] assert_equal :uuid, column.type @@ -50,6 +63,11 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase assert_not column.array end + def test_treat_blank_uuid_as_nil + UUIDType.create! guid: '' + assert_equal(nil, UUIDType.last.guid) + end + def test_uuid_formats ["A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11", "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}", diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb index 08071894c4..47b7d38eda 100644 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -1,11 +1,15 @@ require "cases/helper" -class ViewTest < ActiveRecord::TestCase - self.use_transactional_fixtures = false +module ViewTestConcern + extend ActiveSupport::Concern + + included do + self.use_transactional_fixtures = false + mattr_accessor :view_type + end SCHEMA_NAME = 'test_schema' TABLE_NAME = 'things' - VIEW_NAME = 'view_things' COLUMNS = [ 'id integer', 'name character varying(50)', @@ -14,17 +18,19 @@ class ViewTest < ActiveRecord::TestCase ] class ThingView < ActiveRecord::Base - self.table_name = 'test_schema.view_things' end def setup + super + ThingView.table_name = "#{SCHEMA_NAME}.#{view_type}_things" + @connection = ActiveRecord::Base.connection @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" - @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})" - @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}" + @connection.execute "CREATE #{view_type.humanize} #{ThingView.table_name} AS SELECT * FROM #{SCHEMA_NAME}.#{TABLE_NAME}" end - teardown do + def teardown + super @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end @@ -35,7 +41,7 @@ class ViewTest < ActiveRecord::TestCase def test_column_definitions assert_nothing_raised do - assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}") + assert_equal COLUMNS, columns(ThingView.table_name) end end @@ -47,3 +53,15 @@ class ViewTest < ActiveRecord::TestCase end end + +class ViewTest < ActiveRecord::TestCase + include ViewTestConcern + self.view_type = 'view' +end + +if ActiveRecord::Base.connection.supports_materialized_views? + class MaterializedViewTest < ActiveRecord::TestCase + include ViewTestConcern + self.view_type = 'materialized_view' + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index c2a255451e..14aad61ce2 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -31,7 +31,7 @@ module ActiveRecord def test_connect_with_url original_connection = ActiveRecord::Base.remove_connection tf = Tempfile.open 'whatever' - url = "sqlite3://#{tf.path}" + url = "sqlite3:#{tf.path}" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure @@ -42,7 +42,7 @@ module ActiveRecord def test_connect_memory_with_url original_connection = ActiveRecord::Base.remove_connection - url = "sqlite3:///:memory:" + url = "sqlite3::memory:" ActiveRecord::Base.establish_connection(url) assert ActiveRecord::Base.connection ensure diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index a65f2da7a0..3b484a0d64 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -16,6 +16,8 @@ require 'models/essay' require 'models/toy' require 'models/invoice' require 'models/line_item' +require 'models/column' +require 'models/record' class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, @@ -231,13 +233,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_belongs_to_counter debate = Topic.create("title" => "debate") - assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet" + assert_equal 0, debate.read_attribute("replies_count"), "No replies yet" trash = debate.replies.create("title" => "blah!", "content" => "world around!") - assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created" + assert_equal 1, Topic.find(debate.id).read_attribute("replies_count"), "First reply created" trash.destroy - assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted" + assert_equal 0, Topic.find(debate.id).read_attribute("replies_count"), "First reply deleted" end def test_belongs_to_counter_with_assigning_nil @@ -500,6 +502,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 4, topic.replies.size end + def test_concurrent_counter_cache_double_destroy + topic = Topic.create :title => "Zoom-zoom-zoom" + + 5.times do + topic.replies.create(:title => "re: zoom", :content => "speedy quick!") + end + + assert_equal 5, topic.reload[:replies_count] + assert_equal 5, topic.replies.size + + reply = topic.replies.first + reply_clone = Reply.find(reply.id) + + reply.destroy + assert_equal 4, topic.reload[:replies_count] + + reply_clone.destroy + assert_equal 4, topic.reload[:replies_count] + assert_equal 4, topic.replies.size + end + def test_custom_counter_cache reply = Reply.create(:title => "re: zoom", :content => "speedy quick!") assert_equal 0, reply[:replies_count] @@ -885,4 +908,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end end end + + test 'belongs_to works with model called Record' do + record = Record.create! + Column.create! record: record + assert_equal 1, Column.count + end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 8c9797861c..7eaa5adc86 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1198,6 +1198,14 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal authors(:bob), author end + test "preloading with a polymorphic association and using the existential predicate but also using a select" do + assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer + + assert_nothing_raised do + authors(:david).essays.includes(:writer).select(:name).any? + end + end + test "preloading with a polymorphic association and using the existential predicate" do assert_equal authors(:david), authors(:david).essays.includes(:writer).first.writer diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 49d3301044..c79c0c87c5 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -22,6 +22,10 @@ require 'models/engine' require 'models/categorization' require 'models/minivan' require 'models/speedometer' +require 'models/reference' +require 'models/job' +require 'models/college' +require 'models/student' class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase fixtures :authors, :posts, :comments @@ -39,7 +43,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, :people, :posts, :readers, :taggings, :cars, :essays, - :categorizations + :categorizations, :jobs def setup Client.destroyed_client_ids.clear @@ -63,6 +67,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase dev.developer_projects.map(&:project_id).sort end + def test_has_many_build_with_options + college = College.create(name: 'UFMT') + Student.create(active: true, college_id: college.id, name: 'Sarah') + + assert_equal college.students, Student.where(active: true, college_id: college.id) + end + def test_create_from_association_should_respect_default_scope car = Car.create(:name => 'honda') assert_equal 'honda', car.name @@ -107,6 +118,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, Bulb.count, "bulbs should have been deleted using :delete_all strategy" end + def test_delete_all_on_association_is_the_same_as_not_loaded + author = authors :david + author.thinking_posts.create!(:body => "test") + author.reload + expected_sql = capture_sql { author.thinking_posts.delete_all } + + author.thinking_posts.create!(:body => "test") + author.reload + author.thinking_posts.inspect + loaded_sql = capture_sql { author.thinking_posts.delete_all } + assert_equal(expected_sql, loaded_sql) + end + def test_building_the_associated_object_with_implicit_sti_base_class firm = DependentFirm.new company = firm.companies.build diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index fee0d2c627..e30577fb49 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1102,18 +1102,29 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [posts(:thinking)], person.reload.first_posts end - def test_has_many_through_with_includes_in_through_association_scope + test "has many through with includes in through association scope" do assert_not_empty posts(:welcome).author_address_extra_with_address end - + + test "insert records via has_many_through association with scope" do + club = Club.create! + member = Member.create! + Membership.create!(club: club, member: member) + + club.favourites << member + assert_equal [member], club.favourites + + club.reload + assert_equal [member], club.favourites + def test_has_many_through_unscope_default_scope post = Post.create!(:title => 'Beaches', :body => "I like beaches!") Reader.create! :person => people(:david), :post => post LazyReader.create! :person => people(:susan), :post => post - + assert_equal 2, post.people.to_a.size assert_equal 1, post.lazy_people.to_a.size - + assert_equal 2, post.lazy_readers_unscope_skimmers.to_a.size assert_equal 2, post.lazy_people_unscope_skimmers.to_a.size end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 952baaca36..38e93288e4 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -288,10 +288,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_read_attribute topic = Topic.new topic.title = "Don't change the topic" - assert_equal "Don't change the topic", topic.send(:read_attribute, "title") + assert_equal "Don't change the topic", topic.read_attribute("title") assert_equal "Don't change the topic", topic["title"] - assert_equal "Don't change the topic", topic.send(:read_attribute, :title) + assert_equal "Don't change the topic", topic.read_attribute(:title) assert_equal "Don't change the topic", topic[:title] end @@ -358,10 +358,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase super(attr_name).upcase end - assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title") + assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute("title") assert_equal "STOP CHANGING THE TOPIC", topic["title"] - assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title) + assert_equal "STOP CHANGING THE TOPIC", topic.read_attribute(:title) assert_equal "STOP CHANGING THE TOPIC", topic[:title] end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 4969344763..2e5b8cffa6 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -551,6 +551,41 @@ class BasicsTest < ActiveRecord::TestCase assert_equal one, two end + def test_equality_of_relation_and_collection_proxy + car = Car.create! + car.bulbs.build + car.save + + assert car.bulbs == Bulb.where(car_id: car.id), 'CollectionProxy should be comparable with Relation' + assert Bulb.where(car_id: car.id) == car.bulbs, 'Relation should be comparable with CollectionProxy' + end + + def test_equality_of_relation_and_array + car = Car.create! + car.bulbs.build + car.save + + assert Bulb.where(car_id: car.id) == car.bulbs.to_a, 'Relation should be comparable with Array' + end + + def test_equality_of_relation_and_association_relation + car = Car.create! + car.bulbs.build + car.save + + assert_equal Bulb.where(car_id: car.id), car.bulbs.includes(:car), 'Relation should be comparable with AssociationRelation' + assert_equal car.bulbs.includes(:car), Bulb.where(car_id: car.id), 'AssociationRelation should be comparable with Relation' + end + + def test_equality_of_collection_proxy_and_association_relation + car = Car.create! + car.bulbs.build + car.save + + assert_equal car.bulbs, car.bulbs.includes(:car), 'CollectionProxy should be comparable with AssociationRelation' + assert_equal car.bulbs.includes(:car), car.bulbs, 'AssociationRelation should be comparable with CollectionProxy' + end + def test_hashing assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index db999f90ab..b8de78934e 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -387,6 +387,20 @@ class CalculationsTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Account.count(1, 2, 3) } end + def test_count_with_order + assert_equal 6, Account.order(:credit_limit).count + end + + def test_count_with_reverse_order + assert_equal 6, Account.order(:credit_limit).reverse_order.count + end + + def test_count_with_where_and_order + assert_equal 1, Account.where(firm_name: '37signals').count + assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).count + assert_equal 1, Account.where(firm_name: '37signals').order(:firm_name).reverse_order.count + end + def test_should_sum_expression # Oracle adapter returns floating point value 636.0 after SUM if current_adapter?(:OracleAdapter) diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index c7b64f29c3..c1dd1f1c69 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -127,13 +127,13 @@ module ActiveRecord if current_adapter?(:PostgreSQLAdapter) def test_bigint_column_should_map_to_integer - oid = PostgreSQLAdapter::OID::Identity.new + oid = PostgreSQLAdapter::OID::Integer.new bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint") assert_equal :integer, bigint_column.type end def test_smallint_column_should_map_to_integer - oid = PostgreSQLAdapter::OID::Identity.new + oid = PostgreSQLAdapter::OID::Integer.new smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint") assert_equal :integer, smallint_column.type end diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb deleted file mode 100644 index deed226eab..0000000000 --- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb +++ /dev/null @@ -1,56 +0,0 @@ -require "cases/helper" - -module ActiveRecord - module ConnectionAdapters - class ConnectionPool - def insert_connection_for_test!(c) - synchronize do - @connections << c - @available.add c - end - end - end - - class AbstractAdapterTest < ActiveRecord::TestCase - attr_reader :adapter - - def setup - @adapter = AbstractAdapter.new nil, nil - end - - def test_in_use? - assert_not adapter.in_use?, 'adapter is not in use' - assert adapter.lease, 'lease adapter' - assert adapter.in_use?, 'adapter is in use' - end - - def test_lease_twice - assert adapter.lease, 'should lease adapter' - assert_not adapter.lease, 'should not lease adapter' - end - - def test_expire_mutates_in_use - assert adapter.lease, 'lease adapter' - assert adapter.in_use?, 'adapter is in use' - adapter.expire - assert_not adapter.in_use?, 'adapter is in use' - end - - def test_close - pool = ConnectionPool.new(ConnectionSpecification.new({}, nil)) - pool.insert_connection_for_test! adapter - adapter.pool = pool - - # Make sure the pool marks the connection in use - assert_equal adapter, pool.connection - assert adapter.in_use? - - # Close should put the adapter back in the pool - adapter.close - assert_not adapter.in_use? - - assert_equal adapter, pool.connection - end - end - end -end diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb new file mode 100644 index 0000000000..662e19f35e --- /dev/null +++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb @@ -0,0 +1,54 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class AdapterLeasingTest < ActiveRecord::TestCase + class Pool < ConnectionPool + def insert_connection_for_test!(c) + synchronize do + @connections << c + @available.add c + end + end + end + + def setup + @adapter = AbstractAdapter.new nil, nil + end + + def test_in_use? + assert_not @adapter.in_use?, 'adapter is not in use' + assert @adapter.lease, 'lease adapter' + assert @adapter.in_use?, 'adapter is in use' + end + + def test_lease_twice + assert @adapter.lease, 'should lease adapter' + assert_not @adapter.lease, 'should not lease adapter' + end + + def test_expire_mutates_in_use + assert @adapter.lease, 'lease adapter' + assert @adapter.in_use?, 'adapter is in use' + @adapter.expire + assert_not @adapter.in_use?, 'adapter is in use' + end + + def test_close + pool = Pool.new(ConnectionSpecification.new({}, nil)) + pool.insert_connection_for_test! @adapter + @adapter.pool = pool + + # Make sure the pool marks the connection in use + assert_equal @adapter, pool.connection + assert @adapter.in_use? + + # Close should put the adapter back in the pool + @adapter.close + assert_not @adapter.in_use? + + assert_equal @adapter, pool.connection + end + end + end +end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 2992ceb211..3e33b30144 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -2,132 +2,6 @@ require "cases/helper" module ActiveRecord module ConnectionAdapters - - class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase - - def klass - ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig - end - - def setup - @previous_database_url = ENV.delete("DATABASE_URL") - end - - teardown do - ENV["DATABASE_URL"] = @previous_database_url - end - - def test_jdbc_url - config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } } - actual = klass.new(config).resolve - assert_equal config, actual - end - - def test_environment_does_not_exist_in_config_url_does_exist - ENV['DATABASE_URL'] = "postgres://localhost/foo" - config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } - actual = klass.new(config).resolve - expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } - assert_equal expect_prod, actual["production"] - end - - def test_string_connection - config = { "production" => "postgres://localhost/foo" } - actual = klass.new(config).resolve - expected = { "production" => - { "adapter" => "postgresql", - "database" => "foo", - "host" => "localhost" - } - } - assert_equal expected, actual - end - - def test_url_sub_key - config = { "production" => { "url" => "postgres://localhost/foo" } } - actual = klass.new(config).resolve - expected = { "production" => - { "adapter" => "postgresql", - "database" => "foo", - "host" => "localhost" - } - } - assert_equal expected, actual - end - - def test_hash - config = { "production" => { "adapter" => "postgres", "database" => "foo" } } - actual = klass.new(config).resolve - assert_equal config, actual - end - - def test_blank - config = {} - actual = klass.new(config).resolve - assert_equal config, actual - end - - def test_blank_with_database_url - ENV['DATABASE_URL'] = "postgres://localhost/foo" - - config = {} - actual = klass.new(config).resolve - expected = { "adapter" => "postgresql", - "database" => "foo", - "host" => "localhost" } - assert_equal expected, actual["production"] - assert_equal expected, actual["development"] - assert_equal expected, actual["test"] - assert_equal nil, actual[:production] - assert_equal nil, actual[:development] - assert_equal nil, actual[:test] - end - - def test_url_sub_key_with_database_url - ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO" - - config = { "production" => { "url" => "postgres://localhost/foo" } } - actual = klass.new(config).resolve - expected = { "production" => - { "adapter" => "postgresql", - "database" => "foo", - "host" => "localhost" - } - } - assert_equal expected, actual - end - - def test_merge_no_conflicts_with_database_url - ENV['DATABASE_URL'] = "postgres://localhost/foo" - - config = {"production" => { "pool" => "5" } } - actual = klass.new(config).resolve - expected = { "production" => - { "adapter" => "postgresql", - "database" => "foo", - "host" => "localhost", - "pool" => "5" - } - } - assert_equal expected, actual - end - - def test_merge_conflicts_with_database_url - ENV['DATABASE_URL'] = "postgres://localhost/foo" - - config = {"production" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } - actual = klass.new(config).resolve - expected = { "production" => - { "adapter" => "postgresql", - "database" => "foo", - "host" => "localhost", - "pool" => "5" - } - } - assert_equal expected, actual - end - end - class ConnectionHandlerTest < ActiveRecord::TestCase def setup @klass = Class.new(Base) { def self.name; 'klass'; end } diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb new file mode 100644 index 0000000000..da852aaa02 --- /dev/null +++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb @@ -0,0 +1,192 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + class MergeAndResolveDefaultUrlConfigTest < ActiveRecord::TestCase + def setup + @previous_database_url = ENV.delete("DATABASE_URL") + end + + teardown do + ENV["DATABASE_URL"] = @previous_database_url + end + + def resolve_config(config) + ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve + end + + def resolve_spec(spec, config) + ConnectionSpecification::Resolver.new(resolve_config(config)).resolve(spec) + end + + def test_resolver_with_database_uri_and_current_env_symbol_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = resolve_spec(:default_env, config) + expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_resolver_with_database_uri_and_and_current_env_string_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = assert_deprecated { resolve_spec("default_env", config) } + expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_resolver_with_database_uri_and_known_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "production" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } + actual = resolve_spec(:production, config) + expected = { "adapter"=>"not_postgres", "database"=>"not_foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_resolver_with_database_uri_and_unknown_symbol_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + assert_raises AdapterNotSpecified do + resolve_spec(:production, config) + end + end + + def test_resolver_with_database_uri_and_unknown_string_key + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } + assert_deprecated do + assert_raises AdapterNotSpecified do + resolve_spec("production", config) + end + end + end + + def test_resolver_with_database_uri_and_supplied_url + ENV['DATABASE_URL'] = "not-postgres://not-localhost/not_foo" + config = { "production" => { "adapter" => "also_not_postgres", "database" => "also_not_foo" } } + actual = resolve_spec("postgres://localhost/foo", config) + expected = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_jdbc_url + config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } } + actual = resolve_config(config) + assert_equal config, actual + end + + def test_environment_does_not_exist_in_config_url_does_exist + ENV['DATABASE_URL'] = "postgres://localhost/foo" + config = { "not_default_env" => { "adapter" => "not_postgres", "database" => "not_foo" } } + actual = resolve_config(config) + expect_prod = { "adapter"=>"postgresql", "database"=>"foo", "host"=>"localhost" } + assert_equal expect_prod, actual["default_env"] + end + + def test_url_with_hyphenated_scheme + ENV['DATABASE_URL'] = "ibm-db://localhost/foo" + config = { "default_env" => { "adapter" => "not_postgres", "database" => "not_foo", "host" => "localhost" } } + actual = resolve_spec(:default_env, config) + expected = { "adapter"=>"ibm_db", "database"=>"foo", "host"=>"localhost" } + assert_equal expected, actual + end + + def test_string_connection + config = { "default_env" => "postgres://localhost/foo" } + actual = resolve_config(config) + expected = { "default_env" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" + } + } + assert_equal expected, actual + end + + def test_url_sub_key + config = { "default_env" => { "url" => "postgres://localhost/foo" } } + actual = resolve_config(config) + expected = { "default_env" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" + } + } + assert_equal expected, actual + end + + def test_hash + config = { "production" => { "adapter" => "postgres", "database" => "foo" } } + actual = resolve_config(config) + assert_equal config, actual + end + + def test_blank + config = {} + actual = resolve_config(config) + assert_equal config, actual + end + + def test_blank_with_database_url + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {} + actual = resolve_config(config) + expected = { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" } + assert_equal expected, actual["default_env"] + assert_equal nil, actual["production"] + assert_equal nil, actual["development"] + assert_equal nil, actual["test"] + assert_equal nil, actual[:production] + assert_equal nil, actual[:development] + assert_equal nil, actual[:test] + end + + def test_url_sub_key_with_database_url + ENV['DATABASE_URL'] = "NOT-POSTGRES://localhost/NOT_FOO" + + config = { "default_env" => { "url" => "postgres://localhost/foo" } } + actual = resolve_config(config) + expected = { "default_env" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost" + } + } + assert_equal expected, actual + end + + def test_merge_no_conflicts_with_database_url + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {"default_env" => { "pool" => "5" } } + actual = resolve_config(config) + expected = { "default_env" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost", + "pool" => "5" + } + } + assert_equal expected, actual + end + + def test_merge_conflicts_with_database_url + ENV['DATABASE_URL'] = "postgres://localhost/foo" + + config = {"default_env" => { "adapter" => "NOT-POSTGRES", "database" => "NOT-FOO", "pool" => "5" } } + actual = resolve_config(config) + expected = { "default_env" => + { "adapter" => "postgresql", + "database" => "foo", + "host" => "localhost", + "pool" => "5" + } + } + assert_equal expected, actual + end + end + end +end diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index fdd1914cba..3c2f5d4219 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -82,15 +82,34 @@ module ActiveRecord assert_equal password, spec["password"] end - def test_url_host_db_for_sqlite3 - spec = resolve 'sqlite3://foo:bar@dburl:9000/foo_test' + def test_url_with_authority_for_sqlite3 + spec = resolve 'sqlite3:///foo_test' assert_equal('/foo_test', spec["database"]) end - def test_url_host_memory_db_for_sqlite3 - spec = resolve 'sqlite3://foo:bar@dburl:9000/:memory:' + def test_url_absolute_path_for_sqlite3 + spec = resolve 'sqlite3:/foo_test' + assert_equal('/foo_test', spec["database"]) + end + + def test_url_relative_path_for_sqlite3 + spec = resolve 'sqlite3:foo_test' + assert_equal('foo_test', spec["database"]) + end + + def test_url_memory_db_for_sqlite3 + spec = resolve 'sqlite3::memory:' assert_equal(':memory:', spec["database"]) end + + def test_url_sub_key_for_sqlite3 + spec = resolve :production, 'production' => {"url" => 'sqlite3:foo?encoding=utf8'} + assert_equal({ + "adapter" => "sqlite3", + "database" => "foo", + "encoding" => "utf8" }, spec) + end + end end end diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index f8ebd7caee..3b2f0dfe07 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -194,6 +194,7 @@ class EnumTest < ActiveRecord::TestCase :valid, # generates #valid?, which conflicts with an AR method :save, # generates #save!, which conflicts with an AR method :proposed, # same value as an existing enum + :public, :private, :protected, # generates a method that conflict with ruby words ] conflicts.each_with_index do |value, i| @@ -249,4 +250,40 @@ class EnumTest < ActiveRecord::TestCase valid_book = klass.new(status: "written") assert valid_book.valid? end + + test "enums are distinct per class" do + klass1 = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:proposed, :written] + end + + klass2 = Class.new(ActiveRecord::Base) do + self.table_name = "books" + enum status: [:drafted, :uploaded] + end + + book1 = klass1.proposed.create! + book1.status = :written + assert_equal ['proposed', 'written'], book1.status_change + + book2 = klass2.drafted.create! + book2.status = :uploaded + assert_equal ['drafted', 'uploaded'], book2.status_change + end + + test "enums are inheritable" do + subklass1 = Class.new(Book) + + subklass2 = Class.new(Book) do + enum status: [:drafted, :uploaded] + end + + book1 = subklass1.proposed.create! + book1.status = :written + assert_equal ['proposed', 'written'], book1.status_change + + book2 = subklass2.drafted.create! + book2.status = :uploaded + assert_equal ['drafted', 'uploaded'], book2.status_change + end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 1147418815..8bbc0af758 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -254,7 +254,7 @@ class FixturesTest < ActiveRecord::TestCase def test_fixtures_are_set_up_with_database_env_variable db_url_tmp = ENV['DATABASE_URL'] - ENV['DATABASE_URL'] = "sqlite3:///:memory:" + ENV['DATABASE_URL'] = "sqlite3::memory:" ActiveRecord::Base.stubs(:configurations).returns({}) test_case = Class.new(ActiveRecord::TestCase) do fixtures :accounts @@ -677,6 +677,12 @@ end class FoxyFixturesTest < ActiveRecord::TestCase fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users" + if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' + require 'models/uuid_parent' + require 'models/uuid_child' + fixtures :uuid_parents, :uuid_children + end + def test_identifies_strings assert_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("foo")) assert_not_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("FOO")) @@ -689,6 +695,9 @@ class FoxyFixturesTest < ActiveRecord::TestCase def test_identifies_consistently assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby) assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2) + + assert_equal 'f92b6bda-0d0d-5fe1-9124-502b18badded', ActiveRecord::FixtureSet.identify(:daddy, :uuid) + assert_equal 'b4b10018-ad47-595d-b42f-d8bdaa6d01bf', ActiveRecord::FixtureSet.identify(:sonny, :uuid) end TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 5ed508a799..eaf2cada9d 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -111,6 +111,15 @@ def verify_default_timezone_config end end +def enable_uuid_ossp!(connection) + return false unless connection.supports_extensions? + return true if connection.extension_enabled?('uuid-ossp') + + connection.enable_extension 'uuid-ossp' + connection.commit_db_transaction + connection.reconnect! +end + unless ENV['FIXTURE_DEBUG'] module ActiveRecord::TestFixtures::ClassMethods def try_to_load_dependency_with_silence(*args) diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index ccf19fb4d0..6a02873cba 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -35,6 +35,14 @@ module ActiveRecord assert_no_column TestModel, :last_name end + def test_add_column_without_limit + # TODO: limit: nil should work with all adapters. + skip "MySQL wrongly enforces a limit of 255" if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + add_column :test_models, :description, :string, limit: nil + TestModel.reset_column_information + assert_nil TestModel.columns_hash["description"].limit + end + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_unabstracted_database_dependent_types add_column :test_models, :intelligence_quotient, :tinyint diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 046fe83e54..9209672ac5 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -233,6 +233,22 @@ class PersistenceTest < ActiveRecord::TestCase assert_nothing_raised { Minimalistic.create!(:id => 2) } end + def test_save_with_duping_of_destroyed_object + developer = Developer.create(name: "Kuldeep") + developer.destroy + new_developer = developer.dup + new_developer.save + assert new_developer.persisted? + end + + def test_dup_of_destroyed_object_is_not_destroyed + developer = Developer.create(name: "Kuldeep") + developer.destroy + new_developer = developer.dup + new_developer.save + assert_equal new_developer.destroyed?, false + end + def test_create_many topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) assert_equal 2, topics.size diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index d7ad5ed29f..c085fcf161 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -63,7 +63,7 @@ class ReflectionTest < ActiveRecord::TestCase def test_column_string_type_and_limit assert_equal :string, @first.column_for_attribute("title").type - assert_equal 255, @first.column_for_attribute("title").limit + assert_equal 250, @first.column_for_attribute("title").limit end def test_column_null_not_null @@ -87,6 +87,14 @@ class ReflectionTest < ActiveRecord::TestCase end end + def test_irregular_reflection_class_name + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'plural_irregular', 'plurales_irregulares' + end + reflection = AssociationReflection.new(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base) + assert_equal 'PluralIrregular', reflection.class_name + end + def test_aggregation_reflection reflection_for_address = AggregateReflection.new( :composed_of, :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer @@ -192,7 +200,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_reflection_should_not_raise_error_when_compared_to_other_object - assert_nothing_raised { Firm.reflections[:clients] == Object.new } + assert_nothing_raised { Firm.reflections['clients'] == Object.new } end def test_has_many_through_reflection diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 4fafa668fb..c81a3002d6 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -107,10 +107,18 @@ module ActiveRecord end test 'reverse_order!' do - assert relation.reverse_order!.equal?(relation) - assert relation.reverse_order_value + relation = Post.order('title ASC, comments_count DESC') + + relation.reverse_order! + + assert_equal 'title DESC', relation.order_values.first + assert_equal 'comments_count ASC', relation.order_values.last + + relation.reverse_order! - assert !relation.reverse_order_value + + assert_equal 'title ASC', relation.order_values.first + assert_equal 'comments_count DESC', relation.order_values.last end test 'create_with!' do diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 15611656fd..fb0b906c07 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -16,6 +16,10 @@ module ActiveRecord def self.connection Post.connection end + + def self.table_name + 'fake_table' + end end def test_construction diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fddb7c204a..d054dfa25a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -14,6 +14,7 @@ require 'models/car' require 'models/engine' require 'models/tyre' require 'models/minivan' +require 'models/aircraft' class RelationTest < ActiveRecord::TestCase @@ -365,6 +366,16 @@ class RelationTest < ActiveRecord::TestCase assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash) end + + def test_null_relation_count + ac = Aircraft.new + assert_equal Hash.new, ac.engines.group(:id).count + assert_equal 0, ac.engines.count + ac.save + assert_equal Hash.new, ac.engines.group(:id).count + assert_equal 0, ac.engines.count + end + def test_joins_with_nil_argument assert_nothing_raised { DependentFirm.joins(nil).first } end @@ -573,6 +584,12 @@ class RelationTest < ActiveRecord::TestCase assert_equal expected, actual end + def test_to_sql_on_scoped_proxy + auth = Author.first + Post.where("1=1").written_by(auth) + assert_not auth.posts.to_sql.include?("1=1") + end + def test_loading_with_one_association_with_non_preload posts = Post.eager_load(:last_comment).order('comments.id DESC') post = posts.find { |p| p.id == 1 } @@ -818,6 +835,16 @@ class RelationTest < ActiveRecord::TestCase assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all } end + def test_select_with_aggregates + posts = Post.select(:title, :body) + + assert_equal 11, posts.count(:all) + assert_equal 11, posts.size + assert posts.any? + assert posts.many? + assert_not posts.empty? + end + def test_select_takes_a_variable_list_of_args david = developers(:david) @@ -848,6 +875,17 @@ class RelationTest < ActiveRecord::TestCase assert_equal 9, posts.where(:comments_count => 0).count end + def test_count_on_association_relation + author = Author.last + another_author = Author.first + posts = Post.where(author_id: author.id) + + assert_equal author.posts.where(author_id: author.id).size, posts.count + + assert_equal 0, author.posts.where(author_id: another_author.id).size + assert author.posts.where(author_id: another_author.id).empty? + end + def test_count_with_distinct posts = Post.all @@ -1418,6 +1456,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal [], scope.references_values end + def test_order_with_reorder_nil_removes_the_order + relation = Post.order(:title).reorder(nil) + + assert_nil relation.order_values.first + end + + def test_reverse_order_with_reorder_nil_removes_the_order + relation = Post.order(:title).reverse_order.reorder(nil) + + assert_nil relation.order_values.first + end + def test_presence topics = Topic.all diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 954eab8022..dca85fb5eb 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -51,4 +51,31 @@ class SanitizeTest < ActiveRecord::TestCase select_author_sql = Post.send(:sanitize_sql_array, ['']) assert_equal('', select_author_sql) end + + def test_sanitize_sql_like + assert_equal '100\%', Binary.send(:sanitize_sql_like, '100%') + assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, 'snake_cased_string') + assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint') + assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42') + end + + def test_sanitize_sql_like_with_custom_escape_character + assert_equal '100!%', Binary.send(:sanitize_sql_like, '100%', '!') + assert_equal 'snake!_cased!_string', Binary.send(:sanitize_sql_like, 'snake_cased_string', '!') + assert_equal 'great!!', Binary.send(:sanitize_sql_like, 'great!', '!') + assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', '!') + assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42', '!') + end + + def test_sanitize_sql_like_example_use_case + searchable_post = Class.new(Post) do + def self.search(term) + where("title LIKE ?", sanitize_sql_like(term, '!')) + end + end + + assert_sql(/LIKE '20!% !_reduction!_!!'/) do + searchable_post.search("20% _reduction_!").to_a + end + end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 575eb34a9c..fd0ef2f89f 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -63,7 +63,7 @@ class SchemaDumperTest < ActiveRecord::TestCase next if column_set.empty? lengths = column_set.map do |column| - if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/) + if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean|uuid)\s+"/) match[0].length end end diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index f0ad9ebb8a..59ec2dd6a4 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -291,6 +291,9 @@ class NamedScopingTest < ActiveRecord::TestCase :relation, # private class method on AR::Base :new, # redefined class method on AR::Base :all, # a default scope + :public, + :protected, + :private ] non_conflicts = [ diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 4476ce3410..803a054d7e 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -19,10 +19,14 @@ module ActiveRecord end end - def assert_sql(*patterns_to_match) + def capture_sql SQLCounter.clear_log yield - SQLCounter.log_all + SQLCounter.log_all.dup + end + + def assert_sql(*patterns_to_match) + capture_sql { yield } ensure failed_patterns = [] patterns_to_match.each do |pattern| diff --git a/activerecord/test/fixtures/uuid_children.yml b/activerecord/test/fixtures/uuid_children.yml new file mode 100644 index 0000000000..a7b15016e2 --- /dev/null +++ b/activerecord/test/fixtures/uuid_children.yml @@ -0,0 +1,3 @@ +sonny: + uuid_parent: daddy + name: Sonny diff --git a/activerecord/test/fixtures/uuid_parents.yml b/activerecord/test/fixtures/uuid_parents.yml new file mode 100644 index 0000000000..0b40225c5c --- /dev/null +++ b/activerecord/test/fixtures/uuid_parents.yml @@ -0,0 +1,2 @@ +daddy: + name: Daddy diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 566e0873f1..a762ad4bb5 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -6,6 +6,8 @@ class Club < ActiveRecord::Base has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" belongs_to :category + has_many :favourites, -> { where(memberships: { favourite: true }) }, through: :memberships, source: :member + private def private_method diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb index c7495d7deb..501af4a8dd 100644 --- a/activerecord/test/models/college.rb +++ b/activerecord/test/models/college.rb @@ -1,5 +1,10 @@ require_dependency 'models/arunit2_model' +require 'active_support/core_ext/object/with_options' class College < ARUnit2Model has_many :courses + + with_options dependent: :destroy do |assoc| + assoc.has_many :students, -> { where(active: true) } + end end diff --git a/activerecord/test/models/column.rb b/activerecord/test/models/column.rb new file mode 100644 index 0000000000..499358b4cf --- /dev/null +++ b/activerecord/test/models/column.rb @@ -0,0 +1,3 @@ +class Column < ActiveRecord::Base + belongs_to :record +end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 7bb0caf44b..e472cf951d 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -18,7 +18,6 @@ class Pirate < ActiveRecord::Base has_many :treasures, :as => :looter has_many :treasure_estimates, :through => :treasures, :source => :price_estimates - # These both have :autosave enabled because accepts_nested_attributes_for is used on them. has_one :ship has_one :update_only_ship, :class_name => 'Ship' has_one :non_validated_ship, :class_name => 'Ship' diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index f52130278c..629dfe4765 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -144,7 +144,7 @@ class Post < ActiveRecord::Base has_many :lazy_readers has_many :lazy_readers_skimmers_or_not, -> { where(skimmer: [ true, false ]) }, :class_name => 'LazyReader' - + has_many :lazy_people, :through => :lazy_readers, :source => :person has_many :lazy_readers_unscope_skimmers, -> { skimmers_or_not }, :class_name => 'LazyReader' has_many :lazy_people_unscope_skimmers, :through => :lazy_readers_unscope_skimmers, :source => :person @@ -153,6 +153,10 @@ class Post < ActiveRecord::Base ranked_by_comments.limit_by(limit) end + def self.written_by(author) + where(id: author.posts.pluck(:id)) + end + def self.reset_log @log = [] end diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb index 14b5f60a72..91afc1898c 100644 --- a/activerecord/test/models/reader.rb +++ b/activerecord/test/models/reader.rb @@ -15,7 +15,7 @@ end class LazyReader < ActiveRecord::Base self.table_name = "readers" default_scope -> { where(skimmer: true) } - + scope :skimmers_or_not, -> { unscope(:where => :skimmer) } belongs_to :post diff --git a/activerecord/test/models/record.rb b/activerecord/test/models/record.rb new file mode 100644 index 0000000000..f77ac9fc03 --- /dev/null +++ b/activerecord/test/models/record.rb @@ -0,0 +1,2 @@ +class Record < ActiveRecord::Base +end diff --git a/activerecord/test/models/student.rb b/activerecord/test/models/student.rb index f459f2a9a3..28a0b6c99b 100644 --- a/activerecord/test/models/student.rb +++ b/activerecord/test/models/student.rb @@ -1,3 +1,4 @@ class Student < ActiveRecord::Base has_and_belongs_to_many :lessons + belongs_to :college end diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb index a581b381e8..80d4725f7e 100644 --- a/activerecord/test/models/tag.rb +++ b/activerecord/test/models/tag.rb @@ -3,5 +3,5 @@ class Tag < ActiveRecord::Base has_many :taggables, :through => :taggings has_one :tagging - has_many :tagged_posts, :through => :taggings, :source => :taggable, :source_type => 'Post' -end
\ No newline at end of file + has_many :tagged_posts, :through => :taggings, :source => 'taggable', :source_type => 'Post' +end diff --git a/activerecord/test/models/uuid_child.rb b/activerecord/test/models/uuid_child.rb new file mode 100644 index 0000000000..a3d0962ad6 --- /dev/null +++ b/activerecord/test/models/uuid_child.rb @@ -0,0 +1,3 @@ +class UuidChild < ActiveRecord::Base + belongs_to :uuid_parent +end diff --git a/activerecord/test/models/uuid_parent.rb b/activerecord/test/models/uuid_parent.rb new file mode 100644 index 0000000000..5634f22d0c --- /dev/null +++ b/activerecord/test/models/uuid_parent.rb @@ -0,0 +1,3 @@ +class UuidParent < ActiveRecord::Base + has_many :uuid_children +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index b44e72a67c..da3074e90f 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -17,6 +17,15 @@ ActiveRecord::Schema.define do ActiveRecord::Base.connection.create_table(*args, &block) ActiveRecord::Base.connection.execute "SET GENERATOR #{args.first}_seq TO 10000" end + when "PostgreSQL" + enable_uuid_ossp!(ActiveRecord::Base.connection) + create_table :uuid_parents, id: :uuid, force: true do |t| + t.string :name + end + create_table :uuid_children, id: :uuid, force: true do |t| + t.string :name + t.uuid :uuid_parent_id + end end @@ -161,6 +170,10 @@ ActiveRecord::Schema.define do t.integer :references, null: false end + create_table :columns, force: true do |t| + t.references :record + end + create_table :comments, force: true do |t| t.integer :post_id, null: false # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in @@ -638,6 +651,8 @@ ActiveRecord::Schema.define do create_table :students, force: true do |t| t.string :name + t.boolean :active + t.integer :college_id end create_table :subscribers, force: true, id: false do |t| @@ -671,7 +686,7 @@ ActiveRecord::Schema.define do end create_table :topics, force: true do |t| - t.string :title + t.string :title, limit: 250 t.string :author_name t.string :author_email_address if mysql_56? @@ -817,6 +832,8 @@ ActiveRecord::Schema.define do t.integer :department_id end + create_table :records, force: true do |t| + end except 'SQLite' do # fk_test_has_fk should be before fk_test_has_pk diff --git a/activerecord/test/support/connection_helper.rb b/activerecord/test/support/connection_helper.rb new file mode 100644 index 0000000000..4a19e5df44 --- /dev/null +++ b/activerecord/test/support/connection_helper.rb @@ -0,0 +1,14 @@ +module ConnectionHelper + def run_without_connection + original_connection = ActiveRecord::Base.remove_connection + yield original_connection + ensure + ActiveRecord::Base.establish_connection(original_connection) + end + + # Used to drop all cache query plans in tests. + def reset_connection + original_connection = ActiveRecord::Base.remove_connection + ActiveRecord::Base.establish_connection(original_connection) + end +end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 556e94d184..5e430d20fa 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,48 @@ +* Add `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5` to support stable + UUID fixtures on PostgreSQL. + + *Roderick van Domburg* + +* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is + true. + + This fixes the current situation of: + + 1.second.eql?(1.second) #=> false + + `eql?` also requires that the other object is an `ActiveSupport::Duration`. + This requirement makes `ActiveSupport::Duration`'s behavior consistent with + the behavior of Ruby's numeric types: + + 1.eql?(1.0) #=> false + 1.0.eql?(1) #=> false + + 1.second.eql?(1) #=> false (was true) + 1.eql?(1.second) #=> false + + { 1 => "foo", 1.0 => "bar" } + #=> { 1 => "foo", 1.0 => "bar" } + + { 1 => "foo", 1.second => "bar" } + # now => { 1 => "foo", 1.second => "bar" } + # was => { 1 => "bar" } + + And though the behavior of these hasn't changed, for reference: + + 1 == 1.0 #=> true + 1.0 == 1 #=> true + + 1 == 1.second #=> true + 1.second == 1 #=> true + + *Emily Dobervich* + +* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies + instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is + deprecated. + + *Pavel Pravosud* + * `HashWithIndifferentAccess` better respects `#to_hash` on objects it's given. In particular, `.new`, `#update`, `#merge`, `#replace` all accept objects which respond to `#to_hash`, even if those objects are not Hashes diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index e9ee98a128..73c6b3cb88 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/string/inflections' +require 'active_support/per_thread_registry' module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e14ece7f35..05ca943776 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -131,8 +131,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) lambda { |env| target = env.target @@ -149,6 +147,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback, halted_lambda, filter) lambda { |env| @@ -166,6 +165,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -178,6 +178,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -185,6 +186,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :simple end class After @@ -208,8 +210,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions) lambda { |env| env = next_callback.call env @@ -223,6 +223,7 @@ module ActiveSupport env } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback) lambda { |env| @@ -233,6 +234,7 @@ module ActiveSupport env } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -246,6 +248,7 @@ module ActiveSupport env } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -254,6 +257,7 @@ module ActiveSupport env } end + private_class_method :simple end class Around @@ -269,8 +273,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions) lambda { |env| target = env.target @@ -288,6 +290,7 @@ module ActiveSupport end } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback) lambda { |env| @@ -305,6 +308,7 @@ module ActiveSupport end } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -322,6 +326,7 @@ module ActiveSupport end } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -332,6 +337,7 @@ module ActiveSupport env } end + private_class_method :simple end end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index aa19aed43b..293a3b2619 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/kernel/agnostics' require 'active_support/core_ext/kernel/concern' -require 'active_support/core_ext/kernel/debugger' +require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index fe24f3716d..768b980f21 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -7,6 +7,7 @@ class LoadError ] unless method_defined?(:path) + # Returns the path which was unable to be loaded. def path @path ||= begin REGEXPS.find do |regex| @@ -17,9 +18,11 @@ class LoadError end end + # Returns true if the given path name (except perhaps for the ".rb" + # extension) is the missing file which caused the exception to be raised. def is_missing?(location) location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '') end end -MissingSourceFile = LoadError
\ No newline at end of file +MissingSourceFile = LoadError diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9cd7485e2e..3d2c809c9f 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -78,6 +78,9 @@ end require 'bigdecimal' class BigDecimal + # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead + # raises TypeError exception. Checking here on the runtime whether BigDecimal + # will allow dup or not. begin BigDecimal.new('4.56').dup diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..fec8f7c0ec --- /dev/null +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,47 @@ +module SecureRandom + UUID_DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + + # Generates a v5 non-random UUID (Universally Unique IDentifier). + # + # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs. + # ::uuid_from_hash always generates the same UUID for a given name and namespace combination. + # + # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt + def self.uuid_from_hash(hash_class, uuid_namespace, name) + if hash_class == Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." + end + + hash = hash_class.new + hash.update(uuid_namespace) + hash.update(name) + + ary = hash.digest.unpack('NnnnnN') + ary[2] = (ary[2] & 0x0FFF) | (version << 12) + ary[3] = (ary[3] & 0x3FFF) | 0x8000 + + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + # Convenience method for ::uuid_from_hash using Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + self.uuid_from_hash(Digest::MD5, uuid_namespace, name) + end + + # Convenience method for ::uuid_from_hash using Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + self.uuid_from_hash(Digest::SHA1, uuid_namespace, name) + end + + class << self + # Alias for ::uuid. + alias_method :uuid_v4, :uuid + end +end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 6018fd9641..ebd0dd3fc7 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -64,7 +64,7 @@ class String # Returns the first character. If a limit is supplied, returns a substring # from the beginning of the string until it reaches the limit value. If the - # given limit is greater than or equal to the string length, returns self. + # given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" # str.first # => "h" @@ -76,7 +76,7 @@ class String if limit == 0 '' elsif limit >= size - self + self.dup else to(limit - 1) end @@ -84,7 +84,7 @@ class String # Returns the last character of the string. If a limit is supplied, returns a substring # from the end of the string until it reaches the limit value (counting backwards). If - # the given limit is greater than or equal to the string length, returns self. + # the given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" # str.last # => "o" @@ -96,7 +96,7 @@ class String if limit == 0 '' elsif limit >= size - self + self.dup else from(-limit) end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index cf9b1a4ec0..a943752f17 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -31,7 +31,7 @@ class String def pluralize(count = nil, locale = :en) locale = count if count.is_a?(Symbol) if count == 1 - self + self.dup else ActiveSupport::Inflector.pluralize(self, locale) end @@ -130,6 +130,8 @@ class String # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' # # See also +deconstantize+. def demodulize diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index eb02b6a442..2c8995be9a 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,5 +1,6 @@ require 'erb' require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/deprecation' class ERB module Util @@ -124,7 +125,7 @@ module ActiveSupport #:nodoc: class SafeBuffer < String UNSAFE_STRING_METHODS = %w( capitalize chomp chop delete downcase gsub lstrip next reverse rstrip - slice squeeze strip sub succ swapcase tr tr_s upcase prepend + slice squeeze strip sub succ swapcase tr tr_s upcase ) alias_method :original_concat, :concat @@ -169,15 +170,18 @@ module ActiveSupport #:nodoc: self[0, 0] end - def concat(value) - if !html_safe? || value.html_safe? - super(value) - else - super(ERB::Util.h(value)) + %w[concat prepend].each do |method_name| + define_method method_name do |value| + super(html_escape_interpolated_argument(value)) end end alias << concat + def prepend!(value) + ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend + prepend value + end + def +(other) dup.concat(other) end diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb index ac1ffa4128..4cd6634558 100644 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ b/activesupport/lib/active_support/core_ext/thread.rb @@ -62,6 +62,13 @@ class Thread _locals.has_key?(key.to_sym) end + # Freezes the thread so that thread local variables cannot be set via + # Thread#thread_variable_set, nor can fiber local variables be set. + # + # me = Thread.current + # me.freeze + # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals + # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals def freeze _locals.freeze super diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 9fd26156c7..dbf1f2f373 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -20,7 +20,7 @@ class Time :iso8601 => lambda { |time| time.iso8601 } } - # Converts to a formatted string. See DATE_FORMATS for builtin formats. + # Converts to a formatted string. See DATE_FORMATS for built-in formats. # # This method is aliased to <tt>to_s</tt>. # diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 7df4857c25..09eb732ef7 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -49,6 +49,10 @@ module ActiveSupport end end + def eql?(other) + other.is_a?(Duration) && self == other + end + def self.===(other) #:nodoc: other.is_a?(Duration) rescue ::NoMethodError diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a270c4452f..4a05d678ea 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -155,7 +155,7 @@ module ActiveSupport # # Singular names are not handled correctly: # - # 'business'.classify # => "Busines" + # 'calculus'.classify # => "Calculu" def classify(table_name) # strip out any leading schema name camelize(singularize(table_name.to_s.sub(/.*\./, ''))) @@ -172,6 +172,8 @@ module ActiveSupport # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => "" # # See also +deconstantize+. def demodulize(path) @@ -228,7 +230,7 @@ module ActiveSupport def constantize(camel_cased_word) names = camel_cased_word.split('::') - # Trigger a builtin NameError exception including the ill-formed constant in the message. + # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(camel_cased_word) if names.empty? # Remove the first blank element in case of '::ClassName' notation. diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index e55ffd12c3..dea84e437f 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -12,7 +12,7 @@ module ActiveSupport private def method_missing(method, *arguments, &block) - if arguments.last.is_a?(Proc) + if arguments.first.is_a?(Proc) proc = arguments.pop arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) } else diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index 86193c2b07..447b1d10ad 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -33,7 +33,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_simple_accessor_declaration - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :both end @@ -48,7 +48,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_simple_accessor_declaration_with_instance_reader_false _instance_methods = single_class.public_instance_methods - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false end @@ -60,7 +60,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_working_with_simple_attributes - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :both end @@ -79,7 +79,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_child_class_delegates_to_parent_but_can_be_overridden parent = Class.new - ActiveSupport::Deprecation.silence do + assert_deprecated do parent.superclass_delegating_accessor :both end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 28ba33331e..c8f17f4618 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -31,6 +31,13 @@ class DurationTest < ActiveSupport::TestCase assert !(1.day == 'foo') end + def test_eql + assert 1.minute.eql?(1.minute) + assert 2.days.eql?(48.hours) + assert !1.second.eql?(1) + assert !1.eql?(1.second) + end + def test_inspect assert_equal '0 seconds', 0.seconds.inspect assert_equal '1 month', 1.month.inspect diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 69a380c7cb..d824a16e98 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -635,7 +635,7 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, h['first'] end - def test_indifferent_subhashes + def test_indifferent_sub_hashes h = {'user' => {'id' => 5}}.with_indifferent_access ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 18b251173f..d8bf81d02b 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -137,4 +137,4 @@ class KernelDebuggerTest < ActiveSupport::TestCase ensure Object.send(:remove_const, :Rails) end -end +end if RUBY_VERSION < '2.0.0' diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index 91d558dbb5..91d558dbb5 100644 --- a/activesupport/test/core_ext/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index e0566e012c..84512380cf 100644 --- a/activesupport/test/core_ext/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -5,34 +5,27 @@ require 'active_support/core_ext/numeric/time' class DuplicableTest < ActiveSupport::TestCase RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds] - YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - NO = [] + ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] + # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead + # raises TypeError exception. Checking here on the runtime whether BigDecimal + # will allow dup or not. begin bd = BigDecimal.new('4.56') - YES << bd.dup + ALLOW_DUP << bd.dup rescue TypeError RAISE_DUP << bd end - def test_duplicable - (RAISE_DUP + NO).each do |v| + RAISE_DUP.each do |v| assert !v.duplicable? + assert_raises(TypeError, v.class.name) { v.dup } end - YES.each do |v| - assert v.duplicable?, "#{v.class} should be duplicable" - end - - (YES + NO).each do |v| + ALLOW_DUP.each do |v| + assert v.duplicable?, "#{ v.class } should be duplicable" assert_nothing_raised { v.dup } end - - RAISE_DUP.each do |v| - assert_raises(TypeError, v.class.name) do - v.dup - end - end end end diff --git a/activesupport/test/core_ext/securerandom_test.rb b/activesupport/test/core_ext/securerandom_test.rb new file mode 100644 index 0000000000..71980f6910 --- /dev/null +++ b/activesupport/test/core_ext/securerandom_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'active_support/core_ext/securerandom' + +class SecureRandomExt < ActiveSupport::TestCase + def test_v3_uuids + assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", SecureRandom.uuid_v3(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com") + assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", SecureRandom.uuid_v3(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com") + assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", SecureRandom.uuid_v3(SecureRandom::UUID_OID_NAMESPACE, "1.2.3") + assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", SecureRandom.uuid_v3(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US") + end + + def test_v5_uuids + assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", SecureRandom.uuid_v5(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com") + assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", SecureRandom.uuid_v5(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com") + assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, "1.2.3") + assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", SecureRandom.uuid_v5(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US") + end + + def test_uuid_v4_alias + assert_equal SecureRandom.method(:uuid_v4), SecureRandom.method(:uuid) + end + + def test_invalid_hash_class + assert_raise ArgumentError do + SecureRandom.uuid_from_hash(Digest::SHA2, SecureRandom::UUID_OID_NAMESPACE, '1.2.3') + end + end +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 072b970a2d..95df173880 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -58,6 +58,11 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal("blargles", "blargle".pluralize(2)) end + test 'pluralize with count = 1 still returns new string' do + name = "Kuldeep" + assert_not_same name.pluralize(1), name + end + def test_singularize SingularToPlural.each do |singular, plural| assert_equal(singular, plural.singularize) @@ -296,6 +301,12 @@ class StringAccessTest < ActiveSupport::TestCase assert_equal 'x', 'x'.first(4) end + test "#first with Fixnum >= string length still returns a new string" do + string = "hello" + different_string = string.first(5) + assert_not_same different_string, string + end + test "#last returns the last character" do assert_equal "o", "hello".last assert_equal 'x', 'x'.last @@ -308,6 +319,12 @@ class StringAccessTest < ActiveSupport::TestCase assert_equal 'x', 'x'.last(4) end + test "#last with Fixnum >= string length still returns a new string" do + string = "hello" + different_string = string.last(5) + assert_not_same different_string, string + end + test "access returns a real string" do hash = {} hash["h"] = true @@ -608,6 +625,29 @@ class OutputSafetyTest < ActiveSupport::TestCase assert !@other_combination.html_safe? end + test "Prepending safe onto unsafe yields unsafe" do + @string.prepend "other".html_safe + assert !@string.html_safe? + assert_equal @string, "otherhello" + end + + test "Prepending unsafe onto safe yields escaped safe" do + other = "other".html_safe + other.prepend "<foo>" + assert other.html_safe? + assert_equal other, "<foo>other" + end + + test "Deprecated #prepend! method is still present" do + other = "other".html_safe + + assert_deprecated do + other.prepend! "<foo>" + end + + assert_equal other, "<foo>other" + end + test "Concatting safe onto unsafe yields unsafe" do @other_string = "other" diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 35967ba656..b0b4738eb3 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -200,6 +200,7 @@ class InflectorTest < ActiveSupport::TestCase def test_demodulize assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account") assert_equal "Account", ActiveSupport::Inflector.demodulize("Account") + assert_equal "Account", ActiveSupport::Inflector.demodulize("::Account") assert_equal "", ActiveSupport::Inflector.demodulize("") end diff --git a/activesupport/test/multibyte_conformance.rb b/activesupport/test/multibyte_conformance_test.rb index 2baf724da4..2baf724da4 100644 --- a/activesupport/test/multibyte_conformance.rb +++ b/activesupport/test/multibyte_conformance_test.rb diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 753effb54e..f49431cbbf 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -1,6 +1,9 @@ require 'abstract_unit' require 'active_support/xml_mini' require 'active_support/builder' +require 'active_support/core_ext/array' +require 'active_support/core_ext/hash' +require 'active_support/core_ext/big_decimal' module XmlMiniTest class RenameKeyTest < ActiveSupport::TestCase @@ -88,6 +91,61 @@ module XmlMiniTest assert_xml "<b>Howdy</b>" end + test "#to_tag should use the type value in the options hash" do + @xml.to_tag(:b, "blue", @options.merge(type: 'color')) + assert_xml( "<b type=\"color\">blue</b>" ) + end + + test "#to_tag accepts symbol types" do + @xml.to_tag(:b, :name, @options) + assert_xml( "<b type=\"symbol\">name</b>" ) + end + + test "#to_tag accepts boolean types" do + @xml.to_tag(:b, true, @options) + assert_xml( "<b type=\"boolean\">true</b>") + end + + test "#to_tag accepts float types" do + @xml.to_tag(:b, 3.14, @options) + assert_xml( "<b type=\"float\">3.14</b>") + end + + test "#to_tag accepts decimal types" do + @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options) + assert_xml( "<b type=\"decimal\">1.2</b>") + end + + test "#to_tag accepts date types" do + @xml.to_tag(:b, Date.new(2001,2,3), @options) + assert_xml( "<b type=\"date\">2001-02-03</b>") + end + + test "#to_tag accepts datetime types" do + @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,'+7'), @options) + assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>") + end + + test "#to_tag accepts time types" do + @xml.to_tag(:b, Time.new(1993, 02, 24, 12, 0, 0, "+09:00"), @options) + assert_xml( "<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>") + end + + test "#to_tag accepts array types" do + @xml.to_tag(:b, ["first_name", "last_name"], @options) + assert_xml( "<b type=\"array\"><b>first_name</b><b>last_name</b></b>" ) + end + + test "#to_tag accepts hash types" do + @xml.to_tag(:b, { first_name: "Bob", last_name: "Marley" }, @options) + assert_xml( "<b><first-name>Bob</first-name><last-name>Marley</last-name></b>" ) + end + + test "#to_tag should not add type when skip types option is set" do + @xml.to_tag(:b, "Bob", @options.merge(skip_types: 1)) + assert_xml( "<b>Bob</b>" ) + end + test "#to_tag should dasherize the space when passed a string with spaces as a key" do @xml.to_tag("New York", 33, @options) assert_xml "<New---York type=\"integer\">33</New---York>" @@ -97,7 +155,6 @@ module XmlMiniTest @xml.to_tag(:"New York", 33, @options) assert_xml "<New---York type=\"integer\">33</New---York>" end - # TODO: test the remaining functions hidden in #to_tag. end class WithBackendTest < ActiveSupport::TestCase diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index a7c215c295..14ad4fd424 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1 +1,9 @@ +* Updates the maintenance policy to match the latest versions of Rails + + *Matias Korhonen* + +* Switched the order of `Applying a default scope` and `Merging of scopes` subsections so default scopes are introduced first. + + *Alex Riabov* + Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/Rakefile b/guides/Rakefile index d6dd950d01..94d4be8c0a 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -13,8 +13,8 @@ namespace :guides do desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/kindlepublishing" task :kindle do - unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ - abort "Please `gem install kindlerb`" + unless `kindlerb -v 2> /dev/null` =~ /kindlerb 0.1.1/ + abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH" end unless `convert` =~ /convert/ abort "Please install ImageMagick`" diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index d95354e12d..2435444dc9 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -2,7 +2,6 @@ unless File.exist?('Gemfile') File.write('Gemfile', <<-GEMFILE) source 'https://rubygems.org' gem 'rails', github: 'rails/rails' - gem 'arel', github: 'rails/arel' gem 'sqlite3' GEMFILE diff --git a/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile index 05de8bb536..ba6b733dd2 100644 --- a/guides/code/getting_started/Rakefile +++ b/guides/code/getting_started/Rakefile @@ -3,4 +3,4 @@ require File.expand_path('../config/application', __FILE__) -Blog::Application.load_tasks +Rails.application.load_tasks diff --git a/guides/code/getting_started/config.ru b/guides/code/getting_started/config.ru index ddf869e921..5bc2a619e8 100644 --- a/guides/code/getting_started/config.ru +++ b/guides/code/getting_started/config.ru @@ -1,4 +1,4 @@ # This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) -run Blog::Application +run Rails.application diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb index e7e341c960..ee8d90dc65 100644 --- a/guides/code/getting_started/config/environment.rb +++ b/guides/code/getting_started/config/environment.rb @@ -2,4 +2,4 @@ require File.expand_path('../application', __FILE__) # Initialize the Rails application. -Blog::Application.initialize! +Rails.application.initialize! diff --git a/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb index 7e5692b08b..ae9ffe209a 100644 --- a/guides/code/getting_started/config/environments/development.rb +++ b/guides/code/getting_started/config/environments/development.rb @@ -1,4 +1,4 @@ -Blog::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on diff --git a/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb index 8c514e065e..c8ae858574 100644 --- a/guides/code/getting_started/config/environments/production.rb +++ b/guides/code/getting_started/config/environments/production.rb @@ -1,4 +1,4 @@ -Blog::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. diff --git a/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb index 34ab1530d1..680d0b9e06 100644 --- a/guides/code/getting_started/config/environments/test.rb +++ b/guides/code/getting_started/config/environments/test.rb @@ -1,4 +1,4 @@ -Blog::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's diff --git a/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb index aaf57731be..c2a549c299 100644 --- a/guides/code/getting_started/config/initializers/secret_token.rb +++ b/guides/code/getting_started/config/initializers/secret_token.rb @@ -9,4 +9,4 @@ # Make sure your secret_key_base is kept private # if you're sharing your code publicly. -Blog::Application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53' +Rails.application.config.secret_key_base = 'e8aab50cec8a06a75694111a4cbaf6e22fc288ccbc6b268683aae7273043c69b15ca07d10c92a788dd6077a54762cbfcc55f19c3459f7531221b3169f8171a53' diff --git a/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb index 3b2ca93ab9..1b9fa324d4 100644 --- a/guides/code/getting_started/config/initializers/session_store.rb +++ b/guides/code/getting_started/config/initializers/session_store.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Blog::Application.config.session_store :cookie_store, key: '_blog_session' +Rails.application.config.session_store :cookie_store, key: '_blog_session' diff --git a/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb index 0155b613a3..65d273b58d 100644 --- a/guides/code/getting_started/config/routes.rb +++ b/guides/code/getting_started/config/routes.rb @@ -1,4 +1,4 @@ -Blog::Application.routes.draw do +Rails.application.routes.draw do resources :posts do resources :comments end diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index dd81ec58f9..2d4be0cda7 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -294,7 +294,7 @@ NOTE. The old style `map` commands still work as before with a backwards compati Deprecations * The catch all route for non-REST applications (`/:controller/:action/:id`) is now commented out. -* Routes :path\_prefix no longer exists and :name\_prefix now automatically adds "\_" at the end of the given value. +* Routes `:path_prefix` no longer exists and `:name_prefix` now automatically adds "_" at the end of the given value. More Information: * [The Rails 3 Router: Rack it Up](http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/) diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index ce811a583b..cdcde67869 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -187,7 +187,7 @@ Action Pack Rails will use `layouts/single_car` when a request comes in `:show` action, and use `layouts/application` (or `layouts/cars`, if exists) when a request comes in for any other actions. -* `form\_for` is changed to use `#{action}\_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}\_#{action}`. +* `form_for` is changed to use `#{action}_#{as}` as the css class and id if `:as` option is provided. Earlier versions used `#{as}_#{action}`. * `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`. diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 9fd930963a..7e39f761f2 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -10,11 +10,14 @@ </p> <% else %> <p> - These are the new guides for Rails 4.0 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. + These are the new guides for Rails 4.1 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>. These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together. </p> <% end %> <p> + The guides for Rails 4.0.x are available at <a href="http://guides.rubyonrails.org/v4.0.4/">http://guides.rubyonrails.org/v4.0.4/</a>. +</p> +<p> The guides for Rails 3.2.x are available at <a href="http://guides.rubyonrails.org/v3.2.17/">http://guides.rubyonrails.org/v3.2.17/</a>. </p> <p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 0f46ba8698..1735188f27 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -34,7 +34,7 @@ The naming convention of controllers in Rails favors pluralization of the last w Following this convention will allow you to use the default route generators (e.g. `resources`, etc) without needing to qualify each `:path` or `:controller`, and keeps URL and path helpers' usage consistent throughout your application. See [Layouts & Rendering Guide](layouts_and_rendering.html) for more details. -NOTE: The controller naming convention differs from the naming convention of models, which expected to be named in singular form. +NOTE: The controller naming convention differs from the naming convention of models, which are expected to be named in singular form. Methods and Actions @@ -260,7 +260,7 @@ used: params.require(:log_entry).permit! ``` -This will mark the `:log_entry` parameters hash and any subhash of it +This will mark the `:log_entry` parameters hash and any sub-hash of it permitted. Extreme care should be taken when using `permit!` as it will allow all current and future model attributes to be mass-assigned. @@ -364,21 +364,21 @@ If you need a different session storage mechanism, you can change it in the `con # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rails g active_record:session_migration") -# YourApp::Application.config.session_store :active_record_store +# Rails.application.config.session_store :active_record_store ``` Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in `config/initializers/session_store.rb`: ```ruby # Be sure to restart your server when you modify this file. -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session' +Rails.application.config.session_store :cookie_store, key: '_your_app_session' ``` You can also pass a `:domain` key and specify the domain name for the cookie: ```ruby # Be sure to restart your server when you modify this file. -YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" +Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" ``` Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml` diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 667433285f..fbcce325ed 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -92,6 +92,7 @@ Here is a list with all the available Active Record callbacks, listed in the sam * `around_create` * `after_create` * `after_save` +* `after_commit/after_rollback` ### Updating an Object @@ -103,12 +104,14 @@ Here is a list with all the available Active Record callbacks, listed in the sam * `around_update` * `after_update` * `after_save` +* `after_commit/after_rollback` ### Destroying an Object * `before_destroy` * `around_destroy` * `after_destroy` +* `after_commit/after_rollback` WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 0a332d7dd9..2a76df156c 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1231,6 +1231,35 @@ Using a class method is the preferred way to accept arguments for scopes. These category.posts.created_before(time) ``` +### Applying a default scope + +If we wish for a scope to be applied across all queries to the model we can use the +`default_scope` method within the model itself. + +```ruby +class Client < ActiveRecord::Base + default_scope { where("removed_at IS NULL") } +end +``` + +When queries are executed on this model, the SQL query will now look something like +this: + +```sql +SELECT * FROM clients WHERE removed_at IS NULL +``` + +If you need to do more complex things with a default scope, you can alternatively +define it as a class method: + +```ruby +class Client < ActiveRecord::Base + def self.default_scope + # Should return an ActiveRecord::Relation. + end +end +``` + ### Merging of scopes Just like `where` clauses scopes are merged using `AND` conditions. @@ -1284,36 +1313,6 @@ User.where(state: 'inactive') As you can see above the `default_scope` is being merged in both `scope` and `where` conditions. - -### Applying a default scope - -If we wish for a scope to be applied across all queries to the model we can use the -`default_scope` method within the model itself. - -```ruby -class Client < ActiveRecord::Base - default_scope { where("removed_at IS NULL") } -end -``` - -When queries are executed on this model, the SQL query will now look something like -this: - -```sql -SELECT * FROM clients WHERE removed_at IS NULL -``` - -If you need to do more complex things with a default scope, you can alternatively -define it as a class method: - -```ruby -class Client < ActiveRecord::Base - def self.default_scope - # Should return an ActiveRecord::Relation. - end -end -``` - ### Removing All Scoping If we wish to remove scoping for any reason we can use the `unscoped` method. This is diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 5698dc0413..5a4e15cfa9 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -761,7 +761,7 @@ Arguments may be bare constant names: Math.qualified_const_get("E") # => 2.718281828459045 ``` -These methods are analogous to their builtin counterparts. In particular, +These methods are analogous to their built-in counterparts. In particular, `qualified_constant_defined?` accepts an optional second argument to be able to say whether you want the predicate to look in the ancestors. This flag is taken into account for each constant in the expression while @@ -792,7 +792,7 @@ N.qualified_const_defined?("C::X") # => true As the last example implies, the second argument defaults to true, as in `const_defined?`. -For coherence with the builtin methods only relative paths are accepted. +For coherence with the built-in methods only relative paths are accepted. Absolute qualified constant names like `::Math::PI` raise `NameError`. NOTE: Defined in `active_support/core_ext/module/qualified_const.rb`. @@ -964,20 +964,7 @@ NOTE: Defined in `active_support/core_ext/module/delegation.rb` There are cases where you need to define a method with `define_method`, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either. -The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. Rails uses it in a few places, for instance when it generates an association's API: - -```ruby -redefine_method("#{reflection.name}=") do |new_value| - association = association_instance_get(reflection.name) - - if association.nil? || association.target != new_value - association = association_proxy_class.new(self, reflection) - end - - association.replace(new_value) - association_instance_set(reflection.name, new_value.nil? ? nil : association) -end -``` +The method `redefine_method` prevents such a potential warning, removing the existing method before if needed. NOTE: Defined in `active_support/core_ext/module/remove_method.rb` @@ -1644,6 +1631,9 @@ Given a string with a qualified constant name, `demodulize` returns the very con "Product".demodulize # => "Product" "Backoffice::UsersController".demodulize # => "UsersController" "Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils" +"::Inflections".demodulize # => "Inflections" +"".demodulize # => "" + ``` Active Record for example uses this method to compute the name of a counter cache column: @@ -2920,7 +2910,7 @@ NOTE: Defined in `active_support/core_ext/hash/indifferent_access.rb`. ### Compacting -The methods `compact` and `compact!` return a Hash without items with `nil` value. +The methods `compact` and `compact!` return a Hash without items with `nil` value. ```ruby {a: 1, b: 2, c: nil}.compact # => {a: 1, b: 2} @@ -3847,7 +3837,7 @@ rescue NameError => e end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/name_error.rb`. Extensions to `LoadError` ------------------------- @@ -3870,4 +3860,4 @@ rescue NameError => e end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/load_error.rb`. diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 5bb895cb78..52fc9726d9 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -56,11 +56,11 @@ the comment operator on that line to later enable the asset pipeline: To set asset compression methods, set the appropriate configuration options in `production.rb` - `config.assets.css_compressor` for your CSS and -`config.assets.js_compressor` for your Javascript: +`config.assets.js_compressor` for your JavaScript: ```ruby config.assets.css_compressor = :yui -config.assets.js_compressor = :uglify +config.assets.js_compressor = :uglifier ``` NOTE: The `sass-rails` gem is automatically used for CSS compression if included @@ -245,7 +245,7 @@ When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it. The default locations are: the `images`, `javascripts` and `stylesheets` -directories under the `apps/assets` folder, but these subdirectories +directories under the `app/assets` folder, but these subdirectories are not special - any path under `assets/*` will be searched. For example, these files: @@ -581,23 +581,8 @@ runtime. To disable this behavior you can set: config.assets.raise_runtime_errors = false ``` -When `raise_runtime_errors` is set to `false` sprockets will not check that dependencies of assets are declared properly. Here is a scenario where you must tell the asset pipeline about a dependency: - -If you have `application.css.erb` that references `logo.png` like this: - -```css -#logo { background: url(<%= asset_data_uri 'logo.png' %>) } -``` - -Then you must declare that `logo.png` is a dependency of `application.css.erb`, so when the image gets re-compiled, the css file does as well. You can do this using the `//= depend_on_asset` declaration: - -```css -//= depend_on_asset "logo.png" -#logo { background: url(<%= asset_data_uri 'logo.png' %>) } -``` - -Without this declaration you may experience strange behavior when pushing to production that is difficult to debug. When you have `raise_runtime_errors` set to `true`, dependencies will be checked at runtime so you can ensure that all dependencies are met. - +When this option is true asset pipeline will check if all the assets loaded in your application +are included in the `config.assets.precompile` list. ### Turning Debugging Off @@ -724,17 +709,17 @@ JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and `.scss` files are **not** automatically included as they compile to JS/CSS. If you have other manifests or individual stylesheets and JavaScript files to -include, you can add them to the `precompile` array in `config/application.rb`: +include, you can add them to the `precompile` array in `config/initializers/assets.rb`: ```ruby -config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] +Rails.application.config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] ``` Or, you can opt to precompile all assets with something like this: ```ruby -# config/application.rb -config.assets.precompile << Proc.new do |path| +# config/initializers/assets.rb +Rails.application.config.assets.precompile << Proc.new do |path| if path =~ /\.(css|js)\z/ full_path = Rails.application.assets.resolve(path).to_path app_assets_path = Rails.root.join('app', 'assets').to_path @@ -781,7 +766,7 @@ exception indicating the name of the missing file(s). #### Far-future Expires Header -Precompiled assets exist on the filesystem and are served directly by your web +Precompiled assets exist on the file system and are served directly by your web server. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add those headers. @@ -943,7 +928,7 @@ gem. ```ruby config.assets.css_compressor = :yui ``` -The other option for compressing CSS if you have the sass-rails gem installed is +The other option for compressing CSS if you have the sass-rails gem installed is ```ruby config.assets.css_compressor = :sass @@ -1018,7 +1003,7 @@ The X-Sendfile header is a directive to the web server to ignore the response from the application, and instead serve a specified file from disk. This option is off by default, but can be enabled if your server supports it. When enabled, this passes responsibility for serving the file to the web server, which is -faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file) +faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file) on how to use this feature. Apache and nginx support this option, which can be enabled in @@ -1056,6 +1041,14 @@ cache store. config.assets.cache_store = :memory_store, { size: 32.megabytes } ``` +To disable the assets cache store: + +```ruby +config.assets.configure do |env| + env.cache = ActiveSupport::Cache.lookup_store(:null_store) +end +``` + Adding Assets to Your Gems -------------------------- diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 5ec6ae0f21..df38bd7321 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -571,7 +571,7 @@ If you create an association some time after you build the underlying model, you If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering. -WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper\_boxes" and "papers" to generate a join table name of "papers\_paper\_boxes" because of the length of the name "paper\_boxes", but it in fact generates a join table name of "paper\_boxes\_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings). +WARNING: The precedence between model names is calculated using the `<` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '_' is lexicographically _less_ than 's' in common encodings). Whatever the name, you must manually generate the join table with an appropriate migration. For example, consider these associations: diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index e898d75d1a..b6423dd44e 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -30,13 +30,13 @@ config.action_controller.perform_caching = true Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. -INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Action Caching Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy. -INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Fragment Caching diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 57283f7c40..756c8f8b51 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -254,7 +254,7 @@ $ rails generate scaffold HighScore game:string score:integer The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything. -The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The sqlite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. +The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while. ```bash $ rake db:migrate diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 460fd3c301..ae382fc54d 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -56,7 +56,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such end ``` -* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints builtin in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`. +* `config.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets, or when you want to work around the concurrency constraints built-in in browsers using different domain aliases. Shorter version of `config.action_controller.asset_host`. * `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is false, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array. @@ -580,13 +580,13 @@ The only way to explicitly not use the connection information in `ENV['DATABASE_ ``` $ cat config/database.yml development: - url: sqlite3://localhost/NOT_my_database + url: sqlite3:NOT_my_database $ echo $DATABASE_URL postgresql://localhost/my_database $ rails runner 'puts ActiveRecord::Base.connections' -{"development"=>{"adapter"=>"sqlite3", "host"=>"localhost", "database"=>"NOT_my_database"}} +{"development"=>{"adapter"=>"sqlite3", "database"=>"NOT_my_database"}} ``` Here the connection information in `ENV['DATABASE_URL']` is ignored, note the different adapter and database name. @@ -644,11 +644,9 @@ development: encoding: unicode database: blog_development pool: 5 - username: blog - password: ``` -Prepared Statements can be disabled thus: +Prepared Statements are enabled by default on PostgreSQL. You can be disable prepared statements by setting `prepared_statements` to `false`: ```yaml production: @@ -656,6 +654,16 @@ production: prepared_statements: false ``` +If enabled, Active Record will create up to `1000` prepared statements per database connection by default. To modify this behavior you can set `statement_limit` to a different value: + +``` +production: + adapter: postgresql + statement_limit: 200 +``` + +The more prepared statements in use: the more memory your database will require. If your PostgreSQL database is hitting memory limits, try lowering `statement_limit` or disabling prepared statements. + #### Configuring an SQLite3 Database for JRuby Platform If you choose to use SQLite3 and are using JRuby, your `config/database.yml` will look a little different. Here's the development section: diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 36e3862c6b..90c83a5e05 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -50,7 +50,7 @@ Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to -Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you +Ruby on Rails. If you enter a wish list item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed. Sometimes, the line between 'bug' and 'feature' is a hard one to draw. @@ -69,89 +69,6 @@ won't be accepted." But it's the proper place to discuss new ideas. GitHub Issues are not a particularly good venue for the sometimes long and involved discussions new features require. -Setting Up a Development Environment ------------------------------------- - -To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to set up the tests on your own computer. - -### The Easy Way - -The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box). - -### The Hard Way - -In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html). - - -Running an Application Against Your Local Branch ------------------------------------------------- - -The `--dev` flag of `rails new` generates an application that uses your local -branch: - -```bash -$ cd rails -$ bundle exec rails new ~/my-test-app --dev -``` - -The application generated in `~/my-test-app` runs against your local branch -and in particular sees any modifications upon server reboot. - - -Testing Active Record ---------------------- - -This is how you run the Active Record test suite only for SQLite3: - -```bash -$ cd activerecord -$ bundle exec rake test_sqlite3 -``` - -You can now run the tests as you did for `sqlite3`. The tasks are respectively - -```bash -test_mysql -test_mysql2 -test_postgresql -``` - -Finally, - -```bash -$ bundle exec rake test -``` - -will now run the four of them in turn. - -You can also run any single test separately: - -```bash -$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb -``` - -You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. - -### Warnings - -The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. - -If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: - -```bash -$ RUBYOPT=-W0 bundle exec rake test -``` - -### Older Versions of Ruby on Rails - -If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch: - -```bash -$ git branch --track 3-0-stable origin/3-0-stable -$ git checkout 3-0-stable -``` - -TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with. Helping to Resolve Existing Issues ---------------------------------- @@ -227,9 +144,21 @@ WARNING: Docrails has a very strict policy: no code can be touched whatsoever, n Contributing to the Rails Code ------------------------------ -### Clone the Rails Repository +### Setting Up a Development Environment ### + +To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to setup the tests on your own computer. + +#### The Easy Way + +The easiest and recommended way to get a development environment ready to hack is to use the [Rails development box](https://github.com/rails/rails-dev-box). + +#### The Hard Way + +In case you can't use the Rails development box, see section above, check [this other guide](development_dependencies_install.html). -The first thing you need to do to be able to contribute code is to clone the repository: +### Clone the Rails Repository ### + +To be able to contribute code, you need to clone the Rails repository: ```bash $ git clone git://github.com/rails/rails.git @@ -244,29 +173,31 @@ $ git checkout -b my_new_branch It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. -### Write Your Code +### Running an Application Against Your Local Branch ### + +In case you need a dummy Rails app to test changes, the `--dev` flag of `rails new` generates an application that uses your local branch: + +```bash +$ cd rails +$ bundle exec rails new ~/my-test-app --dev +``` + +The application generated in `~/my-test-app` runs against your local branch +and in particular sees any modifications upon server reboot. -Now get busy and add or edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: +### Write Your Code ### + +Now get busy and add/edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: * Get the code right. * Use Rails idioms and helpers. * Include tests that fail without your code, and pass with it. * Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution. -It is not customary in Rails to run the full test suite before pushing -changes. The railties test suite in particular takes a long time, and even -more if the source code is mounted in `/vagrant` as happens in the recommended -workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). - -As a compromise, test what your code obviously affects, and if the change is -not in railties, run the whole test suite of the affected component. If all -tests are passing, that's enough to propose your contribution. We have -[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching -unexpected breakages elsewhere. TIP: Changes that are cosmetic in nature and do not add anything substantial to the stability, functionality, or testability of Rails will generally not be accepted. -### Follow the Coding Conventions +#### Follow the Coding Conventions Rails follows a simple set of coding style conventions: @@ -284,7 +215,84 @@ Rails follows a simple set of coding style conventions: The above are guidelines - please use your best judgment in using them. -### Updating the CHANGELOG +### Running Tests ### +It is not customary in Rails to run the full test suite before pushing +changes. The railties test suite in particular takes a long time, and even +more if the source code is mounted in `/vagrant` as happens in the recommended +workflow with the [rails-dev-box](https://github.com/rails/rails-dev-box). + +As a compromise, test what your code obviously affects, and if the change is +not in railties, run the whole test suite of the affected component. If all +tests are passing, that's enough to propose your contribution. We have +[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching +unexpected breakages elsewhere. + +#### Entire Rails: +To run all the tests, do: +```bash +$ cd rails +$ bundle exec rake test +``` +#### Particular component of Rails +To run tests only for particular component(ActionPack, ActiveRecord, etc.). For +example, to run `ActionMailer` tests you can: + +```bash +$ cd actionmailer +$ bundle exec rake test +``` + +##### Testing Active Record + +This is how you run the Active Record test suite only for SQLite3: + +```bash +$ cd activerecord +$ bundle exec rake test_sqlite3 +``` + +You can now run the tests as you did for `sqlite3`. The tasks are respectively + +```bash +test_mysql +test_mysql2 +test_postgresql +``` + +Finally, + +```bash +$ bundle exec rake test +``` + +will now run the four of them in turn. + +You can also run any single test separately: + +```bash +$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb +``` + +You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. + +#### Single Test separately +to run just one test. For example, to run `LayoutMailerTest` you can: + +```bash +$ cd actionmailer +$ ruby -w -Ilib:test test/mail_layout_test.rb +``` + +### Warnings ### + +The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings. + +If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag: + +```bash +$ RUBYOPT=-W0 bundle exec rake test +``` +### Updating the CHANGELOG ### The CHANGELOG is an important part of every release. It keeps the list of changes for every Rails version. @@ -309,17 +317,17 @@ A CHANGELOG entry should summarize what was changed and should end with author's Your name can be added directly after the last word if you don't provide any code examples or don't need multiple paragraphs. Otherwise, it's best to make as a new paragraph. -### Sanity Check +### Sanity Check ### You should not be the only person who looks at the code before you submit it. If you know someone else who uses Rails, try asking them if they'll check out your work. If you don't know anyone else using Rails, try hopping into the IRC -room or posting about your idea to the rails-core mailing list. Doing this in -private before you push a patch out publicly is the “smoke test” for a patch: -if you can’t convince one other developer of the beauty of your code, you’re +room or posting about your idea to the rails-core mailing list. Doing this in +private before you push a patch out publicly is the "smoke test" for a patch: +if you can't convince one other developer of the beauty of your code, you’re unlikely to convince the core team either. -### Commit Your Changes +### Commit Your Changes ### When you're happy with the code on your computer, you need to commit the changes to Git: @@ -359,7 +367,7 @@ You can also add bullet points: TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean. -### Update Your Branch +### Update Your Branch ### It's pretty likely that other changes to master have happened while you were working. Go get them: @@ -377,7 +385,7 @@ $ git rebase master No conflicts? Tests still pass? Change still seems reasonable to you? Then move on. -### Fork +### Fork ### Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and press "Fork" in the upper right hand corner. @@ -507,7 +515,19 @@ $ git push origin my_pull_request -f You should be able to refresh the pull request on GitHub and see that it has been updated. -### Backporting + +### Older Versions of Ruby on Rails ### + +If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch: + +```bash +$ git branch --track 3-0-stable origin/3-0-stable +$ git checkout 3-0-stable +``` + +TIP: You may want to [put your Git branch name in your shell prompt](http://qugstart.com/blog/git-and-svn/add-colored-git-branch-name-to-your-shell-prompt/) to make it easier to remember which version of the code you're working with. + +#### Backporting Changes that are merged into master are intended for the next major release of Rails. Sometimes, it might be beneficial for your changes to propagate back to the maintenance releases for older stable branches. Generally, security fixes and bug fixes are good candidates for a backport, while new features and patches that introduce a change in behavior will not be accepted. When in doubt, it is best to consult a Rails team member before backporting your changes to avoid wasted effort. diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index 7c6858fa2c..8767fbecce 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -64,7 +64,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <% end %> <%= author('Pratik Naik', 'lifo') do %> - Pratik Naik is a Ruby on Rails developer at <a href="http://www.37signals.com">37signals</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. + Pratik Naik is a Ruby on Rails developer at <a href="https://basecamp.com/">Basecamp</a> and also a member of the <a href="http://rubyonrails.org/core">Rails core team</a>. He maintains a blog at <a href="http://m.onkey.org">has_many :bugs, :through => :rails</a> and has a semi-active <a href="http://twitter.com/lifo">twitter account</a>. <% end %> <%= author('Emilio Tagua', 'miloops') do %> diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 0e10d1b697..b067d9efb7 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -210,7 +210,7 @@ logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs " ``` ### Impact of Logs on Performance -Logging will always have a small impact on performance of your rails app, +Logging will always have a small impact on performance of your rails app, particularly when logging to disk.However, there are a few subtleties: Using the `:debug` level will have a greater performance penalty than `:fatal`, @@ -224,446 +224,576 @@ Another potential pitfall is that if you have many calls to `Logger` like this logger.debug "Person attributes hash: #{@person.attributes.inspect}" ``` -In the above example, There will be a performance impact even if the allowed -output level doesn't include debug. The reason is that Ruby has to evaluate -these strings, which includes instantiating the somewhat heavy `String` object +In the above example, There will be a performance impact even if the allowed +output level doesn't include debug. The reason is that Ruby has to evaluate +these strings, which includes instantiating the somewhat heavy `String` object and interpolating the variables, and which takes time. -Therefore, it's recommended to pass blocks to the logger methods, as these are -only evaluated if the output level is the same or included in the allowed level +Therefore, it's recommended to pass blocks to the logger methods, as these are +only evaluated if the output level is the same or included in the allowed level (i.e. lazy loading). The same code rewritten would be: ```ruby logger.debug {"Person attributes hash: #{@person.attributes.inspect}"} ``` -The contents of the block, and therefore the string interpolation, is only -evaluated if debug is enabled. This performance savings is only really +The contents of the block, and therefore the string interpolation, is only +evaluated if debug is enabled. This performance savings is only really noticeable with large amounts of logging, but it's a good practice to employ. -Debugging with the `debugger` gem +Debugging with the `byebug` gem --------------------------------- -When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion. +When your code is behaving in unexpected ways, you can try printing to logs or +the console to diagnose the problem. Unfortunately, there are times when this +sort of error tracking is not effective in finding the root cause of a problem. +When you actually need to journey into your running source code, the debugger +is your best companion. -The debugger can also help you if you want to learn about the Rails source code but don't know where to start. Just debug any request to your application and use this guide to learn how to move from the code you have written deeper into Rails code. +The debugger can also help you if you want to learn about the Rails source code +but don't know where to start. Just debug any request to your application and +use this guide to learn how to move from the code you have written deeper into +Rails code. ### Setup -You can use the `debugger` gem to set breakpoints and step through live code in Rails. To install it, just run: +You can use the `byebug` gem to set breakpoints and step through live code in +Rails. To install it, just run: ```bash -$ gem install debugger +$ gem install byebug ``` -Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the `debugger` method. +Inside any Rails application you can then invoke the debugger by calling the +`byebug` method. Here's an example: ```ruby class PeopleController < ApplicationController def new - debugger + byebug @person = Person.new end end ``` -If you see this message in the console or logs: +### The Shell + +As soon as your application calls the `byebug` method, the debugger will be +started in a debugger shell inside the terminal window where you launched your +application server, and you will be placed at the debugger's prompt `(byebug)`. +Before the prompt, the code around the line that is about to be run will be +displayed and the current line will be marked by '=>'. Like this: ``` -***** Debugger requested, but was not available: Start server with --debugger to enable ***** +[1, 10] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } + +(byebug) ``` -Make sure you have started your web server with the option `--debugger`: +If you got there by a browser request, the browser tab containing the request +will be hung until the debugger has finished and the trace has finished +processing the entire request. + +For example: ```bash -$ rails server --debugger => Booting WEBrick -=> Rails 4.0.0 application starting on http://0.0.0.0:3000 -=> Debugger enabled -... -``` +=> Rails 4.1.0 application starting in development on http://0.0.0.0:3000 +=> Run `rails server -h` for more startup options +=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option) +=> Ctrl-C to shutdown server +[2014-04-11 13:11:47] INFO WEBrick 1.3.1 +[2014-04-11 13:11:47] INFO ruby 2.1.1 (2014-02-24) [i686-linux] +[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000 -TIP: In development mode, you can dynamically `require \'debugger\'` instead of restarting the server, even if it was started without `--debugger`. -### The Shell +Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200 + ActiveRecord::SchemaMigration Load (0.2ms) SELECT "schema_migrations".* FROM "schema_migrations" +Processing by PostsController#index as HTML -As soon as your application calls the `debugger` method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt `(rdb:n)`. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run. +[3, 12] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } -If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request. +(byebug) +``` -For example: +Now it's time to explore and dig into your application. A good place to start is +by asking the debugger for help. Type: `help` -```bash -@posts = Post.all -(rdb:7) ``` +(byebug) help -Now it's time to explore and dig into your application. A good place to start is by asking the debugger for help. Type: `help` +byebug 2.7.0 -``` -(rdb:7) help -ruby-debug help v0.10.2 Type 'help <command-name>' for help on a specific command Available commands: -backtrace delete enable help next quit show trace -break disable eval info p reload source undisplay -catch display exit irb pp restart step up -condition down finish list ps save thread var -continue edit frame method putl set tmate where +backtrace delete enable help list pry next restart source up +break disable eval info method ps save step var +catch display exit interrupt next putl set thread +condition down finish irb p quit show trace +continue edit frame kill pp reload skip undisplay ``` -TIP: To view the help menu for any command use `help <command-name>` at the debugger prompt. For example: _`help var`_ - -The next command to learn is one of the most useful: `list`. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use `l` for the `list` command. +TIP: To view the help menu for any command use `help <command-name>` at the +debugger prompt. For example: _`help list`_. You can abbreviate any debugging +command by supplying just enough letters to distinguish them from other +commands, so you can also use `l` for the `list` command, for example. -This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by `=>`. +To see the previous ten lines you should type `list-` (or `l-`) ``` -(rdb:7) list +(byebug) l- + [1, 10] in /PathTo/project/app/controllers/posts_controller.rb 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } -``` + 2 before_action :set_post, only: [:show, :edit, :update, :destroy] + 3 + 4 # GET /posts + 5 # GET /posts.json + 6 def index + 7 byebug + 8 @posts = Post.find_recent + 9 + 10 respond_to do |format| -If you repeat the `list` command, this time using just `l`, the next ten lines of the file will be printed out. - -``` -(rdb:7) l -[11, 20] in /PathTo/project/app/controllers/posts_controller.rb - 11 end - 12 end - 13 - 14 # GET /posts/1 - 15 # GET /posts/1.json - 16 def show - 17 @post = Post.find(params[:id]) - 18 - 19 respond_to do |format| - 20 format.html # show.html.erb ``` -And so on until the end of the current file. When the end of file is reached, the `list` command will start again from the beginning of the file and continue again up to the end, treating the file as a circular buffer. +This way you can move inside the file, being able to see the code above and over +the line where you added the `byebug` call. Finally, to see where you are in +the code again you can type `list=` -On the other hand, to see the previous ten lines you should type `list-` (or `l-`) - -``` -(rdb:7) l- -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger - 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } ``` +(byebug) list= -This way you can move inside the file, being able to see the code above and over the line you added the `debugger`. -Finally, to see where you are in the code again you can type `list=` +[3, 12] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } -``` -(rdb:7) list= -[1, 10] in /PathTo/project/app/controllers/posts_controller.rb - 1 class PostsController < ApplicationController - 2 # GET /posts - 3 # GET /posts.json - 4 def index - 5 debugger -=> 6 @posts = Post.all - 7 - 8 respond_to do |format| - 9 format.html # index.html.erb - 10 format.json { render json: @posts } +(byebug) ``` ### The Context -When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack. - -The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped. - -At any time you can call the `backtrace` command (or its alias `where`) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then `backtrace` will supply the answer. - -``` -(rdb:5) where - #0 PostsController.index - at line /PathTo/project/app/controllers/posts_controller.rb:6 - #1 Kernel.send - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 - #3 ActionController::Filters::InstanceMethods.call_filters(chain#ActionController::Fil...,...) - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb:617 +When you start debugging your application, you will be placed in different +contexts as you go through the different parts of the stack. + +The debugger creates a context when a stopping point or an event is reached. The +context has information about the suspended program which enables the debugger +to inspect the frame stack, evaluate variables from the perspective of the +debugged program, and contains information about the place where the debugged +program is stopped. + +At any time you can call the `backtrace` command (or its alias `where`) to print +the backtrace of the application. This can be very helpful to know how you got +where you are. If you ever wondered about how you got somewhere in your code, +then `backtrace` will supply the answer. + +``` +(byebug) where +--> #0 PostsController.index + at /PathTo/project/test_app/app/controllers/posts_controller.rb:8 + #1 ActionController::ImplicitRender.send_action(method#String, *args#Array) + at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/implicit_render.rb:4 + #2 AbstractController::Base.process_action(action#NilClass, *args#Array) + at /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb:189 + #3 ActionController::Rendering.process_action(action#NilClass, *args#NilClass) + at /PathToGems/actionpack-4.1.0/lib/action_controller/metal/rendering.rb:10 ... ``` -You move anywhere you want in this trace (thus changing the context) by using the `frame _n_` command, where _n_ is the specified frame number. +The current frame is marked with `-->`. You can move anywhere you want in this +trace (thus changing the context) by using the `frame _n_` command, where _n_ is +the specified frame number. If you do that, `byebug` will display your new +context. ``` -(rdb:5) frame 2 -#2 ActionController::Base.perform_action_without_filters - at line /PathTo/project/vendor/rails/actionpack/lib/action_controller/base.rb:1175 +(byebug) frame 2 + +[184, 193] in /PathToGems/actionpack-4.1.0/lib/abstract_controller/base.rb + 184: # is the intended way to override action dispatching. + 185: # + 186: # Notice that the first argument is the method to be dispatched + 187: # which is *not* necessarily the same as the action name. + 188: def process_action(method_name, *args) +=> 189: send_action(method_name, *args) + 190: end + 191: + 192: # Actually call the method associated with the action. Override + 193: # this method if you wish to change how action methods are called, + +(byebug) ``` -The available variables are the same as if you were running the code line by line. After all, that's what debugging is. +The available variables are the same as if you were running the code line by +line. After all, that's what debugging is. -Moving up and down the stack frame: You can use `up [n]` (`u` for abbreviated) and `down [n]` commands in order to change the context _n_ frames up or down the stack respectively. _n_ defaults to one. Up in this case is towards higher-numbered stack frames, and down is towards lower-numbered stack frames. +You can also use `up [n]` (`u` for abbreviated) and `down [n]` commands in order +to change the context _n_ frames up or down the stack respectively. _n_ defaults +to one. Up in this case is towards higher-numbered stack frames, and down is +towards lower-numbered stack frames. ### Threads -The debugger can list, stop, resume and switch between running threads by using the command `thread` (or the abbreviated `th`). This command has a handful of options: +The debugger can list, stop, resume and switch between running threads by using +the `thread` command (or the abbreviated `th`). This command has a handful of +options: * `thread` shows the current thread. -* `thread list` is used to list all threads and their statuses. The plus + character and the number indicates the current thread of execution. +* `thread list` is used to list all threads and their statuses. The plus + +character and the number indicates the current thread of execution. * `thread stop _n_` stop thread _n_. * `thread resume _n_` resumes thread _n_. * `thread switch _n_` switches the current thread context to _n_. -This command is very helpful, among other occasions, when you are debugging concurrent threads and need to verify that there are no race conditions in your code. +This command is very helpful, among other occasions, when you are debugging +concurrent threads and need to verify that there are no race conditions in your +code. ### Inspecting Variables -Any expression can be evaluated in the current context. To evaluate an expression, just type it! - -This example shows how you can print the instance_variables defined within the current context: - -``` -@posts = Post.all -(rdb:11) instance_variables -["@_response", "@action_name", "@url", "@_session", "@_cookies", "@performed_render", "@_flash", "@template", "@_params", "@before_filter_chain_aborted", "@request_origin", "@_headers", "@performed_redirect", "@_request"] -``` - -As you may have figured out, all of the variables that you can access from a controller are displayed. This list is dynamically updated as you execute code. For example, run the next line using `next` (you'll learn more about this command later in this guide). - -``` -(rdb:11) next -Processing PostsController#index (for 127.0.0.1 at 2008-09-04 19:51:34) [GET] - Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--b16e91b992453a8cc201694d660147bba8b0fd0e - Parameters: {"action"=>"index", "controller"=>"posts"} -/PathToProject/posts_controller.rb:8 -respond_to do |format| +Any expression can be evaluated in the current context. To evaluate an +expression, just type it! + +This example shows how you can print the instance variables defined within the +current context: + +``` +[3, 12] in /PathTo/project/app/controllers/posts_controller.rb + 3: + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: byebug +=> 8: @posts = Post.find_recent + 9: + 10: respond_to do |format| + 11: format.html # index.html.erb + 12: format.json { render json: @posts } + +(byebug) instance_variables +[:@_action_has_layout, :@_routes, :@_headers, :@_status, :@_request, + :@_response, :@_env, :@_prefixes, :@_lookup_context, :@_action_name, + :@_response_body, :@marked_for_same_origin_verification, :@_config] +``` + +As you may have figured out, all of the variables that you can access from a +controller are displayed. This list is dynamically updated as you execute code. +For example, run the next line using `next` (you'll learn more about this +command later in this guide). + +``` +(byebug) next +[5, 14] in /PathTo/project/app/controllers/posts_controller.rb + 5 # GET /posts.json + 6 def index + 7 byebug + 8 @posts = Post.find_recent + 9 +=> 10 respond_to do |format| + 11 format.html # index.html.erb + 12 format.json { render json: @posts } + 13 end + 14 end + 15 +(byebug) ``` And then ask again for the instance_variables: ``` -(rdb:11) instance_variables.include? "@posts" +(byebug) instance_variables.include? "@posts" true ``` -Now `@posts` is included in the instance variables, because the line defining it was executed. +Now `@posts` is included in the instance variables, because the line defining it +was executed. -TIP: You can also step into **irb** mode with the command `irb` (of course!). This way an irb session will be started within the context you invoked it. But be warned: this is an experimental feature. +TIP: You can also step into **irb** mode with the command `irb` (of course!). +This way an irb session will be started within the context you invoked it. But +be warned: this is an experimental feature. -The `var` method is the most convenient way to show variables and their values: +The `var` method is the most convenient way to show variables and their values. +Let's let `byebug` to help us with it. ``` -var -(rdb:1) v[ar] const <object> show constants of object -(rdb:1) v[ar] g[lobal] show global variables -(rdb:1) v[ar] i[nstance] <object> show instance variables of object -(rdb:1) v[ar] l[ocal] show local variables +(byebug) help var +v[ar] cl[ass] show class variables of self +v[ar] const <object> show constants of object +v[ar] g[lobal] show global variables +v[ar] i[nstance] <object> show instance variables of object +v[ar] l[ocal] show local variables ``` -This is a great way to inspect the values of the current context variables. For example: +This is a great way to inspect the values of the current context variables. For +example, to check that we have no local variables currently defined. ``` -(rdb:9) var local - __dbg_verbose_save => false +(byebug) var local +(byebug) ``` You can also inspect for an object method this way: ``` -(rdb:9) var instance Post.new -@attributes = {"updated_at"=>nil, "body"=>nil, "title"=>nil, "published"=>nil, "created_at"... +(byebug) var instance Post.new +@_start_transaction_state = {} +@aggregation_cache = {} +@association_cache = {} +@attributes = {"id"=>nil, "created_at"=>nil, "updated_at"=>nil} @attributes_cache = {} -@new_record = true +@changed_attributes = nil +... ``` -TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate Ruby expressions and display the value of variables to the console. +TIP: The commands `p` (print) and `pp` (pretty print) can be used to evaluate +Ruby expressions and display the value of variables to the console. -You can use also `display` to start watching variables. This is a good way of tracking the values of a variable while the execution goes on. +You can use also `display` to start watching variables. This is a good way of +tracking the values of a variable while the execution goes on. ``` -(rdb:1) display @recent_comments -1: @recent_comments = +(byebug) display @posts +1: @posts = nil ``` -The variables inside the displaying list will be printed with their values after you move in the stack. To stop displaying a variable use `undisplay _n_` where _n_ is the variable number (1 in the last example). +The variables inside the displaying list will be printed with their values after +you move in the stack. To stop displaying a variable use `undisplay _n_` where +_n_ is the variable number (1 in the last example). ### Step by Step -Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution. +Now you should know where you are in the running trace and be able to print the +available variables. But lets continue and move on with the application +execution. -Use `step` (abbreviated `s`) to continue running your program until the next logical stopping point and return control to the debugger. +Use `step` (abbreviated `s`) to continue running your program until the next +logical stopping point and return control to the debugger. -TIP: You can also use `step+ n` and `step- n` to move forward or backward `n` steps respectively. +You may also use `next` which is similar to step, but function or method calls +that appear within the line of code are executed without stopping. -You may also use `next` which is similar to step, but function or method calls that appear within the line of code are executed without stopping. As with step, you may use plus sign to move _n_ steps. +TIP: You can also use `step n` or `next n` to move forwards `n` steps at once. -The difference between `next` and `step` is that `step` stops at the next line of code executed, doing just a single step, while `next` moves to the next line without descending inside methods. +The difference between `next` and `step` is that `step` stops at the next line +of code executed, doing just a single step, while `next` moves to the next line +without descending inside methods. -For example, consider this block of code with an included `debugger` statement: +For example, consider the following situation: ```ruby -class Author < ActiveRecord::Base - has_one :editorial - has_many :comments +Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200 +Processing by PostsController#index as HTML - def find_recent_comments(limit = 10) - debugger - @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - end -end +[1, 8] in /home/davidr/Proyectos/test_app/app/models/post.rb + 1: class Post < ActiveRecord::Base + 2: + 3: def self.find_recent(limit = 10) + 4: byebug +=> 5: where('created_at > ?', 1.week.ago).limit(limit) + 6: end + 7: + 8: end + +(byebug) ``` -TIP: You can use the debugger while using `rails console`. Just remember to `require "debugger"` before calling the `debugger` method. +If we use `next`, we want go deep inside method calls. Instead, byebug will go +to the next line within the same context. In this case, this is the last line of +the method, so `byebug` will jump to next next line of the previous frame. ``` -$ rails console -Loading development environment (Rails 4.0.0) ->> require "debugger" -=> [] ->> author = Author.first -=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10"> ->> author.find_recent_comments -/PathTo/project/app/models/author.rb:11 -) -``` +(byebug) next +Next went up a frame because previous frame finished -With the code stopped, take a look around: +[4, 13] in /PathTo/project/test_app/app/controllers/posts_controller.rb + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: @posts = Post.find_recent + 8: +=> 9: respond_to do |format| + 10: format.html # index.html.erb + 11: format.json { render json: @posts } + 12: end + 13: end -``` -(rdb:1) list -[2, 9] in /PathTo/project/app/models/author.rb - 2 has_one :editorial - 3 has_many :comments - 4 - 5 def find_recent_comments(limit = 10) - 6 debugger -=> 7 @recent_comments ||= comments.where("created_at > ?", 1.week.ago).limit(limit) - 8 end - 9 end +(byebug) ``` -You are at the end of the line, but... was this line executed? You can inspect the instance variables. +If we use `step` in the same situation, we will literally go the next ruby +instruction to be executed. In this case, the activesupport's `week` method. ``` -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -``` +(byebug) step -`@recent_comments` hasn't been defined yet, so it's clear that this line hasn't been executed yet. Use the `next` command to move on in the code: +[50, 59] in /PathToGems/activesupport-4.1.0/lib/active_support/core_ext/numeric/time.rb + 50: ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) + 51: end + 52: alias :day :days + 53: + 54: def weeks +=> 55: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) + 56: end + 57: alias :week :weeks + 58: + 59: def fortnights +(byebug) ``` -(rdb:1) next -/PathTo/project/app/models/author.rb:12 -@recent_comments -(rdb:1) var instance -@attributes = {"updated_at"=>"2008-07-31 12:46:10", "id"=>"1", "first_name"=>"Bob", "las... -@attributes_cache = {} -@comments = [] -@recent_comments = [] -``` - -Now you can see that the `@comments` relationship was loaded and @recent_comments defined because the line was executed. -If you want to go deeper into the stack trace you can move single `steps`, through your calling methods and into Rails code. This is one of the best ways to find bugs in your code, or perhaps in Ruby or Rails. +This is one of the best ways to find bugs in your code, or perhaps in Ruby on +Rails. ### Breakpoints -A breakpoint makes your application stop whenever a certain point in the program is reached. The debugger shell is invoked in that line. +A breakpoint makes your application stop whenever a certain point in the program +is reached. The debugger shell is invoked in that line. -You can add breakpoints dynamically with the command `break` (or just `b`). There are 3 possible ways of adding breakpoints manually: +You can add breakpoints dynamically with the command `break` (or just `b`). +There are 3 possible ways of adding breakpoints manually: * `break line`: set breakpoint in the _line_ in the current source file. -* `break file:line [if expression]`: set breakpoint in the _line_ number inside the _file_. If an _expression_ is given it must evaluated to _true_ to fire up the debugger. -* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and \# for class and instance method respectively) defined in _class_. The _expression_ works the same way as with file:line. +* `break file:line [if expression]`: set breakpoint in the _line_ number inside +the _file_. If an _expression_ is given it must evaluated to _true_ to fire up +the debugger. +* `break class(.|\#)method [if expression]`: set breakpoint in _method_ (. and +\# for class and instance method respectively) defined in _class_. The +_expression_ works the same way as with file:line. + + +For example, in the previous situation ``` -(rdb:5) break 10 -Breakpoint 1 file /PathTo/project/vendor/rails/actionpack/lib/action_controller/filters.rb, line 10 +[4, 13] in /PathTo/project/app/controllers/posts_controller.rb + 4: # GET /posts + 5: # GET /posts.json + 6: def index + 7: @posts = Post.find_recent + 8: +=> 9: respond_to do |format| + 10: format.html # index.html.erb + 11: format.json { render json: @posts } + 12: end + 13: end + +(byebug) break 11 +Created breakpoint 1 at /PathTo/project/app/controllers/posts_controller.rb:11 + ``` -Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. +Use `info breakpoints _n_` or `info break _n_` to list breakpoints. If you +supply a number, it lists that breakpoint. Otherwise it lists all breakpoints. ``` -(rdb:5) info breakpoints +(byebug) info breakpoints Num Enb What - 1 y at filters.rb:10 +1 y at /PathTo/project/app/controllers/posts_controller.rb:11 ``` -To delete breakpoints: use the command `delete _n_` to remove the breakpoint number _n_. If no number is specified, it deletes all breakpoints that are currently active.. +To delete breakpoints: use the command `delete _n_` to remove the breakpoint +number _n_. If no number is specified, it deletes all breakpoints that are +currently active. ``` -(rdb:5) delete 1 -(rdb:5) info breakpoints +(byebug) delete 1 +(byebug) info breakpoints No breakpoints. ``` You can also enable or disable breakpoints: -* `enable breakpoints`: allow a list _breakpoints_ or all of them if no list is specified, to stop your program. This is the default state when you create a breakpoint. +* `enable breakpoints`: allow a _breakpoints_ list or all of them if no list is +specified, to stop your program. This is the default state when you create a +breakpoint. * `disable breakpoints`: the _breakpoints_ will have no effect on your program. ### Catching Exceptions -The command `catch exception-name` (or just `cat exception-name`) can be used to intercept an exception of type _exception-name_ when there would otherwise be is no handler for it. +The command `catch exception-name` (or just `cat exception-name`) can be used to +intercept an exception of type _exception-name_ when there would otherwise be no +handler for it. To list all active catchpoints use `catch`. ### Resuming Execution -There are two ways to resume execution of an application that is stopped in the debugger: - -* `continue` [line-specification] \(or `c`): resume program execution, at the address where your script last stopped; any breakpoints set at that address are bypassed. The optional argument line-specification allows you to specify a line number to set a one-time breakpoint which is deleted when that breakpoint is reached. -* `finish` [frame-number] \(or `fin`): execute until the selected stack frame returns. If no frame number is given, the application will run until the currently selected frame returns. The currently selected frame starts out the most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been performed. If a frame number is given it will run until the specified frame returns. +There are two ways to resume execution of an application that is stopped in the +debugger: + +* `continue` [line-specification] \(or `c`): resume program execution, at the +address where your script last stopped; any breakpoints set at that address are +bypassed. The optional argument line-specification allows you to specify a line +number to set a one-time breakpoint which is deleted when that breakpoint is +reached. +* `finish` [frame-number] \(or `fin`): execute until the selected stack frame +returns. If no frame number is given, the application will run until the +currently selected frame returns. The currently selected frame starts out the +most-recent frame or 0 if no frame positioning (e.g up, down or frame) has been +performed. If a frame number is given it will run until the specified frame +returns. ### Editing Two commands allow you to open code from the debugger into an editor: -* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR environment variable. A specific _line_ can also be given. -* `tmate _n_` (abbreviated `tm`): open the current file in TextMate. It uses n-th frame if _n_ is specified. +* `edit [file:line]`: edit _file_ using the editor specified by the EDITOR +environment variable. A specific _line_ can also be given. ### Quitting -To exit the debugger, use the `quit` command (abbreviated `q`), or its alias `exit`. +To exit the debugger, use the `quit` command (abbreviated `q`), or its alias +`exit`. -A simple quit tries to terminate all threads in effect. Therefore your server will be stopped and you will have to start it again. +A simple quit tries to terminate all threads in effect. Therefore your server +will be stopped and you will have to start it again. ### Settings -The `debugger` gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options: - -* `set reload`: Reload source code when changed. -* `set autolist`: Execute `list` command on every breakpoint. -* `set listsize _n_`: Set number of source lines to list by default to _n_. -* `set forcestep`: Make sure the `next` and `step` commands always move to a new line +`byebug` has a few available options to tweak its behaviour: -You can see the full list by using `help set`. Use `help set _subcommand_` to learn about a particular `set` command. +* `set autoreload`: Reload source code when changed (default: true). +* `set autolist`: Execute `list` command on every breakpoint (default: true). +* `set listsize _n_`: Set number of source lines to list by default to _n_ +(default: 10) +* `set forcestep`: Make sure the `next` and `step` commands always move to a new +line. -TIP: You can save these settings in an `.rdebugrc` file in your home directory. The debugger reads these global settings when it starts. +You can see the full list by using `help set`. Use `help set _subcommand_` to +learn about a particular `set` command. -Here's a good start for an `.rdebugrc`: +TIP: You can save these settings in an `.byebugrc` file in your home directory. +The debugger reads these global settings when it starts. For example: ```bash -set autolist set forcestep set listsize 25 ``` @@ -671,35 +801,59 @@ set listsize 25 Debugging Memory Leaks ---------------------- -A Ruby application (on Rails or not), can leak memory - either in the Ruby code or at the C code level. +A Ruby application (on Rails or not), can leak memory - either in the Ruby code +or at the C code level. -In this section, you will learn how to find and fix such leaks by using tool such as Valgrind. +In this section, you will learn how to find and fix such leaks by using tool +such as Valgrind. ### Valgrind -[Valgrind](http://valgrind.org/) is a Linux-only application for detecting C-based memory leaks and race conditions. +[Valgrind](http://valgrind.org/) is a Linux-only application for detecting +C-based memory leaks and race conditions. -There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C extension in the interpreter calls `malloc()` but doesn't properly call `free()`, this memory won't be available until the app terminates. +There are Valgrind tools that can automatically detect many memory management +and threading bugs, and profile your programs in detail. For example, if a C +extension in the interpreter calls `malloc()` but doesn't properly call +`free()`, this memory won't be available until the app terminates. -For further information on how to install Valgrind and use with Ruby, refer to [Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) by Evan Weaver. +For further information on how to install Valgrind and use with Ruby, refer to +[Valgrind and Ruby](http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/) +by Evan Weaver. Plugins for Debugging --------------------- -There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging: - -* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has footnotes that give request information and link back to your source via TextMate. -* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query origin tracing to your logs. -* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin not only runs "EXPLAIN" before each of your select queries in development, but provides a small DIV in the rendered output of each page with the summary of warnings for each query that it analyzed. -* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application. -* [Better Errors](https://github.com/charliesome/better_errors) Replaces the standard Rails error page with a new one containing more contextual information, like source code and variable inspection. -* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails development that will end your tailing of development.log. Have all information about your Rails app requests in the browser - in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more. +There are some Rails plugins to help you to find errors and debug your +application. Here is a list of useful plugins for debugging: + +* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has +footnotes that give request information and link back to your source via +TextMate. +* [Query Trace](https://github.com/ntalbott/query_trace/tree/master) Adds query +origin tracing to your logs. +* [Query Reviewer](https://github.com/nesquena/query_reviewer) This rails plugin +not only runs "EXPLAIN" before each of your select queries in development, but +provides a small DIV in the rendered output of each page with the summary of +warnings for each query that it analyzed. +* [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) +Provides a mailer object and a default set of templates for sending email +notifications when errors occur in a Rails application. +* [Better Errors](https://github.com/charliesome/better_errors) Replaces the +standard Rails error page with a new one containing more contextual information, +like source code and variable inspection. +* [RailsPanel](https://github.com/dejan/rails_panel) Chrome extension for Rails +development that will end your tailing of development.log. Have all information +about your Rails app requests in the browser - in the Developer Tools panel. +Provides insight to db/rendering/total times, parameter list, rendered views and +more. References ---------- * [ruby-debug Homepage](http://bashdb.sourceforge.net/ruby-debug/home-page.html) * [debugger Homepage](https://github.com/cldwalker/debugger) +* [byebug Homepage](https://github.com/deivid-rodriguez/byebug) * [Article: Debugging a Rails application with ruby-debug](http://www.sitepoint.com/debug-rails-app-ruby-debug/) * [Ryan Bates' debugging ruby (revised) screencast](http://railscasts.com/episodes/54-debugging-ruby-revised) * [Ryan Bates' stack trace screencast](http://railscasts.com/episodes/24-the-stack-trace) diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 4ee43b6a97..b0e070120d 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -117,7 +117,7 @@ This command will install all dependencies except the MySQL and PostgreSQL Ruby NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running. -You can use homebrew to install memcached on OSX: +You can use [Homebrew](http://brew.sh/) to install memcached on OSX: ```bash $ brew install memcached @@ -210,6 +210,14 @@ FreeBSD users will have to run the following: # pkg_add -r postgresql92-client postgresql92-server ``` +You can use [Homebrew](http://brew.sh/) to install MySQL and PostgreSQL on OSX: + +```bash +$ brew install mysql +$ brew install postgresql +``` +Follow instructions given by [Homebrew](http://brew.sh/) to start these. + Or install them through ports (they are located under the `databases` folder). If you run into troubles during the installation of MySQL, please see [the MySQL documentation](http://dev.mysql.com/doc/refman/5.1/en/freebsd-installation.html). @@ -245,10 +253,15 @@ $ bundle exec rake mysql:build_databases ``` PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account +This is not needed when installed via [Homebrew](http://brew.sh). ```bash $ sudo -u postgres createuser --superuser $USER ``` +And for OS X (when installed via [Homebrew](http://brew.sh)) +```bash +$ createuser --superuser $USER +``` and then create the test databases with diff --git a/guides/source/engines.md b/guides/source/engines.md index bbd63bb892..8f9ba0995f 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1052,6 +1052,16 @@ This tells the application that you still want to perform a `GET` request to the `index` action of this controller, but you want to use the engine's route to get there, rather than the application's one. +Another way to do this is to assign the `@routes` instance variable to `Engine.routes` in your test setup: + +```ruby +setup do + @routes = Engine.routes +end +``` + +This will also ensure url helpers for the engine will work as expected in your tests. + Improving engine functionality ------------------------------ diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 205e0f6b62..019e7d4cf5 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -1008,4 +1008,4 @@ As a convenience you can instead pass the symbol `:all_blank` which will create ### Adding Fields on the Fly -Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any builtin support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice. +Rather than rendering multiple sets of fields ahead of time you may wish to add them only when a user clicks on an 'Add new address' button. Rails does not provide any built-in support for this. When generating new sets of fields you must ensure the key of the associated array is unique - the current JavaScript date (milliseconds after the epoch) is a common choice. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index c54c9efe94..8966eef76a 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -99,7 +99,7 @@ ruby 2.0.0p353 ``` If you don't have Ruby installed have a look at -[ruby-lang.org](https://www.ruby-lang.org/en/downloads/) for possible ways to +[ruby-lang.org](https://www.ruby-lang.org/en/installation/) for possible ways to install Ruby on your platform. Many popular UNIX-like OSes ship with an acceptable version of SQLite3. Windows @@ -344,7 +344,7 @@ resource. Here's what `config/routes.rb` should look like after the _article resource_ is declared. ```ruby -Blog::Application.routes.draw do +Rails.application.routes.draw do resources :articles @@ -612,7 +612,7 @@ def create end ``` -The `render` method here is taking a very simple hash with a key of `text` and +The `render` method here is taking a very simple hash with a key of `plain` and value of `params[:article].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns an `ActiveSupport::HashWithIndifferentAccess` object, which @@ -863,7 +863,7 @@ def index end ``` -And then finally, add view for this action, located at +And then finally, add the view for this action, located at `app/views/articles/index.html.erb`: ```html+erb @@ -1028,17 +1028,21 @@ something went wrong. To do that, you'll modify ```html+erb <%= form_for :article, url: articles_path do |f| %> + <% if @article.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@article.errors.count, "error") %> prohibited - this article from being saved:</h2> - <ul> - <% @article.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> + <div id="error_explanation"> + <h2> + <%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved: + </h2> + <ul> + <% @article.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> + <p> <%= f.label :title %><br> <%= f.text_field :title %> @@ -1052,6 +1056,7 @@ something went wrong. To do that, you'll modify <p> <%= f.submit %> </p> + <% end %> <%= link_to 'Back', articles_path %> @@ -1100,17 +1105,21 @@ it look as follows: <h1>Editing article</h1> <%= form_for :article, url: article_path(@article), method: :patch do |f| %> + <% if @article.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@article.errors.count, "error") %> prohibited - this article from being saved:</h2> - <ul> - <% @article.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> + <div id="error_explanation"> + <h2> + <%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved: + </h2> + <ul> + <% @article.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> + <p> <%= f.label :title %><br> <%= f.text_field :title %> @@ -1124,6 +1133,7 @@ it look as follows: <p> <%= f.submit %> </p> + <% end %> <%= link_to 'Back', articles_path %> @@ -1136,7 +1146,7 @@ The `method: :patch` option tells Rails that we want this form to be submitted via the `PATCH` HTTP method which is the HTTP method you're expected to use to **update** resources according to the REST protocol. -The first parameter of the `form_tag` can be an object, say, `@article` which would +The first parameter of `form_for` can be an object, say, `@article` which would cause the helper to fill in the form with the fields of the object. Passing in a symbol (`:article`) with the same name as the instance variable (`@article`) also automagically leads to the same behavior. This is what is happening here. More details @@ -1187,14 +1197,14 @@ it appear next to the "Show" link: <th colspan="2"></th> </tr> -<% @articles.each do |article| %> - <tr> - <td><%= article.title %></td> - <td><%= article.text %></td> - <td><%= link_to 'Show', article_path(article) %></td> - <td><%= link_to 'Edit', edit_article_path(article) %></td> - </tr> -<% end %> + <% @articles.each do |article| %> + <tr> + <td><%= article.title %></td> + <td><%= article.text %></td> + <td><%= link_to 'Show', article_path(article) %></td> + <td><%= link_to 'Edit', edit_article_path(article) %></td> + </tr> + <% end %> </table> ``` @@ -1228,17 +1238,21 @@ content: ```html+erb <%= form_for @article do |f| %> + <% if @article.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@article.errors.count, "error") %> prohibited - this article from being saved:</h2> - <ul> - <% @article.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> + <div id="error_explanation"> + <h2> + <%= pluralize(@article.errors.count, "error") %> prohibited + this article from being saved: + </h2> + <ul> + <% @article.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> + </div> <% end %> + <p> <%= f.label :title %><br> <%= f.text_field :title %> @@ -1252,6 +1266,7 @@ content: <p> <%= f.submit %> </p> + <% end %> ``` @@ -1333,16 +1348,17 @@ together. <th colspan="3"></th> </tr> -<% @articles.each do |article| %> - <tr> - <td><%= article.title %></td> - <td><%= article.text %></td> - <td><%= link_to 'Show', article_path(article) %></td> - <td><%= link_to 'Edit', edit_article_path(article) %></td> - <td><%= link_to 'Destroy', article_path(article), - method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> -<% end %> + <% @articles.each do |article| %> + <tr> + <td><%= article.title %></td> + <td><%= article.text %></td> + <td><%= link_to 'Show', article_path(article) %></td> + <td><%= link_to 'Edit', edit_article_path(article) %></td> + <td><%= link_to 'Destroy', article_path(article), + method: :delete, + data: { confirm: 'Are you sure?' } %></td> + </tr> + <% end %> </table> ``` @@ -1552,8 +1568,8 @@ So first, we'll wire up the Article show template </p> <% end %> -<%= link_to 'Back', articles_path %> -| <%= link_to 'Edit', edit_article_path(@article) %> +<%= link_to 'Back', articles_path %> | +<%= link_to 'Edit', edit_article_path(@article) %> ``` This adds a form on the `Article` show page that creates a new comment by @@ -1823,7 +1839,7 @@ database and send us back to the show action for the article. ### Deleting Associated Objects -If you delete an article then its associated comments will also need to be +If you delete an article, its associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the `dependent` option of an association to achieve this. Modify the Article model, `app/models/article.rb`, as follows: @@ -1841,21 +1857,21 @@ Security ### Basic Authentication -If you were to publish your blog online, anybody would be able to add, edit and +If you were to publish your blog online, anyone would be able to add, edit and delete articles or delete comments. Rails provides a very simple HTTP authentication system that will work nicely in this situation. -In the `ArticlesController` we need to have a way to block access to the various -actions if the person is not authenticated, here we can use the Rails -`http_basic_authenticate_with` method, allowing access to the requested +In the `ArticlesController` we need to have a way to block access to the +various actions if the person is not authenticated. Here we can use the Rails +`http_basic_authenticate_with` method, which allows access to the requested action if that method allows it. To use the authentication system, we specify it at the top of our -`ArticlesController`, in this case, we want the user to be authenticated on -every action, except for `index` and `show`, so we write that in -`app/controllers/articles_controller.rb`: +`ArticlesController` in `app/controllers/articles_controller.rb`. In our case, +we want the user to be authenticated on every action except `index` and `show`, +so we write that: ```ruby class ArticlesController < ApplicationController diff --git a/guides/source/i18n.md b/guides/source/i18n.md index bef738b75b..c1b575c7b7 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -92,7 +92,7 @@ Rails adds all `.rb` and `.yml` files from the `config/locales` directory to you The default `en.yml` locale in this directory contains a sample pair of translation strings: -```ruby +```yaml en: hello: "Hello world" ``` @@ -179,7 +179,7 @@ end # in your /etc/hosts file to try this out locally def extract_locale_from_tld parsed_locale = request.host.split('.').last - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end ``` @@ -192,7 +192,7 @@ We can also set the locale from the _subdomain_ in a very similar way: # in your /etc/hosts file to try this out locally def extract_locale_from_subdomain parsed_locale = request.subdomains.first - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end ``` @@ -212,17 +212,16 @@ The most usual way of setting (and passing) the locale would be to include it in This approach has almost the same set of advantages as setting the locale from the domain name: namely that it's RESTful and in accord with the rest of the World Wide Web. It does require a little bit more work to implement, though. -Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL (e.g. `link_to( books_url(locale: I18n.locale))`) would be tedious and probably impossible, of course. +Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL, e.g. `link_to(books_url(locale: I18n.locale))`, would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000515), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionController/Base.html#M000503) and helper methods dependent on it (by implementing/overriding this method). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding this method). We can include something like this in our `ApplicationController` then: ```ruby # app/controllers/application_controller.rb -def default_url_options(options={}) - logger.debug "default_url_options is passed options: #{options.inspect}\n" - { locale: I18n.locale } +def default_url_options(options = {}) + { locale: I18n.locale }.merge options end ``` @@ -310,7 +309,7 @@ You most probably have something like this in one of your applications: ```ruby # config/routes.rb -Yourapp::Application.routes.draw do +Rails.application.routes.draw do root to: "home#index" end ``` @@ -370,7 +369,7 @@ NOTE: Rails adds a `t` (`translate`) helper method to your views so that you do So let's add the missing translations into the dictionary files (i.e. do the "localization" part): -```ruby +```yaml # config/locales/en.yml en: hello_world: Hello world! @@ -422,7 +421,7 @@ OK! Now let's add a timestamp to the view, so we can demo the **date/time locali And in our pirate translations file let's add a time format (it's already there in Rails' defaults for English): -```ruby +```yaml # config/locales/pirate.yml pirate: time: @@ -681,62 +680,13 @@ NOTE: Automatic conversion to HTML safe translate text is only available from th ![i18n demo html safe](images/i18n/demo_html_safe.png) -How to Store your Custom Translations -------------------------------------- - -The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2] - -For example a Ruby Hash providing translations can look like this: - -```ruby -{ - pt: { - foo: { - bar: "baz" - } - } -} -``` - -The equivalent YAML file would look like this: - -```ruby -pt: - foo: - bar: baz -``` - -As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz". - -Here is a "real" example from the Active Support `en.yml` translations YAML file: - -```ruby -en: - date: - formats: - default: "%Y-%m-%d" - short: "%b %d" - long: "%B %d, %Y" -``` - -So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`: - -```ruby -I18n.t 'date.formats.short' -I18n.t 'formats.short', scope: :date -I18n.t :short, scope: 'date.formats' -I18n.t :short, scope: [:date, :formats] -``` - -Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. - ### Translations for Active Record Models You can use the methods `Model.model_name.human` and `Model.human_attribute_name(attribute)` to transparently look up translations for your model and attribute names. For example when you add the following translations: -```ruby +```yaml en: activerecord: models: @@ -751,7 +701,7 @@ Then `User.model_name.human` will return "Dude" and `User.human_attribute_name(" You can also set a plural form for model names, adding as following: -```ruby +```yaml en: activerecord: models: @@ -921,6 +871,55 @@ Rails uses fixed strings and other localizations, such as format strings and oth * `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33) scope. +How to Store your Custom Translations +------------------------------------- + +The Simple backend shipped with Active Support allows you to store translations in both plain Ruby and YAML format.[^2] + +For example a Ruby Hash providing translations can look like this: + +```yaml +{ + pt: { + foo: { + bar: "baz" + } + } +} +``` + +The equivalent YAML file would look like this: + +```yaml +pt: + foo: + bar: baz +``` + +As you see, in both cases the top level key is the locale. `:foo` is a namespace key and `:bar` is the key for the translation "baz". + +Here is a "real" example from the Active Support `en.yml` translations YAML file: + +```yaml +en: + date: + formats: + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" +``` + +So, all of the following equivalent lookups will return the `:short` date format `"%b %d"`: + +```ruby +I18n.t 'date.formats.short' +I18n.t 'formats.short', scope: :date +I18n.t :short, scope: 'date.formats' +I18n.t :short, scope: [:date, :formats] +``` + +Generally we recommend using YAML as a format for storing translations. There are cases, though, where you want to store Ruby lambdas as part of your locale data, e.g. for special date formats. + Customize your I18n Setup ------------------------- diff --git a/guides/source/initialization.md b/guides/source/initialization.md index ec3cec5c6f..00b2761716 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -166,6 +166,7 @@ is called. COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole application runner new version help) def run_command!(command) + command = parse_command(command) if COMMAND_WHITELIST.include?(command) send(command) else @@ -178,8 +179,7 @@ With the `server` command, Rails will further run the following code: ```ruby def set_application_directory! - Dir.chdir(File.expand_path('../../', APP_PATH)) unless - File.exist?(File.expand_path("config.ru")) + Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru")) end def server @@ -187,6 +187,8 @@ def server require_command!("server") Rails::Server.new.tap do |server| + # We need to require application after the server sets environment, + # otherwise the --environment option given to the server won't propagate. require APP_PATH Dir.chdir(Rails.application.root) server.start @@ -207,6 +209,7 @@ sets up the `Rails::Server` class. require 'fileutils' require 'optparse' require 'action_dispatch' +require 'rails' module Rails class Server < ::Rack::Server @@ -273,7 +276,7 @@ def parse_options(args) # http://www.meb.uni-bonn.de/docs/cgi/cl.html args.clear if ENV.include?("REQUEST_METHOD") - options.merge! opt_parser.parse! args + options.merge! opt_parser.parse!(args) options[:config] = ::File.expand_path(options[:config]) ENV["RACK_ENV"] = options[:environment] options @@ -284,13 +287,16 @@ With the `default_options` set to this: ```ruby def default_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + { - environment: ENV['RACK_ENV'] || "development", - pid: nil, - Port: 9292, - Host: "0.0.0.0", - AccessLog: [], - config: "config.ru" + :environment => environment, + :pid => nil, + :Port => 9292, + :Host => default_host, + :AccessLog => [], + :config => "config.ru" } end ``` @@ -348,6 +354,7 @@ private def print_boot_information ... puts "=> Run `rails server -h` for more startup options" + ... puts "=> Ctrl-C to shutdown server" unless options[:daemonize] end @@ -434,7 +441,11 @@ The `app` method here is defined like so: ```ruby def app - @app ||= begin + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config +end +... +private + def build_app_and_options_from_config if !::File.exist? options[:config] abort "configuration #{options[:config]} not found" end @@ -443,7 +454,10 @@ def app self.options.merge! options app end -end + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end ``` The `options[:config]` value defaults to `config.ru` which contains this: @@ -459,8 +473,14 @@ run <%= app_const %> The `Rack::Builder.parse_file` method here takes the content from this `config.ru` file and parses it using this code: ```ruby -app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", - TOPLEVEL_BINDING, config +app = new_from_string cfgfile, config + +... + +def self.new_from_string(builder_script, file="(rackup)") + eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app", + TOPLEVEL_BINDING, file, 0 +end ``` The `initialize` method of `Rack::Builder` will take the block here and execute it within an instance of `Rack::Builder`. This is where the majority of the initialization process of Rails happens. The `require` line for `config/environment.rb` in `config.ru` is the first to run: @@ -473,11 +493,22 @@ require ::File.expand_path('../config/environment', __FILE__) This file is the common file required by `config.ru` (`rails server`) and Passenger. This is where these two ways to run the server meet; everything before this point has been Rack and Rails setup. -This file begins with requiring `config/application.rb`. +This file begins with requiring `config/application.rb`: + +```ruby +require File.expand_path('../application', __FILE__) +``` ### `config/application.rb` -This file requires `config/boot.rb`, but only if it hasn't been required before, which would be the case in `rails server` but **wouldn't** be the case with Passenger. +This file requires `config/boot.rb`: + +```ruby +require File.expand_path('../boot', __FILE__) +``` + +But only if it hasn't been required before, which would be the case in `rails server` +but **wouldn't** be the case with Passenger. Then the fun begins! @@ -498,11 +529,12 @@ This file is responsible for requiring all the individual frameworks of Rails: require "rails" %w( - active_record - action_controller - action_mailer - rails/test_unit - sprockets + active_record + action_controller + action_view + action_mailer + rails/test_unit + sprockets ).each do |framework| begin require "#{framework}/railtie" @@ -526,7 +558,7 @@ The rest of `config/application.rb` defines the configuration for the initialized. When `config/application.rb` has finished loading Rails and defined the application namespace, we go back to `config/environment.rb`, where the application is initialized. For example, if the application was called -`Blog`, here we would find `Blog::Application.initialize!`, which is +`Blog`, here we would find `Rails.application.initialize!`, which is defined in `rails/application.rb` ### `railties/lib/rails/application.rb` @@ -555,7 +587,7 @@ def run_initializers(group=:default, *args) end ``` -The run_initializers code itself is tricky. What Rails is doing here is +The `run_initializers` code itself is tricky. What Rails is doing here is traversing all the class ancestors looking for those that respond to an `initializers` method. It then sorts the ancestors by name, and runs them. For example, the `Engine` class will make all the engines available by @@ -568,7 +600,7 @@ initializers (like building the middleware stack) are run last. The `railtie` initializers are the initializers which have been defined on the `Rails::Application` itself and are run between the `bootstrap` and `finishers`. -After this is done we go back to `Rack::Server` +After this is done we go back to `Rack::Server`. ### Rack: lib/rack/server.rb @@ -576,7 +608,11 @@ Last time we left when the `app` method was being defined: ```ruby def app - @app ||= begin + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config +end +... +private + def build_app_and_options_from_config if !::File.exist? options[:config] abort "configuration #{options[:config]} not found" end @@ -585,7 +621,10 @@ def app self.options.merge! options app end -end + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end ``` At this point `app` is the Rails app itself (a middleware), and what @@ -603,7 +642,7 @@ def build_app(app) end ``` -Remember, `build_app` was called (by wrapped_app) in the last line of `Server#start`. +Remember, `build_app` was called (by `wrapped_app`) in the last line of `Server#start`. Here's how it looked like when we left: ```ruby @@ -611,40 +650,50 @@ server.run wrapped_app, options, &blk ``` At this point, the implementation of `server.run` will depend on the -server you're using. For example, if you were using Mongrel, here's what +server you're using. For example, if you were using Puma, here's what the `run` method would look like: ```ruby -def self.run(app, options={}) - server = ::Mongrel::HttpServer.new( - options[:Host] || '0.0.0.0', - options[:Port] || 8080, - options[:num_processors] || 950, - options[:throttle] || 0, - options[:timeout] || 60) - # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. - # Use is similar to #run, replacing the app argument with a hash of - # { path=>app, ... } or an instance of Rack::URLMap. - if options[:map] - if app.is_a? Hash - app.each do |path, appl| - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - elsif app.is_a? URLMap - app.instance_variable_get(:@mapping).each do |(host, path, appl)| - next if !host.nil? && !options[:Host].nil? && options[:Host] != host - path = '/'+path unless path[0] == ?/ - server.register(path, Rack::Handler::Mongrel.new(appl)) - end - else - raise ArgumentError, "first argument should be a Hash or URLMap" - end - else - server.register('/', Rack::Handler::Mongrel.new(app)) +... +DEFAULT_OPTIONS = { + :Host => '0.0.0.0', + :Port => 8080, + :Threads => '0:16', + :Verbose => false +} + +def self.run(app, options = {}) + options = DEFAULT_OPTIONS.merge(options) + + if options[:Verbose] + app = Rack::CommonLogger.new(app, STDOUT) end + + if options[:environment] + ENV['RACK_ENV'] = options[:environment].to_s + end + + server = ::Puma::Server.new(app) + min, max = options[:Threads].split(':', 2) + + puts "Puma #{::Puma::Const::PUMA_VERSION} starting..." + puts "* Min threads: #{min}, max threads: #{max}" + puts "* Environment: #{ENV['RACK_ENV']}" + puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}" + + server.add_tcp_listener options[:Host], options[:Port] + server.min_threads = min + server.max_threads = max yield server if block_given? - server.run.join + + begin + server.run.join + rescue Interrupt + puts "* Gracefully stopping, waiting for requests to finish" + server.stop(true) + puts "* Goodbye!" + end + end ``` @@ -654,4 +703,4 @@ the last piece of our journey in the Rails initialization process. This high level overview will help you understand when your code is executed and how, and overall become a better Rails developer. If you still want to know more, the Rails source code itself is probably the -best place to go next. +best place to go next.
\ No newline at end of file diff --git a/guides/source/maintenance_policy.md b/guides/source/maintenance_policy.md index 93729c6f72..8f119f36aa 100644 --- a/guides/source/maintenance_policy.md +++ b/guides/source/maintenance_policy.md @@ -20,7 +20,7 @@ Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from. -**Currently included series:** 4.0.z +**Currently included series:** 4.1.z, 4.0.z Security Issues --------------- @@ -35,7 +35,7 @@ be built from 1.2.2, and then added to the end of 1-2-stable. This means that security releases are easy to upgrade to if you're running the latest version of Rails. -**Currently included series:** 4.0.z, 3.2.z +**Currently included series:** 4.1.z, 4.0.z Severe Security Issues ---------------------- @@ -44,7 +44,7 @@ For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team. -**Currently included series:** 4.0.z, 3.2.z +**Currently included series:** 4.1.z, 4.0.z, 3.2.z Unsupported Release Series -------------------------- diff --git a/guides/source/migrations.md b/guides/source/migrations.md index bfee55a95d..c61ccfe94a 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -495,6 +495,7 @@ class ExampleMigration < ActiveRecord::Migration add_column :users, :home_page_url, :string rename_column :users, :email, :email_address end +end ``` Using `reversible` will ensure that the instructions are executed in the diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index 855fab18e3..4f0634d955 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -17,9 +17,9 @@ Model setup To be able to use the nested model functionality in your forms, the model will need to support some basic operations. -First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be build. +First of all, it needs to define a writer method for the attribute that corresponds to the association you are building a nested model form for. The `fields_for` form helper will look for this method to decide whether or not a nested model form should be built. -If the associated object is an array a form builder will be yielded for each object, else only a single form builder will be yielded. +If the associated object is an array, a form builder will be yielded for each object, else only a single form builder will be yielded. Consider a Person model with an associated Address. When asked to yield a nested FormBuilder for the `:address` attribute, the `fields_for` form helper will look for a method on the Person instance named `address_attributes=`. @@ -220,6 +220,6 @@ As you can see it has generated 2 `project name` inputs, one for each new `proje You can basically see the `projects_attributes` hash as an array of attribute hashes, one for each model instance. -NOTE: The reason that `fields_for` constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep. +NOTE: The reason that `fields_for` constructed a hash instead of an array is that it won't work for any form nested deeper than one level deep. TIP: You _can_ however pass an array to the writer method generated by `accepts_nested_attributes_for` if you're using plain Ruby or some other API access. See (TODO) for more info and example. diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 9c92cf3aea..b1b4c8fa4e 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -27,10 +27,9 @@ Rails on Rack ### Rails Application's Rack Object -`ApplicationName::Application` is the primary Rack application object of a Rails +`Rails.application` is the primary Rack application object of a Rails application. Any Rack compliant web server should be using -`ApplicationName::Application` object to serve a Rails -application. `Rails.application` refers to the same application object. +`Rails.application` object to serve a Rails application. ### `rails server` @@ -141,7 +140,7 @@ use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag -run MyApp::Application.routes +run Rails.application.routes ``` The default middlewares shown here (and some others) are each summarized in the [Internal Middlewares](#internal-middleware-stack) section, below. @@ -201,7 +200,7 @@ use ActionDispatch::Static use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8> use Rack::Runtime ... -run Blog::Application.routes +run Rails.application.routes ``` If you want to remove session related middleware, do the following: diff --git a/guides/source/routing.md b/guides/source/routing.md index eef618f28d..0783bce442 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -694,6 +694,8 @@ namespace :admin do end ``` +NOTE: Request constraints work by calling a method on the <a href="action_controller_overview.html#the-request-object">Request object</a> with the same name as the hash key and then compare the return value with the hash value. Therefore, constraint values should match the corresponding Request object method return type. For example: `constraints: { subdomain: 'api' }` will match an `api` subdomain as expected, however using a symbol `constraints: { subdomain: :api }` will not, because `request.subdomain` returns `'api'` as a String. + ### Advanced Constraints If you have a more advanced constraint, you can provide an object that responds to `matches?` that Rails should use. Let's say you wanted to route all users on a blacklist to the `BlacklistController`. You could do: @@ -709,7 +711,7 @@ class BlacklistConstraint end end -TwitterClone::Application.routes.draw do +Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: BlacklistConstraint.new end @@ -718,7 +720,7 @@ end You can also specify constraints as a lambda: ```ruby -TwitterClone::Application.routes.draw do +Rails.application.routes.draw do get '*path', to: 'blacklist#index', constraints: lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) } end diff --git a/guides/source/security.md b/guides/source/security.md index a40c99cbfd..0d347c9e4b 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -60,7 +60,7 @@ Many web applications have an authentication system: a user provides a user name Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: -* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: +* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: ```ruby config.force_ssl = true @@ -151,7 +151,7 @@ The most effective countermeasure is to _issue a new session identifier_ and dec reset_session ``` -If you use the popular RestfulAuthentication plugin for user management, add reset\_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. +If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _you have to transfer them to the new session_. Another countermeasure is to _save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. @@ -239,24 +239,23 @@ Or the attacker places the code into the onmouseover event handler of an image: There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request. -To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller: +To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications: ```ruby -protect_from_forgery +protect_from_forgery with: :exception ``` -This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, the session will be reset. +This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, an exception will be thrown. It is common to use persistent cookies to store user information, with `cookies.permanent` for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself: ```ruby -def handle_unverified_request - super - sign_out_user # Example method that will destroy the user cookies. +rescue_from ActionController::InvalidAuthenticityToken do |exception| + sign_out_user # Example method that will destroy the user cookies end ``` -The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present on a non-GET request. +The above method can be placed in the `ApplicationController` and will be called when a CSRF token is not present or is incorrect on a non-GET request. Note that _cross-site scripting (XSS) vulnerabilities bypass all CSRF protections_. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later. @@ -314,7 +313,7 @@ def sanitize_filename(filename) end ``` -A significant disadvantage of synchronous processing of file uploads (as the attachment\_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server. +A significant disadvantage of synchronous processing of file uploads (as the attachment_fu plugin may do with images), is its _vulnerability to denial-of-service attacks_. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server. The solution to this is best to _process media files asynchronously_: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background. @@ -1003,7 +1002,7 @@ _'1; mode=block' in Rails by default_ - use XSS Auditor and block page if XSS at * X-Content-Type-Options _'nosniff' in Rails by default_ - stops the browser from guessing the MIME type of a file. * X-Content-Security-Policy -[A powerful mechanism for controlling which sites certain content types can be loaded from](http://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html) +[A powerful mechanism for controlling which sites certain content types can be loaded from](http://w3c.github.io/webappsec/specs/content-security-policy/csp-specification.dev.html) * Access-Control-Allow-Origin Used to control which sites are allowed to bypass same origin policies and send cross-origin requests. * Strict-Transport-Security diff --git a/guides/source/testing.md b/guides/source/testing.md index aa37115d14..36d37f3af0 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -796,7 +796,7 @@ when you initiate a Rails project. Brief Note About `MiniTest` ----------------------------- -Ruby ships with a boat load of libraries. Ruby 1.8 provides `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing +Ruby ships with a vast Standard Library for all common use-cases including testing. Ruby 1.8 provided `Test::Unit`, a framework for unit testing in Ruby. All the basic assertions discussed above are actually defined in `Test::Unit::Assertions`. The class `ActiveSupport::TestCase` which we have been using in our unit and functional tests extends `Test::Unit::TestCase`, allowing us to use all of the basic assertions in our tests. Ruby 1.9 introduced `MiniTest`, an updated version of `Test::Unit` which provides a backwards compatible API for `Test::Unit`. You could also use `MiniTest` in Ruby 1.8 by installing the `minitest` gem. diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index d58024df3d..da161f84c9 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -25,8 +25,6 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp Upgrading from Rails 4.0 to Rails 4.1 ------------------------------------- -NOTE: This section is a work in progress. - ### CSRF protection from remote `<script>` tags Or, "whaaat my tests are failing!!!?" @@ -79,12 +77,15 @@ secrets, you need to: secret_key_base: production: - secret_key_base: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> ``` -2. Copy the existing `secret_key_base` from the `secret_token.rb` initializer to - `secrets.yml` under the `production` section. - +2. Use your existing `secret_key_base` from the `secret_token.rb` initializer to + set the SECRET_KEY_BASE environment variable for whichever users run the Rails + app in production mode. Alternately, you can simply copy the existing + `secret_key_base` from the `secret_token.rb` initializer to `secrets.yml` + under the `production` section, replacing '<%= ENV["SECRET_KEY_BASE"] %>'. + 3. Remove the `secret_token.rb` initializer. 4. Use `rake secret` to generate new keys for the `development` and `test` sections. @@ -104,9 +105,9 @@ Applications created before Rails 4.1 uses `Marshal` to serialize cookie values the signed and encrypted cookie jars. If you want to use the new `JSON`-based format in your application, you can add an initializer file with the following content: - ```ruby - Rails.application.config.cookies_serializer :hybrid - ``` +```ruby +Rails.application.config.action_dispatch.cookies_serializer = :hybrid +``` This would transparently migrate your existing `Marshal`-serialized cookies into the new `JSON`-based format. @@ -463,7 +464,7 @@ being used, you can update your form to use the `PUT` method instead: <%= form_for [ :update_name, @user ], method: :put do |f| %> ``` -For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/26/edge-rails-patch-is-the-new-primary-http-method-for-updates/) +For more on PATCH and why this change was made, see [this post](http://weblog.rubyonrails.org/2012/2/25/edge-rails-patch-is-the-new-primary-http-method-for-updates/) on the Rails blog. #### A note about media types diff --git a/rails.gemspec b/rails.gemspec index d1c199a97a..4800df0df4 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -27,5 +27,5 @@ Gem::Specification.new do |s| s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0' + s.add_dependency 'sprockets-rails', '~> 2.1' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index afbebf5b95..6a31a923a7 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,13 @@ +* Fix `console` and `generators` blocks defined at different environments. + + Fixes #14748. + + *Rafael Mendonça França* + +* Move configuration of asset precompile list and version to an initializer. + + *Matthew Draper* + * Do not set the Rails environment to test by default when using test_unit Railtie. *Konstantin Shabanov* diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index e37347b576..2fde974732 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -87,7 +87,7 @@ module Rails class << self def inherited(base) super - Rails.application ||= base.instance + base.instance end # Makes the +new+ method public. @@ -117,6 +117,8 @@ module Rails @railties = nil @message_verifiers = {} + Rails.application ||= self + add_lib_to_load_path! ActiveSupport.run_load_hooks(:before_configuration, self) @@ -151,14 +153,13 @@ module Rails def key_generator # number of iterations selected based on consultation with the google security # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220 - @caching_key_generator ||= begin + @caching_key_generator ||= if secrets.secret_key_base key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000) ActiveSupport::CachingKeyGenerator.new(key_generator) else ActiveSupport::LegacyKeyGenerator.new(config.secret_token) end - end end # Returns a message verifier object. @@ -230,6 +231,18 @@ module Rails self.class.runner(&blk) end + # Sends any console called in the instance of a new application up + # to the +console+ method defined in Rails::Railtie. + def console(&blk) + self.class.console(&blk) + end + + # Sends any generators called in the instance of a new application up + # to the +generators+ method defined in Rails::Railtie. + def generators(&blk) + self.class.generators(&blk) + end + # Sends the +isolate_namespace+ method up to the class method. def isolate_namespace(mod) self.class.isolate_namespace(mod) @@ -332,6 +345,25 @@ module Rails config.helpers_paths end + console do + require "pp" + end + + console do + unless ::Kernel.private_method_defined?(:y) + if RUBY_VERSION >= '2.0' + require "psych/y" + else + module ::Kernel + def y(*objects) + puts ::Psych.dump_stream(*objects) + end + private :y + end + end + end + end + protected alias :build_middleware_stack :app diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index f6bdf129d6..555d8f31e1 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -18,7 +18,14 @@ module Rails opt.on("-e", "--environment=name", String, "Specifies the environment to run this console under (test/development/production).", "Default: development") { |v| options[:environment] = v.strip } - opt.on("--debugger", 'Enable the debugger.') { |v| options[:debugger] = v } + opt.on("--debugger", 'Enable the debugger.') do |v| + if RUBY_VERSION < '2.0.0' + options[:debugger] = v + else + puts "=> Notice: debugger option is ignored since ruby 2.0 and " \ + "it will be removed in future versions" + end + end opt.parse!(arguments) end @@ -69,12 +76,25 @@ module Rails Rails.env = environment end - def debugger? - options[:debugger] + if RUBY_VERSION < '2.0.0' + def debugger? + options[:debugger] + end + + def require_debugger + require 'debugger' + puts "=> Debugger enabled" + rescue LoadError + puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." + exit(1) + end end def start - require_debugger if debugger? + if RUBY_VERSION < '2.0.0' + require_debugger if debugger? + end + set_environment! if environment? if sandbox? @@ -89,13 +109,5 @@ module Rails end console.start end - - def require_debugger - require 'debugger' - puts "=> Debugger enabled" - rescue LoadError - puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle it and try again." - exit(1) - end end end diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index fec4962fb5..6146b6c1db 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -18,7 +18,14 @@ module Rails opts.on("-c", "--config=file", String, "Use custom rackup configuration file") { |v| options[:config] = v } opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true } - opts.on("-u", "--debugger", "Enable the debugger") { options[:debugger] = true } + opts.on("-u", "--debugger", "Enable the debugger") do + if RUBY_VERSION < '2.0.0' + options[:debugger] = true + else + puts "=> Notice: debugger option is ignored since ruby 2.0 and " \ + "it will be removed in future versions" + end + end opts.on("-e", "--environment=name", String, "Specifies the environment to run this server under (test/development/production).", "Default: development") { |v| options[:environment] = v } @@ -75,7 +82,9 @@ module Rails def middleware middlewares = [] - middlewares << [Rails::Rack::Debugger] if options[:debugger] + if RUBY_VERSION < '2.0.0' + middlewares << [Rails::Rack::Debugger] if options[:debugger] + end middlewares << [::Rack::ContentLength] # FIXME: add Rack::Lock in the case people are using webrick. diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 5661094d95..b36ab3d0d5 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -429,7 +429,6 @@ module Rails # Load console and invoke the registered hooks. # Check <tt>Rails::Railtie.console</tt> for more info. def load_console(app=self) - require "pp" require "rails/console/app" require "rails/console/helpers" run_console_blocks(app) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 2fb49479fe..c066f748ee 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -249,7 +249,7 @@ module Rails 'Use SCSS for stylesheets') else gems << GemfileEntry.version('sass-rails', - '~> 4.0.2', + '~> 4.0.3', 'Use SCSS for stylesheets') end diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index 69c10efa47..10f80abb15 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -4,8 +4,8 @@ <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2> <ul> - <%% @<%= singular_table_name %>.errors.full_messages.each do |msg| %> - <li><%%= msg %></li> + <%% @<%= singular_table_name %>.errors.full_messages.each do |message| %> + <li><%%= message %></li> <%% end %> </ul> </div> diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index abf6909a7f..8675d8bc1e 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -231,6 +231,12 @@ module Rails end end + def delete_assets_initializer_skipping_sprockets + if options[:skip_sprockets] + remove_file 'config/initializers/assets.rb' + end + end + def finish_template build(:leftovers) end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index a9b6787894..448b6f4845 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -23,11 +23,15 @@ source 'https://rubygems.org' # gem 'capistrano-rails', group: :development <% unless defined?(JRUBY_VERSION) -%> -# Use debugger +# To use a debugger + <%- if RUBY_VERSION < '2.0.0' -%> # gem 'debugger', group: [:development, :test] + <%- else -%> +# gem 'byebug', group: [:development, :test] + <%- end -%> <% end -%> <% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince/) -%> # Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem 'tzinfo-data', platforms: [:mingw, :mswin] +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw] <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml index 138e3a8664..34fc0e3465 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml @@ -23,7 +23,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="frontbase://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml index 2cdb592eeb..d088dd62bf 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml @@ -61,7 +61,27 @@ test: <<: *default database: <%= app_name[0,4] %>_tst -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="ibm-db://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml index cefd30d519..db0a429753 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -53,7 +53,16 @@ test: <<: *default url: jdbc:db://localhost/<%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# production: - url: <%%= ENV["DATABASE_URL"] %> + url: jdbc:db://localhost/<%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index d31761349c..acb93939e1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -26,6 +26,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. -production: <%%= ENV["DATABASE_URL"] %> +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="mysql://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# +production: + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 0d248dc197..9e99264d33 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -42,7 +42,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml index 66eba3bf0d..28c36eb82f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -18,7 +18,6 @@ test: <<: *default database: db/test.sqlite3 -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml index d618fc28a6..4b2e6646c7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml @@ -32,11 +32,27 @@ test: <<: *default database: <%= app_name %>_test -# Avoid production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. # -# Example: -# mysql2://myuser:mypass@localhost/somedatabase +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> # production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml index d469ec0f99..10ab4c02e2 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml @@ -32,7 +32,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="oracle://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index 93f48656b2..feb25bbc6b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -59,11 +59,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. # -# Example: -# postgres://myuser:mypass@localhost/somedatabase +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> # production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml index 7312ddb6cd..1c1a37ca8d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml @@ -20,11 +20,6 @@ test: <<: *default database: db/test.sqlite3 -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. -# -# Example: -# sqlite3://myuser:mypass@localhost/full/path/to/somedatabase -# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml index aa960e493e..30b0df34a8 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml @@ -42,7 +42,27 @@ test: <<: *default database: <%= app_name %>_test -# Do not keep production credentials in the repository, -# instead read the configuration from the environment. +# As with config/secrets.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password as a unix environment variable when you boot +# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full rundown on how to provide these environment variables in a +# production deployment. +# +# On Heroku and other platform providers, you may have a full connection URL +# available as an environment variable. For example: +# +# DATABASE_URL="sqlserver://myuser:mypass@localhost/somedatabase" +# +# You can use this database configuration with: +# +# production: +# url: <%%= ENV['DATABASE_URL'] %> +# production: - url: <%%= ENV["DATABASE_URL"] %> + <<: *default + database: <%= app_name %>_production + username: <%= app_name %> + password: <%%= ENV['<%= app_name.upcase %>_DATABASE_PASSWORD'] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index b789ed9a94..9ed71687ea 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -33,12 +33,7 @@ Rails.application.configure do # Generate digests for assets URLs. config.assets.digest = true - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) + # `config.assets.precompile` has moved to config/initializers/assets.rb <%- end -%> # Specifies the header that your server uses for sending files. diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt new file mode 100644 index 0000000000..d2f4ec33a6 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb index 7a06a89f0f..7f70458dee 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Rails.application.config.action_dispatch.cookies_serializer = :json
\ No newline at end of file +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb index 72aca7e441..dc1899682b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb @@ -2,4 +2,3 @@ # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf -# Mime::Type.register_alias "text/html", :iphone diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile index f0a832f783..1f704db510 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile @@ -38,6 +38,10 @@ end <% end -%> <% unless defined?(JRUBY_VERSION) -%> -# To use debugger -# gem 'debugger' +# To use a debugger + <%- if RUBY_VERSION < '2.0.0' -%> +# gem 'debugger', group: [:development, :test] + <%- else -%> +# gem 'byebug', group: [:development, :test] + <%- end -%> <% end -%> diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 0e69aa101f..2c3b04043f 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -31,7 +31,7 @@ class <%= controller_class_name %>Controller < ApplicationController if @<%= orm_instance.save %> redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> else - render action: 'new' + render :new end end @@ -40,7 +40,7 @@ class <%= controller_class_name %>Controller < ApplicationController if @<%= orm_instance.update("#{singular_table_name}_params") %> redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> else - render action: 'edit' + render :edit end end diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb index d1ee96f7fd..886f0e52e1 100644 --- a/railties/lib/rails/rack.rb +++ b/railties/lib/rails/rack.rb @@ -1,6 +1,6 @@ module Rails module Rack - autoload :Debugger, "rails/rack/debugger" + autoload :Debugger, "rails/rack/debugger" if RUBY_VERSION < '2.0.0' autoload :Logger, "rails/rack/logger" autoload :LogTailer, "rails/rack/log_tailer" end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 8d7e804bdc..2b33beaa2b 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -221,26 +221,28 @@ module Rails protected def run_console_blocks(app) #:nodoc: - self.class.console.each { |block| block.call(app) } + each_registered_block(:console) { |block| block.call(app) } end def run_generators_blocks(app) #:nodoc: - self.class.generators.each { |block| block.call(app) } + each_registered_block(:generators) { |block| block.call(app) } end def run_runner_blocks(app) #:nodoc: - self.class.runner.each { |block| block.call(app) } + each_registered_block(:runner) { |block| block.call(app) } end def run_tasks_blocks(app) #:nodoc: extend Rake::DSL - self.class.rake_tasks.each { |block| instance_exec(app, &block) } + each_registered_block(:rake_tasks) { |block| instance_exec(app, &block) } + end - # Load also tasks from all superclasses - klass = self.class.superclass + private - while klass.respond_to?(:rake_tasks) - klass.rake_tasks.each { |t| instance_exec(app, &t) } + def each_registered_block(type, &block) + klass = self.class + while klass.respond_to?(type) + klass.public_send(type).each(&block) klass = klass.superclass end end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 2e6356343d..c837fadb40 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -4,6 +4,7 @@ abort("Abort testing: Your Rails environment is running in production mode!") if require 'active_support/testing/autorun' require 'active_support/test_case' +require 'action_controller' require 'action_controller/test_case' require 'action_dispatch/testing/integration' require 'rails/generators/test_case' diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index b235b51d90..51f55a560f 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -199,6 +199,7 @@ module ApplicationTests end test "precompile creates a manifest file with all the assets listed" do + app_file "app/assets/images/rails.png", "notactuallyapng" app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" app_file "app/assets/javascripts/application.js", "alert();" # digest is default in false, we must enable it for test environment @@ -260,7 +261,7 @@ module ApplicationTests test "precompile shouldn't use the digests present in manifest.json" do app_file "app/assets/images/rails.png", "notactuallyapng" - app_file "app/assets/stylesheets/application.css.erb", "//= depend_on rails.png\np { url: <%= asset_path('rails.png') %> }" + app_file "app/assets/stylesheets/application.css.erb", "p { url: <%= asset_path('rails.png') %> }" ENV["RAILS_ENV"] = "production" precompile! @@ -448,23 +449,23 @@ module ApplicationTests test "asset urls should be protocol-relative if no request is in scope" do app_file "app/assets/images/rails.png", "notreallyapng" - app_file "app/assets/javascripts/image_loader.js.erb", 'var src="<%= image_path("rails.png") %>";' + app_file "app/assets/javascripts/image_loader.js.erb", "var src='<%= image_path('rails.png') %>';" add_to_config "config.assets.precompile = %w{image_loader.js}" add_to_config "config.asset_host = 'example.com'" precompile! - assert_match 'src="//example.com/assets/rails.png"', File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first) + assert_match "src='//example.com/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first) end test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri" app_file "app/assets/images/rails.png", "notreallyapng" - app_file "app/assets/javascripts/app.js.erb", 'var src="<%= image_path("rails.png") %>";' + app_file "app/assets/javascripts/app.js.erb", "var src='<%= image_path('rails.png') %>';" add_to_config "config.assets.precompile = %w{app.js}" precompile! - assert_match 'src="/sub/uri/assets/rails.png"', File.read(Dir["#{app_path}/public/assets/app-*.js"].first) + assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first) end test "assets:cache:clean should clean cache" do diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index b11fd55170..09aba1c2e9 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -489,7 +489,7 @@ module ApplicationTests test "valid timezone is setup correctly" do add_to_config <<-RUBY config.root = "#{app_path}" - config.time_zone = "Wellington" + config.time_zone = "Wellington" RUBY require "#{app_path}/config/environment" @@ -500,7 +500,7 @@ module ApplicationTests test "raises when an invalid timezone is defined in the config" do add_to_config <<-RUBY config.root = "#{app_path}" - config.time_zone = "That big hill over yonder hill" + config.time_zone = "That big hill over yonder hill" RUBY assert_raise(ArgumentError) do @@ -511,7 +511,7 @@ module ApplicationTests test "valid beginning of week is setup correctly" do add_to_config <<-RUBY config.root = "#{app_path}" - config.beginning_of_week = :wednesday + config.beginning_of_week = :wednesday RUBY require "#{app_path}/config/environment" @@ -522,7 +522,7 @@ module ApplicationTests test "raises when an invalid beginning of week is defined in the config" do add_to_config <<-RUBY config.root = "#{app_path}" - config.beginning_of_week = :invalid + config.beginning_of_week = :invalid RUBY assert_raise(ArgumentError) do @@ -803,5 +803,81 @@ module ApplicationTests assert_not_nil SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/] end + + test "rake_tasks block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + rake_tasks do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + + Rails.application.load_tasks + assert $ran_block + end + + test "generators block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + generators do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_generators + assert $ran_block + end + + test "console block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + console do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_console + assert $ran_block + end + + test "runner block works at instance level" do + $ran_block = false + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + runner do + $ran_block = true + end + end + RUBY + + require "#{app_path}/config/environment" + + assert !$ran_block + Rails.application.load_runner + assert $ran_block + end end end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 3601a58f67..8e76bf27f3 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -216,8 +216,8 @@ module ApplicationTests require "#{app_path}/config/environment" orig_database_url = ENV.delete("DATABASE_URL") orig_rails_env, Rails.env = Rails.env, 'development' - database_url_db_name = File.join(app_path, "db/database_url_db.sqlite3") - ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}" + database_url_db_name = "db/database_url_db.sqlite3" + ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb index 5bfea599e0..f8d8a673ae 100644 --- a/railties/test/application/multiple_applications_test.rb +++ b/railties/test/application/multiple_applications_test.rb @@ -21,6 +21,12 @@ module ApplicationTests assert_equal Rails.application.config.secret_key_base, clone.config.secret_key_base, "The base secret key on the config should be the same" end + def test_inheriting_multiple_times_from_application + new_application_class = Class.new(Rails::Application) + + assert_not_equal Rails.application.object_id, new_application_class.instance.object_id + end + def test_initialization_of_multiple_copies_of_same_application application1 = AppTemplate::Application.new application2 = AppTemplate::Application.new @@ -116,6 +122,26 @@ module ApplicationTests assert_equal 3, $run_count, "There should have been three initializers that incremented the count" end + def test_consoles_run_on_different_applications_go_to_the_same_class + $run_count = 0 + AppTemplate::Application.console { $run_count += 1 } + AppTemplate::Application.new.console { $run_count += 1 } + + assert_equal 0, $run_count, "Without loading the consoles, the count should be 0" + Rails.application.load_console + assert_equal 2, $run_count, "There should have been two consoles that increment the count" + end + + def test_generators_run_on_different_applications_go_to_the_same_class + $run_count = 0 + AppTemplate::Application.generators { $run_count += 1 } + AppTemplate::Application.new.generators { $run_count += 1 } + + assert_equal 0, $run_count, "Without loading the generators, the count should be 0" + Rails.application.load_generators + assert_equal 2, $run_count, "There should have been two generators that increment the count" + end + def test_runners_run_on_different_applications_go_to_the_same_class $run_count = 0 AppTemplate::Application.runner { $run_count += 1 } diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index b2c52a092f..15414db00f 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -17,11 +17,11 @@ module ApplicationTests end def database_url_db_name - File.join(app_path, "db/database_url_db.sqlite3") + "db/database_url_db.sqlite3" end def set_database_url - ENV['DATABASE_URL'] = File.join("sqlite3://:@localhost", database_url_db_name) + ENV['DATABASE_URL'] = "sqlite3:#{database_url_db_name}" # ensure it's using the DATABASE_URL FileUtils.rm_rf("#{app_path}/config/database.yml") end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index a34beaedb3..1273f9d4c2 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -19,14 +19,8 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert console.sandbox? end - def test_debugger_option - console = Rails::Console.new(app, parse_arguments(["--debugger"])) - assert console.debugger? - end - def test_no_options console = Rails::Console.new(app, parse_arguments([])) - assert !console.debugger? assert !console.sandbox? end @@ -36,13 +30,6 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/Loading \w+ environment \(Rails/, output) end - def test_start_with_debugger - rails_console = Rails::Console.new(app, parse_arguments(["--debugger"])) - rails_console.expects(:require_debugger).returns(nil) - - silence_stream(STDOUT) { rails_console.start } - end - def test_start_with_sandbox app.expects(:sandbox=).with(true) FakeConsole.expects(:start) @@ -52,6 +39,25 @@ class Rails::ConsoleTest < ActiveSupport::TestCase assert_match(/Loading \w+ environment in sandbox \(Rails/, output) end + if RUBY_VERSION < '2.0.0' + def test_debugger_option + console = Rails::Console.new(app, parse_arguments(["--debugger"])) + assert console.debugger? + end + + def test_no_options_does_not_set_debugger_flag + console = Rails::Console.new(app, parse_arguments([])) + assert !console.debugger? + end + + def test_start_with_debugger + rails_console = Rails::Console.new(app, parse_arguments(["--debugger"])) + rails_console.expects(:require_debugger).returns(nil) + + silence_stream(STDOUT) { rails_console.start } + end + end + def test_console_with_environment start ["-e production"] assert_match(/\sproduction\s/, output) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 8e1aeddb2b..007dd886da 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -237,6 +237,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_if_skip_sprockets_is_given run_generator [destination_root, "--skip-sprockets"] + assert_no_file "config/initializers/assets.rb" assert_file "config/application.rb" do |content| assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) end @@ -252,7 +253,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/config\.assets\.digest = true/, content) assert_no_match(/config\.assets\.js_compressor = :uglifier/, content) assert_no_match(/config\.assets\.css_compressor = :sass/, content) - assert_no_match(/config\.assets\.version = '1\.0'/, content) end end @@ -305,14 +305,17 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /gem 'jbuilder'/ end - def test_inclusion_of_debugger + def test_inclusion_of_a_debugger run_generator if defined?(JRUBY_VERSION) assert_file "Gemfile" do |content| + assert_no_match(/byebug/, content) assert_no_match(/debugger/, content) end - else + elsif RUBY_VERSION < '2.0.0' assert_file "Gemfile", /# gem 'debugger'/ + else + assert_file "Gemfile", /# gem 'byebug'/ end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 7a2701f813..853af80111 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -64,14 +64,17 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/ end - def test_inclusion_of_debugger + def test_inclusion_of_a_debugger run_generator [destination_root, '--full'] if defined?(JRUBY_VERSION) assert_file "Gemfile" do |content| + assert_no_match(/byebug/, content) assert_no_match(/debugger/, content) end - else + elsif RUBY_VERSION < '2.0.0' assert_file "Gemfile", /# gem 'debugger'/ + else + assert_file "Gemfile", /# gem 'byebug'/ end end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 26e56a162c..3c1123b53d 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -160,13 +160,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase Unknown::Generators.send :remove_const, :ActiveModel end - def test_new_hash_style - run_generator - assert_file "app/controllers/users_controller.rb" do |content| - assert_match(/render action: 'new'/, content) - end - end - def test_model_name_option run_generator ["Admin::User", "--model-name=User"] assert_file "app/controllers/admin/users_controller.rb" do |content| diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index 4cbd4822be..a458240d2f 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -72,6 +72,14 @@ module RailtiesTest assert $to_prepare end + test "railtie have access to application in before_configuration callbacks" do + $after_initialize = false + class Foo < Rails::Railtie ; config.before_configuration { $before_configuration = Rails.root.to_path } ; end + assert_not $before_configuration + require "#{app_path}/config/environment" + assert_equal app_path, $before_configuration + end + test "railtie can add after_initialize callbacks" do $after_initialize = false class Foo < Rails::Railtie ; config.after_initialize { $after_initialize = true } ; end |