diff options
65 files changed, 829 insertions, 232 deletions
@@ -8,8 +8,7 @@ else gem 'arel' end -gem 'minitest', '~> 3.2.0' -gem 'mocha', '>= 0.11.2' +gem 'mocha', '>= 0.11.2', :require => false gem 'rack-test', github: "brynary/rack-test" gem 'bcrypt-ruby', '~> 3.0.0' gem 'jquery-rails' diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 4d8f739403..96cfb43e0b 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Allow to set default Action Mailer options via `config.action_mailer.default_options=` *Robert Pankowecki* + * Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu* * Asynchronously send messages via the Rails Queue *Brian Cardarella* diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index f31e1e007b..150d435140 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -278,6 +278,11 @@ module ActionMailer #:nodoc: # set something in the defaults using a proc, and then set the same thing inside of your # mailer method, it will get over written by the mailer method. # + # It is also possible to set these default options that will be used in all mailers through + # the <tt>default_options=</tt> configuration in <tt>config/application.rb</tt>: + # + # config.action_mailer.default_options = { from: "no-reply@example.org" } + # # = Callbacks # # You can specify callbacks using before_filter and after_filter for configuring your messages. @@ -421,6 +426,10 @@ module ActionMailer #:nodoc: self.default_params = default_params.merge(value).freeze if value default_params end + # Allows to set defaults through app configuration: + # + # config.action_mailer.default_options = { from: "no-reply@example.org" } + alias :default_options= :default # Receives a raw email, parses it into an email object, decodes it, # instantiates a new mailer, and passes the email object to the mailer @@ -786,4 +795,4 @@ module ActionMailer #:nodoc: ActiveSupport.run_load_hooks(:action_mailer, self) end -end
\ No newline at end of file +end diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index 17b22aea2a..b8d1db9558 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -15,7 +15,7 @@ module ActionMailer #:nodoc: def any(*args, &block) options = args.extract_options! - raise "You have to supply at least one format" if args.empty? + raise ArgumentError, "You have to supply at least one format" if args.empty? args.each { |type| send(type, options.dup, &block) } end alias :all :any diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 144a6bfe39..4ed332d13d 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -653,6 +653,19 @@ class BaseTest < ActiveSupport::TestCase assert_equal "Anonymous mailer body", mailer.welcome.body.encoded.strip end + test "default_from can be set" do + class DefaultFromMailer < ActionMailer::Base + default :to => 'system@test.lindsaar.net' + self.default_options = {from: "robert.pankowecki@gmail.com"} + + def welcome + mail(subject: "subject", body: "hello world") + end + end + + assert_equal ["robert.pankowecki@gmail.com"], DefaultFromMailer.welcome.from + end + protected # Execute the block setting the given values and restoring old values after diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 7edba84ff6..67c9e04ab2 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,15 @@ ## Rails 4.0.0 (unreleased) ## +* Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.: + + class ApplicationController + add_flash_types :error, :warning + end + + If you add the above code, you can use `<%= error %>` in an erb, and `rediect_to /foo, :error => 'message'` in a controller. + + *kennyj* + * Remove Active Model dependency from Action Pack. *Guillermo Iguaran* * Support unicode characters in routes. Route will be automatically escaped, so instead of manually escaping: diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 492329c401..09b9e7ddf0 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -16,6 +16,10 @@ module AbstractController generate_method_for_mime(mime) end + Mime::Type.register_callback do |mime| + generate_method_for_mime(mime) unless self.instance_methods.include?(mime.to_sym) + end + protected def method_missing(symbol, &block) diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index bd768b634e..b078beb675 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -3,19 +3,34 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - delegate :flash, :to => :request - delegate :alert, :notice, :to => "request.flash" - helper_method :alert, :notice + class_attribute :_flash_types, instance_accessor: false + self._flash_types = [] + + delegate :flash, to: :request + add_flash_types(:alert, :notice) end - protected - def redirect_to(options = {}, response_status_and_flash = {}) #:doc: - if alert = response_status_and_flash.delete(:alert) - flash[:alert] = alert + module ClassMethods + def add_flash_types(*types) + types.each do |type| + next if _flash_types.include?(type) + + define_method(type) do + request.flash[type] + end + helper_method type + + _flash_types << type end + end + end - if notice = response_status_and_flash.delete(:notice) - flash[:notice] = notice + protected + def redirect_to(options = {}, response_status_and_flash = {}) #:doc: + self.class._flash_types.each do |flash_type| + if type = response_status_and_flash.delete(flash_type) + flash[flash_type] = type + end end if other_flashes = response_status_and_flash.delete(:flash) diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index f01f4b99a9..bdf6e88699 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -9,8 +9,7 @@ module ActionController :status, :location, :content_type, :to => "@_response" def dispatch(action, request) - @_response = ActionDispatch::Response.new - @_response.request = request + set_response!(request) super(action, request) end @@ -22,5 +21,12 @@ module ActionController def reset_session @_request.reset_session end + + private + + def set_response!(request) + @_response = ActionDispatch::Response.new + @_response.request = request + end end end diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index d1813ee745..0377b8c4cf 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -4,30 +4,25 @@ module ActionController include RackDelegation - def recycle! - @_url_options = nil - end - - - # TODO: Clean this up - def process_with_new_base_test(request, response) - @_request = request - @_response = response - @_response.request = request - ret = process(request.parameters[:action]) - if cookies = @_request.env['action_dispatch.cookies'] - cookies.write(@_response) - end - @_response.prepare! - ret - end - # TODO : Rewrite tests using controller.headers= to use Rack env def headers=(new_headers) @_response ||= ActionDispatch::Response.new @_response.headers.replace(new_headers) end + # Behavior specific to functional tests + module Functional # :nodoc: + def set_response!(request) + end + + def recycle! + @_url_options = nil + self.response_body = nil + self.formats = nil + self.params = nil + end + end + module ClassMethods def before_filters _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name} diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index a1f29ea1bc..0498b9d138 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -143,6 +143,9 @@ module ActionController end class TestRequest < ActionDispatch::TestRequest #:nodoc: + DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup + DEFAULT_ENV.delete 'PATH_INFO' + def initialize(env = {}) super @@ -150,10 +153,6 @@ module ActionController self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16)) end - class Result < ::Array #:nodoc: - def to_s() join '/' end - end - def assign_parameters(routes, controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) extra_keys = routes.extra_keys(parameters) @@ -171,7 +170,7 @@ module ActionController non_path_parameters[key] = value else if value.is_a?(Array) - value = Result.new(value.map(&:to_param)) + value = value.map(&:to_param) else value = value.to_param end @@ -211,6 +210,12 @@ module ActionController cookie_jar.update(@set_cookies) cookie_jar.recycle! end + + private + + def default_env + DEFAULT_ENV + end end class TestResponse < ActionDispatch::TestResponse @@ -430,8 +435,13 @@ module ActionController end # Executes a request simulating HEAD HTTP method and set/volley the response - def head(action, parameters = nil, session = nil, flash = nil) - process(action, "HEAD", parameters, session, flash) + def head(action, *args) + process(action, "HEAD", *args) + end + + # Executes a request simulating OPTIONS HTTP method and set/volley the response + def options(action, *args) + process(action, "OPTIONS", *args) end def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @@ -471,13 +481,17 @@ module ActionController # proper params, as is the case when engaging rack. parameters = paramify_values(parameters) if html_format?(parameters) + @html_document = nil + + unless @controller.respond_to?(:recycle!) + @controller.extend(Testing::Functional) + @controller.class.class_eval { include Testing } + end + @request.recycle! @response.recycle! - @controller.response_body = nil - @controller.formats = nil - @controller.params = nil + @controller.recycle! - @html_document = nil @request.env['REQUEST_METHOD'] = http_method parameters ||= {} @@ -490,26 +504,34 @@ module ActionController @request.session.update(session) if session @request.session["flash"] = @request.flash.update(flash || {}) - @controller.request = @request + @controller.request = @request + @controller.response = @response + build_request_uri(action, parameters) - @controller.class.class_eval { include Testing } - @controller.recycle! - @controller.process_with_new_base_test(@request, @response) + + name = @request.parameters[:action] + + @controller.process(name) + + if cookies = @request.env['action_dispatch.cookies'] + cookies.write(@response) + end + @response.prepare! + @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {} @request.session.delete('flash') if @request.session['flash'].blank? @response end def setup_controller_request_and_response - @request = TestRequest.new - @response = TestResponse.new + @request = TestRequest.new + @response = TestResponse.new + @response.request = @request if klass = self.class.controller_class @controller ||= klass.new rescue nil end - @request.env.delete('PATH_INFO') - if defined?(@controller) && @controller @controller.request = @request @controller.params = {} @@ -523,7 +545,7 @@ module ActionController setup :setup_controller_request_and_response end - private + private def check_required_ivars # Sanity check for required instance variables so we can give an # understandable error message. @@ -564,8 +586,7 @@ module ActionController def html_format?(parameters) return true unless parameters.is_a?(Hash) - format = Mime[parameters[:format]] - format.nil? || format.html? + Mime.fetch(parameters[:format]) { Mime['html'] }.html? end end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index ee1913dbf9..fe39c220a5 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -29,6 +29,11 @@ module Mime Type.lookup_by_extension(type.to_s) end + def self.fetch(type) + return type if type.is_a?(Type) + EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } + end + # Encapsulates the notion of a mime type. Can be used at render time, for example, with: # # class PostsController < ActionController::Base @@ -53,6 +58,8 @@ module Mime cattr_reader :browser_generated_types attr_reader :symbol + @register_callbacks = [] + # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: attr_accessor :order, :name, :q @@ -84,6 +91,10 @@ module Mime TRAILING_STAR_REGEXP = /(text|application)\/\*/ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ + def register_callback(&block) + @register_callbacks << block + end + def lookup(string) LOOKUP[string] end @@ -101,10 +112,15 @@ module Mime def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms)) - SET << Mime.const_get(symbol.upcase) + new_mime = Mime.const_get(symbol.upcase) + SET << new_mime ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last } + + @register_callbacks.each do |callback| + callback.call(new_mime) + end end def parse(accept_header) diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb index a86b510719..639ae6f398 100644 --- a/actionpack/lib/action_dispatch/testing/test_request.rb +++ b/actionpack/lib/action_dispatch/testing/test_request.rb @@ -12,7 +12,7 @@ module ActionDispatch def initialize(env = {}) env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application - super(DEFAULT_ENV.merge(env)) + super(default_env.merge(env)) self.host = 'test.host' self.remote_addr = '0.0.0.0' @@ -69,5 +69,11 @@ module ActionDispatch def cookies @cookies ||= {}.with_indifferent_access end + + private + + def default_env + DEFAULT_ENV + end end end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index e4b34125ad..8340aab4d2 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -90,6 +90,10 @@ class FlashTest < ActionController::TestCase def redirect_with_other_flashes redirect_to '/wonderland', :flash => { :joyride => "Horses!" } end + + def redirect_with_foo_flash + redirect_to "/wonderland", :foo => 'for great justice' + end end tests TestController @@ -203,6 +207,12 @@ class FlashTest < ActionController::TestCase get :redirect_with_other_flashes assert_equal "Horses!", @controller.send(:flash)[:joyride] end + + def test_redirect_to_with_adding_flash_types + @controller.class.add_flash_types :foo + get :redirect_with_foo_flash + assert_equal "for great justice", @controller.send(:flash)[:foo] + end end class FlashIntegrationTest < ActionDispatch::IntegrationTest @@ -210,9 +220,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' class TestController < ActionController::Base - def dont_set_flash - head :ok - end + add_flash_types :bar def set_flash flash["that"] = "hello" @@ -227,6 +235,11 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest def use_flash render :inline => "flash: #{flash["that"]}" end + + def set_bar + flash[:bar] = "for great justice" + head :ok + end end def test_flash @@ -266,6 +279,14 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end + def test_added_flash_types_method + with_test_route_set do + get '/set_bar' + assert_response :success + assert_equal 'for great justice', @controller.bar + end + end + private # Overwrite get to send SessionSecret in env hash diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index bdcd5561a8..c8e036b116 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -505,7 +505,7 @@ class RespondToControllerTest < ActionController::TestCase end class RespondWithController < ActionController::Base - respond_to :html, :json + respond_to :html, :json, :touch respond_to :xml, :except => :using_resource_with_block respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ] @@ -623,12 +623,14 @@ class RespondWithControllerTest < ActionController::TestCase super @request.host = "www.example.com" Mime::Type.register_alias('text/html', :iphone) + Mime::Type.register_alias('text/html', :touch) Mime::Type.register('text/x-mobile', :mobile) end def teardown super Mime::Type.unregister(:iphone) + Mime::Type.unregister(:touch) Mime::Type.unregister(:mobile) end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 49137946fe..8990fc34d6 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -197,6 +197,11 @@ XML assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 } end + def test_options + options :test_params + assert_equal 200, @response.status + end + def test_process_without_flash process :set_flash assert_equal '><', flash['test'] @@ -635,7 +640,7 @@ XML get :test_params, :path => ['hello', 'world'] assert_equal ['hello', 'world'], @request.path_parameters['path'] - assert_equal 'hello/world', @request.path_parameters['path'].to_s + assert_equal 'hello/world', @request.path_parameters['path'].to_param end end @@ -913,4 +918,4 @@ class AnonymousControllerTest < ActionController::TestCase get :index assert_equal 'anonymous', @response.body end -end
\ No newline at end of file +end diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 9d77c3acc5..ed012093a7 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -118,6 +118,20 @@ class MimeTypeTest < ActiveSupport::TestCase end end + test "register callbacks" do + begin + registered_mimes = [] + Mime::Type.register_callback do |mime| + registered_mimes << mime + end + + Mime::Type.register("text/foo", :foo) + assert_equal registered_mimes, [Mime::FOO] + ensure + Mime::Type.unregister(:FOO) + end + end + test "custom type with extension aliases" do begin Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"] diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index c005f040eb..387aafebd4 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -68,14 +68,14 @@ module ActionView assert_nil self.class.determine_default_helper_class("String") end - test "delegates notice to request.flash" do - view.request.flash.expects(:notice).with("this message") - view.notice("this message") + test "delegates notice to request.flash[:notice]" do + view.request.flash.expects(:[]).with(:notice) + view.notice end - test "delegates alert to request.flash" do - view.request.flash.expects(:alert).with("this message") - view.alert("this message") + test "delegates alert to request.flash[:alert]" do + view.request.flash.expects(:[]).with(:alert) + view.alert end test "uses controller lookup context" do diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index f5ea285ccb..ca206ee9aa 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -238,8 +238,7 @@ module ActiveModel # Send observed_method(object) if the method exists and # the observer is enabled for the given object's class. def update(observed_method, object, *extra_args, &block) #:nodoc: - return unless respond_to?(observed_method) - return if disabled_for?(object) + return if !respond_to?(observed_method) || disabled_for?(object) send(observed_method, object, *extra_args, &block) end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index a965fe0494..36d94fb38b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,18 @@ ## Rails 4.0.0 (unreleased) ## +* Add `add_reference` and `remove_reference` schema statements. Aliases, `add_belongs_to` + and `remove_belongs_to` are acceptable. References are reversible. + Examples: + + # Create a user_id column + add_reference(:products, :user) + # Create a supplier_id, supplier_type columns and appropriate index + add_reference(:products, :supplier, polymorphic: true, index: true) + # Remove polymorphic reference + remove_reference(:products, :supplier, polymorphic: true) + + *Aleksey Magusev* + * Add `:default` and `:null` options to `column_exists?`. column_exists?(:testings, :taggable_id, :integer, null: false) @@ -7,14 +20,18 @@ *Aleksey Magusev* -* `ActiveRelation#inspect` no longer calls `#to_a`. This means that in places - where `#inspect` is implied (such as in the console), creating a relation - will not execute it anymore, you'll have to call `#to_a` when necessary: +* `ActiveRecord::Relation#inspect` now makes it clear that you are + dealing with a `Relation` object rather than an array:. + + User.where(:age => 30).inspect + # => <ActiveRecord::Relation [#<User ...>, #<User ...>, ...]> + + User.where(:age => 30).to_a.inspect + # => [#<User ...>, #<User ...>] - User.where(:age => 30) # => returns the relation - User.where(:age => 30).to_a # => executes the query and returns the loaded objects, as before + The number of records displayed will be limited to 10. - *Brian Cardarella* + *Brian Cardarella, Jon Leighton & Damien Mathieu* * Add `collation` and `ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later. Example: @@ -852,7 +869,7 @@ * LRU cache in mysql and sqlite are now per-process caches. - * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache keys are per process id. + * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache keys are per process id. * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto *Aaron Patterson* diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 4af4d28b74..49ab3ab808 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -6,7 +6,7 @@ module ActiveRecord included do # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. - class_attribute :serialized_attributes + class_attribute :serialized_attributes, instance_writer: false self.serialized_attributes = {} end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 3b4537aab4..a6e16da730 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -56,7 +56,7 @@ module ActiveRecord end def select_all(arel, name = nil, binds = []) - if @query_cache_enabled + if @query_cache_enabled && !locked?(arel) sql = to_sql(arel, binds) cache_sql(sql, binds) { super(sql, name, binds) } else @@ -83,6 +83,14 @@ module ActiveRecord result.collect { |row| row.dup } end end + + def locked?(arel) + if arel.respond_to?(:locked) + arel.locked + else + false + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 6f9f0399db..60a9eee7c7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -31,7 +31,7 @@ module ActiveRecord # BigDecimals need to be put in a non-normalized form and quoted. when nil then "NULL" when BigDecimal then value.to_s('F') - when Numeric then value.to_s + when Numeric, ActiveSupport::Duration then value.to_s when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value.to_s}'" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index df78ba6c5a..ef17dfbbc5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -259,7 +259,7 @@ module ActiveRecord end # end EOV end - + # Adds index options to the indexes hash, keyed by column name # This is primarily used to track indexes that need to be created after the table # @@ -282,7 +282,7 @@ module ActiveRecord index_options = options.delete(:index) args.each do |col| column("#{col}_id", :integer, options) - column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil? + column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options end end @@ -441,17 +441,13 @@ module ActiveRecord # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. # <tt>references</tt> and <tt>belongs_to</tt> are acceptable. # - # t.references(:goat) - # t.references(:goat, :polymorphic => true) - # t.belongs_to(:goat) + # t.references(:user) + # t.belongs_to(:supplier, polymorphic: true) + # def references(*args) options = args.extract_options! - polymorphic = options.delete(:polymorphic) - index_options = options.delete(:index) - args.each do |col| - @base.add_column(@table_name, "#{col}_id", :integer, options) - @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil? - @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options + args.each do |ref_name| + @base.add_reference(@table_name, ref_name, options) end end alias :belongs_to :references @@ -459,18 +455,16 @@ module ActiveRecord # Removes a reference. Optionally removes a +type+ column. # <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable. # - # t.remove_references(:goat) - # t.remove_references(:goat, :polymorphic => true) - # t.remove_belongs_to(:goat) + # t.remove_references(:user) + # t.remove_belongs_to(:supplier, polymorphic: true) + # def remove_references(*args) options = args.extract_options! - polymorphic = options.delete(:polymorphic) - args.each do |col| - @base.remove_column(@table_name, "#{col}_id") - @base.remove_column(@table_name, "#{col}_type") unless polymorphic.nil? + args.each do |ref_name| + @base.remove_reference(@table_name, ref_name, options) end end - alias :remove_belongs_to :remove_references + alias :remove_belongs_to :remove_references # Adds a column or columns of a specified type # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 2b0ba2f479..65c7ef0153 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -441,6 +441,42 @@ module ActiveRecord indexes(table_name).detect { |i| i.name == index_name } end + # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided. + # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable. + # + # ====== Create a user_id column + # add_reference(:products, :user) + # + # ====== Create a supplier_id and supplier_type columns + # add_belongs_to(:products, :supplier, polymorphic: true) + # + # ====== Create a supplier_id, supplier_type columns and appropriate index + # add_reference(:products, :supplier, polymorphic: true, index: true) + # + def add_reference(table_name, ref_name, options = {}) + polymorphic = options.delete(:polymorphic) + index_options = options.delete(:index) + add_column(table_name, "#{ref_name}_id", :integer, options) + add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic + add_index(table_name, polymorphic ? %w[id type].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options + end + alias :add_belongs_to :add_reference + + # Removes the reference(s). Also removes a +type+ column if one exists. + # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable. + # + # ====== Remove the reference + # remove_reference(:products, :user, index: true) + # + # ====== Remove polymorphic reference + # remove_reference(:products, :supplier, polymorphic: true) + # + def remove_reference(table_name, ref_name, options = {}) + remove_column(table_name, "#{ref_name}_id") + remove_column(table_name, "#{ref_name}_type") if options[:polymorphic] + end + alias :remove_belongs_to :remove_reference + # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the # entire structure of the database. def structure_dump diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 96b62fdd61..95f4360578 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -51,13 +51,15 @@ module ActiveRecord super || delegate.respond_to?(*args) end - [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method| + [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default, :add_reference, :remove_reference].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{method}(*args) # def create_table(*args) record(:"#{method}", args) # record(:create_table, args) end # end EOV end + alias :add_belongs_to :add_reference + alias :remove_belongs_to :remove_reference private @@ -102,6 +104,16 @@ module ActiveRecord [:remove_timestamps, args] end + def invert_add_reference(args) + [:remove_reference, args] + end + alias :invert_add_belongs_to :invert_add_reference + + def invert_remove_reference(args) + [:add_reference, args] + end + alias :invert_remove_belongs_to :invert_remove_reference + # Forwards any missing method call to the \target. def method_missing(method, *args, &block) @delegate.send(method, *args, &block) diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb index 7b3d926d91..0015e3a567 100644 --- a/activerecord/lib/active_record/model.rb +++ b/activerecord/lib/active_record/model.rb @@ -103,7 +103,9 @@ module ActiveRecord def abstract_class? false end - + + # Defines the name of the table column which will store the class name on single-table + # inheritance situations. def inheritance_column 'type' end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 841681e542..7febb5539f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -351,7 +351,7 @@ module ActiveRecord if respond_to?(method) send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts) else - raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" end end end @@ -373,7 +373,7 @@ module ActiveRecord # }) # # Will update the name of the Person with ID 1, build a new associated - # person with the name `John', and mark the associated Person with ID 2 + # person with the name 'John', and mark the associated Person with ID 2 # for destruction. # # Also accepts an Array of attribute hashes: diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e268d451e0..a39328b89b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -514,6 +514,16 @@ module ActiveRecord @values.dup end + def inspect + limit = [limit_value, 11].compact.min + entries = loaded? ? to_a.take(limit) : limit(limit) + + entries.map!(&:inspect) + entries[10] = '...' if entries.size == 11 + + "#<#{self.class.name} [#{entries.join(', ')}]>" + end + private def references_eager_loaded_tables? diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 2af5b02fb7..d836acf18f 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -43,7 +43,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :stored_attributes + class_attribute :stored_attributes, instance_writer: false self.stored_attributes = {} end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index f1241502f5..fb3dfc2730 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -3,13 +3,17 @@ module ActiveRecord module DatabaseTasks # :nodoc: extend self - TASKS_PATTERNS = { - /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks, - /postgresql/ => ActiveRecord::Tasks::PostgreSQLDatabaseTasks, - /sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks - } LOCAL_HOSTS = ['127.0.0.1', 'localhost'] + def register_task(pattern, task) + @tasks ||= {} + @tasks[pattern] = task + end + + register_task(/mysql/, ActiveRecord::Tasks::MySQLDatabaseTasks) + register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks) + register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks) + def create(*arguments) configuration = arguments.first class_for_adapter(configuration['adapter']).new(*arguments).create @@ -84,8 +88,8 @@ module ActiveRecord private def class_for_adapter(adapter) - key = TASKS_PATTERNS.keys.detect { |pattern| adapter[pattern] } - TASKS_PATTERNS[key] + key = @tasks.keys.detect { |pattern| adapter[pattern] } + @tasks[key] end def each_current_configuration(environment) diff --git a/activerecord/lib/rails/generators/active_record/model/templates/module.rb b/activerecord/lib/rails/generators/active_record/model/templates/module.rb index fca2908080..a3bf1c37b6 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/module.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/module.rb @@ -1,7 +1,7 @@ <% module_namespacing do -%> module <%= class_path.map(&:camelize).join('::') %> def self.table_name_prefix - '<%= class_path.join('_') %>_' + '<%= namespaced? ? namespaced_class_path.join('_') : class_path.join('_') %>_' end end <% end -%> diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb index 4111a5f808..a7b63d15c9 100644 --- a/activerecord/test/cases/column_test.rb +++ b/activerecord/test/cases/column_test.rb @@ -76,6 +76,12 @@ module ActiveRecord date_string = Time.now.utc.strftime("%F") assert_equal date_string, column.type_cast(date_string).strftime("%F") end + + def test_type_cast_duration_to_integer + column = Column.new("field", nil, "integer") + assert_equal 1800, column.type_cast(30.minutes) + assert_equal 7200, column.type_cast(2.hours) + end end end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index afff020561..44d08a8ee4 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -5,7 +5,6 @@ require 'config' gem 'minitest' require 'minitest/autorun' require 'stringio' -require 'mocha' require 'cases/test_case' require 'active_record' diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 063209389f..4614be9650 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -30,61 +30,57 @@ module ActiveRecord def test_references_column_type_adds_id with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}] + @connection.expect :add_reference, nil, [:delete_me, :customer, {}] t.references :customer end end def test_remove_references_column_type_removes_id with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'customer_id'] + @connection.expect :remove_reference, nil, [:delete_me, :customer, {}] t.remove_references :customer end end def test_add_belongs_to_works_like_add_references with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}] + @connection.expect :add_reference, nil, [:delete_me, :customer, {}] t.belongs_to :customer end end def test_remove_belongs_to_works_like_remove_references with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'customer_id'] + @connection.expect :remove_reference, nil, [:delete_me, :customer, {}] t.remove_belongs_to :customer end end def test_references_column_type_with_polymorphic_adds_type with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}] - @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}] - t.references :taggable, :polymorphic => true + @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true] + t.references :taggable, polymorphic: true end end def test_remove_references_column_type_with_polymorphic_removes_type with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'taggable_id'] - @connection.expect :remove_column, nil, [:delete_me, 'taggable_type'] - t.remove_references :taggable, :polymorphic => true + @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true] + t.remove_references :taggable, polymorphic: true end end def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag with_change_table do |t| - @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}] - @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}] - t.references :taggable, :polymorphic => true, :null => false + @connection.expect :add_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false] + t.references :taggable, polymorphic: true, null: false end end def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag with_change_table do |t| - @connection.expect :remove_column, nil, [:delete_me, 'taggable_id'] - @connection.expect :remove_column, nil, [:delete_me, 'taggable_type'] - t.remove_references :taggable, :polymorphic => true, :null => false + @connection.expect :remove_reference, nil, [:delete_me, :taggable, polymorphic: true, null: false] + t.remove_references :taggable, polymorphic: true, null: false end end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 7d026961be..f2213ee6aa 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -110,9 +110,9 @@ module ActiveRecord end def test_invert_add_index_with_name - @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}] - remove = @recorder.inverse.first - assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove + @recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}] + remove = @recorder.inverse.first + assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove end def test_invert_add_index_with_no_options @@ -138,6 +138,30 @@ module ActiveRecord add = @recorder.inverse.first assert_equal [:add_timestamps, [:table]], add end + + def test_invert_add_reference + @recorder.record :add_reference, [:table, :taggable, { polymorphic: true }] + remove = @recorder.inverse.first + assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }]], remove + end + + def test_invert_add_belongs_to_alias + @recorder.record :add_belongs_to, [:table, :user] + remove = @recorder.inverse.first + assert_equal [:remove_reference, [:table, :user]], remove + end + + def test_invert_remove_reference + @recorder.record :remove_reference, [:table, :taggable, { polymorphic: true }] + add = @recorder.inverse.first + assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }]], add + end + + def test_invert_remove_belongs_to_alias + @recorder.record :remove_belongs_to, [:table, :user] + add = @recorder.inverse.first + assert_equal [:add_reference, [:table, :user]], add + end end end end diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb index 20f26786f0..768ebc5861 100644 --- a/activerecord/test/cases/migration/helper.rb +++ b/activerecord/test/cases/migration/helper.rb @@ -14,7 +14,7 @@ module ActiveRecord module TestHelper attr_reader :connection, :table_name - CONNECTION_METHODS = %w[add_column remove_column rename_column add_index change_column rename_table] + CONNECTION_METHODS = %w[add_column remove_column rename_column add_index change_column rename_table column_exists? index_exists? add_reference add_belongs_to remove_reference remove_references remove_belongs_to] class TestModel < ActiveRecord::Base self.table_name = :test_models diff --git a/activerecord/test/cases/migration/references_statements_test.rb b/activerecord/test/cases/migration/references_statements_test.rb new file mode 100644 index 0000000000..144302bd4a --- /dev/null +++ b/activerecord/test/cases/migration/references_statements_test.rb @@ -0,0 +1,111 @@ +require "cases/migration/helper" + +module ActiveRecord + class Migration + class ReferencesStatementsTest < ActiveRecord::TestCase + include ActiveRecord::Migration::TestHelper + + self.use_transactional_fixtures = false + + def setup + super + @table_name = :test_models + + add_column table_name, :supplier_id, :integer + add_index table_name, :supplier_id + end + + def test_creates_reference_id_column + add_reference table_name, :user + assert column_exists?(table_name, :user_id, :integer) + end + + def test_does_not_create_reference_type_column + add_reference table_name, :taggable + refute column_exists?(table_name, :taggable_type, :string) + end + + def test_creates_reference_type_column + add_reference table_name, :taggable, polymorphic: true + assert column_exists?(table_name, :taggable_type, :string) + end + + def test_creates_reference_id_index + add_reference table_name, :user, index: true + assert index_exists?(table_name, :user_id) + end + + def test_does_not_create_reference_id_index + add_reference table_name, :user + refute index_exists?(table_name, :user_id) + end + + def test_creates_polymorphic_index + add_reference table_name, :taggable, polymorphic: true, index: true + assert index_exists?(table_name, [:taggable_id, :taggable_type]) + end + + def test_creates_reference_type_column_with_default + add_reference table_name, :taggable, polymorphic: { default: 'Photo' }, index: true + assert column_exists?(table_name, :taggable_type, :string, default: 'Photo') + end + + def test_creates_named_index + add_reference table_name, :tag, index: { name: 'index_taggings_on_tag_id' } + assert index_exists?(table_name, :tag_id, name: 'index_taggings_on_tag_id') + end + + def test_deletes_reference_id_column + remove_reference table_name, :supplier + refute column_exists?(table_name, :supplier_id, :integer) + end + + def test_deletes_reference_id_index + remove_reference table_name, :supplier + refute index_exists?(table_name, :supplier_id) + end + + def test_does_not_delete_reference_type_column + with_polymorphic_column do + remove_reference table_name, :supplier + + refute column_exists?(table_name, :supplier_id, :integer) + assert column_exists?(table_name, :supplier_type, :string) + end + end + + def test_deletes_reference_type_column + with_polymorphic_column do + remove_reference table_name, :supplier, polymorphic: true + refute column_exists?(table_name, :supplier_type, :string) + end + end + + def test_deletes_polymorphic_index + with_polymorphic_column do + remove_reference table_name, :supplier, polymorphic: true + refute index_exists?(table_name, [:supplier_id, :supplier_type]) + end + end + + def test_add_belongs_to_alias + add_belongs_to table_name, :user + assert column_exists?(table_name, :user_id, :integer) + end + + def test_remove_belongs_to_alias + remove_belongs_to table_name, :supplier + refute column_exists?(table_name, :supplier_id, :integer) + end + + private + + def with_polymorphic_column + add_column table_name, :supplier_type, :string + add_index table_name, [:supplier_id, :supplier_type] + + yield + end + end + end +end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 3daa033ed0..3a234f0cc1 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -196,7 +196,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to - assert_raise_with_message ArgumentError, "Cannot build association looter. Are you trying to build a polymorphic one-to-one association?" do + assert_raise_with_message ArgumentError, "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?" do Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"}) end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 08f655d7fa..0153e74604 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -164,6 +164,14 @@ class QueryCacheTest < ActiveRecord::TestCase end end end + + def test_cache_is_ignored_for_locked_relations + task = Task.find 1 + + Task.cache do + assert_queries(2) { task.lock!; task.lock! } + end + end end class QueryCacheExpiryTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 80ee74e41e..3dd11ae89d 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -216,6 +216,14 @@ module ActiveRecord def test_string_with_crazy_column assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo)) end + + def test_quote_duration + assert_equal "1800", @quoter.quote(30.minutes) + end + + def test_quote_duration_int_column + assert_equal "7200", @quoter.quote(2.hours, FakeColumn.new(:integer)) + end end end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 8544d36aa8..8713b8d5e4 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1311,4 +1311,25 @@ class RelationTest < ActiveRecord::TestCase relation.merge! where: 'foo' end end + + test "relations show the records in #inspect" do + relation = Post.limit(2) + assert_equal "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>", relation.inspect + end + + test "relations limit the records in #inspect at 10" do + relation = Post.limit(11) + assert_equal "#<ActiveRecord::Relation [#{Post.limit(10).map(&:inspect).join(', ')}, ...]>", relation.inspect + end + + test "already-loaded relations don't perform a new query in #inspect" do + relation = Post.limit(2) + relation.to_a + + expected = "#<ActiveRecord::Relation [#{Post.limit(2).map(&:inspect).join(', ')}]>" + + assert_no_queries do + assert_equal expected, relation.inspect + end + end end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index a4c065e667..ce167509c1 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -51,4 +51,11 @@ class SerializationTest < ActiveRecord::TestCase assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}" end end + + def test_serialized_attributes_are_class_level_settings + assert_raise NoMethodError do + topic = Topic.new + topic.serialized_attributes = [] + end + end end diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index 7f2b8945f9..3e60b62fd5 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -120,4 +120,11 @@ class StoreTest < ActiveRecord::TestCase test "stored attributes are returned" do assert_equal [:color, :homepage], Admin::User.stored_attributes[:settings] end + + test "stores_attributes are class level settings" do + assert_raise NoMethodError do + @john.stored_attributes = {} + end + end + end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 8c96a8aaa1..4f3489b7a5 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -16,6 +16,22 @@ module ActiveRecord :postgresql => :postgresql_tasks, :sqlite3 => :sqlite_tasks } + + class DatabaseTasksRegisterTask < ActiveRecord::TestCase + def test_register_task + klazz = Class.new do + def initialize(*arguments); end + def structure_dump(filename); end + end + instance = klazz.new + + klazz.stubs(:new).returns instance + instance.expects(:structure_dump).with("awesome-file.sql") + + ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz) + ActiveRecord::Tasks::DatabaseTasks.structure_dump({'adapter' => :foo}, "awesome-file.sql") + end + end class DatabaseTasksCreateTest < ActiveRecord::TestCase include DatabaseTasksSetupper diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index fa38d5c1e3..836bc2f9cf 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -22,4 +22,5 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.6') s.add_dependency('multi_json', '~> 1.3') s.add_dependency('tzinfo', '~> 0.3.33') + s.add_dependency('minitest', '~> 3.2') end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index e2b46a235a..a6f3b43792 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -1,15 +1,18 @@ +gem 'minitest' # make sure we get the gem, not stdlib require 'minitest/spec' require 'active_support/testing/setup_and_teardown' require 'active_support/testing/assertions' require 'active_support/testing/deprecation' require 'active_support/testing/declarative' require 'active_support/testing/isolation' -require 'active_support/testing/mochaing' +require 'active_support/testing/mocha_module' require 'active_support/core_ext/kernel/reporting' module ActiveSupport class TestCase < ::MiniTest::Spec + include ActiveSupport::Testing::MochaModule + if MiniTest::Unit::VERSION < '2.6.1' class << self alias :name :to_s diff --git a/activesupport/lib/active_support/testing/mocha_module.rb b/activesupport/lib/active_support/testing/mocha_module.rb new file mode 100644 index 0000000000..ed2942d23a --- /dev/null +++ b/activesupport/lib/active_support/testing/mocha_module.rb @@ -0,0 +1,22 @@ +module ActiveSupport + module Testing + module MochaModule + begin + require 'mocha_standalone' + include Mocha::API + + def before_setup + mocha_setup + super + end + + def after_teardown + super + mocha_verify + mocha_teardown + end + rescue LoadError + end + end + end +end diff --git a/activesupport/lib/active_support/testing/mochaing.rb b/activesupport/lib/active_support/testing/mochaing.rb deleted file mode 100644 index 4ad75a6681..0000000000 --- a/activesupport/lib/active_support/testing/mochaing.rb +++ /dev/null @@ -1,7 +0,0 @@ -begin - silence_warnings { require 'mocha' } -rescue LoadError - # Fake Mocha::ExpectationError so we can rescue it in #run. Bleh. - Object.const_set :Mocha, Module.new - Mocha.const_set :ExpectationError, Class.new(StandardError) -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 527fa555b7..a65148cf1f 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -4,20 +4,11 @@ require 'active_support/callbacks' module ActiveSupport module Testing module SetupAndTeardown - - PASSTHROUGH_EXCEPTIONS = [ - NoMemoryError, - SignalException, - Interrupt, - SystemExit - ] - extend ActiveSupport::Concern included do include ActiveSupport::Callbacks define_callbacks :setup, :teardown - end module ClassMethods @@ -30,28 +21,15 @@ module ActiveSupport end end - def run(runner) - result = '.' - begin - run_callbacks :setup do - result = super - end - rescue *PASSTHROUGH_EXCEPTIONS - raise - rescue Exception => e - result = runner.puke(self.class, method_name, e) - ensure - begin - run_callbacks :teardown - rescue *PASSTHROUGH_EXCEPTIONS - raise - rescue Exception => e - result = runner.puke(self.class, method_name, e) - end - end - result + def before_setup + super + run_callbacks :setup end + def after_teardown + run_callbacks :teardown + super + end end end end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 57ed4a6b60..25ed962c23 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -18,8 +18,6 @@ end require 'minitest/autorun' require 'empty_bool' -silence_warnings { require 'mocha' } - ENV['NO_RELOAD'] = '1' require 'active_support' diff --git a/guides/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb index 8bf1192ffe..3daca18a71 100644 --- a/guides/code/getting_started/test/test_helper.rb +++ b/guides/code/getting_started/test/test_helper.rb @@ -3,7 +3,7 @@ require File.expand_path('../../config/environment', __FILE__) require 'rails/test_help' class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting diff --git a/guides/source/4_0_release_notes.textile b/guides/source/4_0_release_notes.textile index e1d6b42e6c..b7ac11999a 100644 --- a/guides/source/4_0_release_notes.textile +++ b/guides/source/4_0_release_notes.textile @@ -112,6 +112,8 @@ h4(#railties_deprecations). Deprecations h3. Action Mailer +* Allow to set default Action Mailer options via <tt>config.action_mailer.default_options=</tt>. + * Raise an <tt>ActionView::MissingTemplate</tt> exception when no implicit template could be found. * Asynchronously send messages via the Rails Queue. @@ -329,6 +331,20 @@ Moved into a separate gem <tt>sprockets-rails</tt>. h3. Active Record +* Add <tt>add_reference</tt> and <tt>remove_reference</tt> schema statements. Aliases, <tt>add_belongs_to</tt> and <tt>remove_belongs_to</tt> are acceptable. References are reversible. + +<ruby> +# Create a user_id column +add_reference(:products, :user) + +# Create a supplier_id, supplier_type columns and appropriate index +add_reference(:products, :supplier, polymorphic: true, index: true) + +# Remove polymorphic reference +remove_reference(:products, :supplier, polymorphic: true) +</ruby> + + * Add <tt>:default</tt> and <tt>:null</tt> options to <tt>column_exists?</tt>. <ruby> @@ -336,14 +352,19 @@ column_exists?(:testings, :taggable_id, :integer, null: false) column_exists?(:testings, :taggable_type, :string, default: 'Photo') </ruby> -* <tt>ActiveRelation#inspect</tt> no longer calls <tt>#to_a</tt>. This means that in places where <tt>#inspect</tt> is implied (such as in the console), creating a relation will not execute it anymore, you'll have to call <tt>#to_a</tt> when necessary: +* <tt>ActiveRecord::Relation#inspect</tt> now makes it clear that you are dealing with a <tt>Relation</tt> object rather than an array: <ruby> -User.where(:age => 30) # => returns the relation -User.where(:age => 30).to_a # => executes the query and returns the loaded objects, as before +User.where(:age => 30).inspect +# => <ActiveRecord::Relation [#<User ...>, #<User ...>]> + +User.where(:age => 30).to_a.inspect +# => [#<User ...>, #<User ...>] </ruby> -* Add <tt>collation</tt> and <tt>ctype</tt> support to PostgreSQL. These are available for PostgreSQL 8.4 or later. +if more than 10 items are returned by the relation, inspect will only show the first 10 followed by ellipsis. + +* Add <tt>:collation</tt> and <tt>:ctype</tt> support to PostgreSQL. These are available for PostgreSQL 8.4 or later. <yaml> development: @@ -506,7 +527,7 @@ Post.find_by! name: 'Spartacus' * Added <tt>ActiveRecord::Base#slice</tt> to return a hash of the given methods with their names as keys and returned values as values. -* Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this commit: https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed. +* Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this "commit":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed. * Added a feature to dump/load internal state of +SchemaCache+ instance because we want to boot more quickly when we have many models. @@ -708,7 +729,7 @@ h4(#activemodel_deprecations). Deprecations h3. Active Resource -* Active Resource is removed from Rails 4.0 and is now a separate gem. TODO: put a link to the gem here. +* Active Resource is removed from Rails 4.0 and is now a separate "gem":https://github.com/rails/activeresource. h3. Active Support diff --git a/guides/source/configuring.textile b/guides/source/configuring.textile index af46538bf5..cd9aab4892 100644 --- a/guides/source/configuring.textile +++ b/guides/source/configuring.textile @@ -424,7 +424,7 @@ There are a number of settings available on +config.action_mailer+: * +config.action_mailer.perform_deliveries+ specifies whether mail will actually be delivered and is true by default. It can be convenient to set it to false for testing. -* +config.action_mailer.default+ configures Action Mailer defaults. These default to: +* +config.action_mailer.default_options+ configures Action Mailer defaults. Use to set options like `from` or `reply_to` for every mailer. These default to: <ruby> :mime_version => "1.0", :charset => "UTF-8", diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb index 0090293200..9afda2d0df 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb @@ -6,7 +6,7 @@ class ActiveSupport::TestCase <% unless options[:skip_active_record] -%> ActiveRecord::Migration.check_pending! - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE index 67f76aad01..c46c86076e 100644 --- a/railties/lib/rails/generators/rails/model/USAGE +++ b/railties/lib/rails/generators/rails/model/USAGE @@ -19,6 +19,55 @@ Description: then the generator will create a module with a table_name_prefix method to prefix the model's table name with the module name (e.g. admin_account) +Available field types: + + Just after the field name you can specify a type like text or boolean. + It will generate the column with the associated SQL type. For instance: + + `rails generate model post title:string body:text` + + will generate a title column with a varchar type and a body column with a text + type. You can use the following types: + + integer + primary_key + decimal + float + boolean + binary + string + text + date + time + datetime + timestamp + + You can also consider `references` as a kind of type. For instance, if you run: + + `rails generate model photo title:string album:references` + + It will generate an album_id column. You should generate this kind of fields when + you will use a `belongs_to` association for instance. `references` also support + the polymorphism, you could enable the polymorphism like this: + + `rails generate model product supplier:references{polymorphic}` + + You can also specify some options just after the field type. You can use the + following options: + + limit Set the maximum size of the field giving a number between curly braces + default Set a default value for the field + precision Defines the precision for the decimal fields + scale Defines the scale for the decimal fields + uniq Defines the field values as unique + index Will add an index on the field + + Examples: + + `rails generate model user pseudo:string{30}` + `rails generate model user pseudo:string:uniq` + + Examples: `rails generate model account` diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore index 458b2c662e..086d87818a 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore +++ b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore @@ -1,8 +1,10 @@ .bundle/ log/*.log pkg/ +<% unless options[:skip_test_unit] && options[:dummy_path] == 'test/dummy' -%> <%= dummy_path %>/db/*.sqlite3 <%= dummy_path %>/db/*.sqlite3-journal <%= dummy_path %>/log/*.log <%= dummy_path %>/tmp/ <%= dummy_path %>/.sass-cache +<% end -%>
\ No newline at end of file diff --git a/railties/lib/rails/queueing.rb b/railties/lib/rails/queueing.rb index b4bc7fcd18..8a76914548 100644 --- a/railties/lib/rails/queueing.rb +++ b/railties/lib/rails/queueing.rb @@ -22,6 +22,13 @@ module Rails @que.dup end + # Marshal and unmarshal job before pushing it onto the queue. This will + # raise an exception on any attempts in tests to push jobs that can't (or + # shouldn't) be marshalled. + def push(job) + super Marshal.load(Marshal.dump(job)) + end + # Drain the queue, running all jobs in a different thread. This method # may not be available on production queues. def drain diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 46bf3bbe48..581ceaf9ce 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -10,7 +10,10 @@ require 'action_dispatch/testing/integration' # Enable turn if it is available begin require 'turn' - MiniTest::Unit.use_natural_language_case_names = true + + Turn.config do |c| + c.natural = true + end rescue LoadError end diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb index da8bdeed52..22d6c20404 100644 --- a/railties/test/application/queue_test.rb +++ b/railties/test/application/queue_test.rb @@ -30,46 +30,68 @@ module ApplicationTests assert_kind_of Rails::Queueing::Queue, Rails.queue end - test "in development mode, an enqueued job will be processed in a separate thread" do - app("development") + class ThreadTrackingJob + def initialize + @origin = Thread.current.object_id + end + + def run + @target = Thread.current.object_id + end - job = Struct.new(:origin, :target).new(Thread.current) - def job.run - self.target = Thread.current + def ran_in_different_thread? + @origin != @target end + def ran? + @target + end + end + + test "in development mode, an enqueued job will be processed in a separate thread" do + app("development") + + job = ThreadTrackingJob.new Rails.queue.push job sleep 0.1 - assert job.target, "The job was run" - assert_not_equal job.origin, job.target + assert job.ran?, "Expected job to be run" + assert job.ran_in_different_thread?, "Expected job to run in a different thread" end test "in test mode, explicitly draining the queue will process it in a separate thread" do app("test") - job = Struct.new(:origin, :target).new(Thread.current) - def job.run - self.target = Thread.current + Rails.queue.push ThreadTrackingJob.new + job = Rails.queue.jobs.last + Rails.queue.drain + + assert job.ran?, "Expected job to be run" + assert job.ran_in_different_thread?, "Expected job to run in a different thread" + end + + class IdentifiableJob + def initialize(id) + @id = id end - Rails.queue.push job - Rails.queue.drain + def ==(other) + other.same_id?(@id) + end + + def same_id?(other_id) + other_id == @id + end - assert job.target, "The job was run" - assert_not_equal job.origin, job.target + def run + end end test "in test mode, the queue can be observed" do app("test") - job = Struct.new(:id) do - def run - end - end - jobs = (1..10).map do |id| - job.new(id) + IdentifiableJob.new(id) end jobs.each do |job| @@ -79,6 +101,29 @@ module ApplicationTests assert_equal jobs, Rails.queue.jobs end + test "in test mode, adding an unmarshallable job will raise an exception" do + app("test") + anonymous_class_instance = Struct.new(:run).new + assert_raises TypeError do + Rails.queue.push anonymous_class_instance + end + end + + test "attempting to marshal a queue will raise an exception" do + app("test") + assert_raises TypeError do + Marshal.dump Rails.queue + end + end + + test "attempting to add a reference to itself to the queue will raise an exception" do + app("test") + job = {reference: Rails.queue} + assert_raises TypeError do + Rails.queue.push job + end + end + def setup_custom_queue add_to_env_config "production", <<-RUBY require "my_queue" diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 2ae9dc61a7..db2b8af217 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -99,7 +99,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase run_generator ["admin/account"] assert_file "app/models/test_app/admin.rb", /module TestApp/, /module Admin/ assert_file "app/models/test_app/admin.rb", /def self\.table_name_prefix/ - assert_file "app/models/test_app/admin.rb", /'admin_'/ + assert_file "app/models/test_app/admin.rb", /'test_app_admin_'/ assert_file "app/models/test_app/admin/account.rb", /module TestApp/, /class Admin::Account < ActiveRecord::Base/ end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 0a235b56d5..6c3e0178f2 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -264,11 +264,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "spec/dummy" assert_file "spec/dummy/config/application.rb" assert_no_file "test" + assert_file '.gitignore' do |contents| + assert_match(/spec\/dummy/, contents) + end end def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path FileUtils.cd(Rails.root) - run_generator([destination_root, "--dummy_path", "spec/dummy" "--skip-test-unit"]) + run_generator([destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"]) assert_file ".gitignore" do |contents| assert_match(/spec\/dummy/, contents) end @@ -280,6 +283,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "bukkits.gemspec" do |contents| assert_no_match(/s.test_files = Dir\["test\/\*\*\/\*"\]/, contents) end + assert_file '.gitignore' do |contents| + assert_no_match(/test\dummy/, contents) + end end def test_skipping_gemspec diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 417d019178..ad2f85a5ff 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -1,7 +1,6 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/model/model_generator' require 'rails/generators/test_unit/model/model_generator' -require 'mocha' class GeneratorsTest < Rails::Generators::TestCase include GeneratorsTestHelper diff --git a/railties/test/queueing/test_queue_test.rb b/railties/test/queueing/test_queue_test.rb index 78c6c617fe..2f0f507adb 100644 --- a/railties/test/queueing/test_queue_test.rb +++ b/railties/test/queueing/test_queue_test.rb @@ -2,22 +2,18 @@ require 'abstract_unit' require 'rails/queueing' class TestQueueTest < ActiveSupport::TestCase - class Job - def initialize(&block) - @block = block - end + def setup + @queue = Rails::Queueing::TestQueue.new + end + class ExceptionRaisingJob def run - @block.call if @block + raise end end - def setup - @queue = Rails::Queueing::TestQueue.new - end - def test_drain_raises - @queue.push Job.new { raise } + @queue.push ExceptionRaisingJob.new assert_raises(RuntimeError) { @queue.drain } end @@ -27,41 +23,80 @@ class TestQueueTest < ActiveSupport::TestCase assert_equal [1,2], @queue.jobs end + class EquivalentJob + def initialize + @initial_id = self.object_id + end + + def run + end + + def ==(other) + other.same_initial_id?(@initial_id) + end + + def same_initial_id?(other_id) + other_id == @initial_id + end + end + def test_contents assert @queue.empty? - job = Job.new + job = EquivalentJob.new @queue.push job refute @queue.empty? assert_equal job, @queue.pop end - def test_order - processed = [] + class ProcessingJob + def self.clear_processed + @processed = [] + end + + def self.processed + @processed + end + + def initialize(object) + @object = object + end + + def run + self.class.processed << @object + end + end - job1 = Job.new { processed << 1 } - job2 = Job.new { processed << 2 } + def test_order + ProcessingJob.clear_processed + job1 = ProcessingJob.new(1) + job2 = ProcessingJob.new(2) @queue.push job1 @queue.push job2 @queue.drain - assert_equal [1,2], processed + assert_equal [1,2], ProcessingJob.processed end - def test_drain - t = nil - ran = false + class ThreadTrackingJob + attr_reader :thread_id - job = Job.new do - ran = true - t = Thread.current + def run + @thread_id = Thread.current.object_id end - @queue.push job + def ran? + @thread_id + end + end + + def test_drain + @queue.push ThreadTrackingJob.new + job = @queue.jobs.last @queue.drain assert @queue.empty? - assert ran, "The job runs synchronously when the queue is drained" - assert_not_equal t, Thread.current + assert job.ran?, "The job runs synchronously when the queue is drained" + assert_not_equal job.thread_id, Thread.current.object_id end end diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb index 1938bfb6c2..c90b795d59 100644 --- a/railties/test/railties/generators_test.rb +++ b/railties/test/railties/generators_test.rb @@ -75,6 +75,18 @@ module RailtiesTests end end + def test_table_name_prefix_is_correctly_namespaced_when_engine_is_mountable + build_mountable_engine + Dir.chdir(engine_path) do + bundled_rails("g model namespaced/topic") + assert_file "app/models/foo_bar/namespaced.rb", /module FooBar\n module Namespaced/ do |content| + assert_class_method :table_name_prefix, content do |method_content| + assert_match(/'foo_bar_namespaced_'/, method_content) + end + end + end + end + def test_helpers_are_correctly_namespaced_when_engine_is_mountable build_mountable_engine Dir.chdir(engine_path) do |