diff options
67 files changed, 598 insertions, 199 deletions
@@ -60,7 +60,7 @@ platforms :ruby do gem 'nokogiri', '>= 1.4.5' # AR - gem 'sqlite3', '~> 1.3.5' + gem 'sqlite3', '~> 1.3.6' group :db do gem 'pg', '>= 0.11.0' diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 4290707a64..ee0e69d87c 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -65,6 +65,7 @@ module ActionController def redirect_to(options = {}, response_status = {}) #:doc: raise ActionControllerError.new("Cannot redirect to nil!") unless options raise AbstractController::DoubleRenderError if response_body + logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger self.status = _extract_redirect_to_status(options, response_status) self.location = _compute_redirect_to_location(options) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 7e33ca2fac..52eb1aa447 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -460,8 +460,11 @@ module ActionView # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags, # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>. - # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this + # + # Options: + # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this # prepends an option with a generic prompt - "Please select" - or the given prompt string. + # * <tt>:divider</tt> - the divider for the options groups. # # Sample usage (Array): # grouped_options = [ @@ -490,15 +493,51 @@ module ActionView # <option value="Canada">Canada</option> # </optgroup> # + # Sample usage (divider): + # grouped_options = [ + # [['United States','US'], 'Canada'], + # ['Denmark','Germany','France'] + # ] + # grouped_options_for_select(grouped_options, divider: '---------') + # + # Possible output: + # <optgroup label="---------"> + # <option value="Denmark">Denmark</option> + # <option value="Germany">Germany</option> + # <option value="France">France</option> + # </optgroup> + # <optgroup label="---------"> + # <option value="US">United States</option> + # <option value="Canada">Canada</option> + # </optgroup> + # # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to # wrap the output in an appropriate <tt><select></tt> tag. - def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil) + def grouped_options_for_select(*args) + grouped_options = args.shift + options = args.extract_options! + selected_key = args.shift + if prompt = args.shift + ActiveSupport::Deprecation.warn 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please pass it in an options hash.' + else + prompt = options[:prompt] + divider = options[:divider] + end + body = "".html_safe - body.safe_concat content_tag(:option, prompt, :value => "") if prompt + + if prompt + body.safe_concat content_tag(:option, prompt_text(prompt), :value => "") + end grouped_options = grouped_options.sort if grouped_options.is_a?(Hash) - grouped_options.each do |label, container| + grouped_options.each do |container| + if divider + label, container = divider, container + else + label, container = container + end body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label) end @@ -714,6 +753,10 @@ module ActionView def value_for_collection(item, value) value.respond_to?(:call) ? value.call(item) : item.send(value) end + + def prompt_text(prompt) + prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select') + end end class FormBuilder diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 74c7d8fa3f..9e5c66f4a9 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -398,7 +398,7 @@ module ActionView # submit_tag "Save edits", :disabled => true # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" /> # - # submit_tag "Complete sale", 'data-disable-with' => "Please wait..." + # submit_tag "Complete sale", :data => { :disable_with => "Please wait..." } # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" /> # # submit_tag nil, :class => "form_submit" diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index 380ebe4b65..e077cd5b3c 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -140,8 +140,7 @@ module ActionView option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags end if value.blank? && options[:prompt] - prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select') - option_tags = content_tag('option', prompt, :value => '') + "\n" + option_tags + option_tags = content_tag('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags end option_tags end diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb index bb968e9f39..0e79609d52 100644 --- a/actionpack/lib/action_view/helpers/tags/date_field.rb +++ b/actionpack/lib/action_view/helpers/tags/date_field.rb @@ -5,7 +5,6 @@ module ActionView def render options = @options.stringify_keys options["value"] = @options.fetch("value") { value(object).try(:to_date) } - options["size"] = nil @options = options super end diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb index 56442e1c14..59f2ff71b4 100644 --- a/actionpack/lib/action_view/helpers/tags/file_field.rb +++ b/actionpack/lib/action_view/helpers/tags/file_field.rb @@ -2,10 +2,6 @@ module ActionView module Helpers module Tags class FileField < TextField #:nodoc: - def render - @options.update(:size => nil) - super - end end end end diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb index ea86596e0b..a8d13dc1b1 100644 --- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb +++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb @@ -2,10 +2,6 @@ module ActionView module Helpers module Tags class HiddenField < TextField #:nodoc: - def render - @options.update(:size => nil) - super - end end end end diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb index e89fdbec46..9cd04434f0 100644 --- a/actionpack/lib/action_view/helpers/tags/number_field.rb +++ b/actionpack/lib/action_view/helpers/tags/number_field.rb @@ -4,7 +4,6 @@ module ActionView class NumberField < TextField #:nodoc: def render options = @options.stringify_keys - options['size'] ||= nil if range = options.delete("in") || options.delete("within") options.update("min" => range.min, "max" => range.max) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 2c24bbb37b..67117077dc 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -235,6 +235,7 @@ module ActionView # # ==== Options # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+. + # * <tt>:wrapper_tag</tt> - String representing the tag wrapper, defaults to <tt>"p"</tt> # # ==== Examples # my_text = "Here is some basic text...\n...with a line break." @@ -253,16 +254,17 @@ module ActionView # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false) # # => "<p><span>I'm allowed!</span> It's true.</p>" def simple_format(text, html_options={}, options={}) - text = '' if text.nil? - text = text.dup - start_tag = tag('p', html_options, true) text = sanitize(text) unless options[:sanitize] == false - text = text.to_str - text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n - text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph - text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br - text.insert 0, start_tag - text.html_safe.safe_concat("</p>") + wrapper_tag = options.fetch(:wrapper_tag, :p) + paragraphs = split_paragraphs(text) + + if paragraphs.empty? + content_tag(wrapper_tag, nil, html_options) + else + paragraphs.map { |paragraph| + content_tag(wrapper_tag, paragraph, html_options, options[:sanitize]) + }.join("\n\n").html_safe + end end # Creates a Cycle object whose _to_s_ method cycles through elements of an @@ -404,6 +406,14 @@ module ActionView @_cycles = Hash.new unless defined?(@_cycles) @_cycles[name] = cycle_object end + + def split_paragraphs(text) + return [] if text.blank? + + text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t| + t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t + end + end end end end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 2cff91adda..9b64bc9d81 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -296,10 +296,34 @@ class FormOptionsHelperTest < ActionView::TestCase ) end - def test_grouped_options_for_select_with_selected_and_prompt + def test_grouped_options_for_select_with_optional_divider assert_dom_equal( + "<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>", + + grouped_options_for_select([['US',"Canada"] , ["GB", "Germany"]], divider: "----------") + ) + end + + def test_grouped_options_for_select_with_selected_and_prompt_deprecated + assert_deprecated 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please pass it in an options hash.' do + assert_dom_equal( "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...") + ) + end + end + + def test_grouped_options_for_select_with_selected_and_prompt + assert_dom_equal( + "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", + grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: "Choose a product...") + ) + end + + def test_grouped_options_for_select_with_selected_and_prompt_true + assert_dom_equal( + "<option value=\"\">Please select</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", + grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: true) ) end @@ -307,10 +331,18 @@ class FormOptionsHelperTest < ActionView::TestCase assert grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]]).html_safe? end + def test_grouped_options_for_select_with_prompt_returns_html_escaped_string_deprecated + ActiveSupport::Deprecation.silence do + assert_dom_equal( + "<option value=\"\"><Choose One></option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", + grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>')) + end + end + def test_grouped_options_for_select_with_prompt_returns_html_escaped_string assert_dom_equal( "<option value=\"\"><Choose One></option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>", - grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>')) + grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, prompt: '<Choose One>')) end def test_optgroups_with_with_options_with_hash diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 5865b7f23c..e5cb273518 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -46,6 +46,14 @@ class TextHelperTest < ActionView::TestCase assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false) end + def test_simple_format_with_custom_wrapper + assert_equal "<div></div>", simple_format(nil, {}, :wrapper_tag => "div") + end + + def test_simple_format_with_custom_wrapper_and_multi_line_breaks + assert_equal "<div>We want to put a wrapper...</div>\n\n<div>...right there.</div>", simple_format("We want to put a wrapper...\n\n...right there.", {}, :wrapper_tag => "div") + end + def test_simple_format_should_not_change_the_text_passed text = "<b>Ok</b><script>code!</script>" text_clone = text.dup diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 5e5405fe27..893fbf92c3 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -229,7 +229,7 @@ module ActiveModel protected def sanitize_for_mass_assignment(attributes, role = nil) - _mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role)) + _mass_assignment_sanitizer.sanitize(self.class, attributes, mass_assignment_authorizer(role)) end def mass_assignment_authorizer(role) diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb index 4491e07a72..44ce5a489d 100644 --- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb +++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb @@ -2,18 +2,18 @@ module ActiveModel module MassAssignmentSecurity class Sanitizer # Returns all attributes not denied by the authorizer. - def sanitize(attributes, authorizer) + def sanitize(klass, attributes, authorizer) rejected = [] sanitized_attributes = attributes.reject do |key, value| rejected << key if authorizer.deny?(key) end - process_removed_attributes(rejected) unless rejected.empty? + process_removed_attributes(klass, rejected) unless rejected.empty? sanitized_attributes end protected - def process_removed_attributes(attrs) + def process_removed_attributes(klass, attrs) raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten" end end @@ -32,8 +32,21 @@ module ActiveModel @target.respond_to?(:logger) && @target.logger end - def process_removed_attributes(attrs) - logger.warn "Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger? + def backtrace + if defined? Rails + Rails.backtrace_cleaner.clean(caller) + else + caller + end + end + + def process_removed_attributes(klass, attrs) + if logger? + logger.warn do + "WARNING: Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}\n" + + backtrace.map { |trace| "\t#{trace}" }.join("\n") + end + end end end @@ -42,9 +55,9 @@ module ActiveModel super() end - def process_removed_attributes(attrs) + def process_removed_attributes(klass, attrs) return if (attrs - insensitive_attributes).empty? - raise ActiveModel::MassAssignmentSecurity::Error.new(attrs) + raise ActiveModel::MassAssignmentSecurity::Error.new(klass, attrs) end def insensitive_attributes @@ -53,8 +66,8 @@ module ActiveModel end class Error < StandardError - def initialize(attrs) - super("Can't mass-assign protected attributes: #{attrs.join(', ')}") + def initialize(klass, attrs) + super("Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}") end end end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 3ed72bae3b..611e9ffd55 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -178,6 +178,12 @@ module ActiveModel end end + # Clean the +Errors+ object if instance is duped + def initialize_dup(other) # :nodoc: + @errors = nil + super + end + # Returns the +Errors+ object that holds all information about attribute error messages. def errors @errors ||= Errors.new(self) diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb index 3660b9b1e5..418a24294a 100644 --- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb +++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb @@ -19,7 +19,7 @@ class SanitizerTest < ActiveModel::TestCase test "sanitize attributes" do original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } - attributes = @logger_sanitizer.sanitize(original_attributes, @authorizer) + attributes = @logger_sanitizer.sanitize(self.class, original_attributes, @authorizer) assert attributes.key?('first_name'), "Allowed key shouldn't be rejected" assert !attributes.key?('admin'), "Denied key should be rejected" @@ -29,14 +29,14 @@ class SanitizerTest < ActiveModel::TestCase original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } log = StringIO.new self.logger = ActiveSupport::Logger.new(log) - @logger_sanitizer.sanitize(original_attributes, @authorizer) + @logger_sanitizer.sanitize(self.class, original_attributes, @authorizer) assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}") end test "debug mass assignment removal with StrictSanitizer" do original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } assert_raise ActiveModel::MassAssignmentSecurity::Error do - @strict_sanitizer.sanitize(original_attributes, @authorizer) + @strict_sanitizer.sanitize(self.class, original_attributes, @authorizer) end end @@ -44,7 +44,7 @@ class SanitizerTest < ActiveModel::TestCase original_attributes = {'id' => 1, 'first_name' => 'allowed'} assert_nothing_raised do - @strict_sanitizer.sanitize(original_attributes, @authorizer) + @strict_sanitizer.sanitize(self.class, original_attributes, @authorizer) end end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index a197dbe748..0c6352cd71 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -4,7 +4,7 @@ require 'models/mass_assignment_specific' class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer - def process_removed_attributes(attrs) + def process_removed_attributes(klass, attrs) raise StandardError end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index a716d0896e..1f5023bf76 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -344,4 +344,19 @@ class ValidationsTest < ActiveModel::TestCase Topic.validates :title, options assert_equal({ :presence => true }, options) end + + def test_dup_validity_is_independent + Topic.validates_presence_of :title + topic = Topic.new("title" => "Litterature") + topic.valid? + + duped = topic.dup + duped.title = nil + assert duped.invalid? + + topic.title = nil + duped.title = 'Mathematics' + assert topic.invalid? + assert duped.valid? + end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e53d688ad9..d19e0c9e9b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -633,7 +633,7 @@ has_one :account end - user.build_account{ |a| a.credit_limit => 100.0 } + user.build_account{ |a| a.credit_limit = 100.0 } The block is called after the instance has been initialized. *Andrew White* @@ -4006,7 +4006,7 @@ * Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 *Alisdair McDiarmid* -* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice +* Fixed saving of in-memory association structures to happen as an after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice * Allow any Enumerable, not just Array, to work as bind variables #1344 *Jeremy Kemper* @@ -5519,7 +5519,7 @@ * Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 *Alisdair McDiarmid* -* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice +* Fixed saving of in-memory association structures to happen as an after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice * Allow any Enumerable, not just Array, to work as bind variables #1344 *Jeremy Kemper* @@ -6441,7 +6441,7 @@ post.categories.push_with_attributes(category, :added_on => Date.today) post.categories.first.added_on # => Date.today - NOTE: The categories table doesn't have a added_on column, it's the categories_post join table that does! + NOTE: The categories table doesn't have an added_on column, it's the categories_post join table that does! * Fixed that :exclusively_dependent and :dependent can't be activated at the same time on has_many associations *Jeremy Kemper* diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 00321ec860..3af5ff3eab 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -242,8 +242,12 @@ module ActiveRecord # This method is abstract in the sense that it relies on # +count_records+, which is a method descendants have to provide. def size - if !find_target? || (loaded? && !options[:uniq]) - target.size + if !find_target? || loaded? + if options[:uniq] + target.uniq.size + else + target.size + end elsif !loaded? && options[:group] load_target.size elsif !loaded? && !options[:uniq] && target.is_a?(Array) @@ -468,6 +472,8 @@ module ActiveRecord raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ "new records could not be saved." end + + new_target end def concat_records(records) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index f17baec722..df78ba6c5a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -23,7 +23,7 @@ module ActiveRecord end def sql_type - base.type_to_sql(type.to_sym, limit, precision, scale) rescue type + base.type_to_sql(type.to_sym, limit, precision, scale) end def to_sql diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 1933ce2b46..01bd3ae26c 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -124,6 +124,7 @@ module ActiveRecord when :binary then "#{klass}.binary_to_string(#{var_name})" when :boolean then "#{klass}.value_to_boolean(#{var_name})" when :hstore then "#{klass}.string_to_hstore(#{var_name})" + when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})" else var_name end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index c82afc232c..df3d5e4657 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -137,6 +137,14 @@ module ActiveRecord end end + class Cidr < Type + def type_cast(value) + return if value.nil? + + ConnectionAdapters::PostgreSQLColumn.string_to_cidr value + end + end + class TypeMap def initialize @mapping = {} @@ -212,11 +220,9 @@ module ActiveRecord # FIXME: why are we keeping these types as strings? alias_type 'tsvector', 'text' alias_type 'interval', 'text' - alias_type 'cidr', 'text' - alias_type 'inet', 'text' - alias_type 'macaddr', 'text' alias_type 'bit', 'text' alias_type 'varbit', 'text' + alias_type 'macaddr', 'text' # FIXME: I don't think this is correct. We should probably be returning a parsed date, # but the tests pass with a string returned. @@ -237,6 +243,9 @@ module ActiveRecord register_type 'polygon', OID::Identity.new register_type 'circle', OID::Identity.new register_type 'hstore', OID::Hstore.new + + register_type 'cidr', OID::Cidr.new + alias_type 'inet', 'cidr' end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4d5459939b..6714970103 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -8,6 +8,8 @@ require 'arel/visitors/bind_visitor' gem 'pg', '~> 0.11' require 'pg' +require 'ipaddr' + module ActiveRecord module ConnectionHandling # Establishes a connection to the database that's used by all Active Record objects @@ -79,6 +81,25 @@ module ActiveRecord end end + def string_to_cidr(string) + if string.nil? + nil + elsif String === string + IPAddr.new(string) + else + string + end + + end + + def cidr_to_string(object) + if IPAddr === object + "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}" + else + object + end + end + private HstorePair = begin quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ @@ -197,6 +218,13 @@ module ActiveRecord :decimal when 'hstore' :hstore + # Network address types + when 'inet' + :inet + when 'cidr' + :cidr + when 'macaddr' + :macaddr # Character types when /^(?:character varying|bpchar)(?:\(\d+\))?$/ :string @@ -211,9 +239,6 @@ module ActiveRecord # Geometric types when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ :string - # Network address types - when /^(?:cidr|inet|macaddr)$/ - :string # Bit strings when /^bit(?: varying)?(?:\(\d+\))?$/ :string @@ -282,6 +307,18 @@ module ActiveRecord def hstore(name, options = {}) column(name, 'hstore', options) end + + def inet(name, options = {}) + column(name, 'inet', options) + end + + def cidr(name, options = {}) + column(name, 'cidr', options) + end + + def macaddr(name, options = {}) + column(name, 'macaddr', options) + end end ADAPTER_NAME = 'PostgreSQL' @@ -301,7 +338,10 @@ module ActiveRecord :boolean => { :name => "boolean" }, :xml => { :name => "xml" }, :tsvector => { :name => "tsvector" }, - :hstore => { :name => "hstore" } + :hstore => { :name => "hstore" }, + :inet => { :name => "inet" }, + :cidr => { :name => "cidr" }, + :macaddr => { :name => "macaddr" } } # Returns 'PostgreSQL' as adapter name for identification purposes. @@ -510,6 +550,11 @@ module ActiveRecord when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column) else super end + when IPAddr + case column.sql_type + when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column) + else super + end when Float if value.infinite? && column.type == :datetime "'#{value.to_s.downcase}'" @@ -549,6 +594,9 @@ module ActiveRecord when Hash return super unless 'hstore' == column.sql_type PostgreSQLColumn.hstore_to_string(value) + when IPAddr + return super unless ['inet','cidr'].includes? column.sql_type + PostgreSQLColumn.cidr_to_string(value) else super end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7b4be67131..d4ffa82b17 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -21,10 +21,6 @@ module ActiveRecord config[:database] = File.expand_path(config[:database], Rails.root) end - unless 'sqlite3' == config[:adapter] - raise ArgumentError, 'adapter name should be "sqlite3"' - end - db = SQLite3::Database.new( config[:database], :results_as_hash => true @@ -195,7 +191,7 @@ module ActiveRecord :decimal => { :name => "decimal" }, :datetime => { :name => "datetime" }, :timestamp => { :name => "datetime" }, - :time => { :name => "time" }, + :time => { :name => "datetime" }, :date => { :name => "date" }, :binary => { :name => "blob" }, :boolean => { :name => "boolean" } @@ -540,7 +536,7 @@ module ActiveRecord :precision => column.precision, :scale => column.scale, :null => column.null) end - @definition.primary_key(primary_key(from)) if primary_key(from) + @definition.primary_key(from_primary_key) if from_primary_key yield @definition if block_given? end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 6a310bba51..b2ed606e5f 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -127,7 +127,7 @@ module ActiveRecord object.is_a?(self) end - # Returns an instance of Arel::Table loaded with the current table name. + # Returns an instance of <tt>Arel::Table</tt> loaded with the curent table name. # # class Post < ActiveRecord::Base # scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0)) @@ -136,7 +136,7 @@ module ActiveRecord @arel_table ||= Arel::Table.new(table_name, arel_engine) end - # Returns the Arel engine + # Returns the Arel engine. def arel_engine @arel_engine ||= connection_handler.retrieve_connection_pool(self) ? self : active_record_super.arel_engine end @@ -210,7 +210,7 @@ module ActiveRecord self end - + ## # :method: clone # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied. @@ -225,9 +225,9 @@ module ActiveRecord # # user.object_id == new_user.object_id # => false # user.name.object_id == new_user.name.object_id # => true - # + # # user.name.object_id == user.dup.name.object_id # => false - + ## # :method: dup # Duped objects have no id assigned and are treated as new records. Note @@ -236,7 +236,7 @@ module ActiveRecord # specific and is therefore left to the application to implement according # to its need. # The dup method does not preserve the timestamps (created|updated)_(at|on). - + ## def initialize_dup(other) # :nodoc: cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 3ce9995031..31d99f0192 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -108,34 +108,57 @@ module ActiveRecord 0 end - # This method is designed to perform select by a single column as direct SQL query - # Returns <tt>Array</tt> with values of the specified column name - # The values has same data type as column. + # Use <tt>pluck</tt> as a shortcut to select a single attribute without + # loading a bunch of records just to grab one attribute you want. + # + # Person.pluck(:name) + # + # instead of + # + # Person.all.map(&:name) + # + # Pluck returns an <tt>Array</tt> of attribute values type-casted to match + # the plucked column name, if it can be deduced. Plucking a SQL fragment + # returns String values by default. # # Examples: # - # Person.pluck(:id) # SELECT people.id FROM people - # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people - # Person.where(:age => 21).limit(5).pluck(:id) # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5 + # Person.pluck(:id) + # # SELECT people.id FROM people + # # => [1, 2, 3] + # + # Person.uniq.pluck(:role) + # # SELECT DISTINCT role FROM people + # # => ['admin', 'member', 'guest'] + # + # Person.where(:age => 21).limit(5).pluck(:id) + # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5 + # # => [2, 3] + # + # Person.pluck('DATEDIFF(updated_at, created_at)') + # # SELECT DATEDIFF(updated_at, created_at) FROM people + # # => ['0', '27761', '173'] # def pluck(column_name) - key = column_name.to_s.split('.', 2).last - if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) column_name = "#{table_name}.#{column_name}" end result = klass.connection.select_all(select(column_name).arel, nil, bind_values) - types = result.column_types.merge klass.column_types - column = types[key] + + key = result.columns.first + column = klass.column_types.fetch(key) { + result.column_types.fetch(key) { + Class.new { def type_cast(v); v; end }.new + } + } result.map do |attributes| - value = klass.initialize_attributes(attributes)[key] - if column - column.type_cast value - else - value - end + raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one? + + value = klass.initialize_attributes(attributes).values.first + + column.type_cast(value) end end diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index d815ab05ac..599e68379a 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -34,6 +34,15 @@ module ActiveRecord ActiveRecord::Migrator.migrations_paths end + def define(info, &block) + instance_eval(&block) + + unless info[:version].blank? + initialize_schema_migrations_table + assume_migrated_upto_version(info[:version], migrations_paths) + end + end + # Eval the given block. All methods available to the current connection # adapter are available within the block, so you can easily use the # database definition DSL to build up your schema (+create_table+, @@ -46,13 +55,7 @@ module ActiveRecord # ... # end def self.define(info={}, &block) - schema = new - schema.instance_eval(&block) - - unless info[:version].blank? - initialize_schema_migrations_table - assume_migrated_upto_version(info[:version], schema.migrations_paths) - end + new.define(info, &block) end end end diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index ed47a26749..5a256b040b 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -201,10 +201,10 @@ module ActiveRecord class << self alias :data_column_name :data_column - + # Use the ActiveRecord::Base.connection by default. attr_writer :connection - + # Use the ActiveRecord::Base.connection_pool by default. attr_writer :connection_pool @@ -218,12 +218,12 @@ module ActiveRecord # Look up a session by id and unmarshal its data if found. def find_by_session_id(session_id) - if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}") + if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id.to_s)}") new(:session_id => session_id, :marshaled_data => record['data']) end end end - + delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self attr_reader :session_id, :new_record @@ -241,6 +241,11 @@ module ActiveRecord @new_record = @marshaled_data.nil? end + # Returns true if the record is persisted, i.e. it's not a new record + def persisted? + !@new_record + end + # Lazy-unmarshal session state. def data unless @data @@ -287,7 +292,7 @@ module ActiveRecord connect = connection connect.delete <<-end_sql, 'Destroy session' DELETE FROM #{table_name} - WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)} + WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id.to_s)} end_sql end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 64e5640791..30e1035300 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -290,7 +290,15 @@ module ActiveRecord status = nil self.class.transaction do add_to_transaction - status = yield + begin + status = yield + rescue ActiveRecord::Rollback + if defined?(@_start_transaction_state) + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + end + status = nil + end + raise ActiveRecord::Rollback unless status end status diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index ce08e4c6a7..34660577da 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -86,9 +86,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase end def test_data_type_of_network_address_types - assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type - assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type - assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type + assert_equal :cidr, @first_network_address.column_for_attribute(:cidr_address).type + assert_equal :inet, @first_network_address.column_for_attribute(:inet_address).type + assert_equal :macaddr, @first_network_address.column_for_attribute(:mac_address).type end def test_data_type_of_bit_string_types @@ -134,9 +134,12 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase assert_equal '-1 years -2 days', @first_time.time_interval end - def test_network_address_values - assert_equal '192.168.0.0/24', @first_network_address.cidr_address - assert_equal '172.16.1.254', @first_network_address.inet_address + def test_network_address_values_ipaddr + cidr_address = IPAddr.new '192.168.0.0/24' + inet_address = IPAddr.new '172.16.1.254' + + assert_equal cidr_address, @first_network_address.cidr_address + assert_equal inet_address, @first_network_address.inet_address assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address end @@ -200,8 +203,8 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase end def test_update_network_address - new_cidr_address = '10.1.2.3/32' - new_inet_address = '10.0.0.0/8' + new_inet_address = '10.1.2.3/32' + new_cidr_address = '10.0.0.0/8' new_mac_address = 'bc:de:f0:12:34:56' assert @first_network_address.cidr_address = new_cidr_address assert @first_network_address.inet_address = new_inet_address diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 17bde6cb62..f786452f94 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -35,6 +35,11 @@ module ActiveRecord assert(!result.rows.first.include?("blob"), "should not store blobs") end + def test_time_column + owner = Owner.create!(:eats_at => Time.utc(1995,1,1,6,0)) + assert_match /1995-01-01/, owner.reload.eats_at.to_s + end + def test_exec_insert column = @conn.columns('items').find { |col| col.name == 'number' } vals = [[column, 10]] @@ -58,18 +63,6 @@ module ActiveRecord end end - def test_connection_no_adapter - assert_raises(ArgumentError) do - Base.sqlite3_connection :database => ':memory:' - end - end - - def test_connection_wrong_adapter - assert_raises(ArgumentError) do - Base.sqlite3_connection :database => ':memory:',:adapter => 'vuvuzela' - end - end - def test_bad_timeout assert_raises(TypeError) do Base.sqlite3_connection :database => ':memory:', diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 588adc38e3..b2eac0349b 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -37,6 +37,13 @@ if ActiveRecord::Base.connection.supports_migrations? end end end + + def test_schema_subclass + Class.new(ActiveRecord::Schema).define(:version => 9) do + create_table :fruits + end + assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } + end end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 470f0c3fd3..a6daf54c01 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -333,6 +333,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 3, project.developers.size end + def test_uniq_when_association_already_loaded + project = projects(:active_record) + project.developers << [ developers(:jamis), developers(:david), developers(:jamis), developers(:david) ] + assert_equal 3, Project.includes(:developers).find(project.id).developers.size + end + def test_deleting david = Developer.find(1) active_record = Project.find(1) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 13d8c68b33..2e24f8ebe1 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1685,6 +1685,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [bulb2], car.reload.bulbs end + def test_replace_returns_new_target + car = Car.create(:name => 'honda') + bulb1 = car.bulbs.create + bulb2 = car.bulbs.create + bulb3 = Bulb.create + + assert_equal [bulb1, bulb2], car.bulbs + result = car.bulbs.replace([bulb1, bulb3]) + assert_equal [bulb1, bulb3], car.bulbs + assert_equal [bulb1, bulb3], result + end + def test_building_has_many_association_with_restrict_dependency option_before = ActiveRecord::Base.dependent_restrict_raises ActiveRecord::Base.dependent_restrict_raises = true diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index c1b0cb8886..66a16d8b5f 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -522,7 +522,7 @@ class BasicsTest < ActiveRecord::TestCase end # Oracle, and Sybase do not have a TIME datatype. - unless current_adapter?(:OracleAdapter, :SybaseAdapter) + unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) def test_utc_as_time_zone Topic.default_timezone = :utc attributes = { "bonus_time" => "5:42:00AM" } @@ -764,6 +764,9 @@ class BasicsTest < ActiveRecord::TestCase end def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing + ActiveRecord::Base.time_zone_aware_attributes = false + ActiveRecord::Base.default_timezone = :local + Time.zone = nil attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12", "written_on(5i)" => "12", "written_on(6i)" => "02" @@ -870,7 +873,7 @@ class BasicsTest < ActiveRecord::TestCase end # Oracle, and Sybase do not have a TIME datatype. - unless current_adapter?(:OracleAdapter, :SybaseAdapter) + unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.default_timezone = :utc @@ -891,6 +894,9 @@ class BasicsTest < ActiveRecord::TestCase end def test_multiparameter_attributes_on_time_with_empty_seconds + ActiveRecord::Base.time_zone_aware_attributes = false + ActiveRecord::Base.default_timezone = :local + Time.zone = nil attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "" @@ -946,7 +952,7 @@ class BasicsTest < ActiveRecord::TestCase def test_attributes_on_dummy_time # Oracle, and Sybase do not have a TIME datatype. - return true if current_adapter?(:OracleAdapter, :SybaseAdapter) + return true if current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) attributes = { "bonus_time" => "5:42:00AM" @@ -1514,11 +1520,7 @@ class BasicsTest < ActiveRecord::TestCase after_seq = Joke.sequence_name assert_not_equal before_columns, after_columns - unless before_seq.nil? && after_seq.nil? - assert_not_equal before_seq, after_seq - assert_equal "cold_jokes_id_seq", before_seq - assert_equal "funny_jokes_id_seq", after_seq - end + assert_not_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil? end def test_dont_clear_sequence_name_when_setting_explicitly diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index e096585f62..041f8ffb7c 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -466,6 +466,21 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [7], Company.joins(:contracts).pluck(:developer_id) end + def test_pluck_with_selection_clause + assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT credit_limit').sort + assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT accounts.credit_limit').sort + assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT(credit_limit)').sort + + # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless + # an alias is provided. Without the alias, the column cannot be found + # and properly typecast. + assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit') + end + + def test_pluck_expects_a_single_selection + assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' } + end + def test_plucks_with_ids assert_equal Company.all.map(&:id).sort, Company.ids.sort end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index f0b1f74bd3..ab61a4dcef 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -83,7 +83,6 @@ module ActiveRecord t.column :one_int, :integer, :limit => 1 t.column :four_int, :integer, :limit => 4 t.column :eight_int, :integer, :limit => 8 - t.column :eleven_int, :integer, :limit => 11 end columns = connection.columns(:testings) @@ -94,20 +93,17 @@ module ActiveRecord one = columns.detect { |c| c.name == "one_int" } four = columns.detect { |c| c.name == "four_int" } eight = columns.detect { |c| c.name == "eight_int" } - eleven = columns.detect { |c| c.name == "eleven_int" } if current_adapter?(:PostgreSQLAdapter) assert_equal 'integer', default.sql_type assert_equal 'smallint', one.sql_type assert_equal 'integer', four.sql_type assert_equal 'bigint', eight.sql_type - assert_equal 'integer', eleven.sql_type elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter) assert_match 'int(11)', default.sql_type assert_match 'tinyint', one.sql_type assert_match 'int', four.sql_type assert_match 'bigint', eight.sql_type - assert_match 'int(11)', eleven.sql_type elsif current_adapter?(:OracleAdapter) assert_equal 'NUMBER(38)', default.sql_type assert_equal 'NUMBER(1)', one.sql_type diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb index 409a558f5c..18f8d82bfe 100644 --- a/activerecord/test/cases/migration/column_attributes_test.rb +++ b/activerecord/test/cases/migration/column_attributes_test.rb @@ -183,6 +183,16 @@ module ActiveRecord assert_instance_of TrueClass, bob.male? assert_kind_of BigDecimal, bob.wealth end + + def test_out_of_range_limit_should_raise + skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + + assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 } + + unless current_adapter?(:PostgreSQLAdapter) + assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff } + end + end end end end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f788690b0d..cad93936c9 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -375,6 +375,27 @@ class MigrationTest < ActiveRecord::TestCase end end + def test_out_of_range_limit_should_raise + skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter) + + Person.connection.drop_table :test_limits rescue nil + assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do + Person.connection.create_table :test_integer_limits, :force => true do |t| + t.column :bigone, :integer, :limit => 10 + end + end + + unless current_adapter?(:PostgreSQLAdapter) + assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do + Person.connection.create_table :test_text_limits, :force => true do |t| + t.column :bigtext, :text, :limit => 0xfffffffff + end + end + end + + Person.connection.drop_table :test_limits rescue nil + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz @@ -883,8 +904,8 @@ class CopyMigrationsTest < ActiveRecord::TestCase def test_skipping_migrations @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps" @existing_migrations = Dir[@migrations_path + "/*.rb"] - - sources = {} + + sources = {} sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps" sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision" diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 15ceaa1fcc..ab80dd1d6d 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -236,6 +236,27 @@ class SchemaDumperTest < ActiveRecord::TestCase end end + def test_schema_dump_includes_inet_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_network_address"} =~ output + assert_match %r{t.inet "inet_address"}, output + end + end + + def test_schema_dump_includes_cidr_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_network_address"} =~ output + assert_match %r{t.cidr "cidr_address"}, output + end + end + + def test_schema_dump_includes_macaddr_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_network_address"} =~ output + assert_match %r{t.macaddr "macaddr_address"}, output + end + end + def test_schema_dump_includes_hstores_shorthand_definition output = standard_dump if %r{create_table "postgresql_hstores"} =~ output diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass_test.rb index 7402b2afd6..6749d4ce98 100644 --- a/activerecord/test/cases/session_store/sql_bypass.rb +++ b/activerecord/test/cases/session_store/sql_bypass_test.rb @@ -18,6 +18,11 @@ module ActiveRecord assert !Session.table_exists? end + def test_new_record? + s = SqlBypass.new :data => 'foo', :session_id => 10 + assert s.new_record?, 'this is a new record!' + end + def test_persisted? s = SqlBypass.new :data => 'foo', :session_id => 10 assert !s.persisted?, 'this is a new record!' diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 9846f5b12d..961ba8d9ba 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -287,8 +287,45 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end + assert topic.id.nil? + assert !topic.persisted? assert_equal %w{ after_rollback }, topic.history end + + class TopicWithManualRollbackObserverAttached < ActiveRecord::Base + self.table_name = :topics + def history + @history ||= [] + end + end + + class TopicWithManualRollbackObserverAttachedObserver < ActiveRecord::Observer + def after_save(record) + record.history.push "after_save" + raise ActiveRecord::Rollback + end + end + + def test_after_save_called_with_manual_rollback + assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' + + topic = TopicWithManualRollbackObserverAttached.new + + assert !topic.save + assert_equal nil, topic.id + assert !topic.persisted? + assert_equal %w{ after_save }, topic.history + end + def test_after_save_called_with_manual_rollback_bang + assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer' + + topic = TopicWithManualRollbackObserverAttached.new + + topic.save! + assert_equal nil, topic.id + assert !topic.persisted? + assert_equal %w{ after_save }, topic.history + end end class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 6422cf6415..3fc58c25b1 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -431,6 +431,7 @@ ActiveRecord::Schema.define do t.string :name t.column :updated_at, :datetime t.column :happy_at, :datetime + t.column :eats_at, :time t.string :essay_id end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 4e319b4bba..0aa3efbb63 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -310,13 +310,13 @@ module ActiveSupport method << "value = nil" method << "halted = false" - callbacks = "value = yield if block_given? && !halted" + callbacks = "value = !halted && (!block_given? || yield)" reverse_each do |callback| callbacks = callback.apply(callbacks) end method << callbacks - method << "halted ? false : (block_given? ? value : true)" + method << "value" method.join("\n") end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9d1630bb7c..dd63b64c77 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -81,30 +81,6 @@ class Numeric end end -class Class - # Classes are not duplicable: - # - # c = Class.new # => #<Class:0x10328fd80> - # c.dup # => #<Class:0x10328fd80> - # - # Note +dup+ returned the same class object. - def duplicable? - false - end -end - -class Module - # Modules are not duplicable: - # - # m = Module.new # => #<Module:0x10328b6e0> - # m.dup # => #<Module:0x10328b6e0> - # - # Note +dup+ returned the same module object. - def duplicable? - false - end -end - require 'bigdecimal' class BigDecimal begin diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index ab49af55bf..fb36262528 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -6,7 +6,6 @@ require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/xchar' require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/string/interpolation' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/exclude' require 'active_support/core_ext/string/strip' diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb deleted file mode 100644 index 7f764e9de1..0000000000 --- a/activesupport/lib/active_support/core_ext/string/interpolation.rb +++ /dev/null @@ -1,2 +0,0 @@ -require 'active_support/i18n' -require 'i18n/core_ext/string/interpolate' 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 6bda970e40..f98d5b3777 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -150,6 +150,20 @@ module ActiveSupport #:nodoc: dup.concat(other) end + def %(args) + args = Array(args) + + args.map! do |arg| + if !html_safe? || arg.html_safe? + arg + else + ERB::Util.h(arg) + end + end + + self.class.new(super(args)) + end + def html_safe? defined?(@html_safe) && @html_safe end diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 9102537810..fc962dcb57 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -16,9 +16,9 @@ module ActiveSupport # # Available behaviors: # - # [+stderr+] Log all deprecation warnings to +$stderr+. + # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>. # [+log+] Log all deprecation warnings to +Rails.logger+. - # [+notify] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. + # [+notify+] Use <tt>ActiveSupport::Notifications</tt> to notify +deprecation.rails+. # [+silence+] Do nothing. # # Setting behaviors only affects deprecations that happen after boot time. diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index 7eb61cd1a0..c04c2ed15b 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -26,7 +26,7 @@ module ActiveSupport inflect.singular(/(ss)$/i, '\1') inflect.singular(/(n)ews$/i, '\1ews') inflect.singular(/([ti])a$/i, '\1um') - inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1\2sis') + inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis') inflect.singular(/(^analy)(sis|ses)$/i, '\1sis') inflect.singular(/([^f])ves$/i, '\1fe') inflect.singular(/(hive)s$/i, '\1') diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 7b7fc81e6c..9d01dc85c0 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -62,7 +62,11 @@ module ActiveSupport end def method_missing(level, message) - @logged[level] << message + if block_given? + @logged[level] << yield + else + @logged[level] << message + end end def logged(level) diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 0059898ebf..28bc06f103 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -271,7 +271,12 @@ module ActiveSupport date_parts = Date._parse(str) return if date_parts.empty? time = Time.parse(str, now) rescue DateTime.parse(str) + if date_parts[:offset].nil? + if date_parts[:hour] && time.hour != date_parts[:hour] + time = DateTime.parse(str) + end + ActiveSupport::TimeWithZone.new(nil, self, time) else time.in_time_zone(self) diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb index 1105353e45..e0566e012c 100644 --- a/activesupport/test/core_ext/duplicable_test.rb +++ b/activesupport/test/core_ext/duplicable_test.rb @@ -5,8 +5,8 @@ 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] - NO = [Class.new, Module.new] + YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] + NO = [] begin bd = BigDecimal.new('4.56') diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 9010a4a716..eee2caa60e 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -439,6 +439,30 @@ class OutputSafetyTest < ActiveSupport::TestCase assert @other_string.html_safe? end + test "Concatting safe onto unsafe with % yields unsafe" do + @other_string = "other%s" + string = @string.html_safe + + @other_string = @other_string % string + assert !@other_string.html_safe? + end + + test "Concatting unsafe onto safe with % yields escaped safe" do + @other_string = "other%s".html_safe + string = @other_string % "<foo>" + + assert_equal "other<foo>", string + assert string.html_safe? + end + + test "Concatting safe onto safe with % yields safe" do + @other_string = "other%s".html_safe + string = @string.html_safe + + @other_string = @other_string % string + assert @other_string.html_safe? + end + test "Concatting a fixnum to safe always yields safe" do string = @string.html_safe string = string.concat(13) diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 4d10cfca25..9fa1f417e4 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -47,6 +47,7 @@ module InflectorTestCases "medium" => "media", "stadium" => "stadia", "analysis" => "analyses", + "my_analysis" => "my_analyses", "node_child" => "node_children", "child" => "children", diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb index 8e160714b1..2a0e8d20ed 100644 --- a/activesupport/test/log_subscriber_test.rb +++ b/activesupport/test/log_subscriber_test.rb @@ -11,7 +11,7 @@ class MyLogSubscriber < ActiveSupport::LogSubscriber def foo(event) debug "debug" - info "info" + info { "info" } warn "warn" end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index d14d01dc30..b9434489bb 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -203,6 +203,24 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Time.utc(1999,12,31,19), twz.time end + def test_parse_should_not_black_out_system_timezone_dst_jump + zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] + zone.stubs(:now).returns(zone.now) + Time.stubs(:parse).with('2012-03-25 03:29', zone.now). + returns(Time.local(0,29,4,25,3,2012,nil,nil,true,"+03:00")) + twz = zone.parse('2012-03-25 03:29') + assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6] + end + + def test_parse_should_black_out_app_timezone_dst_jump + zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] + zone.stubs(:now).returns(zone.now) + Time.stubs(:parse).with('2012-03-11 02:29', zone.now). + returns(Time.local(0,29,2,11,3,2012,nil,nil,false,"+02:00")) + twz = zone.parse('2012-03-11 02:29') + assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6] + end + def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize tzinfo = TZInfo::Timezone.get('America/New_York') zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo) diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile index a9cb424eaa..294ef25b33 100644 --- a/guides/source/active_record_querying.textile +++ b/guides/source/active_record_querying.textile @@ -1260,6 +1260,24 @@ with Client.pluck(:id) </ruby> +h3. +ids+ + ++ids+ can be used to pluck all the IDs for the relation using the table's primary key. + +<ruby> +Person.ids +# SELECT id FROM people +</ruby> + +<ruby> +class Person < ActiveRecord::Base + self.primary_key = "person_id" +end + +Person.ids +# SELECT person_id FROM people +</ruby> + h3. Existence of Objects If you simply want to check for the existence of the object there's a method called +exists?+. This method will query the database using the same query as +find+, but instead of returning an object or collection of objects it will return either +true+ or +false+. diff --git a/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile index 80da1110d7..6443255f5d 100644 --- a/guides/source/active_support_core_extensions.textile +++ b/guides/source/active_support_core_extensions.textile @@ -1277,20 +1277,6 @@ The <tt>inquiry</tt> method converts a string into a +StringInquirer+ object mak "active".inquiry.inactive? # => false </ruby> -h4. Key-based Interpolation - -In Ruby 1.9 the <tt>%</tt> string operator supports key-based interpolation, both formatted and unformatted: - -<ruby> -"Total is %<total>.02f" % {:total => 43.1} # => Total is 43.10 -"I say %{foo}" % {:foo => "wadus"} # => "I say wadus" -"I say %{woo}" % {:foo => "wadus"} # => KeyError -</ruby> - -Active Support adds that functionality to <tt>%</tt> in previous versions of Ruby. - -NOTE: Defined in +active_support/core_ext/string/interpolation.rb+. - h4. +starts_with?+ and +ends_with?+ Active Support defines 3rd person aliases of +String#start_with?+ and +String#end_with?+: diff --git a/guides/source/layouts_and_rendering.textile b/guides/source/layouts_and_rendering.textile index e4a1fd6951..b0a87a5981 100644 --- a/guides/source/layouts_and_rendering.textile +++ b/guides/source/layouts_and_rendering.textile @@ -860,12 +860,6 @@ You can supply a hash of additional HTML options: <%= image_tag "icons/delete.gif", {:height => 45} %> </erb> -You can also supply an alternate image to show on mouseover: - -<erb> -<%= image_tag "home.gif", :onmouseover => "menu/home_highlight.gif" %> -</erb> - You can supply alternate text for the image which will be used if the user has images turned off in their browser. If you do not specify an alt text explicitly, it defaults to the file name of the file, capitalized and with no extension. For example, these two image tags would return the same code: <erb> diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 01df2c5b64..1f88843ee9 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki* + * The application generator generates `public/humans.txt` with some basic data. *Paul Campbell* * Add `config.queue_consumer` to allow the default consumer to be configurable. *Carlos Antonio da Silva* diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index ef48bc4f94..47856c87c6 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -567,8 +567,9 @@ module Rails end initializer :load_environment_config, :before => :load_environment_hook, :group => :all do - environment = paths["config/environments"].existent.first - require environment if environment + paths["config/environments"].existent.each do |environment| + require environment + end end initializer :append_assets_path, :group => :all do |app| diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index d661c62148..10ad475d55 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -249,13 +249,14 @@ module Rails # We unset temporary bundler variables to load proper bundler and Gemfile. # # Thanks to James Tucker for the Gem tricks involved in this call. + _bundle_command = Gem.bin_path('bundler', 'bundle') - bundle_gemfile, rubyopt = ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] - ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] = "", "" + bundle_bin_path, bundle_gemfile, rubyopt = ENV['BUNDLE_BIN_PATH'], ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] + ENV['BUNDLE_BIN_PATH'], ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] = "", "", "" - print `"#{Gem.ruby}" "#{Gem.bin_path('bundler', 'bundle')}" #{command}` + print `"#{Gem.ruby}" "#{_bundle_command}" #{command}` - ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] = bundle_gemfile, rubyopt + ENV['BUNDLE_BIN_PATH'], ENV['BUNDLE_GEMFILE'], ENV['RUBYOPT'] = bundle_bin_path, bundle_gemfile, rubyopt end def run_bundle diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index 3b5cc6648e..3192ec897b 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -10,4 +10,4 @@ * *= require_self *= require_tree . -*/ + */ diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 51374e5f3f..b0e1865b26 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -99,7 +99,13 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase end def test_generation_runs_bundle_install_with_full_and_mountable - result = run_generator [destination_root, "--mountable", "--full"] + result = run_generator [destination_root, "--mountable", "--full", "--dev"] + assert_file "#{destination_root}/Gemfile.lock" do |contents| + assert_match(/bukkits/, contents) + end + assert_match(/run bundle install/, result) + assert_match(/Using bukkits \(0\.0\.1\)/, result) + assert_match(/Your bundle is complete/, result) assert_equal 1, result.scan("Your bundle is complete").size end @@ -348,4 +354,3 @@ protected silence(:stdout){ generator.send(*args, &block) } end end - diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 9a6b2b66ca..55f72f532f 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -537,10 +537,11 @@ YAML assert_equal "foo", last_response.body end - test "it loads its environment file" do + test "it loads its environments file" do @plugin.write "lib/bukkits.rb", <<-RUBY module Bukkits class Engine < ::Rails::Engine + config.paths["config/environments"].push "config/environments/additional.rb" end end RUBY @@ -551,9 +552,16 @@ YAML end RUBY + @plugin.write "config/environments/additional.rb", <<-RUBY + Bukkits::Engine.configure do + config.additional_environment_loaded = true + end + RUBY + boot_rails assert Bukkits::Engine.config.environment_loaded + assert Bukkits::Engine.config.additional_environment_loaded end test "it passes router in env" do |