diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2012-12-18 16:29:00 +0100 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2012-12-18 16:29:00 +0100 |
commit | 09aeee7247682178868241d446051696570e6e12 (patch) | |
tree | 51f4af1494145068345806ab21d2c38578bf6947 | |
parent | f6bbc3f582bfc16da3acc152c702b04102fcab81 (diff) | |
parent | f6d0eda918756bf56e2c05afcaaabda723487017 (diff) | |
download | rails-09aeee7247682178868241d446051696570e6e12.tar.gz rails-09aeee7247682178868241d446051696570e6e12.tar.bz2 rails-09aeee7247682178868241d446051696570e6e12.zip |
Merge
22 files changed, 374 insertions, 49 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index c3df2ebc0c..d213ab057c 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,9 @@ ## Rails 4.0.0 (unreleased) ## +* Handle `:protocol` option in `stylesheet_link_tag` and `javascript_include_tag` + + *Vasiliy Ermolovich* + * Clear url helper methods when routes are reloaded. *Andrew White* * Fix a bug in `ActionDispatch::Request#raw_post` that caused `env['rack.input']` diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index f356c2a85c..1dc51d62e0 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -87,7 +87,7 @@ module ActionDispatch return false unless @routes_app.respond_to?(:routes) if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error) inspector = ActionDispatch::Routing::RoutesInspector.new - inspector.format(@routes_app.routes.routes).join("\n") + inspector.collect_routes(@routes_app.routes.routes) end end end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb index a357a7ba11..6c903d6a17 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -20,4 +20,6 @@ Routes match in priority from top to bottom </p> -<p><pre><%= @routes %></pre></p> +<%= render layout: "routes/route_wrapper" do %> + <%= render partial: "routes/route", collection: @routes %> +<% end %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb new file mode 100644 index 0000000000..400ae97d22 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route.html.erb @@ -0,0 +1,16 @@ +<tr class='route_row' data-helper='path'> + <td data-route-name='<%= route[:name] %>'> + <% if route[:name].present? %> + <%= route[:name] %><span class='helper'>_path</span> + <% end %> + </td> + <td data-route-verb='<%= route[:verb] %>'> + <%= route[:verb] %> + </td> + <td data-route-path='<%= route[:path] %>'> + <%= route[:path] %> + </td> + <td data-route-reqs='<%= route[:reqs] %>'> + <%= route[:reqs] %> + </td> +</tr> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb new file mode 100644 index 0000000000..dc17cb77ef --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb @@ -0,0 +1,56 @@ +<style type='text/css'> + #route_table td {padding: 0 30px;} + #route_table {margin: 0 auto 0;} +</style> + +<table id='route_table' class='route_table'> + <thead> + <tr> + <th>Helper<br /> + <%= link_to "Path", "#", 'data-route-helper' => '_path', + title: "Returns a relative path (without the http or domain)" %> / + <%= link_to "Url", "#", 'data-route-helper' => '_url', + title: "Returns an absolute url (with the http and domain)" %> + </th> + <th>HTTP Verb</th> + <th>Path</th> + <th>Controller#Action</th> + </tr> + </thead> + <tbody> + <%= yield %> + </tbody> +</table> + +<script type='text/javascript'> + function each(elems, func) { + if (!elems instanceof Array) { elems = [elems]; } + for (var i = elems.length; i--; ) { + func(elems[i]); + } + } + + function setValOn(elems, val) { + each(elems, function(elem) { + elem.innerHTML = val; + }); + } + + function onClick(elems, func) { + each(elems, function(elem) { + elem.onclick = func; + }); + } + + // 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"); + var helperElems = document.querySelectorAll('[data-route-name] span.helper'); + setValOn(helperElems, helperTxt); + }); + } + + setupRouteToggleHelperLinks(); +</script> diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index cf2a117966..11743e36f2 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -53,9 +53,11 @@ module ActionView # def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys + path_options = options.extract!('protocol').symbolize_keys + sources.uniq.map { |source| tag_options = { - "src" => path_to_javascript(source) + "src" => path_to_javascript(source, path_options) }.merge(options) content_tag(:script, "", tag_options) }.join("\n").html_safe @@ -89,11 +91,13 @@ module ActionView # def stylesheet_link_tag(*sources) options = sources.extract_options!.stringify_keys + path_options = options.extract!('protocol').symbolize_keys + sources.uniq.map { |source| tag_options = { "rel" => "stylesheet", "media" => "screen", - "href" => path_to_stylesheet(source) + "href" => path_to_stylesheet(source, path_options) }.merge(options) tag(:link, tag_options) }.join("\n").html_safe diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index eb1a54a81f..82c9d383ac 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -358,6 +358,17 @@ class AssetTagHelperTest < ActionView::TestCase assert javascript_include_tag("prototype").html_safe? end + def test_javascript_include_tag_relative_protocol + @controller.config.asset_host = "assets.example.com" + assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag('prototype', protocol: :relative) + end + + def test_javascript_include_tag_default_protocol + @controller.config.asset_host = "assets.example.com" + @controller.config.default_asset_host_protocol = :relative + assert_dom_equal %(<script src="//assets.example.com/javascripts/prototype.js"></script>), javascript_include_tag('prototype') + end + def test_stylesheet_path StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -398,7 +409,18 @@ class AssetTagHelperTest < ActionView::TestCase end def test_stylesheet_link_tag_should_not_output_the_same_asset_twice - assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam') + assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam') + end + + def test_stylesheet_link_tag_with_relative_protocol + @controller.config.asset_host = "assets.example.com" + assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', protocol: :relative) + end + + def test_stylesheet_link_tag_with_default_protocol + @controller.config.asset_host = "assets.example.com" + @controller.config.default_asset_host_protocol = :relative + assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington') end def test_image_path diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 97b7b8cd1e..e7d3b529fe 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,14 @@ ## Rails 4.0.0 (unreleased) ## +* Added support for `validates_uniqueness_of` in PostgreSQL array columns. + Fixes #8075. + + *Pedro Padron* + +* Allow int4range and int8range columns to be created in PostgreSQL and properly convert to/from database. + + *Alexey Vasiliev aka leopard* + * Do not log the binding values for binary columns. *Matthew M. Boedicker* diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index df23dbfb60..fd36a5b075 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -126,6 +126,7 @@ module ActiveRecord when :hstore then "#{klass}.string_to_hstore(#{var_name})" when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})" when :json then "#{klass}.string_to_json(#{var_name})" + when :intrange then "#{klass}.string_to_intrange(#{var_name})" else var_name end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index c04a799b8d..f7d734a2f1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -92,6 +92,36 @@ module ActiveRecord parse_pg_array(string).map{|val| oid.type_cast val} end + def string_to_intrange(string) + if string.nil? + nil + elsif "empty" == string + (nil..nil) + elsif String === string && (matches = /^(\(|\[)([0-9]+),(\s?)([0-9]+)(\)|\])$/i.match(string)) + lower_bound = ("(" == matches[1] ? (matches[2].to_i + 1) : matches[2].to_i) + upper_bound = (")" == matches[5] ? (matches[4].to_i - 1) : matches[4].to_i) + (lower_bound..upper_bound) + else + string + end + end + + def intrange_to_string(object) + if object.nil? + nil + elsif Range === object + if [object.first, object.last].all? { |el| Integer === el } + "[#{object.first.to_i},#{object.exclude_end? ? object.last.to_i : object.last.to_i + 1})" + elsif [object.first, object.last].all? { |el| NilClass === el } + "empty" + else + nil + end + else + object + end + end + private HstorePair = begin diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 52344f61c0..18ea83ce42 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -168,6 +168,14 @@ module ActiveRecord end end + class IntRange < Type + def type_cast(value) + return if value.nil? + + ConnectionAdapters::PostgreSQLColumn.string_to_intrange value + end + end + class TypeMap def initialize @mapping = {} @@ -269,6 +277,9 @@ module ActiveRecord register_type 'hstore', OID::Hstore.new register_type 'json', OID::Json.new + register_type 'int4range', OID::IntRange.new + alias_type 'int8range', 'int4range' + register_type 'cidr', OID::Cidr.new alias_type 'inet', 'cidr' end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 62a4d76928..c2fcef94da 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -31,6 +31,11 @@ module ActiveRecord when 'json' then super(PostgreSQLColumn.json_to_string(value), column) else super end + when Range + case column.sql_type + when 'int4range', 'int8range' then super(PostgreSQLColumn.intrange_to_string(value), column) + else super + end when IPAddr case column.sql_type when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column) @@ -89,6 +94,11 @@ module ActiveRecord when 'json' then PostgreSQLColumn.json_to_string(value) else super(value, column) end + when Range + case column.sql_type + when 'int4range', 'int8range' then PostgreSQLColumn.intrange_to_string(value) + else super(value, column) + end when IPAddr return super(value, column) unless ['inet','cidr'].include? column.sql_type PostgreSQLColumn.cidr_to_string(value) 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 18bf14d1fb..e10b562fa4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -417,6 +417,14 @@ module ActiveRecord when 0..6; "timestamp(#{precision})" else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6") end + when 'intrange' + return 'int4range' unless limit + + case limit + when 1..4; 'int4range' + when 5..8; 'int8range' + else raise(ActiveRecordError, "No range type has byte size #{limit}. Use a numeric with precision 0 instead.") + end else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e24ee1efdd..d62a375470 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -114,6 +114,9 @@ module ActiveRecord # JSON when /\A'(.*)'::json\z/ $1 + # int4range, int8range + when /\A'(.*)'::int(4|8)range\z/ + $1 # Object identifier types when /\A-?\d+\z/ $1 @@ -209,9 +212,12 @@ module ActiveRecord # UUID type when 'uuid' :uuid - # JSON type - when 'json' - :json + # JSON type + when 'json' + :json + # int4range, int8range types + when 'int4range', 'int8range' + :intrange # Small and big integer types when /^(?:small|big)int$/ :integer @@ -289,6 +295,10 @@ module ActiveRecord column(name, 'json', options) end + def intrange(name, options = {}) + column(name, 'intrange', options) + end + def column(name, type = nil, options = {}) super column = self[name] @@ -329,7 +339,8 @@ module ActiveRecord cidr: { name: "cidr" }, macaddr: { name: "macaddr" }, uuid: { name: "uuid" }, - json: { name: "json" } + json: { name: "json" }, + intrange: { name: "int4range" } } include Quoting diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 5fa6a0b892..1427189851 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -1,10 +1,8 @@ -require 'active_support/core_ext/array/prepend_and_append' - module ActiveRecord module Validations class UniquenessValidator < ActiveModel::EachValidator # :nodoc: def initialize(options) - super(options.reverse_merge(:case_sensitive => true)) + super({ case_sensitive: true }.merge!(options)) end # Unfortunately, we have to tie Uniqueness validators to a class. @@ -15,35 +13,19 @@ module ActiveRecord def validate_each(record, attribute, value) finder_class = find_finder_class_for(record) table = finder_class.arel_table - - coder = record.class.serialized_attributes[attribute.to_s] - - if value && coder - value = coder.dump value - end + value = deserialize_attribute(record, attribute, value) relation = build_relation(finder_class, table, attribute, value) - relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted? - - Array(options[:scope]).each do |scope_item| - reflection = record.class.reflect_on_association(scope_item) - if reflection - scope_value = record.send(reflection.foreign_key) - scope_item = reflection.foreign_key - else - scope_value = record.read_attribute(scope_item) - end - relation = relation.and(table[scope_item].eq(scope_value)) - end - + relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted? + relation = scope_relation(record, table, relation) relation = finder_class.unscoped.where(relation) - - if options[:conditions] - relation = relation.merge(options[:conditions]) - end + relation.merge!(options[:conditions]) if options[:conditions] if relation.exists? - record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value)) + error_options = options.except(:case_sensitive, :scope, :conditions) + error_options[:value] = value + + record.errors.add(attribute, :taken, error_options) end end @@ -58,7 +40,7 @@ module ActiveRecord class_hierarchy = [record.class] while class_hierarchy.first != @klass - class_hierarchy.prepend(class_hierarchy.first.superclass) + class_hierarchy.unshift(class_hierarchy.first.superclass) end class_hierarchy.detect { |klass| !klass.abstract_class? } @@ -71,18 +53,37 @@ module ActiveRecord end column = klass.columns_hash[attribute.to_s] - value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text? + value = klass.connection.type_cast(value, column) + value = value.to_s[0, column.limit] if value && column.limit && column.text? if !options[:case_sensitive] && value && column.text? # will use SQL LOWER function before comparison, unless it detects a case insensitive collation - relation = klass.connection.case_insensitive_comparison(table, attribute, column, value) + klass.connection.case_insensitive_comparison(table, attribute, column, value) else - value = klass.connection.case_sensitive_modifier(value) unless value.nil? - relation = table[attribute].eq(value) + value = klass.connection.case_sensitive_modifier(value) unless value.nil? + table[attribute].eq(value) + end + end + + def scope_relation(record, table, relation) + Array(options[:scope]).each do |scope_item| + if reflection = record.class.reflect_on_association(scope_item) + scope_value = record.send(reflection.foreign_key) + scope_item = reflection.foreign_key + else + scope_value = record.read_attribute(scope_item) + end + relation = relation.and(table[scope_item].eq(scope_value)) end relation end + + def deserialize_attribute(record, attribute, value) + coder = record.class.serialized_attributes[attribute.to_s] + value = coder.dump value if value && coder + value + end end module ClassMethods diff --git a/activerecord/test/cases/adapters/postgresql/intrange_test.rb b/activerecord/test/cases/adapters/postgresql/intrange_test.rb new file mode 100644 index 0000000000..58628cebc8 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/intrange_test.rb @@ -0,0 +1,104 @@ +# encoding: utf-8 + +require "cases/helper" +require 'active_record/base' +require 'active_record/connection_adapters/postgresql_adapter' + +class PostgresqlIntrangesTest < ActiveRecord::TestCase + class IntRangeDataType < ActiveRecord::Base + self.table_name = 'intrange_data_type' + end + + def setup + @connection = ActiveRecord::Base.connection + begin + @connection.create_table('intrange_data_type') do |t| + t.intrange 'int_range', :default => (1..10) + t.intrange 'long_int_range', :limit => 8, :default => (1..100) + end + rescue ActiveRecord::StatementInvalid + return skip "do not test on PG without ranges" + end + @int_range_column = IntRangeDataType.columns.find { |c| c.name == 'int_range' } + @long_int_range_column = IntRangeDataType.columns.find { |c| c.name == 'long_int_range' } + end + + def teardown + @connection.execute 'drop table if exists intrange_data_type' + end + + def test_columns + assert_equal :intrange, @int_range_column.type + assert_equal :intrange, @long_int_range_column.type + end + + def test_type_cast_intrange + assert @int_range_column + assert_equal(true, @int_range_column.has_default?) + assert_equal((1..10), @int_range_column.default) + assert_equal("int4range", @int_range_column.sql_type) + + data = "[1,10)" + hash = @int_range_column.class.string_to_intrange data + assert_equal((1..9), hash) + assert_equal((1..9), @int_range_column.type_cast(data)) + + assert_equal((nil..nil), @int_range_column.type_cast("empty")) + assert_equal((1..5), @int_range_column.type_cast('[1,5]')) + assert_equal((2..4), @int_range_column.type_cast('(1,5)')) + assert_equal((2..39), @int_range_column.type_cast('[2,40)')) + assert_equal((10..20), @int_range_column.type_cast('(9,20]')) + end + + def test_type_cast_long_intrange + assert @long_int_range_column + assert_equal(true, @long_int_range_column.has_default?) + assert_equal((1..100), @long_int_range_column.default) + assert_equal("int8range", @long_int_range_column.sql_type) + end + + def test_rewrite + @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 6)')" + x = IntRangeDataType.first + x.int_range = (1..100) + assert x.save! + end + + def test_select + @connection.execute "insert into intrange_data_type (int_range) VALUES ('(1, 4]')" + x = IntRangeDataType.first + assert_equal((2..4), x.int_range) + end + + def test_empty_range + @connection.execute %q|insert into intrange_data_type (int_range) VALUES('empty')| + x = IntRangeDataType.first + assert_equal((nil..nil), x.int_range) + end + + def test_rewrite_to_nil + @connection.execute %q|insert into intrange_data_type (int_range) VALUES('(1, 4]')| + x = IntRangeDataType.first + x.int_range = nil + assert x.save! + assert_equal(nil, x.int_range) + end + + def test_invalid_intrange + assert IntRangeDataType.create!(int_range: ('a'..'d')) + x = IntRangeDataType.first + assert_equal(nil, x.int_range) + end + + def test_save_empty_range + assert IntRangeDataType.create!(int_range: (nil..nil)) + x = IntRangeDataType.first + assert_equal((nil..nil), x.int_range) + end + + def test_save_invalid_data + assert_raises(ActiveRecord::StatementInvalid) do + IntRangeDataType.create!(int_range: "empty1") + end + end +end
\ No newline at end of file diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 46212e49b6..46e767af1a 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -30,6 +30,11 @@ class ReplyWithTitleObject < Reply def title; ReplyTitle.new; end end +class Employee < ActiveRecord::Base + self.table_name = 'postgresql_arrays' + validates_uniqueness_of :nicknames +end + class UniquenessValidationTest < ActiveRecord::TestCase fixtures :topics, 'warehouse-things', :developers @@ -341,16 +346,28 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert w6.errors[:city].any?, "Should have errors for city" assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city" end - + def test_validate_uniqueness_with_conditions Topic.validates_uniqueness_of(:title, :conditions => Topic.where('approved = ?', true)) Topic.create("title" => "I'm a topic", "approved" => true) Topic.create("title" => "I'm an unapproved topic", "approved" => false) - + t3 = Topic.new("title" => "I'm a topic", "approved" => true) assert !t3.valid?, "t3 shouldn't be valid" - + t4 = Topic.new("title" => "I'm an unapproved topic", "approved" => false) assert t4.valid?, "t4 should be valid" end + + def test_validate_uniqueness_with_array_column + return skip "Uniqueness on arrays has only been tested in PostgreSQL so far." if !current_adapter? :PostgreSQLAdapter + + e1 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [1000, 1200]) + assert e1.persisted?, "Saving e1" + + e2 = Employee.create("nicknames" => ["john", "johnny"], "commission_by_quarter" => [2200]) + assert !e2.persisted?, "e2 shouldn't be valid" + assert e2.errors[:nicknames].any?, "Should have errors for nicknames" + assert_equal ["has already been taken"], e2.errors[:nicknames], "Should have uniqueness message for nicknames" + end end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index ab2a63d3ea..96fef3a831 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,7 +1,7 @@ ActiveRecord::Schema.define do %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings postgresql_uuids - postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type).each do |table_name| + postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent postgresql_json_data_type postgresql_intrange_data_type).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -97,6 +97,16 @@ _SQL ); _SQL end + + if 't' == select_value("select 'int4range'=ANY(select typname from pg_type)") + execute <<_SQL + CREATE TABLE postgresql_intrange_data_type ( + id SERIAL PRIMARY KEY, + int_range int4range, + int_long_range int8range + ); +_SQL + end execute <<_SQL CREATE TABLE postgresql_moneys ( diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 239fc10719..a32f33140b 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,8 +1,14 @@ ## Rails 4.0.0 (unreleased) ## +<<<<<<< HEAD * Add app/models/concerns and app/controllers/concerns to the default directory structure and load path *DHH* See http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns for usage instructions. +======= +* The `rails/info/routes` now correctly formats routing output as an html table. + + *Richard Schneeman* +>>>>>>> f6d0eda918756bf56e2c05afcaaabda723487017 * The `public/index.html` is no longer generated for new projects. Page is replaced by internal `welcome_controller` inside of railties. diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index e296637f39..e650f58d20 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -2,6 +2,7 @@ require 'action_dispatch/routing/inspector' class Rails::InfoController < ActionController::Base # :nodoc: self.view_paths = File.expand_path('../templates', __FILE__) + prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH layout 'application' before_filter :require_local! @@ -15,8 +16,7 @@ class Rails::InfoController < ActionController::Base # :nodoc: end def routes - inspector = ActionDispatch::Routing::RoutesInspector.new - @info = inspector.format(_routes.routes).join("\n") + @routes = ActionDispatch::Routing::RoutesInspector.new.collect_routes(_routes.routes) end protected diff --git a/railties/lib/rails/templates/rails/info/routes.html.erb b/railties/lib/rails/templates/rails/info/routes.html.erb index 890f6f5b03..1ea387c63f 100644 --- a/railties/lib/rails/templates/rails/info/routes.html.erb +++ b/railties/lib/rails/templates/rails/info/routes.html.erb @@ -6,4 +6,7 @@ Routes match in priority from top to bottom </p> -<p><pre><%= @info %></pre></p>
\ No newline at end of file +<%# actionpack/lib/action_dispatch/middleware/templates %> +<%= render layout: "routes/route_wrapper" do %> + <%= render partial: "routes/route", collection: @routes %> +<% end %> diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 08fcddd4bf..a9b237d0a5 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -50,7 +50,7 @@ class InfoControllerTest < ActionController::TestCase test "info controller renders with routes" do get :routes - assert_select 'pre' + assert_response :success end end |