diff options
Diffstat (limited to 'railties')
16 files changed, 283 insertions, 162 deletions
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile index 5bac75fe8f..7433507866 100644 --- a/railties/guides/source/api_documentation_guidelines.textile +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -27,7 +27,7 @@ Communicate to the reader the current way of doing things, both explicitly and i Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil? -The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. Historically there has been lack of consistency regarding this, but we checked with David when docrails started. All Rails documentation consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be phenomenal:). +The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. All Rails documentation should consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be phenomenal. Spell names correctly: Arel, Test::Unit, RSpec, HTML, MySQL, JavaScript, ERb. When in doubt, please have a look at some authoritative source like their official documentation. diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 7a033a30d7..ace433e30c 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -9,6 +9,7 @@ In this guide you will: * Generate select boxes from multiple types of data * Understand the date and time helpers Rails provides * Learn what makes a file upload form different +* Learn some cases of building forms to external resources * Find out where to look for complex forms endprologue. @@ -763,6 +764,40 @@ As a shortcut you can append [] to the name and omit the +:index+ option. This i produces exactly the same output as the previous example. +h3. Forms to external resources + +If you need to post some data to an external resource it is still great to build your from using rails form helpers. But sometimes you need to set an +authenticity_token+ for this resource. You can do it by passing an +:authenticity_token => 'your_external_token'+ parameter to the +form_tag+ options: + +<erb> +<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %> + Form contents +<% end %> +</erb> + +Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an +authenticity_token+ hidden field at all. For doing this just pass +false+ to the +:authenticity_token+ option: + +<erb> +<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %> + Form contents +<% end %> +</erb> + +The same technique is available for the +form_for+ too: + +<erb> +<%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f| + Form contents +<% end %> +</erb> + +Or if you don't want to render an +authenticity_token+ field: + +<erb> +<%= form_for @invoice, :url => external_url, :authenticity_token => false do |f| + Form contents +<% end %> +</erb> + h3. Building Complex Forms Many apps grow beyond simple forms editing a single object. For example when creating a Person you might want to allow the user to (on the same form) create multiple address records (home, work, etc.). When later editing that person the user should be able to add, remove or amend addresses as necessary. While this guide has shown you all the pieces necessary to handle this, Rails does not yet have a standard end-to-end way of accomplishing this, but many have come up with viable approaches. These include: @@ -776,6 +811,7 @@ Many apps grow beyond simple forms editing a single object. For example when cre h3. Changelog +* February 5, 2011: Added 'Forms to external resources' section. Timothy N. Tsvetkov <timothy.tsvetkov@gmail.com> * April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com h3. Authors diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 1d81c8f95b..d214031b31 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -391,6 +391,8 @@ NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/ </ruby> +TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id => /[^\/]+/+ allows anything except a slash. + h4. Static Segments You can specify static segments when creating a route: @@ -646,6 +648,8 @@ end NOTE: Of course, you can use the more advanced constraints available in non-resourceful routes in this context. +TIP: By default the +:id+ parameter doesn't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within an +:id+ add a constraint which overrides this - for example +:id => /[^\/]+/+ allows anything except a slash. + h4. Overriding the Named Helpers The +:as+ option lets you override the normal naming for the named route helpers. For example: @@ -852,12 +856,6 @@ You can supply a +:method+ argument to specify the HTTP verb: assert_recognizes({ :controller => "photos", :action => "create" }, { :path => "photos", :method => :post }) </ruby> -You can also use the resourceful helpers to test recognition of a RESTful route: - -<ruby> -assert_recognizes new_photo_url, { :path => "photos", :method => :post } -</ruby> - h5. The +assert_routing+ Assertion The +assert_routing+ assertion checks the route both ways: it tests that the path generates the options, and that the options generate the path. Thus, it combines the functions of +assert_generates+ and +assert_recognizes+. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 149c63cd9e..9cb3a0f008 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -149,7 +149,10 @@ module Rails require "action_dispatch/http/rack_cache" if rack_cache middleware.use ::Rack::Cache, rack_cache if rack_cache - middleware.use ::ActionDispatch::Static, config.static_asset_paths if config.serve_static_assets + if config.serve_static_assets + asset_paths = ActiveSupport::OrderedHash[config.static_asset_paths.to_a.reverse] + middleware.use ::ActionDispatch::Static, asset_paths + end middleware.use ::Rack::Lock unless config.allow_concurrency middleware.use ::Rack::Runtime middleware.use ::Rails::Rack::Logger diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 44a2639488..2af7f85463 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -118,11 +118,11 @@ module Rails end def singular_table_name - @singular_table_name ||= table_name.singularize + @singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name) end def plural_table_name - @plural_table_name ||= table_name.pluralize + @plural_table_name ||= (pluralize_table_names? ? table_name : table_name.pluralize) end def plural_file_name diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 00fe100245..c383d4842f 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -12,7 +12,7 @@ source 'http://rubygems.org' # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) # gem 'ruby-debug' -# gem 'ruby-debug19' +# gem 'ruby-debug19', :require => 'ruby-debug' # Bundle the extra gems: # gem 'bj' diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 6e515756fe..b7f64af339 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -57,5 +57,10 @@ module <%= app_const_base %> # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] + +<% unless options[:skip_active_record] -%> + # Enable IdentityMap for Active Record, to disable set to false or remove the line below. + config.active_record.identity_map = true +<% end -%> end end diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js index 668cffa73a..4dcb3779a2 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js +++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js @@ -1,154 +1,148 @@ -/* - * jquery-ujs - * - * http://github.com/rails/jquery-ujs/blob/master/src/rails.js - * - * This rails.js file supports jQuery 1.4.3 and 1.4.4 . +/** + * Unobtrusive scripting adapter for jQuery * + * Requires jQuery 1.4.3 or later. + * https://github.com/rails/jquery-ujs */ -jQuery(function ($) { - var csrf_token = $('meta[name=csrf-token]').attr('content'), - csrf_param = $('meta[name=csrf-param]').attr('content'); - - $.fn.extend({ - /** - * Triggers a custom event on an element and returns the event result - * this is used to get around not being able to ensure callbacks are placed - * at the end of the chain. - */ - triggerAndReturn: function (name, data) { - var event = new $.Event(name); - this.trigger(event, data); - - return event.result !== false; - }, - - /** - * Handles execution of remote calls. Provides following callbacks: - * - * - ajax:beforeSend - is executed before firing ajax call - * - ajax:success - is executed when status is success - * - ajax:complete - is executed when the request finishes, whether in failure or success - * - ajax:error - is execute in case of error - */ - callRemote: function () { - var el = this, - method = el.attr('method') || el.attr('data-method') || 'GET', - url = el.attr('action') || el.attr('href'), - dataType = el.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType); - - if (url === undefined) { - throw "No URL specified for remote call (action or href must be present)."; - } else { - var $this = $(this), data = el.is('form') ? el.serializeArray() : []; - - $.ajax({ - url: url, - data: data, - dataType: dataType, - type: method.toUpperCase(), - beforeSend: function (xhr) { - if ($this.triggerHandler('ajax:beforeSend') === false) { - return false; - } - }, - success: function (data, status, xhr) { - el.trigger('ajax:success', [data, status, xhr]); - }, - complete: function (xhr) { - el.trigger('ajax:complete', xhr); - }, - error: function (xhr, status, error) { - el.trigger('ajax:error', [xhr, status, error]); - } - }); - } - } - }); - - /** - * confirmation handler - */ - $('body').delegate('a[data-confirm], button[data-confirm], input[data-confirm]', 'click.rails', function () { - var el = $(this); - if (el.triggerAndReturn('confirm')) { - if (!confirm(el.attr('data-confirm'))) { - return false; - } - } - }); - - - - /** - * remote handlers - */ - $('form[data-remote]').live('submit.rails', function (e) { - $(this).callRemote(); - e.preventDefault(); - }); - - $('a[data-remote],input[data-remote]').live('click.rails', function (e) { - $(this).callRemote(); - e.preventDefault(); - }); - - /** - * <%= link_to "Delete", user_path(@user), :method => :delete, :confirm => "Are you sure?" %> - * - * <a href="/users/5" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Delete</a> - */ - $('a[data-method]:not([data-remote])').live('click.rails', function (e){ - var link = $(this), - href = link.attr('href'), - method = link.attr('data-method'), - form = $('<form method="post" action="'+href+'"></form>'), - metadata_input = '<input name="_method" value="'+method+'" type="hidden" />'; - - if (csrf_param !== undefined && csrf_token !== undefined) { - metadata_input += '<input name="'+csrf_param+'" value="'+csrf_token+'" type="hidden" />'; - } - - form.hide() - .append(metadata_input) - .appendTo('body'); - - e.preventDefault(); - form.submit(); - }); - - /** - * disable-with handlers - */ - var disable_with_input_selector = 'input[data-disable-with]', - disable_with_form_remote_selector = 'form[data-remote]:has(' + disable_with_input_selector + ')', - disable_with_form_not_remote_selector = 'form:not([data-remote]):has(' + disable_with_input_selector + ')'; - - var disable_with_input_function = function () { - $(this).find(disable_with_input_selector).each(function () { - var input = $(this); - input.data('enable-with', input.val()) - .attr('value', input.attr('data-disable-with')) - .attr('disabled', 'disabled'); - }); - }; - - $(disable_with_form_remote_selector).live('ajax:before.rails', disable_with_input_function); - $(disable_with_form_not_remote_selector).live('submit.rails', disable_with_input_function); - - $(disable_with_form_remote_selector).live('ajax:complete.rails', function () { - $(this).find(disable_with_input_selector).each(function () { - var input = $(this); - input.removeAttr('disabled') - .val(input.data('enable-with')); - }); - }); - - var jqueryVersion = $().jquery; - - if (!( (jqueryVersion === '1.4.3') || (jqueryVersion === '1.4.4'))){ - alert('This rails.js does not support the jQuery version you are using. Please read documentation.'); +(function($) { + // Triggers an event on an element and returns the event result + function fire(obj, name, data) { + var event = new $.Event(name); + obj.trigger(event, data); + return event.result !== false; + } + + // Submits "remote" forms and links with ajax + function handleRemote(element) { + var method, url, data, + dataType = element.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType); + + if (element.is('form')) { + method = element.attr('method'); + url = element.attr('action'); + data = element.serializeArray(); + // memoized value from clicked submit button + var button = element.data('ujs:submit-button'); + if (button) { + data.push(button); + element.data('ujs:submit-button', null); + } + } else { + method = element.attr('data-method'); + url = element.attr('href'); + data = null; + } + + $.ajax({ + url: url, type: method || 'GET', data: data, dataType: dataType, + // stopping the "ajax:beforeSend" event will cancel the ajax request + beforeSend: function(xhr, settings) { + if (settings.dataType === undefined) { + xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); + } + return fire(element, 'ajax:beforeSend', [xhr, settings]); + }, + success: function(data, status, xhr) { + element.trigger('ajax:success', [data, status, xhr]); + }, + complete: function(xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + }, + error: function(xhr, status, error) { + element.trigger('ajax:error', [xhr, status, error]); + } + }); + } + + // Handles "data-method" on links such as: + // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a> + function handleMethod(link) { + var href = link.attr('href'), + method = link.attr('data-method'), + csrf_token = $('meta[name=csrf-token]').attr('content'), + csrf_param = $('meta[name=csrf-param]').attr('content'), + form = $('<form method="post" action="' + href + '"></form>'), + metadata_input = '<input name="_method" value="' + method + '" type="hidden" />'; + + if (csrf_param !== undefined && csrf_token !== undefined) { + metadata_input += '<input name="' + csrf_param + '" value="' + csrf_token + '" type="hidden" />'; + } + + form.hide().append(metadata_input).appendTo('body'); + form.submit(); + } + + function disableFormElements(form) { + form.find('input[data-disable-with]').each(function() { + var input = $(this); + input.data('ujs:enable-with', input.val()) + .val(input.attr('data-disable-with')) + .attr('disabled', 'disabled'); + }); + } + + function enableFormElements(form) { + form.find('input[data-disable-with]').each(function() { + var input = $(this); + input.val(input.data('ujs:enable-with')).removeAttr('disabled'); + }); + } + + function allowAction(element) { + var message = element.attr('data-confirm'); + return !message || (fire(element, 'confirm') && confirm(message)); + } + + function requiredValuesMissing(form) { + var missing = false; + form.find('input[name][required]').each(function() { + if (!$(this).val()) missing = true; + }); + return missing; } -}); + $('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) { + var link = $(this); + if (!allowAction(link)) return false; + + if (link.attr('data-remote') != undefined) { + handleRemote(link); + return false; + } else if (link.attr('data-method')) { + handleMethod(link); + return false; + } + }); + + $('form').live('submit.rails', function(e) { + var form = $(this), remote = form.attr('data-remote') != undefined; + if (!allowAction(form)) return false; + + // skip other logic when required values are missing + if (requiredValuesMissing(form)) return !remote; + + if (remote) { + handleRemote(form); + return false; + } else { + disableFormElements(form); + } + }); + + $('form input[type=submit], form button[type=submit], form button:not([type])').live('click.rails', function() { + var button = $(this); + if (!allowAction(button)) return false; + // register the pressed submit button + var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null; + button.closest('form').data('ujs:submit-button', data); + }); + + $('form').live('ajax:beforeSend.rails', function(event) { + if (this == event.target) disableFormElements($(this)); + }); + + $('form').live('ajax:complete.rails', function(event) { + if (this == event.target) enableFormElements($(this)); + }); +})( jQuery ); diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js index 4c18cb0c3e..2cd1220786 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js +++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js @@ -189,4 +189,20 @@ document.on('ajax:complete', 'form', function(event, form) { if (form == event.findElement()) enableFormElements(form); }); + + Ajax.Responders.register({ + onCreate: function(request) { + var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; + + if (csrf_meta_tag) { + var header = 'X-CSRF-Token', + token = csrf_meta_tag.readAttribute('content'); + + if (!request.options.requestHeaders) { + request.options.requestHeaders = {}; + } + request.options.requestHeaders[header] = token; + } + } + }); })(); diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index f81002328f..00029e627e 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -19,6 +19,10 @@ if defined?(ActiveRecord) class ActiveSupport::TestCase include ActiveRecord::TestFixtures self.fixture_path = "#{Rails.root}/test/fixtures/" + + setup do + ActiveRecord::IdentityMap.clear + end end ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 475091f789..19311a7fa0 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -1,7 +1,7 @@ require "isolation/abstract_unit" module ApplicationTests - class FrameworlsTest < Test::Unit::TestCase + class FrameworksTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation def setup @@ -166,7 +166,7 @@ module ApplicationTests require "#{app_path}/config/environment" - expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] + expects = [ActiveRecord::IdentityMap::Middleware, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] middleware = Rails.application.config.middleware.map { |m| m.klass } assert_equal expects, middleware & expects end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index a2217888e4..d88bd05a74 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -29,6 +29,7 @@ module ApplicationTests "Rack::Sendfile", "ActionDispatch::Reloader", "ActionDispatch::Callbacks", + "ActiveRecord::IdentityMap::Middleware", "ActiveRecord::ConnectionAdapters::ConnectionManagement", "ActiveRecord::QueryCache", "ActionDispatch::Cookies", @@ -56,6 +57,7 @@ module ApplicationTests boot! assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement") assert !middleware.include?("ActiveRecord::QueryCache") + assert !middleware.include?("ActiveRecord::IdentityMap::Middleware") end test "removes lock if allow concurrency is set" do @@ -112,6 +114,11 @@ module ApplicationTests assert_equal "Rack::Runtime", middleware.fourth end + test "identity map is inserted" do + boot! + assert_equal "ActiveRecord::IdentityMap::Middleware", middleware[9] + end + test "insert middleware before" do add_to_config "config.middleware.insert_before ActionDispatch::Static, Rack::Config" boot! diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 822a6bf032..59e5ef4dee 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -82,5 +82,22 @@ module ApplicationTests assert_match /remove_column\("users", :email\)/, output assert_match /AddEmailToUsers: reverted/, output end + + def test_loading_specific_fixtures + Dir.chdir(app_path) do + `rails generate model user username:string password:string` + `rails generate model product name:string` + `rake db:migrate` + end + + require "#{rails_root}/config/environment" + + # loading a specific fixture + errormsg = Dir.chdir(app_path) { `rake db:fixtures:load FIXTURES=products` } + assert $?.success?, errormsg + + assert_equal 2, ::AppTemplate::Application::Product.count + assert_equal 0, ::AppTemplate::Application::User.count + end end end diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb index 1badae0713..f23701e99e 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -98,6 +98,11 @@ class NamedBaseTest < Rails::Generators::TestCase assert_name g, 'posts', :index_helper end + def test_index_helper_to_pluralize_once + g = generator ['Stadium'] + assert_name g, 'stadia', :index_helper + end + def test_index_helper_with_uncountable g = generator ['Sheep'] assert_name g, 'sheep_index', :index_helper diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 3b03e4eb3d..c5b1cb9a80 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -215,6 +215,13 @@ module TestHelpers end end + def remove_from_config(str) + file = "#{app_path}/config/application.rb" + contents = File.read(file) + contents.sub!(/#{str}/, "") + File.open(file, "w+") { |f| f.puts contents } + end + def app_file(path, contents) FileUtils.mkdir_p File.dirname("#{app_path}/#{path}") File.open("#{app_path}/#{path}", 'w') do |f| @@ -231,6 +238,7 @@ module TestHelpers :activemodel, :activerecord, :activeresource] - arr + remove_from_config "config.active_record.identity_map = true" if to_remove.include? :activerecord $:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' } end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 92aa025238..0ce00db3c4 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -306,6 +306,34 @@ module RailtiesTest assert_equal File.read(File.join(app_path, "public/bukkits/file_from_app.html")), last_response.body end + test "an applications files are given priority over an engines files when served via ActionDispatch::Static" do + add_to_config "config.serve_static_assets = true" + + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + engine_name :bukkits + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + mount Bukkits::Engine => "/bukkits" + end + RUBY + + @plugin.write "public/bukkits.html", "in engine" + + app_file "public/bukkits/bukkits.html", "in app" + + boot_rails + + get('/bukkits/bukkits.html') + + assert_equal 'in app', last_response.body.strip + end + test "shared engine should include application's helpers and own helpers" do app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do |