diff options
39 files changed, 493 insertions, 244 deletions
@@ -7,11 +7,11 @@ gem 'rack-cache', '~> 1.2' gem 'bcrypt-ruby', '~> 3.0.0' gem 'jquery-rails', '~> 2.2.0' gem 'turbolinks' -gem 'coffee-rails', '~> 4.0.0.beta1' +gem 'coffee-rails', '~> 4.0.0' # This needs to be with require false to avoid # it being automatically loaded by sprockets -gem 'uglifier', '~> 1.3', require: false +gem 'uglifier', '>= 1.3.0', require: false group :doc do gem 'sdoc' diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 88cdd53336..cbf5b66003 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,20 @@ ## Rails 4.0.0 (unreleased) ## +* Add support for extracting the port from the `:host` option passed to `url_for`. + + *Andrew White* + +* Add support for removing the subdomain from a url by passing `nil`, `false` or `''`. + Fixes #10180. + + *Derek Watson + Andrew White* + +* Element of the collection for `options_from_collection_for_select` helper can + optionally contain html attributes as the last element of the array as + `options_for_select` helper. + + *Vasiliy Ermolovich* + * Fix explicit names on multiple file fields. If a file field tag has the multiple option, it is turned into an array field (appending `[]`), but if an explicit name is passed to `file_field` the `[]` is not diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index bac994496e..8e7bdf620e 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -309,6 +309,7 @@ module AbstractController RUBY when Proc define_method :_layout_from_proc, &_layout + protected :_layout_from_proc <<-RUBY result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'}) return #{default_behavior} if result.nil? diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index ab5399c8ea..6f5a52c568 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -4,7 +4,9 @@ require 'active_support/core_ext/hash/slice' module ActionDispatch module Http module URL - IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/ + PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ mattr_accessor :tld_length self.tld_length = 1 @@ -28,6 +30,7 @@ module ActionDispatch end def url_for(options = {}) + options = options.dup path = options.delete(:script_name).to_s.chomp("/") path << options.delete(:path).to_s @@ -59,15 +62,20 @@ module ActionDispatch result = "" unless options[:only_path] - protocol = extract_protocol(options) - unless options[:protocol] == false - result << protocol - result << ":" unless result.match(%r{:|//}) + if match = options[:host].match(HOST_REGEXP) + options[:protocol] ||= match[1] unless options[:protocol] == false + options[:host] = match[2] + options[:port] = match[3] unless options.key?(:port) end - result << "//" unless result.match("//") + + options[:protocol] = normalize_protocol(options) + options[:host] = normalize_host(options) + options[:port] = normalize_port(options) + + result << options[:protocol] result << rewrite_authentication(options) - result << host_or_subdomain_and_domain(options) - result << ":#{options.delete(:port)}" if options[:port] + result << options[:host] + result << ":#{options[:port]}" if options[:port] end result end @@ -76,6 +84,10 @@ module ActionDispatch host && IP_HOST_REGEXP !~ host end + def same_host?(options) + (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil? + end + def rewrite_authentication(options) if options[:user] && options[:password] "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@" @@ -84,29 +96,47 @@ module ActionDispatch end end - # Extracts protocol http:// or https:// from options[:host] - # needs to be called whether the :protocol is being used or not - def extract_protocol(options) - if options[:host] && match = options[:host].match(/(^.*:\/\/)(.*)/) - options[:protocol] ||= match[1] - options[:host] = match[2] + def normalize_protocol(options) + case options[:protocol] + when nil + "http://" + when false, "//" + "//" + when PROTOCOL_REGEXP + "#{$1}://" + else + raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}" end - options[:protocol] || "http" end - def host_or_subdomain_and_domain(options) - return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?) + def normalize_host(options) + return options[:host] if !named_host?(options[:host]) || same_host?(options) tld_length = options[:tld_length] || @@tld_length host = "" - unless options[:subdomain] == false - host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param - host << "." + if options[:subdomain] == true || !options.key?(:subdomain) + host << extract_subdomain(options[:host], tld_length).to_param + elsif options[:subdomain].present? + host << options[:subdomain].to_param end + host << "." unless host.empty? host << (options[:domain] || extract_domain(options[:host], tld_length)) host end + + def normalize_port(options) + return nil if options[:port].nil? || options[:port] == false + + case options[:protocol] + when "//" + nil + when "https://" + options[:port].to_i == 443 ? nil : options[:port] + else + options[:port].to_i == 80 ? nil : options[:port] + end + end end def initialize(env) diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 6fda085681..50e1853094 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -102,6 +102,10 @@ module ActionDispatch value === request.send(method).to_s when Array value.include?(request.send(method)) + when TrueClass + request.send(method).present? + when FalseClass + request.send(method).blank? else value === request.send(method) end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index d48a83e6c6..342b6ec23d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -170,9 +170,10 @@ module ActionDispatch def call(t, args) if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t) - @options.merge!(t.url_options) if t.respond_to?(:url_options) - @options[:path] = optimized_helper(args) - ActionDispatch::Http::URL.url_for(@options) + options = @options.dup + options.merge!(t.url_options) if t.respond_to?(:url_options) + options[:path] = optimized_helper(args) + ActionDispatch::Http::URL.url_for(options) else super end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 7e65ebb4e4..719c9c09b5 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -380,7 +380,7 @@ module ActionView # should produce the desired results. def options_from_collection_for_select(collection, value_method, text_method, selected = nil) options = collection.map do |element| - [value_for_collection(element, text_method), value_for_collection(element, value_method)] + [value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)] end selected, disabled = extract_selected_and_disabled(selected) select_deselect = { diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index 92baad4523..4a05c00f8b 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -8,6 +8,8 @@ module AbstractControllerTests include AbstractController::Rendering include AbstractController::Layouts + abstract! + self.view_paths = [ActionView::FixtureResolver.new( "layouts/hello.erb" => "With String <%= yield %>", "layouts/hello_override.erb" => "With Override <%= yield %>", @@ -251,6 +253,10 @@ module AbstractControllerTests assert_equal "Hello nil!", controller.response_body end + test "when layout is specified as a proc, do not leak any methods into controller's action_methods" do + assert_equal Set.new(['index']), WithProc.action_methods + end + test "when layout is specified as a proc, call it and use the layout returned" do controller = WithProc.new controller.process(:index) diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index ba24e7fac5..088ad73f2f 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -89,6 +89,13 @@ module AbstractController ) end + def test_subdomain_may_be_removed_with_blank_string + W.default_url_options[:host] = 'api.basecamphq.com' + assert_equal('http://basecamphq.com/c/a/i', + W.new.url_for(:subdomain => '', :controller => 'c', :action => 'a', :id => 'i') + ) + end + def test_multiple_subdomains_may_be_removed W.default_url_options[:host] = 'mobile.www.api.basecamphq.com' assert_equal('http://basecamphq.com/c/a/i', diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 29703dd5b1..afd456e8e5 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3322,6 +3322,10 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest end get '/' => ok, :as => :alternate_root, :constraints => { :port => 8080 } + + get '/search' => ok, :constraints => { :subdomain => false } + + get '/logs' => ok, :constraints => { :subdomain => true } end end @@ -3348,6 +3352,24 @@ class TestUrlConstraints < ActionDispatch::IntegrationTest get 'http://www.example.com:8080/' assert_response :success end + + test "false constraint expressions check for absence of values" do + get 'http://example.com/search' + assert_response :success + assert_equal 'http://example.com/search', search_url + + get 'http://api.example.com/search' + assert_response :not_found + end + + test "true constraint expressions check for presence of values" do + get 'http://api.example.com/logs' + assert_response :success + assert_equal 'http://api.example.com/logs', logs_url + + get 'http://example.com/logs' + assert_response :not_found + end end class TestInvalidUrls < ActionDispatch::IntegrationTest diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb index 4123529092..f919592d24 100644 --- a/actionpack/test/dispatch/url_generation_test.rb +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -56,6 +56,47 @@ module TestUrlGeneration test "formatting host when protocol is present" do assert_equal "http://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: "http://") end + + test "default ports are removed from the host" do + assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:80", protocol: "http://") + assert_equal "https://www.example.com/foo", foo_url(host: "www.example.com:443", protocol: "https://") + end + + test "port is extracted from the host" do + assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "http://") + end + + test "port option overides the host" do + assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: 8080) + end + + test "port option disables the host when set to nil" do + assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: nil) + end + + test "port option disables the host when set to false" do + assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: false) + end + + test "keep subdomain when key is true" do + assert_equal "http://www.example.com/foo", foo_url(subdomain: true) + end + + test "keep subdomain when key is missing" do + assert_equal "http://www.example.com/foo", foo_url + end + + test "omit subdomain when key is nil" do + assert_equal "http://example.com/foo", foo_url(subdomain: nil) + end + + test "omit subdomain when key is false" do + assert_equal "http://example.com/foo", foo_url(subdomain: false) + end + + test "omit subdomain when key is blank" do + assert_equal "http://example.com/foo", foo_url(subdomain: "") + end end end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 94ae8549f7..1715902927 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -100,6 +100,13 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_collection_options_with_element_attributes + assert_dom_equal( + "<option value=\"USA\" class=\"bold\">USA</option>", + options_from_collection_for_select([[ "USA", "USA", { :class => 'bold' } ]], :first, :second) + ) + end + def test_string_options_for_select options = "<option value=\"Denmark\">Denmark</option><option value=\"USA\">USA</option><option value=\"Sweden\">Sweden</option>" assert_dom_equal( diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 61c217857d..52300fd53f 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 4.0.0 (unreleased) ## +* `0x` prefix must be added when assigning hexadecimal string into `bit` column in PostgreSQL. + + *kennyj* + * Added Statement Cache to allow the caching of a single statement. The cache works by duping the relation returned from yielding a statement, which allows skipping the AST building phase for following executes. The cache returns results in array format. diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 3e3475f709..ce403d7a36 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -24,6 +24,6 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'activemodel', version - s.add_dependency 'arel', '~> 4.0.0.beta2' - s.add_dependency 'activerecord-deprecated_finders', '~> 0.0.3' + s.add_dependency 'arel', '~> 4.0.0' + s.add_dependency 'activerecord-deprecated_finders', '~> 1.0.1' end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 5789a2ae5f..a9ef11aa83 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -28,8 +28,10 @@ module ActiveRecord def string_to_bit(value) case value - when /^[01]*$/ then value # Bit-string notation - when /^[0-9A-F]*$/i then value.hex.to_s(2) # Hexadecimal notation + when /^0x/i + value[2..-1].hex.to_s(2) # Hexadecimal notation + else + value # Bit-string notation end end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 15736575a2..e65dab07ba 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -1,22 +1,22 @@ require 'active_support/lazy_load_hooks' +require 'active_record/explain_registry' module ActiveRecord module Explain - # Relation#explain needs to be able to collect the queries. + # Executes the block with the collect flag enabled. Queries are collected + # asynchronously by the subscriber and returned. def collecting_queries_for_explain # :nodoc: - current = Thread.current - original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] + ExplainRegistry.collect = true yield - return current[:available_queries_for_explain] + ExplainRegistry.queries ensure - # Note that the return value above does not depend on this assignment. - current[:available_queries_for_explain] = original + ExplainRegistry.reset end # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. # Returns a formatted string ready to be logged. def exec_explain(queries) # :nodoc: - str = queries && queries.map do |sql, bind| + str = queries.map do |sql, bind| [].tap do |msg| msg << "EXPLAIN for: #{sql}" unless bind.empty? @@ -31,6 +31,7 @@ module ActiveRecord def str.inspect self end + str end end diff --git a/activerecord/lib/active_record/explain_registry.rb b/activerecord/lib/active_record/explain_registry.rb new file mode 100644 index 0000000000..f5cd57e075 --- /dev/null +++ b/activerecord/lib/active_record/explain_registry.rb @@ -0,0 +1,30 @@ +require 'active_support/per_thread_registry' + +module ActiveRecord + # This is a thread locals registry for EXPLAIN. For example + # + # ActiveRecord::ExplainRegistry.queries + # + # returns the collected queries local to the current thread. + # + # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt> + # for further details. + class ExplainRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :queries, :collect + + def initialize + reset + end + + def collect? + @collect + end + + def reset + @collect = false + @queries = [] + end + end +end diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index 0f927496fb..a3bc56d600 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -1,4 +1,5 @@ require 'active_support/notifications' +require 'active_record/explain_registry' module ActiveRecord class ExplainSubscriber # :nodoc: @@ -7,8 +8,8 @@ module ActiveRecord end def finish(name, id, payload) - if queries = Thread.current[:available_queries_for_explain] - queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload) + if ExplainRegistry.collect? && !ignore_payload?(payload) + ExplainRegistry.queries << payload.values_at(:sql, :binds) end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 2f35b2fbf6..fb1bab6dcc 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -156,7 +156,7 @@ db_namespace = namespace :db do begin puts ActiveRecord::Tasks::DatabaseTasks.collation_current rescue NoMethodError - $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch' + $stderr.puts 'Sorry, your database adapter is not supported yet. Feel free to submit a patch.' end end @@ -170,7 +170,7 @@ db_namespace = namespace :db do pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Migrator.migrations_paths).pending_migrations if pending_migrations.any? - puts "You have #{pending_migrations.size} pending migrations:" + puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" pending_migrations.each do |pending_migration| puts ' %4d %s' % [pending_migration.version, pending_migration.name] end @@ -241,7 +241,7 @@ db_namespace = namespace :db do if File.exists?(file) load(file) else - abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded} + abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} end end diff --git a/activerecord/lib/active_record/runtime_registry.rb b/activerecord/lib/active_record/runtime_registry.rb index 17890dd29f..63e6738622 100644 --- a/activerecord/lib/active_record/runtime_registry.rb +++ b/activerecord/lib/active_record/runtime_registry.rb @@ -1,7 +1,7 @@ require 'active_support/per_thread_registry' module ActiveRecord - # This is a thread locals registry for Active Record. For example + # This is a thread locals registry for Active Record. For example: # # ActiveRecord::RuntimeRegistry.connection_handler # @@ -9,7 +9,7 @@ module ActiveRecord # # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt> # for further details. - class RuntimeRegistry + class RuntimeRegistry # :nodoc: extend ActiveSupport::PerThreadRegistry attr_accessor :connection_handler, :sql_runtime, :connection_id diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 13731beb01..8c17372286 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -545,7 +545,7 @@ _SQL def test_update_bit_string new_bit_string = '11111111' - new_bit_string_varying = 'FF' + new_bit_string_varying = '0xFF' assert @first_bit_string.bit_string = new_bit_string assert @first_bit_string.bit_string_varying = new_bit_string_varying assert @first_bit_string.save @@ -553,6 +553,12 @@ _SQL assert_equal @first_bit_string.bit_string, new_bit_string assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying end + + def test_invalid_hex_string + new_bit_string = 'FF' + @first_bit_string.bit_string = new_bit_string + assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save } + end def test_update_oid new_value = 567890 diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 619d581d5f..0b61f61572 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -22,13 +22,6 @@ module ActiveRecord assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain assert_match %(Seq Scan on audit_logs), explain end - - def test_dont_explain_for_set_search_path - queries = Thread.current[:available_queries_for_explain] = [] - ActiveRecord::Base.connection.schema_search_path = "public" - assert queries.empty? - end - end end end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index b425967678..fb53a92c89 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -1,55 +1,54 @@ require 'cases/helper' +require 'active_record/explain_subscriber' +require 'active_record/explain_registry' if ActiveRecord::Base.connection.supports_explain? class ExplainSubscriberTest < ActiveRecord::TestCase SUBSCRIBER = ActiveRecord::ExplainSubscriber.new - def test_collects_nothing_if_available_queries_for_explain_is_nil - with_queries(nil) do - SUBSCRIBER.finish(nil, nil, {}) - assert_nil Thread.current[:available_queries_for_explain] - end + def setup + ActiveRecord::ExplainRegistry.reset + ActiveRecord::ExplainRegistry.collect = true end def test_collects_nothing_if_the_payload_has_an_exception - with_queries([]) do |queries| - SUBSCRIBER.finish(nil, nil, :exception => Exception.new) - assert queries.empty? - end + SUBSCRIBER.finish(nil, nil, exception: Exception.new) + assert queries.empty? end def test_collects_nothing_for_ignored_payloads - with_queries([]) do |queries| - ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| - SUBSCRIBER.finish(nil, nil, :name => ip) - end - assert queries.empty? + ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| + SUBSCRIBER.finish(nil, nil, name: ip) end + assert queries.empty? + end + + def test_collects_nothing_if_collect_is_false + ActiveRecord::ExplainRegistry.collect = false + SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select 1 from users', binds: [1, 2]) + assert queries.empty? end def test_collects_pairs_of_queries_and_binds sql = 'select 1 from users' binds = [1, 2] - with_queries([]) do |queries| - SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => sql, :binds => binds) - assert_equal 1, queries.size - assert_equal sql, queries[0][0] - assert_equal binds, queries[0][1] - end + SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: sql, binds: binds) + assert_equal 1, queries.size + assert_equal sql, queries[0][0] + assert_equal binds, queries[0][1] end - def test_collects_nothing_if_unexplained_sqls - with_queries([]) do |queries| - SUBSCRIBER.finish(nil, nil, :name => 'SQL', :sql => 'SHOW max_identifier_length') - assert queries.empty? - end + def test_collects_nothing_if_the_statement_is_not_whitelisted + SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'SHOW max_identifier_length') + assert queries.empty? + end + + def teardown + ActiveRecord::ExplainRegistry.reset end - def with_queries(queries) - Thread.current[:available_queries_for_explain] = queries - yield queries - ensure - Thread.current[:available_queries_for_explain] = nil + def queries + ActiveRecord::ExplainRegistry.queries end end end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 545a9ec0af..84a03825dc 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,11 @@ ## Rails 4.0.0 (unreleased) ## +* An `ActiveSupport::Subscriber` class has been extracted from + `ActiveSupport::LogSubscriber`, allowing you to use the event attachment + API for other kinds of subscribers. + + *Daniel Schierbeck* + * `Class#class_attribute` accepts an `instance_predicate` option which defaults to `true`. If set to `false` the predicate method will not be defined. diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 6bfac15289..2368e5ebd4 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -522,7 +522,7 @@ module ActiveSupport def handle_expired_entry(entry, key, options) if entry && entry.expired? race_ttl = options[:race_condition_ttl].to_i - if race_ttl && (Time.now - entry.expires_at <= race_ttl) + if race_ttl && (Time.now.to_f - entry.expires_at <= race_ttl) # When an entry has :race_condition_ttl defined, put the stale entry back into the cache # for a brief period while the entry is begin recalculated. entry.expires_at = Time.now + race_ttl @@ -562,38 +562,38 @@ module ActiveSupport # +:compress+, +:compress_threshold+, and +:expires_in+. def initialize(value, options = {}) if should_compress?(value, options) - @v = compress(value) - @c = true + @value = compress(value) + @compressed = true else - @v = value - end - if expires_in = options[:expires_in] - @x = (Time.now + expires_in).to_i + @value = value end + @created_at = Time.now.to_f + @expires_in = options[:expires_in] + @expires_in = @expires_in.to_f if @expires_in end def value - convert_version_3_entry! if defined?(@value) - compressed? ? uncompress(@v) : @v + convert_version_4beta1_entry! if defined?(@v) + compressed? ? uncompress(@value) : @value end # Check if the entry is expired. The +expires_in+ parameter can override # the value set when the entry was created. def expired? - convert_version_3_entry! if defined?(@value) - if defined?(@x) - @x && @x < Time.now.to_i - else - false - end + convert_version_4beta1_entry! if defined?(@value) + @expires_in && @created_at + @expires_in <= Time.now.to_f end def expires_at - Time.at(@x) if defined?(@x) + @expires_in ? @created_at + @expires_in : nil end def expires_at=(value) - @x = value.to_i + if value + @expires_in = value.to_f - @created_at + else + @expires_in = nil + end end # Returns the size of the cached value. This could be less than @@ -606,9 +606,9 @@ module ActiveSupport when NilClass 0 when String - @v.bytesize + @value.bytesize else - @s = Marshal.dump(@v).bytesize + @s = Marshal.dump(@value).bytesize end end end @@ -616,12 +616,12 @@ module ActiveSupport # Duplicate the value in a class. This is used by cache implementations that don't natively # serialize entries to protect against accidental cache modifications. def dup_value! - convert_version_3_entry! if defined?(@value) - if @v && !compressed? && !(@v.is_a?(Numeric) || @v == true || @v == false) - if @v.is_a?(String) - @v = @v.dup + convert_version_4beta1_entry! if defined?(@v) + if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) + if @value.is_a?(String) + @value = @value.dup else - @v = Marshal.load(Marshal.dump(@v)) + @value = Marshal.load(Marshal.dump(@value)) end end end @@ -637,7 +637,7 @@ module ActiveSupport end def compressed? - defined?(@c) ? @c : false + defined?(@compressed) ? @compressed : false end def compress(value) @@ -650,19 +650,19 @@ module ActiveSupport # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue # to ensure that cache entries created under the old version still work with the new class definition. - def convert_version_3_entry! - if defined?(@value) - @v = @value - remove_instance_variable(:@value) + def convert_version_4beta1_entry! + if defined?(@v) + @value = @v + remove_instance_variable(:@v) end - if defined?(@compressed) - @c = @compressed - remove_instance_variable(:@compressed) + if defined?(@c) + @compressed = @c + remove_instance_variable(:@c) end - if defined?(@expires_in) && defined?(@created_at) && @expires_in && @created_at - @x = (@created_at + @expires_in).to_i - remove_instance_variable(:@created_at) - remove_instance_variable(:@expires_in) + if defined?(@x) && @x + @created_at ||= Time.now.to_f + @expires_in = @x - @created_at + remove_instance_variable(:@x) end end end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index db5f228a70..fb42c4a41e 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -8,6 +8,23 @@ module ActiveSupport # duration of a block. Repeated calls to the cache for the same key will hit the # in-memory cache for faster access. module LocalCache + # Class for storing and registering the local caches. + class LocalCacheRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def cache_for(local_cache_key) + @registry[local_cache_key] + end + + def set_cache_for(local_cache_key, value) + @registry[local_cache_key] = value + end + end + # Simple memory backed cache. This cache is not thread safe and is intended only # for serving as a temporary memory cache for a single thread. class LocalStore < Store @@ -41,24 +58,18 @@ module ActiveSupport # Use a local cache for the duration of block. def with_local_cache - save_val = Thread.current[thread_local_key] - begin - Thread.current[thread_local_key] = LocalStore.new - yield - ensure - Thread.current[thread_local_key] = save_val - end + use_temporary_local_cache(LocalStore.new) { yield } end #-- # This class wraps up local storage for middlewares. Only the middleware method should # construct them. class Middleware # :nodoc: - attr_reader :name, :thread_local_key + attr_reader :name, :local_cache_key - def initialize(name, thread_local_key) + def initialize(name, local_cache_key) @name = name - @thread_local_key = thread_local_key + @local_cache_key = local_cache_key @app = nil end @@ -68,10 +79,10 @@ module ActiveSupport end def call(env) - Thread.current[thread_local_key] = LocalStore.new + LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new) @app.call(env) ensure - Thread.current[thread_local_key] = nil + LocalCacheRegistry.set_cache_for(local_cache_key, nil) end end @@ -80,7 +91,7 @@ module ActiveSupport def middleware @middleware ||= Middleware.new( "ActiveSupport::Cache::Strategy::LocalCache", - thread_local_key) + local_cache_key) end def clear(options = nil) # :nodoc: @@ -95,29 +106,13 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: value = bypass_local_cache{super} - if local_cache - local_cache.mute do - if value - local_cache.write(name, value, options) - else - local_cache.delete(name, options) - end - end - end + increment_or_decrement(value, name, amount, options) value end def decrement(name, amount = 1, options = nil) # :nodoc: value = bypass_local_cache{super} - if local_cache - local_cache.mute do - if value - local_cache.write(name, value, options) - else - local_cache.delete(name, options) - end - end - end + increment_or_decrement(value, name, amount, options) value end @@ -146,21 +141,37 @@ module ActiveSupport end private - def thread_local_key - @thread_local_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym + def increment_or_decrement(value, name, amount, options) + if local_cache + local_cache.mute do + if value + local_cache.write(name, value, options) + else + local_cache.delete(name, options) + end + end + end + end + + def local_cache_key + @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym end def local_cache - Thread.current[thread_local_key] + LocalCacheRegistry.cache_for(local_cache_key) end def bypass_local_cache - save_cache = Thread.current[thread_local_key] + use_temporary_local_cache(nil) { yield } + end + + def use_temporary_local_cache(temporary_cache) + save_cache = LocalCacheRegistry.cache_for(local_cache_key) begin - Thread.current[thread_local_key] = nil + LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache) yield ensure - Thread.current[thread_local_key] = save_cache + LocalCacheRegistry.set_cache_for(local_cache_key, save_cache) end end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index fa1dbfdf06..34859617c9 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -32,7 +32,7 @@ class Class def cattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil @@ -93,7 +93,7 @@ class Class def cattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) unless defined? @@#{sym} @@#{sym} = nil diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index c4b64bd1a6..e95dc5a866 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/class/attribute' +require 'active_support/subscriber' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume @@ -33,7 +34,7 @@ module ActiveSupport # Log subscriber also has some helpers to deal with logging and automatically # flushes all logs when the request finishes (via action_dispatch.callback # notification) in a Rails environment. - class LogSubscriber + class LogSubscriber < Subscriber # Embed in a String to clear all previous ANSI sequences. CLEAR = "\e[0m" BOLD = "\e[1m" @@ -60,18 +61,8 @@ module ActiveSupport attr_writer :logger - def attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications) - log_subscribers << log_subscriber - - log_subscriber.public_methods(false).each do |event| - next if %w{ start finish }.include?(event.to_s) - - notifier.subscribe("#{event}.#{namespace}", log_subscriber) - end - end - def log_subscribers - @@log_subscribers ||= [] + subscribers end # Flush all log_subscribers' logger. @@ -80,39 +71,18 @@ module ActiveSupport end end - def initialize - @queue_key = [self.class.name, object_id].join "-" - super - end - def logger LogSubscriber.logger end def start(name, id, payload) - return unless logger - - e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) - parent = event_stack.last - parent << e if parent - - event_stack.push e + super if logger end def finish(name, id, payload) - return unless logger - - finished = Time.now - event = event_stack.pop - event.end = finished - event.payload.merge!(payload) - - method = name.split('.').first - begin - send(method, event) - rescue Exception => e - logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" - end + super if logger + rescue Exception => e + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" end protected @@ -135,11 +105,5 @@ module ActiveSupport bold = bold ? BOLD : "" "#{bold}#{color}#{text}#{CLEAR}" end - - private - - def event_stack - Thread.current[@queue_key] ||= [] - end end end diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb new file mode 100644 index 0000000000..ab76cf5c85 --- /dev/null +++ b/activesupport/lib/active_support/subscriber.rb @@ -0,0 +1,75 @@ +module ActiveSupport + # ActiveSupport::Subscriber is an object set to consume + # ActiveSupport::Notifications. The subscriber dispatches notifications to + # a registered object based on its given namespace. + # + # An example would be Active Record subscriber responsible for collecting + # statistics about queries: + # + # module ActiveRecord + # class StatsSubscriber < ActiveSupport::Subscriber + # def sql(event) + # Statsd.timing("sql.#{event.payload[:name]}", event.duration) + # end + # end + # end + # + # And it's finally registered as: + # + # ActiveRecord::StatsSubscriber.attach_to :active_record + # + # Since we need to know all instance methods before attaching the log + # subscriber, the line above should be called after your subscriber definition. + # + # After configured, whenever a "sql.active_record" notification is published, + # it will properly dispatch the event (ActiveSupport::Notifications::Event) to + # the +sql+ method. + class Subscriber + class << self + + # Attach the subscriber to a namespace. + def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications) + subscribers << subscriber + + subscriber.public_methods(false).each do |event| + next if %w{ start finish }.include?(event.to_s) + + notifier.subscribe("#{event}.#{namespace}", subscriber) + end + end + + def subscribers + @@subscribers ||= [] + end + end + + def initialize + @queue_key = [self.class.name, object_id].join "-" + super + end + + def start(name, id, payload) + e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) + parent = event_stack.last + parent << e if parent + + event_stack.push e + end + + def finish(name, id, payload) + finished = Time.now + event = event_stack.pop + event.end = finished + event.payload.merge!(payload) + + method = name.split('.').first + send(method, event) + end + + private + + def event_stack + Thread.current[@queue_key] ||= [] + end + end +end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 571be5f296..bcc200cf33 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -943,29 +943,28 @@ class CacheEntryTest < ActiveSupport::TestCase assert_equal value.bytesize, entry.size end - def test_restoring_version_3_entries - version_3_entry = ActiveSupport::Cache::Entry.allocate - version_3_entry.instance_variable_set(:@value, "hello") - version_3_entry.instance_variable_set(:@created_at, Time.now - 60) - entry = Marshal.load(Marshal.dump(version_3_entry)) + def test_restoring_version_4beta1_entries + version_4beta1_entry = ActiveSupport::Cache::Entry.allocate + version_4beta1_entry.instance_variable_set(:@v, "hello") + version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60) + entry = Marshal.load(Marshal.dump(version_4beta1_entry)) assert_equal "hello", entry.value assert_equal false, entry.expired? end - def test_restoring_compressed_version_3_entries - version_3_entry = ActiveSupport::Cache::Entry.allocate - version_3_entry.instance_variable_set(:@value, Zlib::Deflate.deflate(Marshal.dump("hello"))) - version_3_entry.instance_variable_set(:@compressed, true) - entry = Marshal.load(Marshal.dump(version_3_entry)) + def test_restoring_compressed_version_4beta1_entries + version_4beta1_entry = ActiveSupport::Cache::Entry.allocate + version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello"))) + version_4beta1_entry.instance_variable_set(:@c, true) + entry = Marshal.load(Marshal.dump(version_4beta1_entry)) assert_equal "hello", entry.value end - def test_restoring_expired_version_3_entries - version_3_entry = ActiveSupport::Cache::Entry.allocate - version_3_entry.instance_variable_set(:@value, "hello") - version_3_entry.instance_variable_set(:@created_at, Time.now - 60) - version_3_entry.instance_variable_set(:@expires_in, 58.9) - entry = Marshal.load(Marshal.dump(version_3_entry)) + def test_restoring_expired_version_4beta1_entries + version_4beta1_entry = ActiveSupport::Cache::Entry.allocate + version_4beta1_entry.instance_variable_set(:@v, "hello") + version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1) + entry = Marshal.load(Marshal.dump(version_4beta1_entry)) assert_equal "hello", entry.value assert_equal true, entry.expired? end diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb index 8d827f054e..0d5f39a72b 100644 --- a/activesupport/test/core_ext/class/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb @@ -44,16 +44,18 @@ class ClassAttributeAccessorTest < ActiveSupport::TestCase end def test_should_raise_name_error_if_attribute_name_is_invalid - assert_raises NameError do + exception = assert_raises NameError do Class.new do - cattr_reader "invalid attribute name" + cattr_reader "1nvalid" end end + assert_equal "invalid class attribute name: 1nvalid", exception.message - assert_raises NameError do + exception = assert_raises NameError do Class.new do - cattr_writer "invalid attribute name" + cattr_writer "1nvalid" end end + assert_equal "invalid class attribute name: 1nvalid", exception.message end end diff --git a/guides/source/security.md b/guides/source/security.md index d56ce47b3c..b2d09369e2 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -1,4 +1,4 @@ -Ruby On Rails Security Guide +Ruby on Rails Security Guide ============================ This manual describes common security problems in web applications and how to avoid them with Rails. diff --git a/rails.gemspec b/rails.gemspec index a223ea1413..1993467455 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' - s.files = ['README.rdoc'] + Dir['guides/**/*'] + s.files = ['README.md'] + Dir['guides/**/*'] s.add_dependency 'activesupport', version s.add_dependency 'actionpack', version @@ -25,5 +25,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.rc3' + s.add_dependency 'sprockets-rails', '~> 2.0.0.rc4' end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 4fd1f00294..a7c2d2d1f1 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -115,7 +115,7 @@ module Rails end def database_gemfile_entry - options[:skip_active_record] ? "" : + options[:skip_active_record] ? "" : <<-GEMFILE.strip_heredoc.chomp # Use #{options[:database]} as the database for ActiveRecord gem '#{gem_for_database}' @@ -135,13 +135,11 @@ module Rails <<-GEMFILE.strip_heredoc gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}' gem 'arel', github: 'rails/arel' - gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc gem 'rails', github: 'rails/rails' gem 'arel', github: 'rails/arel' - gem 'activerecord-deprecated_finders', github: 'rails/activerecord-deprecated_finders' GEMFILE else <<-GEMFILE.strip_heredoc @@ -190,15 +188,15 @@ module Rails gem 'sass-rails', github: 'rails/sass-rails' # Use Uglifier as compressor for JavaScript assets - gem 'uglifier', '~> 1.3' + gem 'uglifier', '>= 1.3.0' GEMFILE else <<-GEMFILE.strip_heredoc # Use SCSS for stylesheets - gem 'sass-rails', '~> 4.0.0.beta1' + gem 'sass-rails', '~> 4.0.0.rc1' # Use Uglifier as compressor for JavaScript assets - gem 'uglifier', '~> 1.3' + gem 'uglifier', '>= 1.3.0' GEMFILE end @@ -221,7 +219,7 @@ module Rails else <<-GEMFILE # Use CoffeeScript for .js.coffee assets and views - gem 'coffee-rails', '~> 4.0.0.beta1' + gem 'coffee-rails', '~> 4.0.0' GEMFILE end 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 85a1b01cc6..1799e823b6 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -14,14 +14,14 @@ <% attributes.each do |attribute| -%> <div class="field"> <% if attribute.password_digest? -%> - <%%= f.label :password %><br /> + <%%= f.label :password %><br> <%%= f.password_field :password %> </div> <div> - <%%= f.label :password_confirmation %><br /> + <%%= f.label :password_confirmation %><br> <%%= f.password_field :password_confirmation %> <% else -%> - <%%= f.label :<%= attribute.name %> %><br /> + <%%= f.label :<%= attribute.name %> %><br> <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> <% end -%> </div> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index d2fd99fdcb..9d778642f2 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -14,18 +14,18 @@ <tbody> <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %> - <tr> + <tr> <% attributes.reject(&:password_digest?).each do |attribute| -%> - <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> + <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td> <% end -%> - <td><%%= link_to 'Show', <%= singular_table_name %> %></td> - <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> - <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> + <td><%%= link_to 'Show', <%= singular_table_name %> %></td> + <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td> + <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td> + </tr> <%% end %> </tbody> </table> -<br /> +<br> <%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %> diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 1842e09bae..ace804ffe6 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -9,14 +9,14 @@ source 'https://rubygems.org' <%= assets_gemfile_entry %> <%= javascript_gemfile_entry -%> +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 1.0.1' + group :doc do # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', require: false end -# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 1.0.1' - # Use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index 22de640236..25372d0a50 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -277,6 +277,30 @@ module ApplicationTests end end + def test_root_path + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render :text => "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + AppTemplate::Application.routes.draw do + get 'foo', :to => 'foo#index' + root :to => 'foo#index' + end + RUBY + + remove_file 'public/index.html' + + get '/' + assert_equal 'foo', last_response.body + end + test 'routes are added and removed when reloading' do app('development') diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb index 0abb2b7578..7348d70c56 100644 --- a/railties/test/railties/generators_test.rb +++ b/railties/test/railties/generators_test.rb @@ -44,7 +44,7 @@ module RailtiesTests Dir.chdir(engine_path) do File.open("Gemfile", "w") do |f| f.write <<-GEMFILE.gsub(/^ {12}/, '') - source "http://rubygems.org" + source "https://rubygems.org" gem 'rails', path: '#{RAILS_FRAMEWORK_ROOT}' gem 'sqlite3' |