From 28a51fa5ff06aeaff07ecd8fe617643f6378e999 Mon Sep 17 00:00:00 2001 From: Gabriel Gironda Date: Sat, 20 Dec 2008 16:41:14 -0600 Subject: [PATCH] Grumble on Rails, almost --- app/controllers/application_controller.rb | 34 +++- app/controllers/grumblers_controller.rb | 2 + app/controllers/grumbles_controller.rb | 30 ++++ app/controllers/targets_controller.rb | 19 +++ app/helpers/grumblers_helper.rb | 2 + app/helpers/grumbles_helper.rb | 2 + app/helpers/targets_helper.rb | 2 + app/models/grumble.rb | 4 + app/models/target.rb | 3 + config/environment.rb | 5 +- config/initializers/load_libraries.rb | 1 + config/initializers/routing_overrides.rb | 1 + config/routes.rb | 17 ++ test/functional/grumblers_controller_test.rb | 8 + test/functional/grumbles_controller_test.rb | 160 ++++++++++++++++++ test/functional/targets_controller_test.rb | 78 +++++++++ test/test_helper.rb | 12 +- test/unit/grumble_test.rb | 4 + test/unit/helpers/grumblers_helper_test.rb | 4 + test/unit/helpers/grumbles_helper_test.rb | 4 + test/unit/helpers/targets_helper_test.rb | 4 + test/unit/target_test.rb | 6 +- ...N_75a133f92ff7e27b83032babf829d8a58803bb3c | 0 .../rails/actionpack/lib/action_controller.rb | 3 +- .../lib/action_controller/dispatcher.rb | 2 +- .../lib/action_controller/integration.rb | 8 +- .../lib/action_controller/rack_process.rb | 96 ----------- .../lib/action_controller/response.rb | 146 ++++++++++------ .../session/abstract_store.rb | 18 +- .../action_controller/session/cookie_store.rb | 12 +- .../lib/action_controller/test_process.rb | 13 +- .../controller/action_pack_assertions_test.rb | 6 +- .../actionpack/test/controller/cookie_test.rb | 37 ++-- .../actionpack/test/controller/rack_test.rb | 8 +- .../actionpack/test/controller/render_test.rb | 10 +- .../controller/session/cookie_store_test.rb | 26 ++- .../session/mem_cache_store_test.rb | 21 +++ .../test/template/form_helper_test.rb | 2 +- .../lib/active_record/associations.rb | 2 +- .../has_many_through_association.rb | 6 +- .../has_many_through_associations_test.rb | 4 + .../test/cases/reload_models_test.rb | 4 +- 42 files changed, 605 insertions(+), 221 deletions(-) create mode 100644 app/controllers/grumblers_controller.rb create mode 100644 app/controllers/grumbles_controller.rb create mode 100644 app/controllers/targets_controller.rb create mode 100644 app/helpers/grumblers_helper.rb create mode 100644 app/helpers/grumbles_helper.rb create mode 100644 app/helpers/targets_helper.rb create mode 100644 config/initializers/load_libraries.rb create mode 100644 config/initializers/routing_overrides.rb create mode 100644 test/functional/grumblers_controller_test.rb create mode 100644 test/functional/grumbles_controller_test.rb create mode 100644 test/functional/targets_controller_test.rb create mode 100644 test/unit/helpers/grumblers_helper_test.rb create mode 100644 test/unit/helpers/grumbles_helper_test.rb create mode 100644 test/unit/helpers/targets_helper_test.rb create mode 100644 vendor/rails/REVISION_75a133f92ff7e27b83032babf829d8a58803bb3c diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8d392f4..6a0cd8a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,14 +2,34 @@ # Likewise, all the methods added will be available for all controllers. class ApplicationController < ActionController::Base + helper :all # include all helpers, all the time + protect_from_forgery # See ActionController::RequestForgeryProtection for details - # See ActionController::RequestForgeryProtection for details - # Uncomment the :secret if you're not using the cookie session store - protect_from_forgery # :secret => '73e34eb8d66e3353099a52e08d7d9040' - - # See ActionController::Base for details - # Uncomment this to filter the contents of submitted sensitive data parameters - # from your application log (in this case, all fields with names like "password"). + # Scrub sensitive parameters from your log # filter_parameter_logging :password + +private + + def load_target + if target_id = params[:target_id] + @target = Target.find_or_initialize_by_uri(params[:target_id]) + @target.save if @target.new_record? + raise ActiveRecord::RecordInvalid.new(@target) unless @target.valid? + else + raise ActiveRecord::RecordNotFound + end + end + + def render_json(obj, opts = {}) + json = obj.to_json + json = "var grumbleData = (#{json});\n\nGrumble.#{opts[:callback]}(grumbleData)\n" if opts[:callback] && callback_requested? + response.content_type = 'application/json' + render :text => json, :status => opts[:status] || :ok + end + + def callback_requested? + params[:callback] == 'true' + end + end diff --git a/app/controllers/grumblers_controller.rb b/app/controllers/grumblers_controller.rb new file mode 100644 index 0000000..92d5875 --- /dev/null +++ b/app/controllers/grumblers_controller.rb @@ -0,0 +1,2 @@ +class GrumblersController < ApplicationController +end diff --git a/app/controllers/grumbles_controller.rb b/app/controllers/grumbles_controller.rb new file mode 100644 index 0000000..0116b94 --- /dev/null +++ b/app/controllers/grumbles_controller.rb @@ -0,0 +1,30 @@ +class GrumblesController < ApplicationController + before_filter :load_target + + def index + grumbles = @target.grumbles.map { |grumble| grumble_attributes(grumble) } + render_json({:grumbles => grumbles}, :callback => 'getGrumbles') + end + + def create + grumble = @target.grumbles.build(params[:grumble].slice(:subject, :body, :anon_grumbler_name)) + if grumble.save + #status(201) + render_json({:grumble => grumble_attributes(grumble)}, :callback => 'grumbleCreated', :status => :created) + else + throw(:halt, [406, [:invalid_record, grumble]]) + end + end + +private + + def grumble_attributes(grumble) + grumble_data = {:id => grumble.uuid, :grumble_url => grumble_url(:id => grumble, :target_id => grumble.target), + :grumbler_name => grumble.grumbler_name, :subject => grumble.subject, :body => grumble.body, + :created_at => grumble.created_at} + grumble_data[:grumbler_url] = grumbler_url(grumble.grumbler) if grumble.grumbler + grumble_data + end + + +end diff --git a/app/controllers/targets_controller.rb b/app/controllers/targets_controller.rb new file mode 100644 index 0000000..76186e6 --- /dev/null +++ b/app/controllers/targets_controller.rb @@ -0,0 +1,19 @@ +class TargetsController < ApplicationController + before_filter :load_target + rescue_from ActiveRecord::RecordNotFound, :with => :target_not_found + rescue_from ActiveRecord::RecordInvalid, :with => :target_invalid + + def show + render_json({:target => {:grumble_count => @target.grumbles.count.to_i, + :new_grumble_url => new_grumble_url(:target_id => @target), + :grumble_index_url => grumbles_url(:target_id => @target)}}, + :callback => 'getTarget') + end + +private + + def target_not_found + render :nothing => true, :status => :not_found + end + +end diff --git a/app/helpers/grumblers_helper.rb b/app/helpers/grumblers_helper.rb new file mode 100644 index 0000000..b7327bf --- /dev/null +++ b/app/helpers/grumblers_helper.rb @@ -0,0 +1,2 @@ +module GrumblersHelper +end diff --git a/app/helpers/grumbles_helper.rb b/app/helpers/grumbles_helper.rb new file mode 100644 index 0000000..53779e4 --- /dev/null +++ b/app/helpers/grumbles_helper.rb @@ -0,0 +1,2 @@ +module GrumblesHelper +end diff --git a/app/helpers/targets_helper.rb b/app/helpers/targets_helper.rb new file mode 100644 index 0000000..8484878 --- /dev/null +++ b/app/helpers/targets_helper.rb @@ -0,0 +1,2 @@ +module TargetsHelper +end diff --git a/app/models/grumble.rb b/app/models/grumble.rb index ed4ecc5..abbd1de 100644 --- a/app/models/grumble.rb +++ b/app/models/grumble.rb @@ -16,6 +16,10 @@ def registered_grumbler? !grumbler.nil? end + def to_param + uuid + end + private def set_uuid diff --git a/app/models/target.rb b/app/models/target.rb index d77e23f..393a6d1 100644 --- a/app/models/target.rb +++ b/app/models/target.rb @@ -6,6 +6,9 @@ class Target < ActiveRecord::Base validate :validate_uri has_many :grumbles, :order => 'created_at DESC' + def to_param + uri + end private def validate_uri diff --git a/config/environment.rb b/config/environment.rb index 6c6ba1c..f8d3b00 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -32,7 +32,6 @@ config.gem 'mocha' config.gem 'haml' config.gem 'uuidtools' - # Only load the plugins named here, in the order given. By default, all plugins # in vendor/plugins are loaded in alphabetical order. # :all can be used as a placeholder for all plugins not explicitly named @@ -48,7 +47,7 @@ # Make Time.zone default to the specified zone, and make Active Record store time values # in the database in UTC, and return them converted to the specified local zone. # Run "rake -D time" for a list of tasks for finding time zone names. Comment line to use default local time. - config.time_zone = 'UTC' + config.time_zone = ENV['TZ'] = 'UTC' # The internationalization framework can be changed to have another default locale (standard is :en) or more load paths. # All files from config/locales/*.rb,yml are added automatically. @@ -77,4 +76,4 @@ # Activate observers that should always be running # Please note that observers generated using script/generate observer need to have an _observer suffix # config.active_record.observers = :cacher, :garbage_collector, :forum_observer -end +end \ No newline at end of file diff --git a/config/initializers/load_libraries.rb b/config/initializers/load_libraries.rb new file mode 100644 index 0000000..991d057 --- /dev/null +++ b/config/initializers/load_libraries.rb @@ -0,0 +1 @@ +require 'ruby-debug' if Rails.env.test? || Rails.env.development? \ No newline at end of file diff --git a/config/initializers/routing_overrides.rb b/config/initializers/routing_overrides.rb new file mode 100644 index 0000000..466b237 --- /dev/null +++ b/config/initializers/routing_overrides.rb @@ -0,0 +1 @@ +#ActionController::Routing::SEPARATORS.delete('.') \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 4f3d9d2..bcb1b9d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,21 @@ ActionController::Routing::Routes.draw do |map| + + # targets GET /targets(.:format) {:controller=>"targets", :action=>"index"} + # POST /targets(.:format) {:controller=>"targets", :action=>"create"} + # new_target GET /targets/new(.:format) {:controller=>"targets", :action=>"new"} + # edit_target GET /targets/:id/edit(.:format) {:controller=>"targets", :action=>"edit"} + # target GET /targets/:id(.:format) {:controller=>"targets", :action=>"show"} + # PUT /targets/:id(.:format) {:controller=>"targets", :action=>"update"} + # DELETE /targets/:id(.:format) {:controller=>"targets", :action=>"destroy"} + map.new_target '/targets', :controller => 'targets', :action => 'new' + map.target '/targets/:target_id', :controller => 'targets', :action => 'show' + + map.new_grumble '/targets/:target_id/grumbles/new', :controller => 'grumbles', :action => 'new' + map.grumbles '/targets/:target_id/grumbles', :controller => 'grumbles', :action => 'index' + map.grumble '/targets/:target_id/grumbles/:id', :controller => 'grumbles', :action => 'show' + + map.grumbler '/grumblers/:id', :controller => 'grumblers', :action => 'show' + # The priority is based upon order of creation: first created -> highest priority. # Sample of regular route: diff --git a/test/functional/grumblers_controller_test.rb b/test/functional/grumblers_controller_test.rb new file mode 100644 index 0000000..0981ddd --- /dev/null +++ b/test/functional/grumblers_controller_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class GrumblersControllerTest < ActionController::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end diff --git a/test/functional/grumbles_controller_test.rb b/test/functional/grumbles_controller_test.rb new file mode 100644 index 0000000..e362ead --- /dev/null +++ b/test/functional/grumbles_controller_test.rb @@ -0,0 +1,160 @@ +require 'test_helper' + +class GrumblesControllerTest < ActionController::TestCase + + should "call a callback function if callback=true parameter is given" do + get :index, :target_id => "http://www.example.com/posts/12/", :callback => "true" + assert_match %r[Grumble.getGrumbles\(.*\)], @response.body + end + + context "GET request with valid target" do + setup do + @target = Factory(:target) + json_response { get :index, :target_id => @target.uri } + end + + should "return a JSON list of grumbles" do + assert_equal @target.grumbles.size, @json_response['grumbles'].size + end + + context "returned grumble" do + context "by a non registered grumbler" do + setup do + grumble = @target.grumbles.detect { |g| !g.registered_grumbler? } + @json_grumble = @json_response['grumbles'].detect { |g| g['id'] == grumble.uuid } + end + + should "have a grumble_url" do + deny @json_grumble['grumble_url'].blank? + end + + should "have a grumbler_name" do + deny @json_grumble['grumbler_name'].blank? + end + + should "not have a grumbler_url" do + assert @json_grumble['grumbler_url'].blank? + end + + should "have a subject" do + deny @json_grumble['subject'].blank? + end + + should "have a body" do + deny @json_grumble['body'].blank? + end + + should "have a created_at" do + deny @json_grumble['created_at'].blank? + end + + end # by a non registered grumbler + + context "by a registered grumbler" do + setup do + grumble = @target.grumbles.detect(&:registered_grumbler?) + @json_grumble = @json_response['grumbles'].detect { |g| g['id'] == grumble.uuid } + end + + should "have a grumble_url" do + deny @json_grumble['grumble_url'].blank? + end + + should "have a grumbler_name" do + deny @json_grumble['grumbler_name'].blank? + end + + should "have a grumbler_url" do + deny @json_grumble['grumbler_url'].blank? + end + + should "have a subject" do + deny @json_grumble['subject'].blank? + end + + should "have a body" do + deny @json_grumble['body'].blank? + end + + should "have a created_at" do + deny @json_grumble['created_at'].blank? + end + + end # by a registered grumbler + + end # returned grumbles + + end # GET + + context "POST request with valid target" do + + context "with valid grumble attributes and a brand new target" do + setup do + @target = Factory.build(:target) + grumble_attrs = Factory.attributes_for(:grumble) + json_response { post :create, :target_id => @target.uri, :grumble => grumble_attrs } + @json_grumble = @json_response['grumble'] + end + + should_change "Target.count", :by => 1 + should_change "Grumble.count", :by => 1 + + should "create a target with the URI specified" do + assert Target.find_by_uri(@target.uri) + end + + end # with valid grumble attributes and a brand new target + + setup do + @target = Factory(:target) + end + + context "by a non registered user" do + + should "call a callback function if callback=true parameter is given" do + grumble_attrs = Factory.attributes_for(:grumble) + post :create, :target_id => @target.uri, :grumble => grumble_attrs, :callback => 'true' + assert_match %r[Grumble.grumbleCreated\(.*\)], @response.body + end + + context "with valid grumble attributes" do + setup do + grumble_attrs = Factory.attributes_for(:grumble) + json_response { post :create, :target_id => @target.uri, :grumble => grumble_attrs } + @json_grumble = @json_response['grumble'] + end + + should "respond with 201 created" do + assert_response :created + end + + should "have a grumble_url" do + deny @json_grumble['grumble_url'].blank? + end + + should "have a grumbler_name" do + deny @json_grumble['grumbler_name'].blank? + end + + should "not have a grumbler_url" do + assert @json_grumble['grumbler_url'].blank? + end + + should "have a subject" do + deny @json_grumble['subject'].blank? + end + + should "have a body" do + deny @json_grumble['body'].blank? + end + + should "have a created_at" do + deny @json_grumble['created_at'].blank? + end + end # with valid grumble attributes + + end # by a non registered user + + end # POST request with valid target + +end diff --git a/test/functional/targets_controller_test.rb b/test/functional/targets_controller_test.rb new file mode 100644 index 0000000..7d611f0 --- /dev/null +++ b/test/functional/targets_controller_test.rb @@ -0,0 +1,78 @@ +require 'test_helper' + +class TargetsControllerTest < ActionController::TestCase + + context "GET request" do + context "with a valid URI that has no grumbles" do + + setup do + json_response { get :show, :target_id => "http://www.example.com/posts/12" } + end + + should "return a 200 status" do + assert_response :success + end + + should "return JSON data" do + assert_nothing_raised { JSON.parse(@response.body) } + end + + should "return a 0 grumble count" do + assert_equal 0, @json_response['target']['grumble_count'] + end + + should "return a URI for a new grumble" do + assert_match %r[/grumbles/new$], @json_response['target']['new_grumble_url'] + end + + should "return a URI for the grumble list" do + assert_match %r[/grumbles$], @json_response['target']['grumble_index_url'] + end + + should "only return expected keys in JSON response" do + expected_keys = %w[grumble_count new_grumble_url grumble_index_url] + assert_equal expected_keys.sort, @json_response['target'].keys.sort + end + + should "have a content type of application/json" do + assert_equal "application/json", @response.content_type + end + + end # with a valid URI + + context "exception handling" do + setup do + rescue_action_in_public! + end + + should_eventually "return a 406 status with a non HTTP or HTTPS url except rescue_action_in_public! is fucked" do + get :show, :target_id => "git://www.example.com/posts/12" + assert_response :not_acceptable + end + + should_eventually "return a 404 with no URI except rescue_action_in_public! is fucked" do + get :show + assert_response :missing + end + + end # exception handling + + should "return a 200 status with a valid HTTPS url" do + get :show, :target_id => "https://www.example.com/posts/12" + assert_response :success + end + + should "return the number of grumbles for a URI with grumbles" do + target = Factory(:target) + json_response { get :show, :target_id => target.uri } + assert(@json_response['target']['grumble_count'] > 0) + end + + should "call a callback function if callback=true parameter is given" do + get :show, :target_id => "https://www.example.com/posts/12", :callback => 'true' + assert_match %r[Grumble.getTarget\(.*\)], @response.body + end + + end # GET request (show) + +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 9936dcb..3d3f89c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -2,6 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + "/../config/environment") require 'test_help' require 'factories/model_factory' +require 'json' class ActiveSupport::TestCase # Transactional fixtures accelerate your tests by wrapping each test method @@ -29,14 +30,13 @@ class ActiveSupport::TestCase # then set this back to true. self.use_instantiated_fixtures = false - # Setup all fixtures in test/fixtures/*.(yml|csv) 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 - fixtures :all - # Add more helper methods to be used by all tests here... def deny(obj, *args) assert(!obj, *args) end + + def json_response + yield if block_given? + @json_response = ::JSON.parse(@response.body) + end end diff --git a/test/unit/grumble_test.rb b/test/unit/grumble_test.rb index eab0db5..c1a2d33 100644 --- a/test/unit/grumble_test.rb +++ b/test/unit/grumble_test.rb @@ -35,6 +35,10 @@ class GrumbleTest < ActiveSupport::TestCase setup { @grumble = Factory(:grumble) } should_require_unique_attributes :uuid + should "return the uuid when converting to a param" do + assert_equal @grumble.uuid, @grumble.to_param + end + context "with a registered grumbler" do should "delegate grumbler_name to the grumbler if present" do diff --git a/test/unit/helpers/grumblers_helper_test.rb b/test/unit/helpers/grumblers_helper_test.rb new file mode 100644 index 0000000..68d4c73 --- /dev/null +++ b/test/unit/helpers/grumblers_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class GrumblersHelperTest < ActionView::TestCase +end diff --git a/test/unit/helpers/grumbles_helper_test.rb b/test/unit/helpers/grumbles_helper_test.rb new file mode 100644 index 0000000..d617fac --- /dev/null +++ b/test/unit/helpers/grumbles_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class GrumblesHelperTest < ActionView::TestCase +end diff --git a/test/unit/helpers/targets_helper_test.rb b/test/unit/helpers/targets_helper_test.rb new file mode 100644 index 0000000..f0f5a09 --- /dev/null +++ b/test/unit/helpers/targets_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class TargetsHelperTest < ActionView::TestCase +end diff --git a/test/unit/target_test.rb b/test/unit/target_test.rb index 5b6b6a4..0904acf 100644 --- a/test/unit/target_test.rb +++ b/test/unit/target_test.rb @@ -7,8 +7,12 @@ class TargetTest < ActiveSupport::TestCase context "with an existing target" do - setup { Factory(:target) } + setup { @target = Factory(:target) } should_require_unique_attributes :uri + + should "return URI as to_param" do + assert_equal @target.uri, @target.to_param + end end context "validate uri-ness of uri" do diff --git a/vendor/rails/REVISION_75a133f92ff7e27b83032babf829d8a58803bb3c b/vendor/rails/REVISION_75a133f92ff7e27b83032babf829d8a58803bb3c new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/actionpack/lib/action_controller.rb b/vendor/rails/actionpack/lib/action_controller.rb index eaf7779..ae94782 100644 --- a/vendor/rails/actionpack/lib/action_controller.rb +++ b/vendor/rails/actionpack/lib/action_controller.rb @@ -42,7 +42,6 @@ def self.load_all! end autoload :AbstractRequest, 'action_controller/request' - autoload :AbstractResponse, 'action_controller/response' autoload :Base, 'action_controller/base' autoload :Benchmarking, 'action_controller/benchmarking' autoload :Caching, 'action_controller/caching' @@ -61,8 +60,8 @@ def self.load_all! autoload :MimeResponds, 'action_controller/mime_responds' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' autoload :RackRequest, 'action_controller/rack_process' - autoload :RackResponse, 'action_controller/rack_process' autoload :RecordIdentifier, 'action_controller/record_identifier' + autoload :Response, 'action_controller/response' autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' autoload :Rescue, 'action_controller/rescue' autoload :Resources, 'action_controller/resources' diff --git a/vendor/rails/actionpack/lib/action_controller/dispatcher.rb b/vendor/rails/actionpack/lib/action_controller/dispatcher.rb index 11c4f05..e1eaaf7 100644 --- a/vendor/rails/actionpack/lib/action_controller/dispatcher.rb +++ b/vendor/rails/actionpack/lib/action_controller/dispatcher.rb @@ -98,7 +98,7 @@ def call(env) def _call(env) @request = RackRequest.new(env) - @response = RackResponse.new + @response = Response.new dispatch end diff --git a/vendor/rails/actionpack/lib/action_controller/integration.rb b/vendor/rails/actionpack/lib/action_controller/integration.rb index 8c2e319..d952c34 100644 --- a/vendor/rails/actionpack/lib/action_controller/integration.rb +++ b/vendor/rails/actionpack/lib/action_controller/integration.rb @@ -181,7 +181,7 @@ def redirect? # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will # automatically be upcased, with the prefix 'HTTP_' added if needed. # - # This method returns an AbstractResponse object, which one can use to + # This method returns an Response object, which one can use to # inspect the details of the response. Furthermore, if this method was # called from an ActionController::IntegrationTest object, then that # object's @response instance variable will point to the same @@ -331,10 +331,10 @@ def process(method, path, parameters = nil, headers = nil) @response = @controller.response else # Decorate responses from Rack Middleware and Rails Metal - # as an AbstractResponse for the purposes of integration testing - @response = AbstractResponse.new + # as an Response for the purposes of integration testing + @response = Response.new @response.status = status.to_s - @response.headers = @headers + @response.headers.replace(@headers) @response.body = @body end diff --git a/vendor/rails/actionpack/lib/action_controller/rack_process.rb b/vendor/rails/actionpack/lib/action_controller/rack_process.rb index e7d0040..8c6db91 100644 --- a/vendor/rails/actionpack/lib/action_controller/rack_process.rb +++ b/vendor/rails/actionpack/lib/action_controller/rack_process.rb @@ -70,100 +70,4 @@ def reset_session @env['rack.session'] = {} end end - - class RackResponse < AbstractResponse #:nodoc: - def initialize - @writer = lambda { |x| @body << x } - @block = nil - super() - end - - def to_a(&block) - @block = block - if [204, 304].include?(status.to_i) - headers.delete("Content-Type") - [status, headers.to_hash, []] - else - [status, headers.to_hash, self] - end - end - - def each(&callback) - if @body.respond_to?(:call) - @writer = lambda { |x| callback.call(x) } - @body.call(self, self) - elsif @body.is_a?(String) - @body.each_line(&callback) - else - @body.each(&callback) - end - - @writer = callback - @block.call(self) if @block - end - - def write(str) - @writer.call str.to_s - str - end - - def close - @body.close if @body.respond_to?(:close) - end - - def empty? - @block == nil && @body.empty? - end - - def prepare! - super - - convert_language! - convert_expires! - set_status! - set_cookies! - end - - private - def convert_language! - headers["Content-Language"] = headers.delete("language") if headers["language"] - end - - def convert_expires! - headers["Expires"] = headers.delete("") if headers["expires"] - end - - def convert_content_type! - super - headers['Content-Type'] = headers.delete('type') || "text/html" - headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset'] - end - - def set_content_length! - super - headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"] - end - - def set_status! - self.status ||= "200 OK" - end - - def set_cookies! - # Convert 'cookie' header to 'Set-Cookie' headers. - # Because Set-Cookie header can appear more the once in the response body, - # we store it in a line break separated string that will be translated to - # multiple Set-Cookie header by the handler. - if cookie = headers.delete('cookie') - cookies = [] - - case cookie - when Array then cookie.each { |c| cookies << c.to_s } - when Hash then cookie.each { |_, c| cookies << c.to_s } - else cookies << cookie.to_s - end - - headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact - end - end - end end diff --git a/vendor/rails/actionpack/lib/action_controller/response.rb b/vendor/rails/actionpack/lib/action_controller/response.rb index e1bf5bb..866616b 100644 --- a/vendor/rails/actionpack/lib/action_controller/response.rb +++ b/vendor/rails/actionpack/lib/action_controller/response.rb @@ -1,24 +1,25 @@ require 'digest/md5' module ActionController # :nodoc: - # Represents an HTTP response generated by a controller action. One can use an - # ActionController::AbstractResponse object to retrieve the current state of the - # response, or customize the response. An AbstractResponse object can either - # represent a "real" HTTP response (i.e. one that is meant to be sent back to the - # web browser) or a test response (i.e. one that is generated from integration - # tests). See CgiResponse and TestResponse, respectively. + # Represents an HTTP response generated by a controller action. One can use + # an ActionController::Response object to retrieve the current state + # of the response, or customize the response. An Response object can + # either represent a "real" HTTP response (i.e. one that is meant to be sent + # back to the web browser) or a test response (i.e. one that is generated + # from integration tests). See CgiResponse and TestResponse, respectively. # - # AbstractResponse is mostly a Ruby on Rails framework implement detail, and should - # never be used directly in controllers. Controllers should use the methods defined - # in ActionController::Base instead. For example, if you want to set the HTTP - # response's content MIME type, then use ActionControllerBase#headers instead of - # AbstractResponse#headers. + # Response is mostly a Ruby on Rails framework implement detail, and + # should never be used directly in controllers. Controllers should use the + # methods defined in ActionController::Base instead. For example, if you want + # to set the HTTP response's content MIME type, then use + # ActionControllerBase#headers instead of Response#headers. # - # Nevertheless, integration tests may want to inspect controller responses in more - # detail, and that's when AbstractResponse can be useful for application developers. - # Integration test methods such as ActionController::Integration::Session#get and - # ActionController::Integration::Session#post return objects of type TestResponse - # (which are of course also of type AbstractResponse). + # Nevertheless, integration tests may want to inspect controller responses in + # more detail, and that's when Response can be useful for application + # developers. Integration test methods such as + # ActionController::Integration::Session#get and + # ActionController::Integration::Session#post return objects of type + # TestResponse (which are of course also of type Response). # # For example, the following demo integration "test" prints the body of the # controller response to the console: @@ -29,22 +30,24 @@ module ActionController # :nodoc: # puts @response.body # end # end - class AbstractResponse + class Response < Rack::Response DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } attr_accessor :request - attr_accessor :status - # The body content (e.g. HTML) of the response, as a String. - attr_accessor :body - # The headers of the response, as a Hash. It maps header names to header values. - attr_accessor :headers attr_accessor :session, :cookies, :assigns, :template, :layout attr_accessor :redirected_to, :redirected_to_method_params delegate :default_charset, :to => 'ActionController::Base' def initialize - @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], [] + @status = 200 + @header = DEFAULT_HEADERS.merge("cookie" => []) + + @writer = lambda { |x| @body << x } + @block = nil + + @body = "", + @session, @assigns = [], [] end def location; headers['Location'] end @@ -107,11 +110,11 @@ def last_modified=(utc_time) def etag headers['ETag'] end - + def etag? headers.include?('ETag') end - + def etag=(etag) if etag.blank? headers.delete('ETag') @@ -140,22 +143,45 @@ def prepare! handle_conditional_get! set_content_length! convert_content_type! + + convert_language! + convert_expires! + set_cookies! + end + + def each(&callback) + if @body.respond_to?(:call) + @writer = lambda { |x| callback.call(x) } + @body.call(self, self) + elsif @body.is_a?(String) + @body.each_line(&callback) + else + @body.each(&callback) + end + + @writer = callback + @block.call(self) if @block + end + + def write(str) + @writer.call str.to_s + str end private - def handle_conditional_get! - if etag? || last_modified? - set_conditional_cache_control! - elsif nonempty_ok_response? - self.etag = body - - if request && request.etag_matches?(etag) - self.status = '304 Not Modified' - self.body = '' - end - - set_conditional_cache_control! - end + def handle_conditional_get! + if etag? || last_modified? + set_conditional_cache_control! + elsif nonempty_ok_response? + self.etag = body + + if request && request.etag_matches?(etag) + self.status = '304 Not Modified' + self.body = '' + end + + set_conditional_cache_control! + end end def nonempty_ok_response? @@ -170,23 +196,43 @@ def set_conditional_cache_control! end def convert_content_type! - if content_type = headers.delete("Content-Type") - self.headers["type"] = content_type - end - if content_type = headers.delete("Content-type") - self.headers["type"] = content_type - end - if content_type = headers.delete("content-type") - self.headers["type"] = content_type - end + headers['Content-Type'] ||= "text/html" + headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset'] end - - # Don't set the Content-Length for block-based bodies as that would mean reading it all into memory. Not nice - # for, say, a 2GB streaming file. + + # Don't set the Content-Length for block-based bodies as that would mean + # reading it all into memory. Not nice for, say, a 2GB streaming file. def set_content_length! unless body.respond_to?(:call) || (status && status.to_s[0..2] == '304') self.headers["Content-Length"] ||= body.size end + headers["Content-Length"] = headers["Content-Length"].to_s if headers["Content-Length"] + end + + def convert_language! + headers["Content-Language"] = headers.delete("language") if headers["language"] + end + + def convert_expires! + headers["Expires"] = headers.delete("") if headers["expires"] + end + + def set_cookies! + # Convert 'cookie' header to 'Set-Cookie' headers. + # Because Set-Cookie header can appear more the once in the response body, + # we store it in a line break separated string that will be translated to + # multiple Set-Cookie header by the handler. + if cookie = headers.delete('cookie') + cookies = [] + + case cookie + when Array then cookie.each { |c| cookies << c.to_s } + when Hash then cookie.each { |_, c| cookies << c.to_s } + else cookies << cookie.to_s + end + + headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact + end end end end diff --git a/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb b/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb index d4b185a..bf09fd3 100644 --- a/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb +++ b/vendor/rails/actionpack/lib/action_controller/session/abstract_store.rb @@ -53,6 +53,10 @@ def data end private + def loaded? + @loaded + end + def load! @id, session = @by.send(:load_session, @env) replace(session) @@ -91,19 +95,23 @@ def initialize(app, options = {}) def call(env) session = SessionHash.new(self, env) - original_session = session.dup env[ENV_SESSION_KEY] = session env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup response = @app.call(env) - session = env[ENV_SESSION_KEY] - unless session == original_session + session_data = env[ENV_SESSION_KEY] + if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) options = env[ENV_SESSION_OPTIONS_KEY] - sid = session.id - unless set_session(env, sid, session.to_hash) + if session_data.is_a?(AbstractStore::SessionHash) + sid = session_data.id + else + sid = generate_sid + end + + unless set_session(env, sid, session_data.to_hash) return response end diff --git a/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb b/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb index ba63f85..135beda 100644 --- a/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb +++ b/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb @@ -89,16 +89,14 @@ def initialize(app, options = {}) end def call(env) - session_data = AbstractStore::SessionHash.new(self, env) - original_value = session_data.dup - - env[ENV_SESSION_KEY] = session_data + env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = @default_options status, headers, body = @app.call(env) - unless env[ENV_SESSION_KEY] == original_value - session_data = marshal(env[ENV_SESSION_KEY].to_hash) + session_data = env[ENV_SESSION_KEY] + if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) + session_data = marshal(session_data.to_hash) raise CookieOverflow if session_data.size > MAX @@ -153,7 +151,7 @@ def load_session(env) # Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) - @verifier.generate( persistent_session_id!(session)) + @verifier.generate(persistent_session_id!(session)) end # Unmarshal cookie data to a hash and verify its integrity. diff --git a/vendor/rails/actionpack/lib/action_controller/test_process.rb b/vendor/rails/actionpack/lib/action_controller/test_process.rb index d3c66dd..c4d7d52 100644 --- a/vendor/rails/actionpack/lib/action_controller/test_process.rb +++ b/vendor/rails/actionpack/lib/action_controller/test_process.rb @@ -264,7 +264,12 @@ def has_template_object?(name=nil) # # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value def cookies - headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash } + cookies = {} + Array(headers['Set-Cookie']).each do |cookie| + key, value = cookie.split(";").first.split("=") + cookies[key] = [value].compact + end + cookies end # Returns binary content (downloadable file), converted to a String @@ -285,8 +290,8 @@ def binary_content # TestResponse, which represent the HTTP response results of the requested # controller actions. # - # See AbstractResponse for more information on controller response objects. - class TestResponse < AbstractResponse + # See Response for more information on controller response objects. + class TestResponse < Response include TestResponseBehavior def recycle! @@ -430,7 +435,7 @@ def assigns(key = nil) end def session - @response.session + @request.session end def flash diff --git a/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb b/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb index 87c12ee..cb7922e 100644 --- a/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb +++ b/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb @@ -500,17 +500,17 @@ class ActionPackHeaderTest < ActionController::TestCase def test_rendering_xml_sets_content_type process :hello_xml_world - assert_equal('application/xml; charset=utf-8', @response.headers['type']) + assert_equal('application/xml; charset=utf-8', @response.headers['Content-Type']) end def test_rendering_xml_respects_content_type @response.headers['type'] = 'application/pdf' process :hello_xml_world - assert_equal('application/pdf; charset=utf-8', @response.headers['type']) + assert_equal('application/pdf; charset=utf-8', @response.headers['Content-Type']) end def test_render_text_with_custom_content_type get :render_text_with_custom_content_type - assert_equal 'application/rss+xml; charset=utf-8', @response.headers['type'] + assert_equal 'application/rss+xml; charset=utf-8', @response.headers['Content-Type'] end end diff --git a/vendor/rails/actionpack/test/controller/cookie_test.rb b/vendor/rails/actionpack/test/controller/cookie_test.rb index 5a6fb49..4b96951 100644 --- a/vendor/rails/actionpack/test/controller/cookie_test.rb +++ b/vendor/rails/actionpack/test/controller/cookie_test.rb @@ -18,7 +18,7 @@ def set_multiple_cookies cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) } cookies["login"] = "XJ-122" end - + def access_frozen_cookies cookies["will"] = "work" end @@ -36,8 +36,8 @@ def authenticate_with_http_only cookies["user_name"] = { :value => "david", :http_only => true } end - def rescue_action(e) - raise unless ActionView::MissingTemplate # No templates here, and we don't care about the output + def rescue_action(e) + raise unless ActionView::MissingTemplate # No templates here, and we don't care about the output end end @@ -51,40 +51,46 @@ def setup def test_setting_cookie get :authenticate - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], @response.headers["cookie"] + assert_equal ["user_name=david; path=/"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => ["david"]}, @response.cookies) end def test_setting_cookie_for_fourteen_days get :authenticate_for_fourteen_days - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"] + assert_equal ["user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => ["david"]}, @response.cookies) end def test_setting_cookie_for_fourteen_days_with_symbols get :authenticate_for_fourteen_days_with_symbols - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"] + assert_equal ["user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => ["david"]}, @response.cookies) end def test_setting_cookie_with_http_only get :authenticate_with_http_only - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "http_only" => true) ], @response.headers["cookie"] - assert_equal CGI::Cookie::new("name" => "user_name", "value" => "david", "path" => "/", "http_only" => true).to_s, @response.headers["cookie"][0].to_s + assert_equal ["user_name=david; path=/; HttpOnly"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => ["david"]}, @response.cookies) end def test_multiple_cookies get :set_multiple_cookies assert_equal 2, @response.cookies.size + assert_equal "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT", @response.headers["Set-Cookie"][0] + assert_equal "login=XJ-122; path=/", @response.headers["Set-Cookie"][1] + assert_equal({"login" => ["XJ-122"], "user_name" => ["david"]}, @response.cookies) end def test_setting_test_cookie assert_nothing_raised { get :access_frozen_cookies } end - + def test_expiring_cookie get :logout - assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)) ], @response.headers["cookie"] - assert_equal CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)).value, [] - end - + assert_equal ["user_name=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => []}, @response.cookies) + end + def test_cookiejar_accessor @request.cookies["user_name"] = CGI::Cookie.new("name" => "user_name", "value" => "david", "expires" => Time.local(2025, 10, 10)) @controller.request = @request @@ -100,11 +106,10 @@ def test_cookiejar_accessor_with_array_value jar = ActionController::CookieJar.new(@controller) assert_equal a, jar["pages"] end - + def test_delete_cookie_with_path get :delete_cookie_with_path - assert_equal "/beaten", @response.headers["cookie"].first.path - assert_not_equal "/", @response.headers["cookie"].first.path + assert_equal ["user_name=; path=/beaten; expires=Thu, 01 Jan 1970 00:00:00 GMT"], @response.headers["Set-Cookie"] end def test_cookie_to_s_simple_values diff --git a/vendor/rails/actionpack/test/controller/rack_test.rb b/vendor/rails/actionpack/test/controller/rack_test.rb index 51f4329..81d103f 100644 --- a/vendor/rails/actionpack/test/controller/rack_test.rb +++ b/vendor/rails/actionpack/test/controller/rack_test.rb @@ -229,7 +229,7 @@ def test_body_should_be_rewound class RackResponseTest < BaseRackTest def setup super - @response = ActionController::RackResponse.new + @response = ActionController::Response.new end def test_simple_output @@ -237,7 +237,7 @@ def test_simple_output @response.prepare! status, headers, body = @response.to_a - assert_equal "200 OK", status + assert_equal 200, status assert_equal({ "Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "private, max-age=0, must-revalidate", @@ -258,7 +258,7 @@ def test_streaming_block @response.prepare! status, headers, body = @response.to_a - assert_equal "200 OK", status + assert_equal 200, status assert_equal({"Content-Type" => "text/html; charset=utf-8", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers) parts = [] @@ -270,7 +270,7 @@ def test_streaming_block class RackResponseHeadersTest < BaseRackTest def setup super - @response = ActionController::RackResponse.new + @response = ActionController::Response.new @response.status = "200 OK" end diff --git a/vendor/rails/actionpack/test/controller/render_test.rb b/vendor/rails/actionpack/test/controller/render_test.rb index e0f05d6..8e08a5a 100644 --- a/vendor/rails/actionpack/test/controller/render_test.rb +++ b/vendor/rails/actionpack/test/controller/render_test.rb @@ -208,7 +208,7 @@ def heading def greeting # let's just rely on the template end - + def blank_response render :text => ' ' end @@ -1099,14 +1099,14 @@ def test_template_with_locals def test_update_page get :update_page assert_template nil - assert_equal 'text/javascript; charset=utf-8', @response.headers['type'] + assert_equal 'text/javascript; charset=utf-8', @response.headers['Content-Type'] assert_equal 2, @response.body.split($/).length end def test_update_page_with_instance_variables get :update_page_with_instance_variables assert_template nil - assert_equal 'text/javascript; charset=utf-8', @response.headers['type'] + assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"] assert_match /balance/, @response.body assert_match /\$37/, @response.body end @@ -1114,7 +1114,7 @@ def test_update_page_with_instance_variables def test_update_page_with_view_method get :update_page_with_view_method assert_template nil - assert_equal 'text/javascript; charset=utf-8', @response.headers['type'] + assert_equal 'text/javascript; charset=utf-8', @response.headers["Content-Type"] assert_match /2 people/, @response.body end @@ -1384,7 +1384,7 @@ def setup @request.host = "www.nextangle.com" @expected_bang_etag = etag_for(expand_key([:foo, 123])) end - + def test_render_blank_body_shouldnt_set_etag get :blank_response assert !@response.etag? diff --git a/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb b/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb index ad8ff09..69aec59 100644 --- a/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb +++ b/vendor/rails/actionpack/test/controller/session/cookie_store_test.rb @@ -32,6 +32,11 @@ def get_session_value render :text => "foo: #{session[:foo].inspect}" end + def call_reset_session + reset_session + head :ok + end + def raise_data_overflow session[:foo] = 'bye!' * 1024 head :ok @@ -89,7 +94,7 @@ def test_setting_session_value with_test_route_set do get '/set_session_value' assert_response :success - session_payload = Verifier.generate( Marshal.load(response.body) ) + session_payload = Verifier.generate(Marshal.load(response.body)) assert_equal ["_myapp_session=#{session_payload}; path=/"], headers['Set-Cookie'] end @@ -139,6 +144,25 @@ def test_doesnt_write_session_cookie_if_session_is_unchanged end end + def test_setting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + session_payload = Verifier.generate(Marshal.load(response.body)) + assert_equal ["_myapp_session=#{session_payload}; path=/"], + headers['Set-Cookie'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + assert_not_equal session_payload, cookies[SessionKey] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end + def test_persistent_session_id with_test_route_set do cookies[SessionKey] = SignedBar diff --git a/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb b/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb index 52e31b7..eb896a3 100644 --- a/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb +++ b/vendor/rails/actionpack/test/controller/session/mem_cache_store_test.rb @@ -16,6 +16,11 @@ def get_session_value render :text => "foo: #{session[:foo].inspect}" end + def call_reset_session + reset_session + head :ok + end + def rescue_action(e) raise end end @@ -63,6 +68,22 @@ def test_prevents_session_fixation assert_equal nil, cookies['_session_id'] end end + + def test_setting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + end + end rescue LoadError, RuntimeError $stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again." end diff --git a/vendor/rails/actionpack/test/template/form_helper_test.rb b/vendor/rails/actionpack/test/template/form_helper_test.rb index 52e8bf3..9454fd7 100644 --- a/vendor/rails/actionpack/test/template/form_helper_test.rb +++ b/vendor/rails/actionpack/test/template/form_helper_test.rb @@ -463,7 +463,7 @@ def test_nested_fields_for_with_nested_collections assert_dom_equal expected, output_buffer end - def test_nested_fields_for_with_index + def test_nested_fields_for_with_index_and_parent_fields form_for('post', @post, :index => 1) do |c| concat c.text_field(:title) c.fields_for('comment', @comment, :index => 1) do |r| diff --git a/vendor/rails/activerecord/lib/active_record/associations.rb b/vendor/rails/activerecord/lib/active_record/associations.rb index 3cee9c7..07bc50c 100755 --- a/vendor/rails/activerecord/lib/active_record/associations.rb +++ b/vendor/rails/activerecord/lib/active_record/associations.rb @@ -2198,7 +2198,7 @@ def association_join protected def aliased_table_name_for(name, suffix = nil) - if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son} + if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{active_record.connection.quote_table_name name.downcase}\son} @join_dependency.table_aliases[name] += 1 end diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb index a0bb3a4..2eeeb28 100644 --- a/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -160,9 +160,9 @@ def construct_joins(custom_joins = nil) end "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [ - @reflection.through_reflection.table_name, - @reflection.table_name, reflection_primary_key, - @reflection.through_reflection.table_name, source_primary_key, + @reflection.through_reflection.quoted_table_name, + @reflection.quoted_table_name, reflection_primary_key, + @reflection.through_reflection.quoted_table_name, source_primary_key, polymorphic_join ] end diff --git a/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb index 0bfda33..ad6a5d6 100644 --- a/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -204,6 +204,10 @@ def test_count_with_include_should_alias_join_table assert_equal 2, people(:michael).posts.count(:include => :readers) end + def test_inner_join_with_quoted_table_name + assert_equal 2, people(:michael).jobs.size + end + def test_get_ids assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort end diff --git a/vendor/rails/activerecord/test/cases/reload_models_test.rb b/vendor/rails/activerecord/test/cases/reload_models_test.rb index 411b5f6..0d16a35 100644 --- a/vendor/rails/activerecord/test/cases/reload_models_test.rb +++ b/vendor/rails/activerecord/test/cases/reload_models_test.rb @@ -3,6 +3,8 @@ require 'models/pet' class ReloadModelsTest < ActiveRecord::TestCase + fixtures :pets + def test_has_one_with_reload pet = Pet.find_by_name('parrot') pet.owner = Owner.find_by_name('ashley') @@ -17,4 +19,4 @@ def test_has_one_with_reload pet.owner = Owner.find_by_name('ashley') assert_equal pet.owner, Owner.find_by_name('ashley') end -end \ No newline at end of file +end