diff --git a/app/controllers/application.rb b/app/controllers/application.rb index 8bc45e4a..125b282e 100644 --- a/app/controllers/application.rb +++ b/app/controllers/application.rb @@ -2,7 +2,7 @@ # Likewise will all the methods added be available for all controllers. require_dependency "login_system" -require_dependency "source_view" +require_dependency "tracks/source_view" require "redcloth" require 'date' @@ -20,6 +20,7 @@ class ApplicationController < ActionController::Base layout proc{ |controller| controller.mobile? ? "mobile" : "standard" } before_filter :set_session_expiration + before_filter :set_time_zone prepend_before_filter :login_required prepend_before_filter :enable_mobile_content_negotiation after_filter :restore_content_type_for_mobile @@ -178,6 +179,14 @@ class ApplicationController < ActionController::Base raise ArgumentError.new("invalid value for Boolean: \"#{s}\"") end + def self.openid_enabled? + Tracks::Config.openid_enabled? + end + + def openid_enabled? + self.class.openid_enabled? + end + private def parse_date_per_user_prefs( s ) @@ -213,4 +222,8 @@ class ApplicationController < ActionController::Base logger.error("ERROR: #{message}") if type == :error end + def set_time_zone + Time.zone = current_user.prefs.time_zone if logged_in? + end + end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb index 0a7ba7ea..d6d41f4b 100644 --- a/app/controllers/login_controller.rb +++ b/app/controllers/login_controller.rb @@ -6,11 +6,11 @@ class LoginController < ApplicationController skip_before_filter :login_required before_filter :login_optional before_filter :get_current_user - open_id_consumer if Tracks::Config.openid_enabled? + open_id_consumer if openid_enabled? def login @page_title = "TRACKS::Login" - @openid_url = cookies[:openid_url] if Tracks::Config.openid_enabled? + @openid_url = cookies[:openid_url] if openid_enabled? case request.method when :post if @user = User.authenticate(params['user_login'], params['user_password']) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index c28490ca..7caf0cd9 100755 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -415,7 +415,7 @@ class StatsController < ApplicationController @max=0 @actions_creation_hour_array = Array.new(24) { |i| 0} @actions_creation_hour.each do |r| - hour = current_user.prefs.tz.adjust(r.created_at).hour + hour = r.created_at.hour @actions_creation_hour_array[hour] += 1 end 0.upto(23) { |i| @max = @actions_creation_hour_array[i] if @actions_creation_hour_array[i] > @max} @@ -423,7 +423,7 @@ class StatsController < ApplicationController # convert to hash to be able to fill in non-existing days @actions_completion_hour_array = Array.new(24) { |i| 0} @actions_completion_hour.each do |r| - hour = current_user.prefs.tz.adjust(r.completed_at).hour + hour = r.completed_at.hour @actions_completion_hour_array[hour] += 1 end 0.upto(23) { |i| @max = @actions_completion_hour_array[i] if @actions_completion_hour_array[i] > @max} @@ -446,7 +446,7 @@ class StatsController < ApplicationController @max=0 @actions_creation_hour_array = Array.new(24) { |i| 0} @actions_creation_hour.each do |r| - hour = current_user.prefs.tz.adjust(r.created_at).hour + hour = r.created_at.hour @actions_creation_hour_array[hour] += 1 end 0.upto(23) { |i| @max = @actions_creation_hour_array[i] if @actions_creation_hour_array[i] > @max} @@ -454,7 +454,7 @@ class StatsController < ApplicationController # convert to hash to be able to fill in non-existing days @actions_completion_hour_array = Array.new(24) { |i| 0} @actions_completion_hour.each do |r| - hour = current_user.prefs.tz.adjust(r.completed_at).hour + hour = r.completed_at.hour @actions_completion_hour_array[hour] += 1 end 0.upto(23) { |i| @max = @actions_completion_hour_array[i] if @actions_completion_hour_array[i] > @max} diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 27f9d23d..7cbf79a1 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -267,9 +267,9 @@ class TodosController < ApplicationController def completed @page_title = "TRACKS::Completed tasks" @done = current_user.completed_todos - @done_today = @done.completed_within current_user.time - 1.day - @done_this_week = @done.completed_within current_user.time - 1.week - @done_this_month = @done.completed_within current_user.time - 4.week + @done_today = @done.completed_within Time.zone.now - 1.day + @done_this_week = @done.completed_within Time.zone.now - 1.week + @done_this_month = @done.completed_within Time.zone.now - 4.week @count = @done_today.size + @done_this_week.size + @done_this_month.size end @@ -277,7 +277,7 @@ class TodosController < ApplicationController @page_title = "TRACKS::Archived completed tasks" @done = current_user.completed_todos @count = @done.size - @done_archive = @done.completed_more_than current_user.time - 28.days + @done_archive = @done.completed_more_than Time.zone.now - 28.days end def list_deferred @@ -392,7 +392,7 @@ class TodosController < ApplicationController if params.key?('due') due_within = params['due'].to_i - due_within_when = current_user.time + due_within.days + due_within_when = Time.zone.now + due_within.days condition_builder.add('todos.due <= ?', due_within_when) due_within_date_s = due_within_when.strftime("%Y-%m-%d") @title << " due today" if (due_within == 0) @@ -402,7 +402,7 @@ class TodosController < ApplicationController if params.key?('done') done_in_last = params['done'].to_i - condition_builder.add('todos.completed_at >= ?', current_user.time - done_in_last.days) + condition_builder.add('todos.completed_at >= ?', Time.zone.now - done_in_last.days) @title << " actions completed" @description << " in the last #{done_in_last.to_s} days" end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index bd5fe39e..a444220b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,6 @@ class UsersController < ApplicationController - if Tracks::Config.openid_enabled? + if openid_enabled? open_id_consumer before_filter :begin_open_id_auth, :only => :update_auth_type end @@ -153,7 +153,7 @@ class UsersController < ApplicationController end def update_auth_type - if (params[:user][:auth_type] == 'open_id') && Tracks::Config.openid_enabled? + if (params[:user][:auth_type] == 'open_id') && openid_enabled? case open_id_response.status when OpenID::SUCCESS # The URL was a valid identity URL. Now we just need to send a redirect @@ -179,7 +179,7 @@ class UsersController < ApplicationController end def complete - return unless Tracks::Config.openid_enabled? + return unless openid_enabled? openid_url = session['openid_url'] if openid_url.blank? notify :error, "expected an openid_url" diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d1d2c1a1..a4147313 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -3,7 +3,7 @@ module ApplicationHelper def user_time - current_user.time + Time.zone.now end # Replicates the link_to method but also checks request.request_uri to find diff --git a/app/models/preference.rb b/app/models/preference.rb index 8bf328b3..6b76fd97 100644 --- a/app/models/preference.rb +++ b/app/models/preference.rb @@ -1,8 +1,5 @@ class Preference < ActiveRecord::Base belongs_to :user - composed_of :tz, - :class_name => 'TimeZone', - :mapping => %w(time_zone name) def self.due_styles { :due_in_n_days => 0, :due_on => 1} diff --git a/app/models/project.rb b/app/models/project.rb index 526788cc..de0ad2c6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1,7 +1,7 @@ class Project < ActiveRecord::Base has_many :todos, :dependent => :delete_all, :include => :context has_many :notes, :dependent => :delete_all, :order => "created_at DESC" - belongs_to :default_context, :dependent => :nullify, :class_name => "Context", :foreign_key => "default_context_id" + belongs_to :default_context, :class_name => "Context", :foreign_key => "default_context_id" belongs_to :user validates_presence_of :name, :message => "project must have a name" diff --git a/app/models/todo.rb b/app/models/todo.rb index c7a3c38d..cb820a7b 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -1,6 +1,6 @@ class Todo < ActiveRecord::Base - belongs_to :context, :order => 'name' + belongs_to :context belongs_to :project belongs_to :user diff --git a/app/models/user.rb b/app/models/user.rb index be38573e..98eed40b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -158,7 +158,7 @@ class User < ActiveRecord::Base end def time - prefs.tz.adjust(Time.now.utc) + Time.now.in_time_zone(prefs.time_zone) end def date diff --git a/app/views/preferences/index.html.erb b/app/views/preferences/index.html.erb index c0c72586..985834dd 100644 --- a/app/views/preferences/index.html.erb +++ b/app/views/preferences/index.html.erb @@ -7,7 +7,7 @@
  • Last name: <%= current_user.last_name %>
  • Date format: <%= prefs.date_format %> Your current date: <%= format_date(user_time) %>
  • Title date format: <%= prefs.title_date_format %> Your current title date: <%= user_time.strftime(prefs.title_date_format) %>
  • -
  • Time zone: <%= prefs.tz %> Your current time: <%= user_time.strftime('%I:%M %p') %>
  • +
  • Time zone: <%= prefs.time_zone %> Your current time: <%= user_time.strftime('%I:%M %p') %>
  • Week starts on: <%= Preference.day_number_to_name_map[prefs.week_starts] %>
  • Show the last <%= prefs.show_number_completed %> completed items
  • Show completed projects in sidebar: <%= prefs.show_completed_projects_in_sidebar %>
  • diff --git a/config/boot.rb b/config/boot.rb index 5697cc1b..cd21fb9e 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -24,9 +24,8 @@ module Rails File.exist?("#{RAILS_ROOT}/vendor/rails") end - # FIXME : Ruby 1.9 def preinitialize - load(preinitializer_path) if File.exists?(preinitializer_path) + load(preinitializer_path) if File.exist?(preinitializer_path) end def preinitializer_path @@ -44,6 +43,7 @@ module Rails class VendorBoot < Boot def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + Rails::Initializer.run(:install_gem_spec_stubs) end end diff --git a/config/environment.rb.tmpl b/config/environment.rb.tmpl index e80b02db..c7c7f780 100644 --- a/config/environment.rb.tmpl +++ b/config/environment.rb.tmpl @@ -48,6 +48,10 @@ Rails::Initializer.run do |config| # Make Active Record use UTC-base instead of local time config.active_record.default_timezone = :utc + # You''ll probably want to change this to the time zone of the computer where Tracks is running + # run rake time:zones:local have Rails suggest time zone names on your system + config.time_zone = 'UTC' + # Use Active Record's schema dumper instead of SQL when creating the test database # (enables use of different database adapters for development and test environments) config.active_record.schema_format = :ruby @@ -66,18 +70,13 @@ end # Include your application configuration below -# Time zone setting. Set your local time zone here. # -# You should be able to find a list of time zones in /usr/share/zoneinfo -# e.g. if you are in the Eastern time zone of the US, set the value below. -# ENV['TZ'] = 'US/Eastern' - # Leave this alone or set it to one or more of ['database', 'ldap', 'open_id']. # If you choose ldap, see the additional configuration options further down. AUTHENTICATION_SCHEMES = ['database'] require 'name_part_finder' -require 'todo_list' -require 'config' +require 'tracks/todo_list' +require 'tracks/config' require 'activerecord_base_tag_extensions' # Needed for tagging-specific extensions require 'digest/sha1' #Needed to support 'rake db:fixtures:load' on some ruby installs: http://dev.rousette.org.uk/ticket/557 require 'prototype_helper_extensions' diff --git a/config/environments/test.rb b/config/environments/test.rb index cdadae4b..b94fbcc6 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -26,6 +26,8 @@ config.action_controller.allow_forgery_protection = false # config.pre_loaded_fixtures = false SALT = "change-me" unless defined?( SALT ).nil? +config.time_zone = 'UTC' + config.after_initialize do require File.expand_path(File.dirname(__FILE__) + "/../../test/selenium_helper") end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index fd8d3975..9d20146f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -57,7 +57,11 @@ ActionController::Routing::Routes.draw do |map| map.preferences 'preferences', :controller => 'preferences', :action => 'index' map.integrations 'integrations', :controller => 'integrations', :action => 'index' - + + if Rails.env == 'test' + map.connect '/selenium_helper/login', :controller => 'selenium_helper', :action => 'login' + end + # Install the default route as the lowest priority. map.connect ':controller/:action/:id' diff --git a/db/schema.rb b/db/schema.rb index 2d5c11ee..51db5c38 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,5 +1,5 @@ # This file is auto-generated from the current state of the database. Instead of editing this file, -# please use the migrations feature of ActiveRecord to incrementally modify your database, and +# please use the migrations feature of Active Record to incrementally modify your database, and # then regenerate this schema definition. # # Note that this schema.rb definition is the authoritative source for your database schema. If you need @@ -9,43 +9,43 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 38) do +ActiveRecord::Schema.define(:version => 20080617044632) do create_table "contexts", :force => true do |t| - t.string "name", :null => false - t.integer "position" - t.boolean "hide", :default => false - t.integer "user_id", :default => 1 + t.string "name", :default => "", :null => false + t.integer "position", :limit => 11 + t.boolean "hide", :default => false + t.integer "user_id", :limit => 11, :default => 1 t.datetime "created_at" t.datetime "updated_at" end - add_index "contexts", ["user_id", "name"], :name => "index_contexts_on_user_id_and_name" add_index "contexts", ["user_id"], :name => "index_contexts_on_user_id" + add_index "contexts", ["user_id", "name"], :name => "index_contexts_on_user_id_and_name" create_table "notes", :force => true do |t| - t.integer "user_id", :null => false - t.integer "project_id", :null => false + t.integer "user_id", :limit => 11, :null => false + t.integer "project_id", :limit => 11, :null => false t.text "body" t.datetime "created_at" t.datetime "updated_at" end - add_index "notes", ["user_id"], :name => "index_notes_on_user_id" add_index "notes", ["project_id"], :name => "index_notes_on_project_id" + add_index "notes", ["user_id"], :name => "index_notes_on_user_id" create_table "open_id_associations", :force => true do |t| t.binary "server_url" t.string "handle" t.binary "secret" - t.integer "issued" - t.integer "lifetime" + t.integer "issued", :limit => 11 + t.integer "lifetime", :limit => 11 t.string "assoc_type" end create_table "open_id_nonces", :force => true do |t| t.string "nonce" - t.integer "created" + t.integer "created", :limit => 11 end create_table "open_id_settings", :force => true do |t| @@ -54,40 +54,40 @@ ActiveRecord::Schema.define(:version => 38) do end create_table "preferences", :force => true do |t| - t.integer "user_id", :null => false + t.integer "user_id", :limit => 11, :null => false t.string "date_format", :limit => 40, :default => "%d/%m/%Y", :null => false - t.integer "week_starts", :default => 0, :null => false - t.integer "show_number_completed", :default => 5, :null => false - t.integer "staleness_starts", :default => 7, :null => false + t.integer "week_starts", :limit => 11, :default => 0, :null => false + t.integer "show_number_completed", :limit => 11, :default => 5, :null => false + t.integer "staleness_starts", :limit => 11, :default => 7, :null => false t.boolean "show_completed_projects_in_sidebar", :default => true, :null => false t.boolean "show_hidden_contexts_in_sidebar", :default => true, :null => false - t.integer "due_style", :default => 0, :null => false + t.integer "due_style", :limit => 11, :default => 0, :null => false t.string "admin_email", :default => "butshesagirl@rousette.org.uk", :null => false - t.integer "refresh", :default => 0, :null => false + t.integer "refresh", :limit => 11, :default => 0, :null => false t.boolean "verbose_action_descriptors", :default => false, :null => false t.boolean "show_hidden_projects_in_sidebar", :default => true, :null => false t.string "time_zone", :default => "London", :null => false t.boolean "show_project_on_todo_done", :default => false, :null => false t.string "title_date_format", :default => "%A, %d %B %Y", :null => false - t.integer "mobile_todos_per_page", :default => 6, :null => false + t.integer "mobile_todos_per_page", :limit => 11, :default => 6, :null => false end add_index "preferences", ["user_id"], :name => "index_preferences_on_user_id" create_table "projects", :force => true do |t| - t.string "name", :null => false - t.integer "position" - t.integer "user_id", :default => 1 + t.string "name", :default => "", :null => false + t.integer "position", :limit => 11 + t.integer "user_id", :limit => 11, :default => 1 t.text "description" t.string "state", :limit => 20, :default => "active", :null => false t.datetime "created_at" t.datetime "updated_at" - t.integer "default_context_id" + t.integer "default_context_id", :limit => 11 t.datetime "completed_at" end - add_index "projects", ["user_id", "name"], :name => "index_projects_on_user_id_and_name" add_index "projects", ["user_id"], :name => "index_projects_on_user_id" + add_index "projects", ["user_id", "name"], :name => "index_projects_on_user_id_and_name" create_table "sessions", :force => true do |t| t.string "session_id" @@ -98,10 +98,10 @@ ActiveRecord::Schema.define(:version => 38) do add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id" create_table "taggings", :force => true do |t| - t.integer "taggable_id" - t.integer "tag_id" + t.integer "taggable_id", :limit => 11 + t.integer "tag_id", :limit => 11 t.string "taggable_type" - t.integer "user_id" + t.integer "user_id", :limit => 11 end add_index "taggings", ["tag_id", "taggable_id", "taggable_type"], :name => "index_taggings_on_tag_id_and_taggable_id_and_taggable_type" @@ -115,27 +115,27 @@ ActiveRecord::Schema.define(:version => 38) do add_index "tags", ["name"], :name => "index_tags_on_name" create_table "todos", :force => true do |t| - t.integer "context_id", :null => false - t.integer "project_id" - t.string "description", :null => false + t.integer "context_id", :limit => 11, :null => false + t.integer "project_id", :limit => 11 + t.string "description", :default => "", :null => false t.text "notes" t.datetime "created_at" t.date "due" t.datetime "completed_at" - t.integer "user_id", :default => 1 + t.integer "user_id", :limit => 11, :default => 1 t.date "show_from" t.string "state", :limit => 20, :default => "immediate", :null => false end - add_index "todos", ["user_id", "context_id"], :name => "index_todos_on_user_id_and_context_id" - add_index "todos", ["context_id"], :name => "index_todos_on_context_id" - add_index "todos", ["project_id"], :name => "index_todos_on_project_id" - add_index "todos", ["user_id", "project_id"], :name => "index_todos_on_user_id_and_project_id" add_index "todos", ["user_id", "state"], :name => "index_todos_on_user_id_and_state" + add_index "todos", ["user_id", "project_id"], :name => "index_todos_on_user_id_and_project_id" + add_index "todos", ["project_id"], :name => "index_todos_on_project_id" + add_index "todos", ["context_id"], :name => "index_todos_on_context_id" + add_index "todos", ["user_id", "context_id"], :name => "index_todos_on_user_id_and_context_id" create_table "users", :force => true do |t| - t.string "login", :limit => 80, :null => false - t.string "crypted_password", :limit => 40, :null => false + t.string "login", :limit => 80, :default => "", :null => false + t.string "crypted_password", :limit => 40 t.string "token" t.boolean "is_admin", :default => false, :null => false t.string "first_name" diff --git a/lib/config.rb b/lib/tracks/config.rb similarity index 100% rename from lib/config.rb rename to lib/tracks/config.rb diff --git a/lib/source_view.rb b/lib/tracks/source_view.rb similarity index 100% rename from lib/source_view.rb rename to lib/tracks/source_view.rb diff --git a/lib/todo_list.rb b/lib/tracks/todo_list.rb similarity index 100% rename from lib/todo_list.rb rename to lib/tracks/todo_list.rb diff --git a/public/javascripts/controls.js b/public/javascripts/controls.js index fbc4418b..5aaf0bb2 100644 --- a/public/javascripts/controls.js +++ b/public/javascripts/controls.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) // Contributors: diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js index ccf4a1e4..bf5cfea6 100644 --- a/public/javascripts/dragdrop.js +++ b/public/javascripts/dragdrop.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) // // script.aculo.us is freely distributable under the terms of an MIT-style license. diff --git a/public/javascripts/effects.js b/public/javascripts/effects.js index 65aed239..f030b5db 100644 --- a/public/javascripts/effects.js +++ b/public/javascripts/effects.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) diff --git a/script/dbconsole b/script/dbconsole new file mode 100755 index 00000000..caa60ce8 --- /dev/null +++ b/script/dbconsole @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/dbconsole' diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml index 010c4fdc..40a79310 100644 --- a/test/fixtures/projects.yml +++ b/test/fixtures/projects.yml @@ -1,7 +1,7 @@ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html <% def today - Time.now.utc.beginning_of_day.to_s(:db) + Time.zone.now.beginning_of_day.to_s(:db) end %> diff --git a/test/fixtures/todos.yml b/test/fixtures/todos.yml index cbc6f545..52db71ea 100644 --- a/test/fixtures/todos.yml +++ b/test/fixtures/todos.yml @@ -2,23 +2,23 @@ <% def today - Time.now.utc.beginning_of_day.to_s(:db) + Time.zone.now.beginning_of_day.to_s(:db) end def next_week - 1.week.from_now.beginning_of_day.utc.to_s(:db) + 1.week.from_now.beginning_of_day.to_s(:db) end def last_week - 1.week.ago.utc.beginning_of_day.to_s(:db) + 1.week.ago.beginning_of_day.to_s(:db) end def two_weeks_ago - 2.weeks.ago.utc.beginning_of_day.to_s(:db) + 2.weeks.ago.beginning_of_day.to_s(:db) end def two_weeks_hence - 2.weeks.from_now.utc.beginning_of_day.to_s(:db) + 2.weeks.from_now.beginning_of_day.to_s(:db) end %> diff --git a/test/functional/contexts_controller_test.rb b/test/functional/contexts_controller_test.rb index 2d745ad0..2e2c5cfc 100644 --- a/test/functional/contexts_controller_test.rb +++ b/test/functional/contexts_controller_test.rb @@ -103,7 +103,7 @@ class ContextsControllerTest < TodoContainerControllerTestBase assert_select 'p', /\d+ actions. Context is (Active|Hidden)./ end end - assert_select 'published', /(#{contexts(:agenda).created_at.xmlschema}|#{contexts(:library).created_at.xmlschema})/ + assert_select 'published', /(#{Regexp.escape(contexts(:agenda).created_at.xmlschema)}|#{Regexp.escape(contexts(:library).created_at.xmlschema)})/ end end end diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 5e27811b..4644b9f3 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -160,7 +160,7 @@ class ProjectsControllerTest < TodoContainerControllerTestBase assert_select 'p', /\d+ actions. Project is (active|hidden|completed)./ end end - assert_select 'published', /(#{projects(:timemachine).updated_at.xmlschema}|#{projects(:moremoney).updated_at.xmlschema})/ + assert_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/ end end end diff --git a/test/functional/todos_controller_test.rb b/test/functional/todos_controller_test.rb index a2fa5ee3..ec6f2da5 100644 --- a/test/functional/todos_controller_test.rb +++ b/test/functional/todos_controller_test.rb @@ -245,7 +245,7 @@ class TodosControllerTest < Test::Rails::TestCase assert_xml_select 'entry', 10 do assert_xml_select 'title', /.+/ assert_xml_select 'content[type="html"]', /.*/ - assert_xml_select 'published', /(#{projects(:timemachine).updated_at.xmlschema}|#{projects(:moremoney).updated_at.xmlschema})/ + assert_xml_select 'published', /(#{Regexp.escape(projects(:timemachine).updated_at.xmlschema)}|#{Regexp.escape(projects(:moremoney).updated_at.xmlschema)})/ end end end diff --git a/test/selenium_helper.rb b/test/selenium_helper.rb index 6eec78bd..31f462e4 100644 --- a/test/selenium_helper.rb +++ b/test/selenium_helper.rb @@ -12,8 +12,6 @@ class SeleniumHelperController < ActionController::Base end end -ActionController::Routing::Routes.add_route '/selenium_helper/login', :controller => 'selenium_helper', :action => 'login' - module SeleniumOnRails::TestBuilderActions def login options = {} options = {options => nil} unless options.is_a? Hash diff --git a/test/unit/preference_test.rb b/test/unit/preference_test.rb index 27306282..30daace5 100644 --- a/test/unit/preference_test.rb +++ b/test/unit/preference_test.rb @@ -12,7 +12,6 @@ class PreferenceTest < Test::Rails::TestCase def test_time_zone assert_equal 'London', @admin_user.preference.time_zone - assert_equal @admin_user.preference.tz, TimeZone['London'] end def test_show_project_on_todo_done diff --git a/test/unit/todo_test.rb b/test/unit/todo_test.rb index cfb05eca..9227f40b 100644 --- a/test/unit/todo_test.rb +++ b/test/unit/todo_test.rb @@ -19,8 +19,8 @@ class TodoTest < Test::Rails::TestCase assert_equal "Call Bill Gates to find out how much he makes per day", @not_completed1.description assert_nil @not_completed1.notes assert @not_completed1.completed? == false - assert_equal 1.week.ago.utc.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M") - assert_equal 2.week.from_now.utc.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d") + assert_equal 1.week.ago.beginning_of_day.strftime("%Y-%m-%d %H:%M"), @not_completed1.created_at.strftime("%Y-%m-%d %H:%M") + assert_equal 2.week.from_now.beginning_of_day.strftime("%Y-%m-%d"), @not_completed1.due.strftime("%Y-%m-%d") assert_nil @not_completed1.completed_at assert_equal 1, @not_completed1.user_id end diff --git a/test/views/todos_helper_test.rb b/test/views/todos_helper_test.rb index 211a2b05..843d859e 100644 --- a/test/views/todos_helper_test.rb +++ b/test/views/todos_helper_test.rb @@ -10,10 +10,6 @@ class TodosHelperTest < Test::Rails::HelperTestCase include ApplicationHelper include TodosHelper - def user_time - Time.now - end - def format_date(date) if date date_format = "%d/%m/%Y" @@ -31,7 +27,7 @@ class TodosHelperTest < Test::Rails::HelperTestCase end def test_show_date_today - date = Time.now.to_date + date = Time.zone.now.to_date html = show_date(date) formatted_date = format_date(date) assert_equal %Q{Show Today }, html diff --git a/vendor/plugins/selenium-on-rails/init.rb b/vendor/plugins/selenium-on-rails/init.rb index 05d43a8c..e7e8aa32 100644 --- a/vendor/plugins/selenium-on-rails/init.rb +++ b/vendor/plugins/selenium-on-rails/init.rb @@ -7,6 +7,8 @@ if envs.include? RAILS_ENV require 'selenium_controller' require File.dirname(__FILE__) + '/routes' + SeleniumController.prepend_view_path File.expand_path(File.dirname(__FILE__) + '/lib/views') + else #erase all traces $LOAD_PATH.delete lib_path diff --git a/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/paths.rb b/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/paths.rb index 03dfc7f2..5e9bdd4a 100644 --- a/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/paths.rb +++ b/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/paths.rb @@ -13,13 +13,8 @@ module SeleniumOnRails File.expand_path(File.dirname(__FILE__) + '/../views/' + view) end - # Returns the path to the layout template. The path is relative in relation - # to the app/views/ directory since Rails doesn't support absolute paths - # to layout templates. def layout_path - rails_root = Pathname.new File.expand_path(File.join(RAILS_ROOT, 'app/views')) - view_path = Pathname.new view_path('layout') - view_path.relative_path_from(rails_root).to_s + '/layout.rhtml' end def fixtures_path diff --git a/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/rselenese.rb b/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/rselenese.rb index fea4a57c..a2f2365e 100644 --- a/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/rselenese.rb +++ b/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/rselenese.rb @@ -8,7 +8,7 @@ # See SeleniumOnRails::TestBuilder for a list of available commands. class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder end -ActionView::Base.register_template_handler 'rsel', SeleniumOnRails::RSelenese +ActionView::Template.register_template_handler 'rsel', SeleniumOnRails::RSelenese class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder attr_accessor :view @@ -20,16 +20,16 @@ class SeleniumOnRails::RSelenese < SeleniumOnRails::TestBuilder end # Render _template_ using _local_assigns_. - def render template, local_assigns - title = (@view.assigns['page_title'] or local_assigns['page_title']) + def render template + title = @view.assigns['page_title'] table(title) do test = self #to enable test.command - - assign_locals_code = '' - local_assigns.each_key {|key| assign_locals_code << "#{key} = local_assigns[#{key.inspect}];"} - - eval assign_locals_code + "\n" + template + eval template.source end end + + def compilable? + false + end end \ No newline at end of file diff --git a/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/selenese.rb b/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/selenese.rb index 4f9e4524..5ccb77a4 100644 --- a/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/selenese.rb +++ b/vendor/plugins/selenium-on-rails/lib/selenium_on_rails/selenese.rb @@ -1,6 +1,6 @@ class SeleniumOnRails::Selenese end -ActionView::Base.register_template_handler 'sel', SeleniumOnRails::Selenese +ActionView::Template.register_template_handler 'sel', SeleniumOnRails::Selenese class SeleniumOnRails::Selenese @@ -8,8 +8,8 @@ class SeleniumOnRails::Selenese @view = view end - def render template, local_assigns - name = (@view.assigns['page_title'] or local_assigns['page_title']) + def render template + name = @view.assigns['page_title'] lines = template.strip.split "\n" html = '' html << extract_comments(lines) @@ -18,6 +18,10 @@ class SeleniumOnRails::Selenese raise 'You cannot have comments in the middle of commands!' if next_line lines, :any html end + + def compilable? + false + end private def next_line lines, expects diff --git a/vendor/plugins/will_paginate/.gitignore b/vendor/plugins/will_paginate/.gitignore index 6ac3a1f1..2b437b91 100644 --- a/vendor/plugins/will_paginate/.gitignore +++ b/vendor/plugins/will_paginate/.gitignore @@ -1,2 +1,4 @@ -/pkg /doc +/rails +*.gem +/coverage diff --git a/vendor/plugins/will_paginate/.manifest b/vendor/plugins/will_paginate/.manifest new file mode 100644 index 00000000..d65c5c0f --- /dev/null +++ b/vendor/plugins/will_paginate/.manifest @@ -0,0 +1,49 @@ +CHANGELOG +LICENSE +README.rdoc +Rakefile +examples +examples/apple-circle.gif +examples/index.haml +examples/index.html +examples/pagination.css +examples/pagination.sass +init.rb +lib +lib/will_paginate +lib/will_paginate.rb +lib/will_paginate/array.rb +lib/will_paginate/collection.rb +lib/will_paginate/core_ext.rb +lib/will_paginate/finder.rb +lib/will_paginate/named_scope.rb +lib/will_paginate/named_scope_patch.rb +lib/will_paginate/version.rb +lib/will_paginate/view_helpers.rb +test +test/boot.rb +test/collection_test.rb +test/console +test/database.yml +test/finder_test.rb +test/fixtures +test/fixtures/admin.rb +test/fixtures/developer.rb +test/fixtures/developers_projects.yml +test/fixtures/project.rb +test/fixtures/projects.yml +test/fixtures/replies.yml +test/fixtures/reply.rb +test/fixtures/schema.rb +test/fixtures/topic.rb +test/fixtures/topics.yml +test/fixtures/user.rb +test/fixtures/users.yml +test/helper.rb +test/lib +test/lib/activerecord_test_case.rb +test/lib/activerecord_test_connector.rb +test/lib/load_fixtures.rb +test/lib/view_test_process.rb +test/tasks.rake +test/view_test.rb \ No newline at end of file diff --git a/vendor/plugins/will_paginate/CHANGELOG b/vendor/plugins/will_paginate/CHANGELOG new file mode 100644 index 00000000..19c8fb7d --- /dev/null +++ b/vendor/plugins/will_paginate/CHANGELOG @@ -0,0 +1,92 @@ +== master + +* ActiveRecord 2.1: remove :include from count query when tables are not + referenced in :conditions + +== 2.3.2, released 2008-05-16 + +* Fixed LinkRenderer#stringified_merge by removing "return" from iterator block +* Ensure that 'href' values in pagination links are escaped URLs + +== 2.3.1, released 2008-05-04 + +* Fixed page numbers not showing with custom routes and implicit first page +* Try to use Hanna for documentation (falls back to default RDoc template if not) + +== 2.3.0, released 2008-04-29 + +* Changed LinkRenderer to receive collection, options and reference to view template NOT in + constructor, but with the #prepare method. This is a step towards supporting passing of + LinkRenderer (or subclass) instances that may be preconfigured in some way +* LinkRenderer now has #page_link and #page_span methods for easier customization of output in + subclasses +* Changed page_entries_info() method to adjust its output according to humanized class name of + collection items. Override this with :entry_name parameter (singular). + + page_entries_info(@posts) + #-> "Displaying all 12 posts" + page_entries_info(@posts, :entry_name => 'item') + #-> "Displaying all 12 items" + +== 2.2.3, released 2008-04-26 + +* will_paginate gem is no longer published on RubyForge, but on + gems.github.com: + + gem sources -a http://gems.github.com/ (you only need to do this once) + gem install mislav-will_paginate + +* extract reusable pagination testing stuff into WillPaginate::View +* rethink the page URL construction mechanizm to be more bulletproof when + combined with custom routing for page parameter +* test that anchor parameter can be used in pagination links + +== 2.2.2, released 2008-04-21 + +* Add support for page parameter in custom routes like "/foo/page/2" +* Change output of "page_entries_info" on single-page collection and erraneous + output with empty collection as reported by Tim Chater + +== 2.2.1, released 2008-04-08 + +* take less risky path when monkeypatching named_scope; fix that it no longer + requires ActiveRecord::VERSION +* use strings in "respond_to?" calls to work around a bug in acts_as_ferret + stable (ugh) +* add rake release task + + +== 2.2.0, released 2008-04-07 + +=== API changes +* Rename WillPaginate::Collection#page_count to "total_pages" for consistency. + If you implemented this interface, change your implementation accordingly. +* Remove old, deprecated style of calling Array#paginate as "paginate(page, + per_page)". If you want to specify :page, :per_page or :total_entries, use a + parameter hash. +* Rename LinkRenderer#url_options to "url_for" and drastically optimize it + +=== View changes +* Added "prev_page" and "next_page" CSS classes on previous/next page buttons +* Add examples of pagination links styling in "examples/index.html" +* Change gap in pagination links from "..." to + "". +* Add "paginated_section", a block helper that renders pagination both above and + below content in the block +* Add rel="prev|next|start" to page links + +=== Other + +* Add ability to opt-in for Rails 2.1 feature "named_scope" by calling + WillPaginate.enable_named_scope (tested in Rails 1.2.6 and 2.0.2) +* Support complex page parameters like "developers[page]" +* Move Array#paginate definition to will_paginate/array.rb. You can now easily + use pagination on arrays outside of Rails: + + gem 'will_paginate' + require 'will_paginate/array' + +* Add "paginated_each" method for iterating through every record by loading only + one page of records at the time +* Rails 2: Rescue from WillPaginate::InvalidPage error with 404 Not Found by + default diff --git a/vendor/plugins/will_paginate/README b/vendor/plugins/will_paginate/README deleted file mode 100644 index 162c1891..00000000 --- a/vendor/plugins/will_paginate/README +++ /dev/null @@ -1,180 +0,0 @@ -= WillPaginate - -Pagination is just limiting the number of records displayed. Why should you let -it get in your way while developing, then? This plugin makes magic happen. Did -you ever want to be able to do just this on a model: - - Post.paginate :page => 1, :order => 'created_at DESC' - -... and then render the page links with a single view helper? Well, now you -can. - -Ryan Bates made an awesome screencast[http://railscasts.com/episodes/51], -check it out. - -Your mind reels with questions? Join our Google -group[http://groups.google.com/group/will_paginate]. - -== Installation - -Will Paginate officially supports Rails versions 1.2.6 and 2.0.x. - -Previously, the plugin was available on the following SVN location: - - svn://errtheblog.com/svn/plugins/will_paginate - -In February 2008, it moved to GitHub[http://github.com/mislav/will_paginate/tree] -to be tracked with git. The SVN repo continued to have updates, but not -forever. Therefore you should switch to using the gem: - - gem install will_paginate --no-ri - -After that, you can remove the plugin from your applications and add -a simple require to the end of config/environment.rb: - - require 'will_paginate' - -That's it, just remember to install the gem on all machines that -you are deploying to. - -The second option is to download and extract the tarball from GitHub. Here is the -link for downloading the current state of the master branch: -http://github.com/mislav/will_paginate/tarball/master - -Extract it to vendor/plugins. The directory will have a default name -like "mislav-will_paginate-master"; you can rename it to "will_paginate" for -simplicity. - -== Example usage - -Use a paginate finder in the controller: - - @posts = Post.paginate_by_board_id @board.id, :page => params[:page], :order => 'updated_at DESC' - -Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the -records. Don't forget to tell it which page you want, or it will complain! -Read more on WillPaginate::Finder::ClassMethods. - -Render the posts in your view like you would normally do. When you need to render -pagination, just stick this in: - - <%= will_paginate @posts %> - -You're done. (Copy and paste the example fancy CSS styles from the bottom.) You -can find the option list at WillPaginate::ViewHelpers. - -How does it know how much items to fetch per page? It asks your model by calling -its per_page class method. You can define it like this: - - class Post < ActiveRecord::Base - cattr_reader :per_page - @@per_page = 50 - end - -... or like this: - - class Post < ActiveRecord::Base - def self.per_page - 50 - end - end - -... or don't worry about it at all. WillPaginate defines it to be 30 by default. -But you can always specify the count explicitly when calling +paginate+: - - @posts = Post.paginate :page => params[:page], :per_page => 50 - -The +paginate+ finder wraps the original finder and returns your resultset that now has -some new properties. You can use the collection as you would with any ActiveRecord -resultset. WillPaginate view helpers also need that object to be able to render pagination: - -
      - <% for post in @posts -%> -
    1. Render `post` in some nice way.
    2. - <% end -%> -
    - -

    Now let's render us some pagination!

    - <%= will_paginate @posts %> - -More detailed documentation: - -* WillPaginate::Finder::ClassMethods for pagination on your models; -* WillPaginate::ViewHelpers for your views. - -== Oh noes, a bug! - -Tell us what happened so we can fix it, quick! Issues are filed on the Lighthouse project: -http://err.lighthouseapp.com/projects/466-plugins/tickets?q=tagged:will_paginate - -Steps to make an awesome bug report: - -1. Run rake test in the will_paginate directory. (You will need SQLite3.) - Copy the output if there are failing tests. -2. Register on Lighthouse to create a new ticket. -3. Write a descriptive, short title. Provide as much info as you can in the body. - Assign the ticket to Mislav and tag it with meaningful tags, "will_paginate" - being among them. -4. Yay! You will be notified on updates automatically. - -Here is an example of a great bug report and patch: -http://err.lighthouseapp.com/projects/466/tickets/172-total_entries-ignored-in-paginate_by_sql - -== Authors, credits, contact - -Want to discuss, request features, ask questions? Join the Google group: -http://groups.google.com/group/will_paginate - -Authors:: Mislav Marohnić, PJ Hyett -Original announcement:: http://errtheblog.com/post/929 -Original PHP source:: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php - -All these people helped making will_paginate what it is now with their code -contributions or simply awesome ideas: - -Chris Wanstrath, Dr. Nic Williams, K. Adam Christensen, Mike Garey, Bence -Golda, Matt Aimonetti, Charles Brian Quinn, Desi McAdam, James Coglan, Matijs -van Zuijlen, Maria, Brendan Ribera, Todd Willey, Bryan Helmkamp, Jan Berkel. - -== Usable pagination in the UI - -Copy the following CSS into your stylesheet for a good start: - - .pagination { - padding: 3px; - margin: 3px; - } - .pagination a { - padding: 2px 5px 2px 5px; - margin: 2px; - border: 1px solid #aaaadd; - text-decoration: none; - color: #000099; - } - .pagination a:hover, .pagination a:active { - border: 1px solid #000099; - color: #000; - } - .pagination span.current { - padding: 2px 5px 2px 5px; - margin: 2px; - border: 1px solid #000099; - font-weight: bold; - background-color: #000099; - color: #FFF; - } - .pagination span.disabled { - padding: 2px 5px 2px 5px; - margin: 2px; - border: 1px solid #eee; - color: #ddd; - } - -More reading about pagination as design pattern: - -* Pagination 101: - http://kurafire.net/log/archive/2007/06/22/pagination-101 -* Pagination gallery: - http://www.smashingmagazine.com/2007/11/16/pagination-gallery-examples-and-good-practices/ -* Pagination on Yahoo Design Pattern Library: - http://developer.yahoo.com/ypatterns/parent.php?pattern=pagination diff --git a/vendor/plugins/will_paginate/README.rdoc b/vendor/plugins/will_paginate/README.rdoc new file mode 100644 index 00000000..94ebdc3d --- /dev/null +++ b/vendor/plugins/will_paginate/README.rdoc @@ -0,0 +1,131 @@ += WillPaginate + +Pagination is just limiting the number of records displayed. Why should you let +it get in your way while developing, then? This plugin makes magic happen. Did +you ever want to be able to do just this on a model: + + Post.paginate :page => 1, :order => 'created_at DESC' + +... and then render the page links with a single view helper? Well, now you +can. + +Some resources to get you started: + +* Your mind reels with questions? Join our + {Google group}[http://groups.google.com/group/will_paginate]. +* The will_paginate project page: http://github.com/mislav/will_paginate +* How to report bugs: http://github.com/mislav/will_paginate/wikis/report-bugs +* Ryan Bates made an awesome screencast[http://railscasts.com/episodes/51], + check it out. + +== Installation + +The recommended way is that you get the gem: + + gem install mislav-will_paginate --source http://gems.github.com/ + +After that you don't need the will_paginate plugin in your Rails +application anymore. Just add a simple require to the end of +"config/environment.rb": + + gem 'mislav-will_paginate', '~> 2.2' + require 'will_paginate' + +That's it. Remember to install the gem on all machines that you are +deploying to. + +There are extensive +{installation instructions}[http://github.com/mislav/will_paginate/wikis/installation] +on {the wiki}[http://github.com/mislav/will_paginate/wikis]. + + +== Example usage + +Use a paginate finder in the controller: + + @posts = Post.paginate_by_board_id @board.id, :page => params[:page], :order => 'updated_at DESC' + +Yeah, +paginate+ works just like +find+ -- it just doesn't fetch all the +records. Don't forget to tell it which page you want, or it will complain! +Read more on WillPaginate::Finder::ClassMethods. + +Render the posts in your view like you would normally do. When you need to render +pagination, just stick this in: + + <%= will_paginate @posts %> + +You're done. (Copy and paste the example fancy CSS styles from the bottom.) You +can find the option list at WillPaginate::ViewHelpers. + +How does it know how much items to fetch per page? It asks your model by calling +its per_page class method. You can define it like this: + + class Post < ActiveRecord::Base + cattr_reader :per_page + @@per_page = 50 + end + +... or like this: + + class Post < ActiveRecord::Base + def self.per_page + 50 + end + end + +... or don't worry about it at all. WillPaginate defines it to be 30 by default. +But you can always specify the count explicitly when calling +paginate+: + + @posts = Post.paginate :page => params[:page], :per_page => 50 + +The +paginate+ finder wraps the original finder and returns your resultset that now has +some new properties. You can use the collection as you would with any ActiveRecord +resultset. WillPaginate view helpers also need that object to be able to render pagination: + +
      + <% for post in @posts -%> +
    1. Render `post` in some nice way.
    2. + <% end -%> +
    + +

    Now let's render us some pagination!

    + <%= will_paginate @posts %> + +More detailed documentation: + +* WillPaginate::Finder::ClassMethods for pagination on your models; +* WillPaginate::ViewHelpers for your views. + + +== Authors and credits + +Authors:: Mislav Marohnić, PJ Hyett +Original announcement:: http://errtheblog.com/post/929 +Original PHP source:: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php + +All these people helped making will_paginate what it is now with their code +contributions or just simply awesome ideas: + +Chris Wanstrath, Dr. Nic Williams, K. Adam Christensen, Mike Garey, Bence +Golda, Matt Aimonetti, Charles Brian Quinn, Desi McAdam, James Coglan, Matijs +van Zuijlen, Maria, Brendan Ribera, Todd Willey, Bryan Helmkamp, Jan Berkel, +Lourens Naudé, Rick Olson, Russell Norris, Piotr Usewicz, Chris Eppstein. + + +== Usable pagination in the UI + +There are some CSS styles to get you started in the "examples/" directory. They +are showcased in the "examples/index.html" file. + +More reading about pagination as design pattern: + +* Pagination 101: + http://kurafire.net/log/archive/2007/06/22/pagination-101 +* Pagination gallery: + http://www.smashingmagazine.com/2007/11/16/pagination-gallery-examples-and-good-practices/ +* Pagination on Yahoo Design Pattern Library: + http://developer.yahoo.com/ypatterns/parent.php?pattern=pagination + +Want to discuss, request features, ask questions? Join the +{Google group}[http://groups.google.com/group/will_paginate]. + diff --git a/vendor/plugins/will_paginate/Rakefile b/vendor/plugins/will_paginate/Rakefile index e21df943..c3cf1c61 100644 --- a/vendor/plugins/will_paginate/Rakefile +++ b/vendor/plugins/will_paginate/Rakefile @@ -1,65 +1,62 @@ -require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' +require 'rubygems' +begin + hanna_dir = '/home/mislav/projects/hanna/lib' + $:.unshift hanna_dir if File.exists? hanna_dir + require 'hanna/rdoctask' +rescue LoadError + require 'rake' + require 'rake/rdoctask' +end +load 'test/tasks.rake' desc 'Default: run unit tests.' task :default => :test -desc 'Test the will_paginate plugin.' -Rake::TestTask.new(:test) do |t| - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -# I want to specify environment variables at call time -class EnvTestTask < Rake::TestTask - attr_accessor :env - - def ruby(*args) - env.each { |key, value| ENV[key] = value } if env - super - env.keys.each { |key| ENV.delete key } if env - end -end - -for configuration in %w( sqlite3 mysql postgres ) - EnvTestTask.new("test_#{configuration}") do |t| - t.pattern = 'test/finder_test.rb' - t.verbose = true - t.env = { 'DB' => configuration } - end -end - -task :test_databases => %w(test_mysql test_sqlite3 test_postgres) - -desc %{Test everything on SQLite3, MySQL and PostgreSQL} -task :test_full => %w(test test_mysql test_postgres) - -desc %{Test everything with Rails 1.2.x and 2.0.x gems} -task :test_all do - all = Rake::Task['test_full'] - ENV['RAILS_VERSION'] = '~>1.2.6' - all.invoke - # reset the invoked flag - %w( test_full test test_mysql test_postgres ).each do |name| - Rake::Task[name].instance_variable_set '@already_invoked', false - end - # do it again - ENV['RAILS_VERSION'] = '~>2.0.2' - all.invoke -end - desc 'Generate RDoc documentation for the will_paginate plugin.' Rake::RDocTask.new(:rdoc) do |rdoc| - files = ['README', 'LICENSE', 'lib/**/*.rb'] - rdoc.rdoc_files.add(files) - rdoc.main = "README" # page to start on - rdoc.title = "will_paginate" + rdoc.rdoc_files.include('README.rdoc', 'LICENSE', 'CHANGELOG'). + include('lib/**/*.rb'). + exclude('lib/will_paginate/named_scope*'). + exclude('lib/will_paginate/array.rb'). + exclude('lib/will_paginate/version.rb') - templates = %w[/Users/chris/ruby/projects/err/rock/template.rb /var/www/rock/template.rb] - rdoc.template = templates.find { |t| File.exists? t } + rdoc.main = "README.rdoc" # page to start on + rdoc.title = "will_paginate documentation" rdoc.rdoc_dir = 'doc' # rdoc output folder - rdoc.options << '--inline-source' - rdoc.options << '--charset=UTF-8' + rdoc.options << '--inline-source' << '--charset=UTF-8' + rdoc.options << '--webcvs=http://github.com/mislav/will_paginate/tree/master/' +end + +desc %{Update ".manifest" with the latest list of project filenames. Respect\ +.gitignore by excluding everything that git ignores. Update `files` and\ +`test_files` arrays in "*.gemspec" file if it's present.} +task :manifest do + list = Dir['**/*'].sort + spec_file = Dir['*.gemspec'].first + list -= [spec_file] if spec_file + + File.read('.gitignore').each_line do |glob| + glob = glob.chomp.sub(/^\//, '') + list -= Dir[glob] + list -= Dir["#{glob}/**/*"] if File.directory?(glob) and !File.symlink?(glob) + puts "excluding #{glob}" + end + + if spec_file + spec = File.read spec_file + spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \) )/mx do + assignment = $1 + bunch = $2 ? list.grep(/^test\//) : list + '%s%%w(%s)' % [assignment, bunch.join(' ')] + end + + File.open(spec_file, 'w') {|f| f << spec } + end + File.open('.manifest', 'w') {|f| f << list.join("\n") } +end + +task :examples do + %x(haml examples/index.haml examples/index.html) + %x(sass examples/pagination.sass examples/pagination.css) end diff --git a/vendor/plugins/will_paginate/examples/apple-circle.gif b/vendor/plugins/will_paginate/examples/apple-circle.gif new file mode 100644 index 00000000..df8cbf7c Binary files /dev/null and b/vendor/plugins/will_paginate/examples/apple-circle.gif differ diff --git a/vendor/plugins/will_paginate/examples/index.haml b/vendor/plugins/will_paginate/examples/index.haml new file mode 100644 index 00000000..fb41ac8a --- /dev/null +++ b/vendor/plugins/will_paginate/examples/index.haml @@ -0,0 +1,69 @@ +!!! +%html +%head + %title Samples of pagination styling for will_paginate + %link{ :rel => 'stylesheet', :type => 'text/css', :href => 'pagination.css' } + %style{ :type => 'text/css' } + :sass + html + :margin 0 + :padding 0 + :background #999 + :font normal 76% "Lucida Grande", Verdana, Helvetica, sans-serif + body + :margin 2em + :padding 2em + :border 2px solid gray + :background white + :color #222 + h1 + :font-size 2em + :font-weight normal + :margin 0 0 1em 0 + h2 + :font-size 1.4em + :margin 1em 0 .5em 0 + pre + :font-size 13px + :font-family Monaco, "DejaVu Sans Mono", "Bitstream Vera Mono", "Courier New", monospace + +- pagination = '« Previous 1 3 4 5 6 7 8 9 29 30 ' +- pagination_no_page_links = '« Previous ' + +%body + %h1 Samples of pagination styling for will_paginate + %p + Find these styles in "examples/pagination.css" of will_paginate library. + There is a Sass version of it for all you sassy people. + %p + Read about good rules for pagination: + %a{ :href => 'http://kurafire.net/log/archive/2007/06/22/pagination-101' } Pagination 101 + %p + %em Warning: + page links below don't lead anywhere (so don't click on them). + + %h2 Unstyled pagination (ewww!) + %div= pagination + + %h2 Digg.com + .digg_pagination= pagination + + %h2 Digg-style, no page links + .digg_pagination= pagination_no_page_links + %p Code that renders this: + %pre= '%s' % %[<%= will_paginate @posts, :page_links => false %>].gsub('<', '<').gsub('>', '>') + + %h2 Digg-style, extra content + .digg_pagination + .page_info Displaying entries 1 - 6 of 180 in total + = pagination + %p Code that renders this: + %pre= '%s' % %[
    \n
    \n <%= page_entries_info @posts %>\n
    \n <%= will_paginate @posts, :container => false %>\n
    ].gsub('<', '<').gsub('>', '>') + + %h2 Apple.com store + .apple_pagination= pagination + + %h2 Flickr.com + .flickr_pagination + = pagination + .page_info (118 photos) diff --git a/vendor/plugins/will_paginate/examples/index.html b/vendor/plugins/will_paginate/examples/index.html new file mode 100644 index 00000000..858f7c6f --- /dev/null +++ b/vendor/plugins/will_paginate/examples/index.html @@ -0,0 +1,92 @@ + + + + + Samples of pagination styling for will_paginate + + + + +

    Samples of pagination styling for will_paginate

    +

    + Find these styles in "examples/pagination.css" of will_paginate library. + There is a Sass version of it for all you sassy people. +

    +

    + Read about good rules for pagination: + Pagination 101 +

    +

    + Warning: + page links below don't lead anywhere (so don't click on them). +

    +

    + Unstyled pagination (ewww!) +

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Digg.com

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Digg-style, no page links

    +
    + « Previous +
    +

    Code that renders this:

    +
    +    <%= will_paginate @posts, :page_links => false %>
    +  
    +

    Digg-style, extra content

    +
    +
    + Displaying entries 1 - 6 of 180 in total +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Code that renders this:

    +
    +    <div class="digg_pagination">
    +      <div clas="page_info">
    +        <%= page_entries_info @posts %>
    +      </div>
    +      <%= will_paginate @posts, :container => false %>
    +    </div>
    +  
    +

    Apple.com store

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    +

    Flickr.com

    +
    + « Previous 1 3 4 5 6 7 8 9 29 30 +
    (118 photos)
    +
    + diff --git a/vendor/plugins/will_paginate/examples/pagination.css b/vendor/plugins/will_paginate/examples/pagination.css new file mode 100644 index 00000000..b55e9779 --- /dev/null +++ b/vendor/plugins/will_paginate/examples/pagination.css @@ -0,0 +1,90 @@ +.digg_pagination { + background: white; + /* self-clearing method: */ } + .digg_pagination a, .digg_pagination span { + padding: .2em .5em; + display: block; + float: left; + margin-right: 1px; } + .digg_pagination span.disabled { + color: #999; + border: 1px solid #DDD; } + .digg_pagination span.current { + font-weight: bold; + background: #2E6AB1; + color: white; + border: 1px solid #2E6AB1; } + .digg_pagination a { + text-decoration: none; + color: #105CB6; + border: 1px solid #9AAFE5; } + .digg_pagination a:hover, .digg_pagination a:focus { + color: #003; + border-color: #003; } + .digg_pagination .page_info { + background: #2E6AB1; + color: white; + padding: .4em .6em; + width: 22em; + margin-bottom: .3em; + text-align: center; } + .digg_pagination .page_info b { + color: #003; + background: #6aa6ed; + padding: .1em .25em; } + .digg_pagination:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; } + * html .digg_pagination { + height: 1%; } + *:first-child+html .digg_pagination { + overflow: hidden; } + +.apple_pagination { + background: #F1F1F1; + border: 1px solid #E5E5E5; + text-align: center; + padding: 1em; } + .apple_pagination a, .apple_pagination span { + padding: .2em .3em; } + .apple_pagination span.disabled { + color: #AAA; } + .apple_pagination span.current { + font-weight: bold; + background: transparent url(apple-circle.gif) no-repeat 50% 50%; } + .apple_pagination a { + text-decoration: none; + color: black; } + .apple_pagination a:hover, .apple_pagination a:focus { + text-decoration: underline; } + +.flickr_pagination { + text-align: center; + padding: .3em; } + .flickr_pagination a, .flickr_pagination span { + padding: .2em .5em; } + .flickr_pagination span.disabled { + color: #AAA; } + .flickr_pagination span.current { + font-weight: bold; + color: #FF0084; } + .flickr_pagination a { + border: 1px solid #DDDDDD; + color: #0063DC; + text-decoration: none; } + .flickr_pagination a:hover, .flickr_pagination a:focus { + border-color: #003366; + background: #0063DC; + color: white; } + .flickr_pagination .page_info { + color: #aaa; + padding-top: .8em; } + .flickr_pagination .prev_page, .flickr_pagination .next_page { + border-width: 2px; } + .flickr_pagination .prev_page { + margin-right: 1em; } + .flickr_pagination .next_page { + margin-left: 1em; } diff --git a/vendor/plugins/will_paginate/examples/pagination.sass b/vendor/plugins/will_paginate/examples/pagination.sass new file mode 100644 index 00000000..737a97be --- /dev/null +++ b/vendor/plugins/will_paginate/examples/pagination.sass @@ -0,0 +1,91 @@ +.digg_pagination + :background white + a, span + :padding .2em .5em + :display block + :float left + :margin-right 1px + span.disabled + :color #999 + :border 1px solid #DDD + span.current + :font-weight bold + :background #2E6AB1 + :color white + :border 1px solid #2E6AB1 + a + :text-decoration none + :color #105CB6 + :border 1px solid #9AAFE5 + &:hover, &:focus + :color #003 + :border-color #003 + .page_info + :background #2E6AB1 + :color white + :padding .4em .6em + :width 22em + :margin-bottom .3em + :text-align center + b + :color #003 + :background = #2E6AB1 + 60 + :padding .1em .25em + + /* self-clearing method: + &:after + :content "." + :display block + :height 0 + :clear both + :visibility hidden + * html & + :height 1% + *:first-child+html & + :overflow hidden + +.apple_pagination + :background #F1F1F1 + :border 1px solid #E5E5E5 + :text-align center + :padding 1em + a, span + :padding .2em .3em + span.disabled + :color #AAA + span.current + :font-weight bold + :background transparent url(apple-circle.gif) no-repeat 50% 50% + a + :text-decoration none + :color black + &:hover, &:focus + :text-decoration underline + +.flickr_pagination + :text-align center + :padding .3em + a, span + :padding .2em .5em + span.disabled + :color #AAA + span.current + :font-weight bold + :color #FF0084 + a + :border 1px solid #DDDDDD + :color #0063DC + :text-decoration none + &:hover, &:focus + :border-color #003366 + :background #0063DC + :color white + .page_info + :color #aaa + :padding-top .8em + .prev_page, .next_page + :border-width 2px + .prev_page + :margin-right 1em + .next_page + :margin-left 1em diff --git a/vendor/plugins/will_paginate/init.rb b/vendor/plugins/will_paginate/init.rb index dc0572d9..838d30ec 100644 --- a/vendor/plugins/will_paginate/init.rb +++ b/vendor/plugins/will_paginate/init.rb @@ -1,2 +1 @@ require 'will_paginate' -WillPaginate.enable diff --git a/vendor/plugins/will_paginate/lib/will_paginate.rb b/vendor/plugins/will_paginate/lib/will_paginate.rb index c0a5ce13..366e39cc 100644 --- a/vendor/plugins/will_paginate/lib/will_paginate.rb +++ b/vendor/plugins/will_paginate/lib/will_paginate.rb @@ -21,7 +21,7 @@ module WillPaginate require 'will_paginate/view_helpers' ActionView::Base.class_eval { include ViewHelpers } - if ActionController::Base.respond_to? :rescue_responses + if defined?(ActionController::Base) and ActionController::Base.respond_to? :rescue_responses ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found end end @@ -33,28 +33,45 @@ module WillPaginate require 'will_paginate/finder' ActiveRecord::Base.class_eval { include Finder } - associations = ActiveRecord::Associations - collection = associations::AssociationCollection - - # to support paginating finders on associations, we have to mix in the - # method_missing magic from WillPaginate::Finder::ClassMethods to AssociationProxy - # subclasses, but in a different way for Rails 1.2.x and 2.0 - (collection.instance_methods.include?(:create!) ? - collection : collection.subclasses.map(&:constantize) - ).push(associations::HasManyThroughAssociation).each do |klass| + # support pagination on associations + a = ActiveRecord::Associations + returning([ a::AssociationCollection ]) { |classes| + # detect http://dev.rubyonrails.org/changeset/9230 + unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation + classes << a::HasManyThroughAssociation + end + }.each do |klass| klass.class_eval do include Finder::ClassMethods alias_method_chain :method_missing, :paginate end end end + + # Enable named_scope, a feature of Rails 2.1, even if you have older Rails + # (tested on Rails 2.0.2 and 1.2.6). + # + # You can pass +false+ for +patch+ parameter to skip monkeypatching + # *associations*. Use this if you feel that named_scope broke + # has_many, has_many :through or has_and_belongs_to_many associations in + # your app. By passing +false+, you can still use named_scope in + # your models, but not through associations. + def enable_named_scope(patch = true) + return if defined? ActiveRecord::NamedScope + require 'will_paginate/named_scope' + require 'will_paginate/named_scope_patch' if patch + + ActiveRecord::Base.class_eval do + include WillPaginate::NamedScope + end + end end module Deprecation #:nodoc: extend ActiveSupport::Deprecation def self.warn(message, callstack = caller) - message = 'WillPaginate: ' + message.strip.gsub(/ {3,}/, ' ') + message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ') behavior.call(message, callstack) if behavior && !silenced? end @@ -63,3 +80,7 @@ module WillPaginate end end end + +if defined?(Rails) and defined?(ActiveRecord) and defined?(ActionController) + WillPaginate.enable +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/array.rb b/vendor/plugins/will_paginate/lib/will_paginate/array.rb new file mode 100644 index 00000000..d061d2be --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/array.rb @@ -0,0 +1,16 @@ +require 'will_paginate/collection' + +# http://www.desimcadam.com/archives/8 +Array.class_eval do + def paginate(options = {}) + raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options + + WillPaginate::Collection.create( + options[:page] || 1, + options[:per_page] || 30, + options[:total_entries] || self.length + ) { |pager| + pager.replace self[pager.offset, pager.per_page].to_a + } + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/collection.rb b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb index f7961312..89d992f6 100644 --- a/vendor/plugins/will_paginate/lib/will_paginate/collection.rb +++ b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb @@ -1,11 +1,18 @@ -require 'will_paginate' - module WillPaginate - # = OMG, invalid page number! + # = Invalid page number error # This is an ArgumentError raised in case a page was requested that is either # zero or negative number. You should decide how do deal with such errors in # the controller. # + # If you're using Rails 2, then this error will automatically get handled like + # 404 Not Found. The hook is in "will_paginate.rb": + # + # ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found + # + # If you don't like this, use your preffered method of rescuing exceptions in + # public from your controllers to handle this differently. The +rescue_from+ + # method is a nice addition to Rails 2. + # # This error is *not* raised when a page further than the last page is # requested. Use WillPaginate::Collection#out_of_bounds? method to # check for those cases and manually deal with them as you see fit. @@ -15,26 +22,34 @@ module WillPaginate end end - # Arrays returned from paginating finds are, in fact, instances of this. - # You may think of WillPaginate::Collection as an ordinary array with some - # extra properties. Those properties are used by view helpers to generate + # = The key to pagination + # Arrays returned from paginating finds are, in fact, instances of this little + # class. You may think of WillPaginate::Collection as an ordinary array with + # some extra properties. Those properties are used by view helpers to generate # correct page links. # # WillPaginate::Collection also assists in rolling out your own pagination # solutions: see +create+. + # + # If you are writing a library that provides a collection which you would like + # to conform to this API, you don't have to copy these methods over; simply + # make your plugin/gem dependant on the "will_paginate" gem: # + # gem 'will_paginate' + # require 'will_paginate/collection' + # + # # now use WillPaginate::Collection directly or subclass it class Collection < Array - attr_reader :current_page, :per_page, :total_entries + attr_reader :current_page, :per_page, :total_entries, :total_pages - # Arguments to this constructor are the current page number, per-page limit + # Arguments to the constructor are the current page number, per-page limit # and the total number of entries. The last argument is optional because it # is best to do lazy counting; in other words, count *conditionally* after # populating the collection using the +replace+ method. - # def initialize(page, per_page, total = nil) @current_page = page.to_i raise InvalidPage.new(page, @current_page) if @current_page < 1 - @per_page = per_page.to_i + @per_page = per_page.to_i raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1 self.total_entries = total if total @@ -65,29 +80,25 @@ module WillPaginate # end # end # + # The Array#paginate API has since then changed, but this still serves as a + # fine example of WillPaginate::Collection usage. def self.create(page, per_page, total = nil, &block) pager = new(page, per_page, total) yield pager pager end - # The total number of pages. - def page_count - @total_pages - end - # Helper method that is true when someone tries to fetch a page with a # larger number than the last page. Can be used in combination with flashes # and redirecting. def out_of_bounds? - current_page > page_count + current_page > total_pages end # Current offset of the paginated collection. If we're on the first page, # it is always 0. If we're on the 2nd page and there are 30 entries per page, # the offset is 30. This property is useful if you want to render ordinals # besides your records: simply start with offset + 1. - # def offset (current_page - 1) * per_page end @@ -99,7 +110,7 @@ module WillPaginate # current_page + 1 or nil if there is no next page def next_page - current_page < page_count ? (current_page + 1) : nil + current_page < total_pages ? (current_page + 1) : nil end def total_entries=(number) @@ -120,13 +131,15 @@ module WillPaginate # +total_entries+ and set it to a proper value if it's +nil+. See the example # in +create+. def replace(array) - returning super do - # The collection is shorter then page limit? Rejoice, because - # then we know that we are on the last page! - if total_entries.nil? and length > 0 and length < per_page - self.total_entries = offset + length - end + result = super + + # The collection is shorter then page limit? Rejoice, because + # then we know that we are on the last page! + if total_entries.nil? and length < per_page and (current_page == 1 or length > 0) + self.total_entries = offset + length end + + result end end end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb index 461153fd..32f10f50 100644 --- a/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb +++ b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb @@ -1,5 +1,5 @@ -require 'will_paginate' require 'set' +require 'will_paginate/array' unless Hash.instance_methods.include? 'except' Hash.class_eval do @@ -30,51 +30,3 @@ unless Hash.instance_methods.include? 'slice' end end end - -unless Hash.instance_methods.include? 'rec_merge!' - Hash.class_eval do - # Same as Hash#merge!, but recursively merges sub-hashes - # (stolen from Haml) - def rec_merge!(other) - other.each do |key, other_value| - value = self[key] - if value.is_a?(Hash) and other_value.is_a?(Hash) - value.rec_merge! other_value - else - self[key] = other_value - end - end - self - end - end -end - -require 'will_paginate/collection' - -unless Array.instance_methods.include? 'paginate' - # http://www.desimcadam.com/archives/8 - Array.class_eval do - def paginate(options_or_page = {}, per_page = nil) - if options_or_page.nil? or Fixnum === options_or_page - if defined? WillPaginate::Deprecation - WillPaginate::Deprecation.warn <<-DEPR - Array#paginate now conforms to the main, ActiveRecord::Base#paginate API. You should \ - call it with a parameters hash (:page, :per_page). The old API (numbers as arguments) \ - has been deprecated and is going to be unsupported in future versions of will_paginate. - DEPR - end - page = options_or_page - options = {} - else - options = options_or_page - page = options[:page] - raise ArgumentError, "wrong number of arguments (1 hash or 2 Fixnums expected)" if per_page - per_page = options[:per_page] - end - - WillPaginate::Collection.create(page || 1, per_page || 30, options[:total_entries] || size) do |pager| - pager.replace self[pager.offset, pager.per_page].to_a - end - end - end -end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finder.rb b/vendor/plugins/will_paginate/lib/will_paginate/finder.rb index 5d2d73cc..ec670eaa 100644 --- a/vendor/plugins/will_paginate/lib/will_paginate/finder.rb +++ b/vendor/plugins/will_paginate/lib/will_paginate/finder.rb @@ -2,7 +2,7 @@ require 'will_paginate/core_ext' module WillPaginate # A mixin for ActiveRecord::Base. Provides +per_page+ class method - # and makes +paginate+ finders possible with some method_missing magic. + # and hooks things up to provide paginating finders. # # Find out more in WillPaginate::Finder::ClassMethods # @@ -18,9 +18,9 @@ module WillPaginate # = Paginating finders for ActiveRecord models # - # WillPaginate adds +paginate+ and +per_page+ methods to ActiveRecord::Base - # class methods and associations. It also hooks into +method_missing+ to - # intercept pagination calls to dynamic finders such as + # WillPaginate adds +paginate+, +per_page+ and other methods to + # ActiveRecord::Base class methods and associations. It also hooks into + # +method_missing+ to intercept pagination calls to dynamic finders such as # +paginate_by_user_id+ and translate them to ordinary finders # (+find_all_by_user_id+ in this case). # @@ -85,6 +85,31 @@ module WillPaginate pager.total_entries = wp_count(count_options, args, finder) unless pager.total_entries end end + + # Iterates through all records by loading one page at a time. This is useful + # for migrations or any other use case where you don't want to load all the + # records in memory at once. + # + # It uses +paginate+ internally; therefore it accepts all of its options. + # You can specify a starting page with :page (default is 1). Default + # :order is "id", override if necessary. + # + # See http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord where + # Jamis Buck describes this and also uses a more efficient way for MySQL. + def paginated_each(options = {}, &block) + options = { :order => 'id', :page => 1 }.merge options + options[:page] = options[:page].to_i + options[:total_entries] = 0 # skip the individual count queries + total = 0 + + begin + collection = paginate(options) + total += collection.each(&block).size + options[:page] += 1 + end until collection.size < collection.per_page + + total + end # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string # based on the params otherwise used by paginating finds: +page+ and @@ -159,19 +184,27 @@ module WillPaginate unless options[:select] and options[:select] =~ /^\s*DISTINCT\b/i excludees << :select # only exclude the select param if it doesn't begin with DISTINCT end + # count expects (almost) the same options as find count_options = options.except *excludees # merge the hash found in :count # this allows you to specify :select, :order, or anything else just for the count query count_options.update options[:count] if options[:count] + + # we may be in a model or an association proxy + klass = (@owner and @reflection) ? @reflection.klass : self + + # forget about includes if they are irrelevant (Rails 2.1) + if count_options[:include] and + klass.private_methods.include?('references_eager_loaded_tables?') and + !klass.send(:references_eager_loaded_tables?, count_options) + count_options.delete :include + end # we may have to scope ... counter = Proc.new { count(count_options) } - # we may be in a model or an association proxy! - klass = (@owner and @reflection) ? @reflection.klass : self - count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with')) # scope_out adds a 'with_finder' method which acts like with_scope, if it's present # then execute the count with the scoping provided by the with_finder diff --git a/vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb b/vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb new file mode 100644 index 00000000..6f00cf76 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb @@ -0,0 +1,132 @@ +## stolen from: http://dev.rubyonrails.org/browser/trunk/activerecord/lib/active_record/named_scope.rb?rev=9084 + +module WillPaginate + # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate, + # but in other aspects when managing complex conditions that you want to be reusable. + module NamedScope + # All subclasses of ActiveRecord::Base have two named_scopes: + # * all, which is similar to a find(:all) query, and + # * scoped, which allows for the creation of anonymous scopes, on the fly: + # + # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions) + # + # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing + # intermediate values (scopes) around as first-class objects is convenient. + def self.included(base) + base.class_eval do + extend ClassMethods + named_scope :all + named_scope :scoped, lambda { |scope| scope } + end + end + + module ClassMethods + def scopes #:nodoc: + read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) + end + + # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query, + # such as :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions. + # + # class Shirt < ActiveRecord::Base + # named_scope :red, :conditions => {:color => 'red'} + # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] + # end + # + # The above calls to named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, + # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}). + # + # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object + # constructed by a has_many declaration. For instance, you can invoke Shirt.red.find(:first), Shirt.red.count, + # Shirt.red.find(:all, :conditions => {:size => 'small'}). Also, just + # as with the association objects, name scopes acts like an Array, implementing Enumerable; Shirt.red.each(&block), + # Shirt.red.first, and Shirt.red.inject(memo, &block) all behave as if Shirt.red really were an Array. + # + # These named scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only. + # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments + # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count). + # + # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to + # has_many associations. If, + # + # class Person < ActiveRecord::Base + # has_many :shirts + # end + # + # then elton.shirts.red.dry_clean_only will return all of Elton's red, dry clean + # only shirts. + # + # Named scopes can also be procedural. + # + # class Shirt < ActiveRecord::Base + # named_scope :colored, lambda { |color| + # { :conditions => { :color => color } } + # } + # end + # + # In this example, Shirt.colored('puce') finds all puce shirts. + # + # Named scopes can also have extensions, just as with has_many declarations: + # + # class Shirt < ActiveRecord::Base + # named_scope :red, :conditions => {:color => 'red'} do + # def dom_id + # 'red_shirts' + # end + # end + # end + # + def named_scope(name, options = {}, &block) + scopes[name] = lambda do |parent_scope, *args| + Scope.new(parent_scope, case options + when Hash + options + when Proc + options.call(*args) + end, &block) + end + (class << self; self end).instance_eval do + define_method name do |*args| + scopes[name].call(self, *args) + end + end + end + end + + class Scope #:nodoc: + attr_reader :proxy_scope, :proxy_options + [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ } + delegate :scopes, :with_scope, :to => :proxy_scope + + def initialize(proxy_scope, options, &block) + [options[:extend]].flatten.each { |extension| extend extension } if options[:extend] + extend Module.new(&block) if block_given? + @proxy_scope, @proxy_options = proxy_scope, options.except(:extend) + end + + def reload + load_found; self + end + + protected + def proxy_found + @found || load_found + end + + private + def method_missing(method, *args, &block) + if scopes.include?(method) + scopes[method].call(self, *args) + else + with_scope :find => proxy_options do + proxy_scope.send(method, *args, &block) + end + end + end + + def load_found + @found = find(:all) + end + end + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/named_scope_patch.rb b/vendor/plugins/will_paginate/lib/will_paginate/named_scope_patch.rb new file mode 100644 index 00000000..bdc1997f --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/named_scope_patch.rb @@ -0,0 +1,39 @@ +## based on http://dev.rubyonrails.org/changeset/9084 + +ActiveRecord::Associations::AssociationProxy.class_eval do + protected + def with_scope(*args, &block) + @reflection.klass.send :with_scope, *args, &block + end +end + +[ ActiveRecord::Associations::AssociationCollection, + ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass| + klass.class_eval do + protected + alias :method_missing_without_scopes :method_missing_without_paginate + def method_missing_without_paginate(method, *args, &block) + if @reflection.klass.scopes.include?(method) + @reflection.klass.scopes[method].call(self, *args, &block) + else + method_missing_without_scopes(method, *args, &block) + end + end + end +end + +# Rails 1.2.6 +ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do + protected + def method_missing(method, *args, &block) + if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) + super + elsif @reflection.klass.scopes.include?(method) + @reflection.klass.scopes[method].call(self, *args) + else + @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do + @reflection.klass.send(method, *args, &block) + end + end + end +end if ActiveRecord::Base.respond_to? :find_first diff --git a/vendor/plugins/will_paginate/lib/will_paginate/version.rb b/vendor/plugins/will_paginate/lib/will_paginate/version.rb new file mode 100644 index 00000000..6bcd90d6 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/version.rb @@ -0,0 +1,9 @@ +module WillPaginate + module VERSION + MAJOR = 2 + MINOR = 3 + TINY = 3 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb index 34e2ff09..c53e44ec 100644 --- a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb @@ -49,12 +49,13 @@ module WillPaginate # * :param_name -- parameter name for page number in URLs (default: :page) # * :params -- additional parameters when generating pagination links # (eg. :controller => "foo", :action => nil) - # * :renderer -- class name of the link renderer (default: WillPaginate::LinkRenderer) + # * :renderer -- class name, class or instance of a link renderer (default: + # WillPaginate::LinkRenderer) # * :page_links -- when false, only previous/next links are rendered (default: true) # * :container -- toggles rendering of the DIV container for pagination links, set to # false only when you are rendering your own pagination markup (default: true) - # * :id -- HTML ID for the container (default: nil). Pass +true+ to have the ID automatically - # generated from the class name of objects in collection: for example, paginating + # * :id -- HTML ID for the container (default: nil). Pass +true+ to have the ID + # automatically generated from the class name of objects in collection: for example, paginating # ArticleComment models would yield an ID of "article_comments_pagination". # # All options beside listed ones are passed as HTML attributes to the container @@ -85,29 +86,99 @@ module WillPaginate collection_name = "@#{controller.controller_name}" collection = instance_variable_get(collection_name) raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " + - "forget to specify the collection object for will_paginate?" unless collection + "forget to pass the collection object for will_paginate?" unless collection end # early exit if there is nothing to render - return nil unless collection.page_count > 1 + return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1 + options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options - # create the renderer instance - renderer_class = options[:renderer].to_s.constantize - renderer = renderer_class.new collection, options, self + + # get the renderer instance + renderer = case options[:renderer] + when String + options[:renderer].to_s.constantize.new + when Class + options[:renderer].new + else + options[:renderer] + end # render HTML for pagination + renderer.prepare collection, options, self renderer.to_html end + + # Wrapper for rendering pagination links at both top and bottom of a block + # of content. + # + # <% paginated_section @posts do %> + #
      + # <% for post in @posts %> + #
    1. ...
    2. + # <% end %> + #
    + # <% end %> + # + # will result in: + # + # + #
      + # ... + #
    + # + # + # Arguments are passed to a will_paginate call, so the same options + # apply. Don't use the :id option; otherwise you'll finish with two + # blocks of pagination links sharing the same ID (which is invalid HTML). + def paginated_section(*args, &block) + pagination = will_paginate(*args).to_s + content = pagination + capture(&block) + pagination + concat content, block.binding + end # Renders a helpful message with numbers of displayed vs. total entries. # You can use this as a blueprint for your own, similar helpers. # # <%= page_entries_info @posts %> - # #-> Displaying entries 6 - 10 of 26 in total - def page_entries_info(collection) - %{Displaying entries %d - %d of %d in total} % [ - collection.offset + 1, - collection.offset + collection.length, - collection.total_entries - ] + # #-> Displaying posts 6 - 10 of 26 in total + # + # By default, the message will use the humanized class name of objects + # in collection: for instance, "project types" for ProjectType models. + # Override this to your liking with the :entry_name parameter: + # + # <%= page_entries_info @posts, :entry_name => 'item' %> + # #-> Displaying items 6 - 10 of 26 in total + def page_entries_info(collection, options = {}) + entry_name = options[:entry_name] || + (collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' ')) + + if collection.total_pages < 2 + case collection.size + when 0; "No #{entry_name.pluralize} found" + when 1; "Displaying 1 #{entry_name}" + else; "Displaying all #{collection.size} #{entry_name.pluralize}" + end + else + %{Displaying #{entry_name.pluralize} %d - %d of %d in total} % [ + collection.offset + 1, + collection.offset + collection.length, + collection.total_entries + ] + end + end + + def self.total_pages_for_collection(collection) #:nodoc: + if collection.respond_to?('page_count') and !collection.respond_to?('total_pages') + WillPaginate::Deprecation.warn <<-MSG + You are using a paginated collection of class #{collection.class.name} + which conforms to the old API of WillPaginate::Collection by using + `page_count`, while the current method name is `total_pages`. Please + upgrade yours or 3rd-party code that provides the paginated collection. + MSG + class << collection + def total_pages; page_count; end + end + end + collection.total_pages end end @@ -115,22 +186,43 @@ module WillPaginate # links. It is used by +will_paginate+ helper internally. class LinkRenderer - def initialize(collection, options, template) + # The gap in page links is represented by: + # + # + attr_accessor :gap_marker + + def initialize + @gap_marker = '' + end + + # * +collection+ is a WillPaginate::Collection instance or any other object + # that conforms to that API + # * +options+ are forwarded from +will_paginate+ view helper + # * +template+ is the reference to the template being rendered + def prepare(collection, options, template) @collection = collection @options = options @template = template + + # reset values in case we're re-using this instance + @total_pages = @param_name = @url_string = nil end + # Process it! This method returns the complete HTML string which contains + # pagination links. Feel free to subclass LinkRenderer and change this + # method as you see fit. def to_html links = @options[:page_links] ? windowed_links : [] # previous/next buttons - links.unshift page_link_or_span(@collection.previous_page, 'disabled', @options[:prev_label]) - links.push page_link_or_span(@collection.next_page, 'disabled', @options[:next_label]) + links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:prev_label]) + links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label]) html = links.join(@options[:separator]) @options[:container] ? @template.content_tag(:div, html, html_attributes) : html end + # Returns the subset of +options+ this instance was initialized with that + # represent HTML attributes for the container element of pagination links. def html_attributes return @html_attributes if @html_attributes @html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class]) @@ -143,20 +235,21 @@ module WillPaginate protected - def gap_marker; '...'; end - + # Collects link items for visible page numbers. def windowed_links prev = nil visible_page_numbers.inject [] do |links, n| # detect gaps: links << gap_marker if prev and n > prev + 1 - links << page_link_or_span(n) + links << page_link_or_span(n, 'current') prev = n links end end + # Calculates visible page numbers using the :inner_window and + # :outer_window options. def visible_page_numbers inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i window_from = current_page - inner_window @@ -166,9 +259,11 @@ module WillPaginate if window_to > total_pages window_from -= window_to - total_pages window_to = total_pages - elsif window_from < 1 + end + if window_from < 1 window_to += 1 - window_from window_from = 1 + window_to = total_pages if window_to > total_pages end visible = (1..total_pages).to_a @@ -180,21 +275,63 @@ module WillPaginate visible end - def page_link_or_span(page, span_class = 'current', text = nil) + def page_link_or_span(page, span_class, text = nil) text ||= page.to_s + if page and page != current_page - @template.link_to text, url_options(page), :rel => rel_value(page) + classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last + page_link page, text, :rel => rel_value(page), :class => classnames else - @template.content_tag :span, text, :class => span_class + page_span page, text, :class => span_class end end - def url_options(page) - options = { param_name => page } - # page links should preserve GET parameters - options = params.merge(options) if @template.request.get? - options.rec_merge!(@options[:params]) if @options[:params] - return options + def page_link(page, text, attributes = {}) + @template.link_to text, url_for(page), attributes + end + + def page_span(page, text, attributes = {}) + @template.content_tag :span, text, attributes + end + + # Returns URL params for +page_link_or_span+, taking the current GET params + # and :params option into account. + def url_for(page) + page_one = page == 1 + unless @url_string and !page_one + @url_params = {} + # page links should preserve GET parameters + stringified_merge @url_params, @template.params if @template.request.get? + stringified_merge @url_params, @options[:params] if @options[:params] + + if complex = param_name.index(/[^\w-]/) + page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest). + parse_query_parameters("#{param_name}=#{page}") + + stringified_merge @url_params, page_param + else + @url_params[param_name] = page_one ? 1 : 2 + end + + url = @template.url_for(@url_params) + return url if page_one + + if complex + @url_string = url.sub(%r!((?:\?|&)#{CGI.escape param_name}=)#{page}!, '\1@') + return url + else + @url_string = url + @url_params[param_name] = 3 + @template.url_for(@url_params).split(//).each_with_index do |char, i| + if char == '3' and url[i, 1] == '2' + @url_string[i] = '@' + break + end + end + end + end + # finally! + @url_string.sub '@', page.to_s end private @@ -212,15 +349,25 @@ module WillPaginate end def total_pages - @collection.page_count + @total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection) end def param_name - @param_name ||= @options[:param_name].to_sym + @param_name ||= @options[:param_name].to_s end - def params - @params ||= @template.params.to_hash.symbolize_keys + # Recursively merge into target hash by using stringified keys from the other one + def stringified_merge(target, other) + other.each do |key, value| + key = key.to_s # this line is what it's all about! + existing = target[key] + + if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?) + stringified_merge(existing || (target[key] = {}), value) + else + target[key] = value + end + end end end end diff --git a/vendor/plugins/will_paginate/test/boot.rb b/vendor/plugins/will_paginate/test/boot.rb index f1bd72b2..622fc93c 100644 --- a/vendor/plugins/will_paginate/test/boot.rb +++ b/vendor/plugins/will_paginate/test/boot.rb @@ -19,5 +19,3 @@ else gem 'activerecord' end end - -$:.unshift "#{plugin_root}/lib" diff --git a/vendor/plugins/will_paginate/test/array_pagination_test.rb b/vendor/plugins/will_paginate/test/collection_test.rb similarity index 66% rename from vendor/plugins/will_paginate/test/array_pagination_test.rb rename to vendor/plugins/will_paginate/test/collection_test.rb index 0aad4c8f..b3360901 100644 --- a/vendor/plugins/will_paginate/test/array_pagination_test.rb +++ b/vendor/plugins/will_paginate/test/collection_test.rb @@ -1,5 +1,5 @@ -require File.dirname(__FILE__) + '/helper' -require 'will_paginate/core_ext' +require 'helper' +require 'will_paginate/array' class ArrayPaginationTest < Test::Unit::TestCase def test_simple @@ -11,7 +11,8 @@ class ArrayPaginationTest < Test::Unit::TestCase { :page => 3, :per_page => 5, :expected => [] }, ]. each do |conditions| - assert_equal conditions[:expected], collection.paginate(conditions.slice(:page, :per_page)) + expected = conditions.delete :expected + assert_equal expected, collection.paginate(conditions) end end @@ -22,14 +23,8 @@ class ArrayPaginationTest < Test::Unit::TestCase end def test_deprecated_api - assert_deprecated 'paginate API' do - result = (1..50).to_a.paginate(2, 10) - assert_equal 2, result.current_page - assert_equal (11..20).to_a, result - assert_equal 50, result.total_entries - end - - assert_deprecated { [].paginate nil } + assert_raise(ArgumentError) { [].paginate(2) } + assert_raise(ArgumentError) { [].paginate(2, 10) } end def test_total_entries_has_precedence @@ -50,14 +45,28 @@ class ArrayPaginationTest < Test::Unit::TestCase end assert_equal entries, collection - assert_respond_to_all collection, %w(page_count each offset size current_page per_page total_entries) + assert_respond_to_all collection, %w(total_pages each offset size current_page per_page total_entries) assert_kind_of Array, collection assert_instance_of Array, collection.entries assert_equal 3, collection.offset - assert_equal 4, collection.page_count + assert_equal 4, collection.total_pages assert !collection.out_of_bounds? end + def test_previous_next_pages + collection = create(1, 1, 3) + assert_nil collection.previous_page + assert_equal 2, collection.next_page + + collection = create(2, 1, 3) + assert_equal 1, collection.previous_page + assert_equal 3, collection.next_page + + collection = create(3, 1, 3) + assert_equal 2, collection.previous_page + assert_nil collection.next_page + end + def test_out_of_bounds entries = create(2, 3, 2){} assert entries.out_of_bounds? @@ -90,13 +99,20 @@ class ArrayPaginationTest < Test::Unit::TestCase pager.replace array(0) end assert_equal nil, entries.total_entries + + entries = create(1) do |pager| + # collection is empty and we're on page 1, + # so the whole thing must be empty, too + pager.replace array(0) + end + assert_equal 0, entries.total_entries end def test_invalid_page - bad_input = [0, -1, nil, '', 'Schnitzel'] + bad_inputs = [0, -1, nil, '', 'Schnitzel'] - bad_input.each do |bad| - assert_raise(WillPaginate::InvalidPage) { create(bad) } + bad_inputs.each do |bad| + assert_raise(WillPaginate::InvalidPage) { create bad } end end @@ -104,6 +120,11 @@ class ArrayPaginationTest < Test::Unit::TestCase assert_raise(ArgumentError) { create(1, -1) } end + def test_page_count_was_removed + assert_raise(NoMethodError) { create.page_count } + # It's `total_pages` now. + end + private def create(page = 2, limit = 5, total = nil, &block) if block_given? @@ -116,16 +137,4 @@ class ArrayPaginationTest < Test::Unit::TestCase def array(size = 3) Array.new(size) end - - def collect_deprecations - old_behavior = WillPaginate::Deprecation.behavior - deprecations = [] - WillPaginate::Deprecation.behavior = Proc.new do |message, callstack| - deprecations << message - end - result = yield - [result, deprecations] - ensure - WillPaginate::Deprecation.behavior = old_behavior - end end diff --git a/vendor/plugins/will_paginate/test/console b/vendor/plugins/will_paginate/test/console index 53b8de4f..3f282f11 100755 --- a/vendor/plugins/will_paginate/test/console +++ b/vendor/plugins/will_paginate/test/console @@ -1,9 +1,8 @@ #!/usr/bin/env ruby irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' libs = [] -dirname = File.dirname(__FILE__) libs << 'irb/completion' -libs << File.join(dirname, 'lib', 'load_fixtures') +libs << File.join('lib', 'load_fixtures') -exec "#{irb}#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt" +exec "#{irb} -Ilib:test#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt" diff --git a/vendor/plugins/will_paginate/test/finder_test.rb b/vendor/plugins/will_paginate/test/finder_test.rb index b272341b..001cf3a7 100644 --- a/vendor/plugins/will_paginate/test/finder_test.rb +++ b/vendor/plugins/will_paginate/test/finder_test.rb @@ -1,8 +1,9 @@ -require File.dirname(__FILE__) + '/helper' -require File.dirname(__FILE__) + '/lib/activerecord_test_case' +require 'helper' +require 'lib/activerecord_test_case' require 'will_paginate' WillPaginate.enable_activerecord +WillPaginate.enable_named_scope class FinderTest < ActiveRecordTestCase fixtures :topics, :replies, :users, :projects, :developers_projects @@ -12,18 +13,18 @@ class FinderTest < ActiveRecordTestCase end def test_simple_paginate - entries = Topic.paginate :page => nil - assert_equal 1, entries.current_page - assert_nil entries.previous_page - assert_nil entries.next_page - assert_equal 1, entries.page_count - assert_equal 4, entries.size + assert_queries(1) do + entries = Topic.paginate :page => nil + assert_equal 1, entries.current_page + assert_equal 1, entries.total_pages + assert_equal 4, entries.size + end - entries = Topic.paginate :page => 2 - assert_equal 2, entries.current_page - assert_equal 1, entries.previous_page - assert_equal 1, entries.page_count - assert entries.empty? + assert_queries(2) do + entries = Topic.paginate :page => 2 + assert_equal 1, entries.total_pages + assert entries.empty? + end end def test_parameter_api @@ -41,31 +42,31 @@ class FinderTest < ActiveRecordTestCase def test_paginate_with_per_page entries = Topic.paginate :page => 1, :per_page => 1 assert_equal 1, entries.size - assert_equal 4, entries.page_count + assert_equal 4, entries.total_pages # Developer class has explicit per_page at 10 entries = Developer.paginate :page => 1 assert_equal 10, entries.size - assert_equal 2, entries.page_count + assert_equal 2, entries.total_pages entries = Developer.paginate :page => 1, :per_page => 5 assert_equal 11, entries.total_entries assert_equal 5, entries.size - assert_equal 3, entries.page_count + assert_equal 3, entries.total_pages end def test_paginate_with_order entries = Topic.paginate :page => 1, :order => 'created_at desc' expected = [topics(:futurama), topics(:harvey_birdman), topics(:rails), topics(:ar)].reverse assert_equal expected, entries.to_a - assert_equal 1, entries.page_count + assert_equal 1, entries.total_pages end def test_paginate_with_conditions entries = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago] expected = [topics(:rails), topics(:ar)] assert_equal expected, entries.to_a - assert_equal 1, entries.page_count + assert_equal 1, entries.total_pages end def test_paginate_with_include_and_conditions @@ -85,11 +86,14 @@ class FinderTest < ActiveRecordTestCase end def test_paginate_with_include_and_order - entries = Topic.paginate \ - :page => 1, - :include => :replies, - :order => 'replies.created_at asc, topics.created_at asc', - :per_page => 10 + entries = nil + assert_queries(2) do + entries = Topic.paginate \ + :page => 1, + :include => :replies, + :order => 'replies.created_at asc, topics.created_at asc', + :per_page => 10 + end expected = Topic.find :all, :include => 'replies', @@ -104,7 +108,7 @@ class FinderTest < ActiveRecordTestCase entries, project = nil, projects(:active_record) assert_nothing_raised "THIS IS A BUG in Rails 1.2.3 that was fixed in [7326]. " + - "Please upgrade to the 1-2-stable branch or edge Rails." do + "Please upgrade to a newer version of Rails." do entries = project.topics.paginate \ :page => 1, :include => :replies, @@ -125,10 +129,12 @@ class FinderTest < ActiveRecordTestCase expected_name_ordered = [projects(:action_controller), projects(:active_record)] expected_id_ordered = [projects(:active_record), projects(:action_controller)] - # with association-specified order - entries = dhh.projects.paginate(:page => 1) - assert_equal expected_name_ordered, entries - assert_equal 2, entries.total_entries + assert_queries(2) do + # with association-specified order + entries = dhh.projects.paginate(:page => 1) + assert_equal expected_name_ordered, entries + assert_equal 2, entries.total_entries + end # with explicit order entries = dhh.projects.paginate(:page => 1, :order => 'projects.id') @@ -148,29 +154,43 @@ class FinderTest < ActiveRecordTestCase def test_paginate_association_extension project = Project.find(:first) - entries = project.replies.paginate_recent :page => 1 - assert_equal [replies(:brave)], entries + + assert_queries(2) do + entries = project.replies.paginate_recent :page => 1 + assert_equal [replies(:brave)], entries + end end def test_paginate_with_joins - entries = Developer.paginate :page => 1, - :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id', - :conditions => 'project_id = 1' - assert_equal 2, entries.size - developer_names = entries.map { |d| d.name } - assert developer_names.include?('David') - assert developer_names.include?('Jamis') + entries = nil + + assert_queries(1) do + entries = Developer.paginate :page => 1, + :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id', + :conditions => 'project_id = 1' + assert_equal 2, entries.size + developer_names = entries.map &:name + assert developer_names.include?('David') + assert developer_names.include?('Jamis') + end - expected = entries.to_a - entries = Developer.paginate :page => 1, - :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id', - :conditions => 'project_id = 1', :count => { :select => "users.id" } - assert_equal expected, entries.to_a + assert_queries(1) do + expected = entries.to_a + entries = Developer.paginate :page => 1, + :joins => 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id', + :conditions => 'project_id = 1', :count => { :select => "users.id" } + assert_equal expected, entries.to_a + assert_equal 2, entries.total_entries + end end def test_paginate_with_group - entries = Developer.paginate :page => 1, :per_page => 10, - :group => 'salary', :select => 'salary', :order => 'salary' + entries = nil + assert_queries(1) do + entries = Developer.paginate :page => 1, :per_page => 10, + :group => 'salary', :select => 'salary', :order => 'salary' + end + expected = [ users(:david), users(:jamis), users(:dev_10), users(:poor_jamis) ].map(&:salary).sort assert_equal expected, entries.map(&:salary) end @@ -201,6 +221,56 @@ class FinderTest < ActiveRecordTestCase assert_equal 2, entries.total_entries end + ## named_scope ## + + def test_paginate_in_named_scope + entries = Developer.poor.paginate :page => 1, :per_page => 1 + + assert_equal 1, entries.size + assert_equal 2, entries.total_entries + end + + def test_paginate_in_named_scope_on_habtm_association + project = projects(:active_record) + assert_queries(2) do + entries = project.developers.poor.paginate :page => 1, :per_page => 1 + + assert_equal 1, entries.size, 'one developer should be found' + assert_equal 1, entries.total_entries, 'only one developer should be found' + end + end + + def test_paginate_in_named_scope_on_hmt_association + project = projects(:active_record) + expected = [replies(:brave)] + + assert_queries(2) do + entries = project.replies.recent.paginate :page => 1, :per_page => 1 + assert_equal expected, entries + assert_equal 1, entries.total_entries, 'only one reply should be found' + end + end + + def test_paginate_in_named_scope_on_has_many_association + project = projects(:active_record) + expected = [topics(:ar)] + + assert_queries(2) do + entries = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1 + assert_equal expected, entries + assert_equal 1, entries.total_entries, 'only one topic should be found' + end + end + + ## misc ## + + def test_count_and_total_entries_options_are_mutually_exclusive + e = assert_raise ArgumentError do + Developer.paginate :page => 1, :count => {}, :total_entries => 1 + end + assert_match /exclusive/, e.to_s + end + def test_readonly assert_nothing_raised { Developer.paginate :readonly => true, :page => 1 } end @@ -216,13 +286,15 @@ class FinderTest < ActiveRecordTestCase end # Is this Rails 2.0? Find out by testing find_all which was removed in [6998] - unless Developer.respond_to? :find_all + unless ActiveRecord::Base.respond_to? :find_all def test_paginate_array_of_ids # AR finders also accept arrays of IDs # (this was broken in Rails before [6912]) - entries = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id') - assert_equal (4..6).to_a, entries.map(&:id) - assert_equal 8, entries.total_entries + assert_queries(1) do + entries = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id') + assert_equal (4..6).to_a, entries.map(&:id) + assert_equal 8, entries.total_entries + end end end @@ -230,7 +302,7 @@ class FinderTest < ActiveRecordTestCase def test_implicit_all_with_dynamic_finders Topic.expects(:find_all_by_foo).returns([]) Topic.expects(:count).returns(0) - Topic.paginate_by_foo :page => 1 + Topic.paginate_by_foo :page => 2 end def test_guessing_the_total_count @@ -241,6 +313,14 @@ class FinderTest < ActiveRecordTestCase assert_equal 6, entries.total_entries end + def test_guessing_that_there_are_no_records + Topic.expects(:find).returns([]) + Topic.expects(:count).never + + entries = Topic.paginate :page => 1, :per_page => 4 + assert_equal 0, entries.total_entries + end + def test_extra_parameters_stay_untouched Topic.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5)) Topic.expects(:count).with({:foo => 'bar'}).returns(1) @@ -251,13 +331,13 @@ class FinderTest < ActiveRecordTestCase def test_count_skips_select Developer.stubs(:find).returns([]) Developer.expects(:count).with({}).returns(0) - Developer.paginate :select => 'salary', :page => 1 + Developer.paginate :select => 'salary', :page => 2 end def test_count_select_when_distinct Developer.stubs(:find).returns([]) Developer.expects(:count).with(:select => 'DISTINCT salary').returns(0) - Developer.paginate :select => 'DISTINCT salary', :page => 1 + Developer.paginate :select => 'DISTINCT salary', :page => 2 end def test_should_use_scoped_finders_if_present @@ -288,16 +368,16 @@ class FinderTest < ActiveRecordTestCase Developer.expects(:find_by_sql).returns([]) Developer.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0) - entries = Developer.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 1 + Developer.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 2 end # TODO: counts are still wrong def test_ability_to_use_with_custom_finders # acts_as_taggable defines find_tagged_with(tag, options) - Topic.expects(:find_tagged_with).with('will_paginate', :offset => 0, :limit => 5).returns([]) + Topic.expects(:find_tagged_with).with('will_paginate', :offset => 5, :limit => 5).returns([]) Topic.expects(:count).with({}).returns(0) - Topic.paginate_tagged_with 'will_paginate', :page => 1, :per_page => 5 + Topic.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5 end def test_array_argument_doesnt_eliminate_count @@ -310,7 +390,6 @@ class FinderTest < ActiveRecordTestCase def test_paginating_finder_doesnt_mangle_options Developer.expects(:find).returns([]) - Developer.expects(:count).returns(0) options = { :page => 1 } options.expects(:delete).never options_before = options.dup @@ -318,5 +397,38 @@ class FinderTest < ActiveRecordTestCase Developer.paginate(options) assert_equal options, options_before end + + def test_paginated_each + collection = stub('collection', :size => 5, :empty? => false, :per_page => 5) + collection.expects(:each).times(2).returns(collection) + last_collection = stub('collection', :size => 4, :empty? => false, :per_page => 5) + last_collection.expects(:each).returns(last_collection) + + params = { :order => 'id', :total_entries => 0 } + + Developer.expects(:paginate).with(params.merge(:page => 2)).returns(collection) + Developer.expects(:paginate).with(params.merge(:page => 3)).returns(collection) + Developer.expects(:paginate).with(params.merge(:page => 4)).returns(last_collection) + + assert_equal 14, Developer.paginated_each(:page => '2') { } + end + + # detect ActiveRecord 2.1 + if ActiveRecord::Base.private_methods.include?('references_eager_loaded_tables?') + def test_removes_irrelevant_includes_in_count + Developer.expects(:find).returns([1]) + Developer.expects(:count).with({}).returns(0) + + Developer.paginate :page => 1, :per_page => 1, :include => :projects + end + + def test_doesnt_remove_referenced_includes_in_count + Developer.expects(:find).returns([1]) + Developer.expects(:count).with({ :include => :projects, :conditions => 'projects.id > 2' }).returns(0) + + Developer.paginate :page => 1, :per_page => 1, + :include => :projects, :conditions => 'projects.id > 2' + end + end end end diff --git a/vendor/plugins/will_paginate/test/fixtures/developer.rb b/vendor/plugins/will_paginate/test/fixtures/developer.rb index 6650a980..7105355d 100644 --- a/vendor/plugins/will_paginate/test/fixtures/developer.rb +++ b/vendor/plugins/will_paginate/test/fixtures/developer.rb @@ -7,5 +7,7 @@ class Developer < User end end + named_scope :poor, :conditions => ['salary <= ?', 80000], :order => 'salary' + def self.per_page() 10 end end diff --git a/vendor/plugins/will_paginate/test/fixtures/projects.yml b/vendor/plugins/will_paginate/test/fixtures/projects.yml index 02800c78..74f3c32f 100644 --- a/vendor/plugins/will_paginate/test/fixtures/projects.yml +++ b/vendor/plugins/will_paginate/test/fixtures/projects.yml @@ -1,7 +1,6 @@ -action_controller: - id: 2 - name: Active Controller - active_record: id: 1 name: Active Record +action_controller: + id: 2 + name: Active Controller diff --git a/vendor/plugins/will_paginate/test/fixtures/reply.rb b/vendor/plugins/will_paginate/test/fixtures/reply.rb index ea84042b..ecaf3c1f 100644 --- a/vendor/plugins/will_paginate/test/fixtures/reply.rb +++ b/vendor/plugins/will_paginate/test/fixtures/reply.rb @@ -1,5 +1,7 @@ class Reply < ActiveRecord::Base belongs_to :topic, :include => [:replies] + + named_scope :recent, :conditions => ['replies.created_at > ?', 15.minutes.ago] validates_presence_of :content end diff --git a/vendor/plugins/will_paginate/test/fixtures/topic.rb b/vendor/plugins/will_paginate/test/fixtures/topic.rb index 12b8747b..77be0dda 100644 --- a/vendor/plugins/will_paginate/test/fixtures/topic.rb +++ b/vendor/plugins/will_paginate/test/fixtures/topic.rb @@ -1,4 +1,6 @@ class Topic < ActiveRecord::Base has_many :replies, :dependent => :destroy, :order => 'replies.created_at DESC' belongs_to :project + + named_scope :mentions_activerecord, :conditions => ['topics.title LIKE ?', '%ActiveRecord%'] end diff --git a/vendor/plugins/will_paginate/test/helper.rb b/vendor/plugins/will_paginate/test/helper.rb index b8683ce8..ad52b1b6 100644 --- a/vendor/plugins/will_paginate/test/helper.rb +++ b/vendor/plugins/will_paginate/test/helper.rb @@ -4,7 +4,7 @@ require 'rubygems' # gem install redgreen for colored test output begin require 'redgreen'; rescue LoadError; end -require File.join(File.dirname(__FILE__), 'boot') unless defined?(ActiveRecord) +require 'boot' unless defined?(ActiveRecord) class Test::Unit::TestCase protected @@ -13,6 +13,18 @@ class Test::Unit::TestCase [method.to_s, method.to_sym].each { |m| assert_respond_to object, m } end end + + def collect_deprecations + old_behavior = WillPaginate::Deprecation.behavior + deprecations = [] + WillPaginate::Deprecation.behavior = Proc.new do |message, callstack| + deprecations << message + end + result = yield + [result, deprecations] + ensure + WillPaginate::Deprecation.behavior = old_behavior + end end # Wrap tests that use Mocha and skip if unavailable. diff --git a/vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb b/vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb index a39b4a50..8f66ebee 100644 --- a/vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb +++ b/vendor/plugins/will_paginate/test/lib/activerecord_test_case.rb @@ -1,4 +1,4 @@ -require File.join(File.dirname(__FILE__), 'activerecord_test_connector') +require 'lib/activerecord_test_connector' class ActiveRecordTestCase < Test::Unit::TestCase # Set our fixture path @@ -18,6 +18,19 @@ class ActiveRecordTestCase < Test::Unit::TestCase # Default so Test::Unit::TestCase doesn't complain def test_truth end + + protected + + def assert_queries(num = 1) + $query_count = 0 + yield + ensure + assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed." + end + + def assert_no_queries(&block) + assert_queries(0, &block) + end end ActiveRecordTestConnector.setup diff --git a/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb b/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb index 35fde8ea..0decd8a2 100644 --- a/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb +++ b/vendor/plugins/will_paginate/test/lib/activerecord_test_connector.rb @@ -6,6 +6,8 @@ class ActiveRecordTestConnector cattr_accessor :able_to_connect cattr_accessor :connected + FIXTURES_PATH = File.join(File.dirname(__FILE__), '..', 'fixtures') + # Set our defaults self.connected = false self.able_to_connect = true @@ -14,13 +16,12 @@ class ActiveRecordTestConnector unless self.connected || !self.able_to_connect setup_connection load_schema - # require_fixture_models - Dependencies.load_paths.unshift(File.dirname(__FILE__) + "/../fixtures") + Dependencies.load_paths.unshift FIXTURES_PATH self.connected = true end rescue Exception => e # errors from ActiveRecord setup - $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}" - #$stderr.puts " #{e.backtrace.join("\n ")}\n" + $stderr.puts "\nSkipping ActiveRecord tests: #{e}" + $stderr.puts "Install SQLite3 to run the full test suite for will_paginate.\n\n" self.able_to_connect = false end @@ -38,7 +39,7 @@ class ActiveRecordTestConnector ActiveRecord::Base.establish_connection(configuration) ActiveRecord::Base.configurations = { db => configuration } - ActiveRecord::Base.connection + prepare ActiveRecord::Base.connection unless Object.const_defined?(:QUOTED_TYPE) Object.send :const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type') @@ -48,13 +49,21 @@ class ActiveRecordTestConnector def self.load_schema ActiveRecord::Base.silence do ActiveRecord::Migration.verbose = false - load File.dirname(__FILE__) + "/../fixtures/schema.rb" + load File.join(FIXTURES_PATH, 'schema.rb') end end - def self.require_fixture_models - models = Dir.glob(File.dirname(__FILE__) + "/../fixtures/*.rb") - models = (models.grep(/user.rb/) + models).uniq - models.each { |f| require f } + def self.prepare(conn) + class << conn + IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SHOW FIELDS /] + + def execute_with_counting(sql, name = nil, &block) + $query_count ||= 0 + $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r } + execute_without_counting(sql, name, &block) + end + + alias_method_chain :execute, :counting + end end end diff --git a/vendor/plugins/will_paginate/test/lib/html_inner_text.rb b/vendor/plugins/will_paginate/test/lib/html_inner_text.rb deleted file mode 100644 index 7bb62464..00000000 --- a/vendor/plugins/will_paginate/test/lib/html_inner_text.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'action_controller/test_process' - -module HTML - class Node - def inner_text - children.map(&:inner_text).join('') - end - end - - class Text - def inner_text - self.to_s - end - end - - class Tag - def inner_text - childless?? '' : super - end - end -end diff --git a/vendor/plugins/will_paginate/test/lib/load_fixtures.rb b/vendor/plugins/will_paginate/test/lib/load_fixtures.rb index c9f4d1fb..10d6f420 100644 --- a/vendor/plugins/will_paginate/test/lib/load_fixtures.rb +++ b/vendor/plugins/will_paginate/test/lib/load_fixtures.rb @@ -1,13 +1,11 @@ -dirname = File.dirname(__FILE__) -require File.join(dirname, '..', 'boot') -require File.join(dirname, 'activerecord_test_connector') +require 'boot' +require 'lib/activerecord_test_connector' # setup the connection ActiveRecordTestConnector.setup # load all fixtures -fixture_path = File.join(dirname, '..', 'fixtures') -Fixtures.create_fixtures(fixture_path, ActiveRecord::Base.connection.tables) +Fixtures.create_fixtures(ActiveRecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables) require 'will_paginate' WillPaginate.enable_activerecord diff --git a/vendor/plugins/will_paginate/test/lib/view_test_process.rb b/vendor/plugins/will_paginate/test/lib/view_test_process.rb new file mode 100644 index 00000000..e4e79d57 --- /dev/null +++ b/vendor/plugins/will_paginate/test/lib/view_test_process.rb @@ -0,0 +1,165 @@ +require 'action_controller' +require 'action_controller/test_process' + +require 'will_paginate' +WillPaginate.enable_actionpack + +ActionController::Routing::Routes.draw do |map| + map.connect 'dummy/page/:page', :controller => 'dummy' + map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots' + map.connect 'ibocorp/:page', :controller => 'ibocorp', + :requirements => { :page => /\d+/ }, + :defaults => { :page => 1 } + + map.connect ':controller/:action/:id' +end + +ActionController::Base.perform_caching = false + +class WillPaginate::ViewTestCase < Test::Unit::TestCase + def setup + super + @controller = DummyController.new + @request = @controller.request + @html_result = nil + @template = '<%= will_paginate collection, options %>' + + @view = ActionView::Base.new + @view.assigns['controller'] = @controller + @view.assigns['_request'] = @request + @view.assigns['_params'] = @request.params + end + + def test_no_complain; end + + protected + + def paginate(collection = {}, options = {}, &block) + if collection.instance_of? Hash + page_options = { :page => 1, :total_entries => 11, :per_page => 4 }.merge(collection) + collection = [1].paginate(page_options) + end + + locals = { :collection => collection, :options => options } + + if defined? ActionView::InlineTemplate + # Rails 2.1 + args = [ ActionView::InlineTemplate.new(@view, @template, locals) ] + else + # older Rails versions + args = [nil, @template, nil, locals] + end + + @html_result = @view.render_template(*args) + @html_document = HTML::Document.new(@html_result, true, false) + + if block_given? + classname = options[:class] || WillPaginate::ViewHelpers.pagination_options[:class] + assert_select("div.#{classname}", 1, 'no main DIV', &block) + end + end + + def response_from_page_or_rjs + @html_document.root + end + + def validate_page_numbers expected, links, param_name = :page + param_pattern = /\W#{CGI.escape(param_name.to_s)}=([^&]*)/ + + assert_equal(expected, links.map { |e| + e['href'] =~ param_pattern + $1 ? $1.to_i : $1 + }) + end + + def assert_links_match pattern, links = nil, numbers = nil + links ||= assert_select 'div.pagination a[href]' do |elements| + elements + end + + pages = [] if numbers + + links.each do |el| + assert_match pattern, el['href'] + if numbers + el['href'] =~ pattern + pages << ($1.nil?? nil : $1.to_i) + end + end + + assert_equal numbers, pages, "page numbers don't match" if numbers + end + + def assert_no_links_match pattern + assert_select 'div.pagination a[href]' do |elements| + elements.each do |el| + assert_no_match pattern, el['href'] + end + end + end +end + +class DummyRequest + attr_accessor :symbolized_path_parameters + + def initialize + @get = true + @params = {} + @symbolized_path_parameters = { :controller => 'foo', :action => 'bar' } + end + + def get? + @get + end + + def post + @get = false + end + + def relative_url_root + '' + end + + def params(more = nil) + @params.update(more) if more + @params + end +end + +class DummyController + attr_reader :request + attr_accessor :controller_name + + def initialize + @request = DummyRequest.new + @url = ActionController::UrlRewriter.new(@request, @request.params) + end + + def params + @request.params + end + + def url_for(params) + @url.rewrite(params) + end +end + +module HTML + Node.class_eval do + def inner_text + children.map(&:inner_text).join('') + end + end + + Text.class_eval do + def inner_text + self.to_s + end + end + + Tag.class_eval do + def inner_text + childless?? '' : super + end + end +end diff --git a/vendor/plugins/will_paginate/test/pagination_test.rb b/vendor/plugins/will_paginate/test/pagination_test.rb deleted file mode 100644 index f1163912..00000000 --- a/vendor/plugins/will_paginate/test/pagination_test.rb +++ /dev/null @@ -1,272 +0,0 @@ -require File.dirname(__FILE__) + '/helper' -require 'action_controller' -require File.dirname(__FILE__) + '/lib/html_inner_text' - -ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' -end - -ActionController::Base.perform_caching = false - -require 'will_paginate' -WillPaginate.enable_actionpack - -class PaginationTest < Test::Unit::TestCase - - class DevelopersController < ActionController::Base - def list_developers - @options = session[:wp] || {} - - @developers = (1..11).to_a.paginate( - :page => params[@options[:param_name] || :page] || 1, - :per_page => params[:per_page] || 4 - ) - - render :inline => '<%= will_paginate @developers, @options %>' - end - - def guess_collection_name - @developers = session[:wp] - @options = session[:wp_options] - render :inline => '<%= will_paginate @options %>' - end - - protected - def rescue_errors(e) raise e end - def rescue_action(e) raise e end - end - - def setup - @controller = DevelopersController.new - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - super - end - - def test_will_paginate - get :list_developers - - entries = assigns :developers - assert entries - assert_equal 4, entries.size - - assert_select 'div.pagination', 1, 'no main DIV' do |pagination| - assert_select 'a[href]', 3 do |elements| - validate_page_numbers [2,3,2], elements - assert_select elements.last, ':last-child', "Next »" - end - assert_select 'span', 2 - assert_select 'span.disabled:first-child', "« Previous" - assert_select 'span.current', entries.current_page.to_s - assert_equal '« Previous 1 2 3 Next »', pagination.first.inner_text - end - end - - def test_will_paginate_with_options - get :list_developers, { :page => 2 }, :wp => { - :class => 'will_paginate', :prev_label => 'Prev', :next_label => 'Next' - } - assert_response :success - - entries = assigns :developers - assert entries - assert_equal 4, entries.size - - assert_select 'div.will_paginate', 1, 'no main DIV' do - assert_select 'a[href]', 4 do |elements| - validate_page_numbers [1,1,3,3], elements - # test rel attribute values: - assert_select elements[1], 'a', '1' do |link| - assert_equal 'prev start', link.first['rel'] - end - assert_select elements.first, 'a', "Prev" do |link| - assert_equal 'prev start', link.first['rel'] - end - assert_select elements.last, 'a', "Next" do |link| - assert_equal 'next', link.first['rel'] - end - end - assert_select 'span.current', entries.current_page.to_s - end - end - - def test_will_paginate_without_container - get :list_developers, {}, :wp => { :container => false } - assert_select 'div.pagination', 0, 'no main DIV' - assert_select 'a[href]', 3 - end - - def test_will_paginate_without_page_links - get :list_developers, { :page => 2 }, :wp => { :page_links => false } - assert_select 'a[href]', 2 do |elements| - validate_page_numbers [1,3], elements - end - end - - def test_will_paginate_preserves_parameters_on_get - get :list_developers, :foo => { :bar => 'baz' } - assert_links_match /foo%5Bbar%5D=baz/ - end - - def test_will_paginate_doesnt_preserve_parameters_on_post - post :list_developers, :foo => 'bar' - assert_no_links_match /foo=bar/ - end - - def test_adding_additional_parameters - get :list_developers, {}, :wp => { :params => { :foo => 'bar' } } - assert_links_match /foo=bar/ - end - - def test_removing_arbitrary_parameters - get :list_developers, { :foo => 'bar' }, :wp => { :params => { :foo => nil } } - assert_no_links_match /foo=bar/ - end - - def test_adding_additional_route_parameters - get :list_developers, {}, :wp => { :params => { :controller => 'baz' } } - assert_links_match %r{\Wbaz/list_developers\W} - end - - def test_will_paginate_with_custom_page_param - get :list_developers, { :developers_page => 2 }, :wp => { :param_name => :developers_page } - assert_response :success - - entries = assigns :developers - assert entries - assert_equal 4, entries.size - - assert_select 'div.pagination', 1, 'no main DIV' do - assert_select 'a[href]', 4 do |elements| - validate_page_numbers [1,1,3,3], elements, :developers_page - end - assert_select 'span.current', entries.current_page.to_s - end - end - - def test_will_paginate_windows - get :list_developers, { :page => 6, :per_page => 1 }, :wp => { :inner_window => 1 } - assert_response :success - - entries = assigns :developers - assert entries - assert_equal 1, entries.size - - assert_select 'div.pagination', 1, 'no main DIV' do |pagination| - assert_select 'a[href]', 8 do |elements| - validate_page_numbers [5,1,2,5,7,10,11,7], elements - assert_select elements.first, 'a', "« Previous" - assert_select elements.last, 'a', "Next »" - end - assert_select 'span.current', entries.current_page.to_s - assert_equal '« Previous 1 2 ... 5 6 7 ... 10 11 Next »', pagination.first.inner_text - end - end - - def test_will_paginate_eliminates_small_gaps - get :list_developers, { :page => 6, :per_page => 1 }, :wp => { :inner_window => 2 } - assert_response :success - - assert_select 'div.pagination', 1, 'no main DIV' do - assert_select 'a[href]', 12 do |elements| - validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements - end - end - end - - def test_no_pagination - get :list_developers, :per_page => 12 - entries = assigns :developers - assert_equal 1, entries.page_count - assert_equal 11, entries.size - - assert_equal '', @response.body - end - - def test_faulty_input_raises_error - assert_raise WillPaginate::InvalidPage do - get :list_developers, :page => 'foo' - end - end - - uses_mocha 'helper internals' do - def test_collection_name_can_be_guessed - collection = mock - collection.expects(:page_count).returns(1) - get :guess_collection_name, {}, :wp => collection - end - end - - def test_inferred_collection_name_raises_error_when_nil - ex = assert_raise ArgumentError do - get :guess_collection_name, {}, :wp => nil - end - assert ex.message.include?('@developers') - end - - def test_setting_id_for_container - get :list_developers - assert_select 'div.pagination', 1 do |div| - assert_nil div.first['id'] - end - # magic ID - get :list_developers, {}, :wp => { :id => true } - assert_select 'div.pagination', 1 do |div| - assert_equal 'fixnums_pagination', div.first['id'] - end - # explicit ID - get :list_developers, {}, :wp => { :id => 'custom_id' } - assert_select 'div.pagination', 1 do |div| - assert_equal 'custom_id', div.first['id'] - end - end - - if ActionController::Base.respond_to? :rescue_responses - def test_rescue_response_hook_presence - assert_equal :not_found, - DevelopersController.rescue_responses['WillPaginate::InvalidPage'] - end - end - -protected - - def validate_page_numbers expected, links, param_name = :page - param_pattern = /\W#{param_name}=([^&]*)/ - - assert_equal(expected, links.map { |e| - e['href'] =~ param_pattern - $1 ? $1.to_i : $1 - }) - end - - def assert_links_match pattern - assert_select 'div.pagination a[href]' do |elements| - elements.each do |el| - assert_match pattern, el['href'] - end - end - end - - def assert_no_links_match pattern - assert_select 'div.pagination a[href]' do |elements| - elements.each do |el| - assert_no_match pattern, el['href'] - end - end - end -end - -class ViewHelpersTest < Test::Unit::TestCase - include WillPaginate::ViewHelpers - - def test_page_entries_info - arr = ('a'..'z').to_a - collection = arr.paginate :page => 2, :per_page => 5 - assert_equal %{Displaying entries 6 - 10 of 26 in total}, - page_entries_info(collection) - - collection = arr.paginate :page => 7, :per_page => 4 - assert_equal %{Displaying entries 25 - 26 of 26 in total}, - page_entries_info(collection) - end -end diff --git a/vendor/plugins/will_paginate/test/tasks.rake b/vendor/plugins/will_paginate/test/tasks.rake new file mode 100644 index 00000000..4b2de3b8 --- /dev/null +++ b/vendor/plugins/will_paginate/test/tasks.rake @@ -0,0 +1,56 @@ +require 'rake/testtask' + +desc 'Test the will_paginate plugin.' +Rake::TestTask.new(:test) do |t| + t.pattern = 'test/**/*_test.rb' + t.verbose = true + t.libs << 'test' +end + +# I want to specify environment variables at call time +class EnvTestTask < Rake::TestTask + attr_accessor :env + + def ruby(*args) + env.each { |key, value| ENV[key] = value } if env + super + env.keys.each { |key| ENV.delete key } if env + end +end + +for configuration in %w( sqlite3 mysql postgres ) + EnvTestTask.new("test_#{configuration}") do |t| + t.pattern = 'test/finder_test.rb' + t.verbose = true + t.env = { 'DB' => configuration } + t.libs << 'test' + end +end + +task :test_databases => %w(test_mysql test_sqlite3 test_postgres) + +desc %{Test everything on SQLite3, MySQL and PostgreSQL} +task :test_full => %w(test test_mysql test_postgres) + +desc %{Test everything with Rails 1.2.x and 2.0.x gems} +task :test_all do + all = Rake::Task['test_full'] + ENV['RAILS_VERSION'] = '~>1.2.6' + all.invoke + # reset the invoked flag + %w( test_full test test_mysql test_postgres ).each do |name| + Rake::Task[name].instance_variable_set '@already_invoked', false + end + # do it again + ENV['RAILS_VERSION'] = '~>2.0.2' + all.invoke +end + +task :rcov do + excludes = %w( lib/will_paginate/named_scope* + lib/will_paginate/core_ext.rb + lib/will_paginate.rb + rails* ) + + system %[rcov -Itest:lib test/*.rb -x #{excludes.join(',')}] +end diff --git a/vendor/plugins/will_paginate/test/view_test.rb b/vendor/plugins/will_paginate/test/view_test.rb new file mode 100644 index 00000000..b6d1a207 --- /dev/null +++ b/vendor/plugins/will_paginate/test/view_test.rb @@ -0,0 +1,355 @@ +require 'helper' +require 'lib/view_test_process' + +class AdditionalLinkAttributesRenderer < WillPaginate::LinkRenderer + def initialize(link_attributes = nil) + super() + @additional_link_attributes = link_attributes || { :default => 'true' } + end + + def page_link(page, text, attributes = {}) + @template.link_to text, url_for(page), attributes.merge(@additional_link_attributes) + end +end + +class ViewTest < WillPaginate::ViewTestCase + + ## basic pagination ## + + def test_will_paginate + paginate do |pagination| + assert_select 'a[href]', 3 do |elements| + validate_page_numbers [2,3,2], elements + assert_select elements.last, ':last-child', "Next »" + end + assert_select 'span', 2 + assert_select 'span.disabled:first-child', '« Previous' + assert_select 'span.current', '1' + assert_equal '« Previous 1 2 3 Next »', pagination.first.inner_text + end + end + + def test_no_pagination_when_page_count_is_one + paginate :per_page => 30 + assert_equal '', @html_result + end + + def test_will_paginate_with_options + paginate({ :page => 2 }, + :class => 'will_paginate', :prev_label => 'Prev', :next_label => 'Next') do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements + # test rel attribute values: + assert_select elements[1], 'a', '1' do |link| + assert_equal 'prev start', link.first['rel'] + end + assert_select elements.first, 'a', "Prev" do |link| + assert_equal 'prev start', link.first['rel'] + end + assert_select elements.last, 'a', "Next" do |link| + assert_equal 'next', link.first['rel'] + end + end + assert_select 'span.current', '2' + end + end + + def test_will_paginate_using_renderer_class + paginate({}, :renderer => AdditionalLinkAttributesRenderer) do + assert_select 'a[default=true]', 3 + end + end + + def test_will_paginate_using_renderer_instance + renderer = WillPaginate::LinkRenderer.new + renderer.gap_marker = '~~' + + paginate({ :per_page => 2 }, :inner_window => 0, :outer_window => 0, :renderer => renderer) do + assert_select 'span.my-gap', '~~' + end + + renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered') + paginate({}, :renderer => renderer) do + assert_select 'a[title=rendered]', 3 + end + end + + def test_prev_next_links_have_classnames + paginate do |pagination| + assert_select 'span.disabled.prev_page:first-child' + assert_select 'a.next_page[href]:last-child' + end + end + + def test_full_output + paginate + expected = <<-HTML + + HTML + expected.strip!.gsub!(/\s{2,}/, ' ') + + assert_dom_equal expected, @html_result + end + + def test_escaping_of_urls + paginate({:page => 1, :per_page => 1, :total_entries => 2}, + :page_links => false, :params => { :tag => '
    ' }) + + assert_select 'a[href]', 1 do |links| + query = links.first['href'].split('?', 2)[1] + assert_equal %w(page=2 tag=%3Cbr%3E), query.split('&').sort + end + end + + ## advanced options for pagination ## + + def test_will_paginate_without_container + paginate({}, :container => false) + assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t' + assert_select 'a[href]', 3 + end + + def test_will_paginate_without_page_links + paginate({ :page => 2 }, :page_links => false) do + assert_select 'a[href]', 2 do |elements| + validate_page_numbers [1,3], elements + end + end + end + + def test_will_paginate_windows + paginate({ :page => 6, :per_page => 1 }, :inner_window => 1) do |pagination| + assert_select 'a[href]', 8 do |elements| + validate_page_numbers [5,1,2,5,7,10,11,7], elements + assert_select elements.first, 'a', '« Previous' + assert_select elements.last, 'a', 'Next »' + end + assert_select 'span.current', '6' + assert_equal '« Previous 1 2 … 5 6 7 … 10 11 Next »', pagination.first.inner_text + end + end + + def test_will_paginate_eliminates_small_gaps + paginate({ :page => 6, :per_page => 1 }, :inner_window => 2) do + assert_select 'a[href]', 12 do |elements| + validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements + end + end + end + + def test_container_id + paginate do |div| + assert_nil div.first['id'] + end + + # magic ID + paginate({}, :id => true) do |div| + assert_equal 'fixnums_pagination', div.first['id'] + end + + # explicit ID + paginate({}, :id => 'custom_id') do |div| + assert_equal 'custom_id', div.first['id'] + end + end + + ## other helpers ## + + def test_paginated_section + @template = <<-ERB + <% paginated_section collection, options do %> + <%= content_tag :div, '', :id => "developers" %> + <% end %> + ERB + + paginate + assert_select 'div.pagination', 2 + assert_select 'div.pagination + div#developers', 1 + end + + def test_page_entries_info + @template = '<%= page_entries_info collection %>' + array = ('a'..'z').to_a + + paginate array.paginate(:page => 2, :per_page => 5) + assert_equal %{Displaying strings 6 - 10 of 26 in total}, + @html_result + + paginate array.paginate(:page => 7, :per_page => 4) + assert_equal %{Displaying strings 25 - 26 of 26 in total}, + @html_result + end + + def test_page_entries_info_with_longer_class_name + @template = '<%= page_entries_info collection %>' + collection = ('a'..'z').to_a.paginate + collection.first.stubs(:class).returns(mock('class', :name => 'ProjectType')) + + paginate collection + assert @html_result.index('project types'), "expected <#{@html_result.inspect}> to mention 'project types'" + end + + def test_page_entries_info_with_single_page_collection + @template = '<%= page_entries_info collection %>' + + paginate(('a'..'d').to_a.paginate(:page => 1, :per_page => 5)) + assert_equal %{Displaying all 4 strings}, @html_result + + paginate(['a'].paginate(:page => 1, :per_page => 5)) + assert_equal %{Displaying 1 string}, @html_result + + paginate([].paginate(:page => 1, :per_page => 5)) + assert_equal %{No entries found}, @html_result + end + + def test_page_entries_info_with_custom_entry_name + @template = '<%= page_entries_info collection, :entry_name => "author" %>' + + entries = (1..20).to_a + + paginate(entries.paginate(:page => 1, :per_page => 5)) + assert_equal %{Displaying authors 1 - 5 of 20 in total}, @html_result + + paginate(entries.paginate(:page => 1, :per_page => 20)) + assert_equal %{Displaying all 20 authors}, @html_result + + paginate(['a'].paginate(:page => 1, :per_page => 5)) + assert_equal %{Displaying 1 author}, @html_result + + paginate([].paginate(:page => 1, :per_page => 5)) + assert_equal %{No authors found}, @html_result + end + + ## parameter handling in page links ## + + def test_will_paginate_preserves_parameters_on_get + @request.params :foo => { :bar => 'baz' } + paginate + assert_links_match /foo%5Bbar%5D=baz/ + end + + def test_will_paginate_doesnt_preserve_parameters_on_post + @request.post + @request.params :foo => 'bar' + paginate + assert_no_links_match /foo=bar/ + end + + def test_adding_additional_parameters + paginate({}, :params => { :foo => 'bar' }) + assert_links_match /foo=bar/ + end + + def test_adding_anchor_parameter + paginate({}, :params => { :anchor => 'anchor' }) + assert_links_match /#anchor$/ + end + + def test_removing_arbitrary_parameters + @request.params :foo => 'bar' + paginate({}, :params => { :foo => nil }) + assert_no_links_match /foo=bar/ + end + + def test_adding_additional_route_parameters + paginate({}, :params => { :controller => 'baz', :action => 'list' }) + assert_links_match %r{\Wbaz/list\W} + end + + def test_will_paginate_with_custom_page_param + paginate({ :page => 2 }, :param_name => :developers_page) do + assert_select 'a[href]', 4 do |elements| + validate_page_numbers [1,1,3,3], elements, :developers_page + end + end + end + + def test_complex_custom_page_param + @request.params :developers => { :page => 2 } + + paginate({ :page => 2 }, :param_name => 'developers[page]') do + assert_select 'a[href]', 4 do |links| + assert_links_match /\?developers%5Bpage%5D=\d+$/, links + validate_page_numbers [1,1,3,3], links, 'developers[page]' + end + end + end + + def test_custom_routing_page_param + @request.symbolized_path_parameters.update :controller => 'dummy', :action => nil + paginate :per_page => 2 do + assert_select 'a[href]', 6 do |links| + assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2] + end + end + end + + def test_custom_routing_page_param_with_dot_separator + @request.symbolized_path_parameters.update :controller => 'dummy', :action => 'dots' + paginate :per_page => 2 do + assert_select 'a[href]', 6 do |links| + assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2] + end + end + end + + def test_custom_routing_with_first_page_hidden + @request.symbolized_path_parameters.update :controller => 'ibocorp', :action => nil + paginate :page => 2, :per_page => 2 do + assert_select 'a[href]', 7 do |links| + assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5, 6, 3] + end + end + end + + ## internal hardcore stuff ## + + class LegacyCollection < WillPaginate::Collection + alias :page_count :total_pages + undef :total_pages + end + + def test_deprecation_notices_with_page_count + collection = LegacyCollection.new(1, 1, 2) + + assert_deprecated collection.class.name do + paginate collection + end + end + + uses_mocha 'view internals' do + def test_collection_name_can_be_guessed + collection = mock + collection.expects(:total_pages).returns(1) + + @template = '<%= will_paginate options %>' + @controller.controller_name = 'developers' + @view.assigns['developers'] = collection + + paginate(nil) + end + end + + def test_inferred_collection_name_raises_error_when_nil + @template = '<%= will_paginate options %>' + @controller.controller_name = 'developers' + + e = assert_raise ArgumentError do + paginate(nil) + end + assert e.message.include?('@developers') + end + + if ActionController::Base.respond_to? :rescue_responses + # only on Rails 2 + def test_rescue_response_hook_presence + assert_equal :not_found, + ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] + end + end + +end diff --git a/vendor/plugins/will_paginate/will_paginate.gemspec b/vendor/plugins/will_paginate/will_paginate.gemspec new file mode 100644 index 00000000..92e00978 --- /dev/null +++ b/vendor/plugins/will_paginate/will_paginate.gemspec @@ -0,0 +1,21 @@ +Gem::Specification.new do |s| + s.name = 'will_paginate' + s.version = '2.3.2' + s.date = '2008-05-16' + + s.summary = "Most awesome pagination solution for Rails" + s.description = "The will_paginate library provides a simple, yet powerful and extensible API for ActiveRecord pagination and rendering of pagination links in ActionView templates." + + s.authors = ['Mislav Marohnić', 'PJ Hyett'] + s.email = 'mislav.marohnic@gmail.com' + s.homepage = 'http://github.com/mislav/will_paginate/wikis' + + s.has_rdoc = true + s.rdoc_options = ['--main', 'README.rdoc'] + s.rdoc_options << '--inline-source' << '--charset=UTF-8' + s.extra_rdoc_files = ['README.rdoc', 'LICENSE', 'CHANGELOG'] + s.add_dependency 'activesupport', ['>= 1.4.4'] + + s.files = %w(CHANGELOG LICENSE README.rdoc Rakefile examples examples/apple-circle.gif examples/index.haml examples/index.html examples/pagination.css examples/pagination.sass init.rb lib lib/will_paginate lib/will_paginate.rb lib/will_paginate/array.rb lib/will_paginate/collection.rb lib/will_paginate/core_ext.rb lib/will_paginate/finder.rb lib/will_paginate/named_scope.rb lib/will_paginate/named_scope_patch.rb lib/will_paginate/version.rb lib/will_paginate/view_helpers.rb test test/boot.rb test/collection_test.rb test/console test/database.yml test/finder_test.rb test/fixtures test/fixtures/admin.rb test/fixtures/developer.rb test/fixtures/developers_projects.yml test/fixtures/project.rb test/fixtures/projects.yml test/fixtures/replies.yml test/fixtures/reply.rb test/fixtures/schema.rb test/fixtures/topic.rb test/fixtures/topics.yml test/fixtures/user.rb test/fixtures/users.yml test/helper.rb test/lib test/lib/activerecord_test_case.rb test/lib/activerecord_test_connector.rb test/lib/load_fixtures.rb test/lib/view_test_process.rb test/tasks.rake test/view_test.rb) + s.test_files = %w(test/boot.rb test/collection_test.rb test/console test/database.yml test/finder_test.rb test/fixtures test/fixtures/admin.rb test/fixtures/developer.rb test/fixtures/developers_projects.yml test/fixtures/project.rb test/fixtures/projects.yml test/fixtures/replies.yml test/fixtures/reply.rb test/fixtures/schema.rb test/fixtures/topic.rb test/fixtures/topics.yml test/fixtures/user.rb test/fixtures/users.yml test/helper.rb test/lib test/lib/activerecord_test_case.rb test/lib/activerecord_test_connector.rb test/lib/load_fixtures.rb test/lib/view_test_process.rb test/tasks.rake test/view_test.rb) +end diff --git a/vendor/rails/.gitignore b/vendor/rails/.gitignore new file mode 100644 index 00000000..8c5dfb64 --- /dev/null +++ b/vendor/rails/.gitignore @@ -0,0 +1,14 @@ +debug.log +activeresource/doc +activerecord/doc +actionpack/doc +actionmailer/doc +activesupport/doc +railties/doc +activeresource/pkg +activerecord/pkg +actionpack/pkg +actionmailer/pkg +activesupport/pkg +railties/pkg +*.rbc diff --git a/vendor/rails/Rakefile b/vendor/rails/Rakefile index 30537755..11d205a7 100644 --- a/vendor/rails/Rakefile +++ b/vendor/rails/Rakefile @@ -15,7 +15,7 @@ task :default => :test desc "Run #{task_name} task for all projects" task task_name do PROJECTS.each do |project| - system %(cd #{project} && #{env} rake #{task_name}) + system %(cd #{project} && #{env} #{$0} #{task_name}) end end end diff --git a/vendor/rails/actionmailer/CHANGELOG b/vendor/rails/actionmailer/CHANGELOG index 8ba3ccde..bdae0d4d 100644 --- a/vendor/rails/actionmailer/CHANGELOG +++ b/vendor/rails/actionmailer/CHANGELOG @@ -1,3 +1,14 @@ +*2.1.0 (May 31st, 2008)* + +* Fixed that a return-path header would be ignored #7572 [joost] + +* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 [iaddict, Tarmo Tänav] + +* Updated TMail to version 1.2.1 [raasdnil] + +* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick] + + *2.0.2* (December 16th, 2007) * Included in Rails 2.0.2 diff --git a/vendor/rails/actionmailer/MIT-LICENSE b/vendor/rails/actionmailer/MIT-LICENSE index 007cc942..13c90d46 100644 --- a/vendor/rails/actionmailer/MIT-LICENSE +++ b/vendor/rails/actionmailer/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2007 David Heinemeier Hansson +Copyright (c) 2004-2008 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/vendor/rails/actionmailer/README b/vendor/rails/actionmailer/README index 67e8266e..0e16ea6e 100755 --- a/vendor/rails/actionmailer/README +++ b/vendor/rails/actionmailer/README @@ -19,8 +19,7 @@ are all set up this way. An example of such a method: recipients recipient subject "[Signed up] Welcome #{recipient}" from "system@loudthinking.com" - - body(:recipient => recipient) + body :recipient => recipient end The body of the email is created by using an Action View template (regular @@ -78,21 +77,26 @@ Example: end end -This Mailman can be the target for Postfix. In Rails, you would use the runner like this: +This Mailman can be the target for Postfix or other MTAs. In Rails, you would use the runner in the +trivial case like this: ./script/runner 'Mailman.receive(STDIN.read)' +However, invoking Rails in the runner for each mail to be received is very resource intensive. A single +instance of Rails should be run within a daemon if it is going to be utilized to process more than just +a limited number of email. + == Configuration The Base class has the full list of configuration options. Here's an example: -ActionMailer::Base.smtp_settings = { - :address=>'smtp.yourserver.com', # default: localhost - :port=>'25', # default: 25 - :user_name=>'user', - :password=>'pass', - :authentication=>:plain # :plain, :login or :cram_md5 -} + ActionMailer::Base.smtp_settings = { + :address => 'smtp.yourserver.com', # default: localhost + :port => '25', # default: 25 + :user_name => 'user', + :password => 'pass', + :authentication => :plain # :plain, :login or :cram_md5 + } == Dependencies diff --git a/vendor/rails/actionmailer/Rakefile b/vendor/rails/actionmailer/Rakefile index 4622871a..c09526cb 100755 --- a/vendor/rails/actionmailer/Rakefile +++ b/vendor/rails/actionmailer/Rakefile @@ -4,7 +4,7 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require 'rake/contrib/rubyforgepublisher' +require 'rake/contrib/sshpublisher' require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version') PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' @@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "actionmailer" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 2.0.2' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.1.0' + PKG_BUILD) s.has_rdoc = true s.requirements << 'none' @@ -87,6 +87,7 @@ end desc "Publish the release files to RubyForge." task :release => [ :package ] do require 'rubyforge' + require 'rake/contrib/rubyforgepublisher' packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" } diff --git a/vendor/rails/actionmailer/lib/action_mailer.rb b/vendor/rails/actionmailer/lib/action_mailer.rb index ec803f5a..2e324d46 100755 --- a/vendor/rails/actionmailer/lib/action_mailer.rb +++ b/vendor/rails/actionmailer/lib/action_mailer.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb b/vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb index cfd72301..e77029af 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb @@ -16,7 +16,7 @@ module ActionMailer define_method(name) do |*parameters| raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1 if parameters.empty? - if instance_variables.include?(ivar) + if instance_variable_names.include?(ivar) instance_variable_get(ivar) end else diff --git a/vendor/rails/actionmailer/lib/action_mailer/base.rb b/vendor/rails/actionmailer/lib/action_mailer/base.rb index b15c010e..e0651321 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/base.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/base.rb @@ -5,17 +5,17 @@ require 'action_mailer/utils' require 'tmail/net' module ActionMailer #:nodoc: - # ActionMailer allows you to send email from your application using a mailer model and views. + # Action Mailer allows you to send email from your application using a mailer model and views. # # # = Mailer Models # - # To use ActionMailer, you need to create a mailer model. - # + # To use Action Mailer, you need to create a mailer model. + # # $ script/generate mailer Notifier # - # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then - # used to set variables to be used in the mail template, to change options on the mail, or + # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then + # used to set variables to be used in the mail template, to change options on the mail, or # to add attachments. # # Examples: @@ -35,25 +35,30 @@ module ActionMailer #:nodoc: # * subject - The subject of your email. Sets the Subject: header. # * from - Who the email you are sending is from. Sets the From: header. # * cc - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the Cc: header. - # * bcc - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the Bcc header. + # * bcc - Takes one or more email addresses. These addresses will receive a blind carbon copy of your email. Sets the Bcc: header. + # * reply_to - Takes one or more email addresses. These addresses will be listed as the default recipients when replying to your email. Sets the Reply-To: header. # * sent_on - The date on which the message was sent. If not set, the header wil be set by the delivery agent. # * content_type - Specify the content type of the message. Defaults to text/plain. # * headers - Specify additional headers to be set for the message, e.g. headers 'X-Mail-Count' => 107370. # + # When a headers 'return-path' is specified, that value will be used as the 'envelope from' + # address. Setting this is useful when you want delivery notifications sent to a different address than + # the one in from. + # # The body method has special behavior. It takes a hash which generates an instance variable # named after each key in the hash containing the value that that key points to. # - # So, for example, body "account" => recipient would result - # in an instance variable @account with the value of recipient being accessible in the + # So, for example, body :account => recipient would result + # in an instance variable @account with the value of recipient being accessible in the # view. # # # = Mailer views # - # Like ActionController, each mailer class has a corresponding view directory + # Like Action Controller, each mailer class has a corresponding view directory # in which each method of the class looks for a template with its name. # To define a template to be used with a mailing, create an .erb file with the same name as the method - # in your mailer model. For example, in the mailer defined above, the template at + # in your mailer model. For example, in the mailer defined above, the template at # app/views/notifier/signup_notification.erb would be used to generate the email. # # Variables defined in the model are accessible as instance variables in the view. @@ -67,33 +72,48 @@ module ActionMailer #:nodoc: # # You got a new note! # <%= truncate(note.body, 25) %> - # # - # = Generating URLs for mailer views # - # If your view includes URLs from the application, you need to use url_for in the mailing method instead of the view. - # Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request. That's - # why you need to jump this little hoop and supply all the details needed for the URL. Example: + # = Generating URLs # - # def signup_notification(recipient) - # recipients recipient.email_address_with_name - # from "system@example.com" - # subject "New account information" - # body :account => recipient, - # :home_page => url_for(:host => "example.com", :controller => "welcome", :action => "greeting") - # end + # URLs can be generated in mailer views using url_for or named routes. + # Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request, + # so you'll need to provide all of the details needed to generate a URL. # - # You can now access @home_page in the template and get http://example.com/welcome/greeting. + # When using url_for you'll need to provide the :host, :controller, and :action: + # + # <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %> + # + # When using named routes you only need to supply the :host: + # + # <%= users_url(:host => "example.com") %> + # + # You will want to avoid using the name_of_route_path form of named routes because it doesn't make sense to + # generate relative URLs in email messages. + # + # It is also possible to set a default host that will be used in all mailers by setting the :host option in + # the ActionMailer::Base.default_url_options hash as follows: + # + # ActionMailer::Base.default_url_options[:host] = "example.com" + # + # This can also be set as a configuration option in config/environment.rb: + # + # config.action_mailer.default_url_options = { :host => "example.com" } + # + # If you do decide to set a default :host for your mailers you will want to use the + # :only_path => false option when using url_for. This will ensure that absolute URLs are generated because + # the url_for view helper will, by default, generate relative URLs when a :host option isn't + # explicitly provided. # # = Sending mail # - # Once a mailer action and template are defined, you can deliver your message or create it and save it + # Once a mailer action and template are defined, you can deliver your message or create it and save it # for delivery later: # # Notifier.deliver_signup_notification(david) # sends the email # mail = Notifier.create_signup_notification(david) # => a tmail object # Notifier.deliver(mail) - # + # # You never instantiate your mailer class. Rather, your delivery instance # methods are automatically wrapped in class methods that start with the word # deliver_ followed by the name of the mailer method that you would @@ -108,13 +128,13 @@ module ActionMailer #:nodoc: # # class MyMailer < ActionMailer::Base # def signup_notification(recipient) - # recipients recipient.email_address_with_name - # subject "New account information" - # body "account" => recipient - # from "system@example.com" - # content_type "text/html" # Here's where the magic happens + # recipients recipient.email_address_with_name + # subject "New account information" + # from "system@example.com" + # body :account => recipient + # content_type "text/html" # end - # end + # end # # # = Multipart email @@ -126,6 +146,7 @@ module ActionMailer #:nodoc: # recipients recipient.email_address_with_name # subject "New account information" # from "system@example.com" + # content_type "multipart/alternative" # # part :content_type => "text/html", # :body => render_message("signup-as-html", :account => recipient) @@ -136,21 +157,26 @@ module ActionMailer #:nodoc: # end # end # end - # - # Multipart messages can also be used implicitly because ActionMailer will automatically + # + # Multipart messages can also be used implicitly because Action Mailer will automatically # detect and use multipart templates, where each template is named after the name of the action, followed # by the content type. Each such detected template will be added as separate part to the message. - # + # # For example, if the following templates existed: # * signup_notification.text.plain.erb # * signup_notification.text.html.erb # * signup_notification.text.xml.builder # * signup_notification.text.x-yaml.erb - # - # Each would be rendered and added as a separate part to the message, - # with the corresponding content type. The same body hash is passed to - # each template. # + # Each would be rendered and added as a separate part to the message, + # with the corresponding content type. The content type for the entire + # message is automatically set to multipart/alternative, which indicates + # that the email contains multiple different representations of the same email + # body. The same body hash is passed to each template. + # + # Implicit template rendering is not performed if any attachments or parts have been added to the email. + # This means that you'll have to manually add each part to the email and set the content type of the email + # to multipart/alternative. # # = Attachments # @@ -172,51 +198,52 @@ module ActionMailer #:nodoc: # a.body = generate_your_pdf_here() # end # end - # end + # end # # # = Configuration options # # These options are specified on the class level, like ActionMailer::Base.template_root = "/my/templates" # - # * template_root - template root determines the base from which template references will be made. + # * template_root - Determines the base from which template references will be made. # # * logger - the logger is used for generating information on the mailing run if available. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. # - # * smtp_settings - Allows detailed configuration for :smtp delivery method: - # * :address Allows you to use a remote mail server. Just change it from its default "localhost" setting. - # * :port On the off chance that your mail server doesn't run on port 25, you can change it. - # * :domain If you need to specify a HELO domain, you can do it here. - # * :user_name If your mail server requires authentication, set the username in this setting. - # * :password If your mail server requires authentication, set the password in this setting. - # * :authentication If your mail server requires authentication, you need to specify the authentication type here. - # This is a symbol and one of :plain, :login, :cram_md5 + # * smtp_settings - Allows detailed configuration for :smtp delivery method: + # * :address - Allows you to use a remote mail server. Just change it from its default "localhost" setting. + # * :port - On the off chance that your mail server doesn't run on port 25, you can change it. + # * :domain - If you need to specify a HELO domain, you can do it here. + # * :user_name - If your mail server requires authentication, set the username in this setting. + # * :password - If your mail server requires authentication, set the password in this setting. + # * :authentication - If your mail server requires authentication, you need to specify the authentication type here. + # This is a symbol and one of :plain, :login, :cram_md5. # - # * sendmail_settings - Allows you to override options for the :sendmail delivery method - # * :location The location of the sendmail executable, defaults to "/usr/sbin/sendmail" - # * :arguments The command line arguments - # * raise_delivery_errors - whether or not errors should be raised if the email fails to be delivered. + # * sendmail_settings - Allows you to override options for the :sendmail delivery method. + # * :location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail. + # * :arguments - The command line arguments. Defaults to -i -t. # - # * delivery_method - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test. + # * raise_delivery_errors - Whether or not errors should be raised if the email fails to be delivered. # - # * perform_deliveries - Determines whether deliver_* methods are actually carried out. By default they are, + # * delivery_method - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test. + # + # * perform_deliveries - Determines whether deliver_* methods are actually carried out. By default they are, # but this can be turned off to help functional testing. # - # * deliveries - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful + # * deliveries - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful # for unit and functional testing. # # * default_charset - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also - # pick a different charset from inside a method with @charset. + # pick a different charset from inside a method with +charset+. # * default_content_type - The default content type used for the main part of the message. Defaults to "text/plain". You - # can also pick a different content type from inside a method with @content_type. - # * default_mime_version - The default mime version used for the message. Defaults to "1.0". You - # can also pick a different value from inside a method with @mime_version. + # can also pick a different content type from inside a method with +content_type+. + # * default_mime_version - The default mime version used for the message. Defaults to 1.0. You + # can also pick a different value from inside a method with +mime_version+. # * default_implicit_parts_order - When a message is built implicitly (i.e. multiple parts are assembled from templates # which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to - # ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client + # ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client # and appear last in the mime encoded message. You can also pick a different order from inside a method with - # @implicit_parts_order. + # +implicit_parts_order+. class Base include AdvAttrAccessor, PartContainer include ActionController::UrlWriter if Object.const_defined?(:ActionController) @@ -229,16 +256,16 @@ module ActionMailer #:nodoc: cattr_accessor :template_extensions @@template_extensions = ['erb', 'builder', 'rhtml', 'rxml'] - @@smtp_settings = { - :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, + @@smtp_settings = { + :address => "localhost", + :port => 25, + :domain => 'localhost.localdomain', + :user_name => nil, + :password => nil, :authentication => nil } cattr_accessor :smtp_settings - + @@sendmail_settings = { :location => '/usr/sbin/sendmail', :arguments => '-i -t' @@ -250,10 +277,10 @@ module ActionMailer #:nodoc: superclass_delegating_accessor :delivery_method self.delivery_method = :smtp - + @@perform_deliveries = true cattr_accessor :perform_deliveries - + @@deliveries = [] cattr_accessor :deliveries @@ -262,7 +289,7 @@ module ActionMailer #:nodoc: @@default_content_type = "text/plain" cattr_accessor :default_content_type - + @@default_mime_version = "1.0" cattr_accessor :default_mime_version @@ -271,47 +298,51 @@ module ActionMailer #:nodoc: # Specify the BCC addresses for the message adv_attr_accessor :bcc - + # Define the body of the message. This is either a Hash (in which case it # specifies the variables to pass to the template when it is rendered), # or a string, in which case it specifies the actual text of the message. adv_attr_accessor :body - + # Specify the CC addresses for the message. adv_attr_accessor :cc - + # Specify the charset to use for the message. This defaults to the # +default_charset+ specified for ActionMailer::Base. adv_attr_accessor :charset - + # Specify the content type for the message. This defaults to text/plain # in most cases, but can be automatically set in some situations. adv_attr_accessor :content_type - + # Specify the from address for the message. adv_attr_accessor :from - + + # Specify the address (if different than the "from" address) to direct + # replies to this message. + adv_attr_accessor :reply_to + # Specify additional headers to be added to the message. adv_attr_accessor :headers - + # Specify the order in which parts should be sorted, based on content-type. # This defaults to the value for the +default_implicit_parts_order+. adv_attr_accessor :implicit_parts_order - + # Defaults to "1.0", but may be explicitly given if needed. adv_attr_accessor :mime_version - + # The recipient addresses for the message, either as a string (for a single # address) or an array (for multiple addresses). adv_attr_accessor :recipients - + # The date on which the message was sent. If not set (the default), the # header will be set by the delivery agent. adv_attr_accessor :sent_on - + # Specify the subject of the message. adv_attr_accessor :subject - + # Specify the template name to use for current message. This is the "base" # template name, without the extension or directory, and may be used to # have multiple mailer methods share the same template. @@ -327,7 +358,7 @@ module ActionMailer #:nodoc: self.class.mailer_name end end - + def mailer_name=(value) self.class.mailer_name = value end @@ -357,8 +388,8 @@ module ActionMailer #:nodoc: # Receives a raw email, parses it into an email object, decodes it, # instantiates a new mailer, and passes the email object to the mailer - # object's #receive method. If you want your mailer to be able to - # process incoming messages, you'll need to implement a #receive + # object's +receive+ method. If you want your mailer to be able to + # process incoming messages, you'll need to implement a +receive+ # method that accepts the email object as a parameter: # # class MyMailer < ActionMailer::Base @@ -387,12 +418,17 @@ module ActionMailer #:nodoc: # templating language other than rhtml or rxml are supported. # To use this, include in your template-language plugin's init # code or on a per-application basis, this can be invoked from - # config/environment.rb: + # config/environment.rb: # # ActionMailer::Base.register_template_extension('haml') def register_template_extension(extension) template_extensions << extension end + + def template_root=(root) + write_inheritable_attribute(:template_root, root) + ActionView::TemplateFinder.process_view_paths(root) + end end # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer @@ -400,7 +436,7 @@ module ActionMailer #:nodoc: # remain uninitialized (useful when you only need to invoke the "receive" # method, for instance). def initialize(method_name=nil, *parameters) #:nodoc: - create!(method_name, *parameters) if method_name + create!(method_name, *parameters) if method_name end # Initialize the mailer via the given +method_name+. The body will be @@ -459,11 +495,14 @@ module ActionMailer #:nodoc: end # Delivers a TMail::Mail object. By default, it delivers the cached mail - # object (from the #create! method). If no cached mail object exists, and + # object (from the create! method). If no cached mail object exists, and # no alternate has been given as the parameter, this will fail. def deliver!(mail = @mail) raise "no mail object available for delivery!" unless mail - logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil? + unless logger.nil? + logger.info "Sent mail to #{Array(recipients).join(', ')}" + logger.debug "\n#{mail.encoded}" + end begin __send__("perform_delivery_#{delivery_method}", mail) if perform_deliveries @@ -483,7 +522,7 @@ module ActionMailer #:nodoc: @content_type ||= @@default_content_type.dup @implicit_parts_order ||= @@default_implicit_parts_order.dup @template ||= method_name - @mailer_name ||= Inflector.underscore(self.class.name) + @mailer_name ||= self.class.name.underscore @parts ||= [] @headers ||= {} @body ||= {} @@ -542,13 +581,14 @@ module ActionMailer #:nodoc: def create_mail m = TMail::Mail.new - m.subject, = quote_any_if_necessary(charset, subject) - m.to, m.from = quote_any_address_if_necessary(charset, recipients, from) - m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil? - m.cc = quote_address_if_necessary(cc, charset) unless cc.nil? - + m.subject, = quote_any_if_necessary(charset, subject) + m.to, m.from = quote_any_address_if_necessary(charset, recipients, from) + m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil? + m.cc = quote_address_if_necessary(cc, charset) unless cc.nil? + m.reply_to = quote_address_if_necessary(reply_to, charset) unless reply_to.nil? m.mime_version = mime_version unless mime_version.nil? - m.date = sent_on.to_time rescue sent_on if sent_on + m.date = sent_on.to_time rescue sent_on if sent_on + headers.each { |k, v| m[k] = v } real_content_type, ctype_attrs = parse_content_type @@ -569,7 +609,7 @@ module ActionMailer #:nodoc: part = (TMail::Mail === p ? p : p.to_mail(self)) m.parts << part end - + if real_content_type =~ /multipart/ ctype_attrs.delete "charset" m.set_content_type(real_content_type, nil, ctype_attrs) @@ -582,15 +622,18 @@ module ActionMailer #:nodoc: def perform_delivery_smtp(mail) destinations = mail.destinations mail.ready_to_send + sender = mail['return-path'] || mail.from - Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain], + Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password], smtp_settings[:authentication]) do |smtp| - smtp.sendmail(mail.encoded, mail.from, destinations) + smtp.sendmail(mail.encoded, sender, destinations) end end def perform_delivery_sendmail(mail) - IO.popen("#{sendmail_settings[:location]} #{sendmail_settings[:arguments]}","w+") do |sm| + sendmail_args = sendmail_settings[:arguments] + sendmail_args += " -f \"#{mail['return-path']}\"" if mail['return-path'] + IO.popen("#{sendmail_settings[:location]} #{sendmail_args}","w+") do |sm| sm.print(mail.encoded.gsub(/\r/, '')) sm.flush end diff --git a/vendor/rails/actionmailer/lib/action_mailer/helpers.rb b/vendor/rails/actionmailer/lib/action_mailer/helpers.rb index 7777d16d..9c5fcc6a 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/helpers.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/helpers.rb @@ -22,7 +22,7 @@ module ActionMailer module ClassMethods # Makes all the (instance) methods in the helper module available to templates rendered through this controller. - # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules + # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules # available to the templates. def add_template_helper(helper_module) #:nodoc: master_helper_module.module_eval "include #{helper_module}" @@ -34,7 +34,7 @@ module ActionMailer # helper FooHelper # includes FooHelper in the template class. # helper { def foo() "#{bar} is the very best" end } - # evaluates the block in the template class, adding method #foo. + # evaluates the block in the template class, adding method +foo+. # helper(:three, BlindHelper) { def mice() 'mice' end } # does all three. def helper(*args, &block) @@ -45,7 +45,7 @@ module ActionMailer when String, Symbol file_name = arg.to_s.underscore + '_helper' class_name = file_name.camelize - + begin require_dependency(file_name) rescue LoadError => load_error @@ -87,17 +87,17 @@ module ActionMailer attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } end - private + private def inherited_with_helper(child) inherited_without_helper(child) begin child.master_helper_module = Module.new child.master_helper_module.send! :include, master_helper_module - child.helper child.name.underscore + child.helper child.name.to_s.underscore rescue MissingSourceFile => e - raise unless e.is_missing?("helpers/#{child.name.underscore}_helper") + raise unless e.is_missing?("helpers/#{child.name.to_s.underscore}_helper") end - end + end end private diff --git a/vendor/rails/actionmailer/lib/action_mailer/part.rb b/vendor/rails/actionmailer/lib/action_mailer/part.rb index de1b1689..2dabf15f 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/part.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/part.rb @@ -5,7 +5,7 @@ require 'action_mailer/utils' module ActionMailer # Represents a subpart of an email message. It shares many similar # attributes of ActionMailer::Base. Although you can create parts manually - # and add them to the #parts list of the mailer, it is easier + # and add them to the +parts+ list of the mailer, it is easier # to use the helper methods in ActionMailer::PartContainer. class Part include ActionMailer::AdvAttrAccessor @@ -13,7 +13,7 @@ module ActionMailer # Represents the body of the part, as a string. This should not be a # Hash (like ActionMailer::Base), but if you want a template to be rendered - # into the body of a subpart you can do it with the mailer's #render method + # into the body of a subpart you can do it with the mailer's +render+ method # and assign the result here. adv_attr_accessor :body diff --git a/vendor/rails/actionmailer/lib/action_mailer/quoting.rb b/vendor/rails/actionmailer/lib/action_mailer/quoting.rb index d6e04e4d..a2f2c707 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/quoting.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/quoting.rb @@ -24,6 +24,8 @@ module ActionMailer # Quote the given text if it contains any "illegal" characters def quote_if_necessary(text, charset) + text = text.dup.force_encoding(Encoding::ASCII_8BIT) if text.respond_to?(:force_encoding) + (text =~ CHARS_NEEDING_QUOTING) ? quoted_printable(text, charset) : text @@ -38,7 +40,7 @@ module ActionMailer # regular email address, or it can be a phrase followed by an address in # brackets. The phrase is the only part that will be quoted, and only if # it needs to be. This allows extended characters to be used in the - # "to", "from", "cc", and "bcc" headers. + # "to", "from", "cc", "bcc" and "reply-to" headers. def quote_address_if_necessary(address, charset) if Array === address address.map { |a| quote_address_if_necessary(a, charset) } diff --git a/vendor/rails/actionmailer/lib/action_mailer/test_case.rb b/vendor/rails/actionmailer/lib/action_mailer/test_case.rb index cc75b392..d474afe3 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/test_case.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/test_case.rb @@ -8,11 +8,13 @@ module ActionMailer "test case definition" end end - # New Test Super class for forward compatibility. - # To override + class TestCase < ActiveSupport::TestCase include ActionMailer::Quoting + setup :initialize_test_deliveries + setup :set_expected_mail + class << self def tests(mailer) write_inheritable_attribute(:mailer_class, mailer) @@ -33,15 +35,18 @@ module ActionMailer end end - def setup - ActionMailer::Base.delivery_method = :test - ActionMailer::Base.perform_deliveries = true - ActionMailer::Base.deliveries = [] + protected + def initialize_test_deliveries + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + end - @expected = TMail::Mail.new - @expected.set_content_type "text", "plain", { "charset" => charset } - @expected.mime_version = '1.0' - end + def set_expected_mail + @expected = TMail::Mail.new + @expected.set_content_type "text", "plain", { "charset" => charset } + @expected.mime_version = '1.0' + end private def charset diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor.rb index 0ad7386f..7a20e9bd 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor.rb @@ -2,9 +2,9 @@ require 'rubygems' begin - gem 'tmail', '~> 1.1.0' + gem 'tmail', '~> 1.2.3' rescue Gem::LoadError - $:.unshift "#{File.dirname(__FILE__)}/vendor/tmail-1.1.0" + $:.unshift "#{File.dirname(__FILE__)}/vendor/tmail-1.2.3" end begin diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/Makefile b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/Makefile deleted file mode 100644 index 346353b8..00000000 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# -# lib/tmail/Makefile -# - -debug: - rm -f parser.rb - make parser.rb DEBUG=true - -parser.rb: parser.y - if [ "$(DEBUG)" = true ]; then \ - racc -v -g -o$@ parser.y ;\ - else \ - racc -E -o$@ parser.y ;\ - fi - -clean: - rm -f parser.rb parser.output - -distclean: clean diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/address.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/address.rb deleted file mode 100755 index 224ed709..00000000 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/address.rb +++ /dev/null @@ -1,245 +0,0 @@ -=begin rdoc - -= Address handling class - -=end -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/encode' -require 'tmail/parser' - - -module TMail - - class Address - - include TextUtils - - def Address.parse( str ) - Parser.parse :ADDRESS, str - end - - def address_group? - false - end - - def initialize( local, domain ) - if domain - domain.each do |s| - raise SyntaxError, 'empty word in domain' if s.empty? - end - end - - @local = local - @domain = domain - @name = nil - @routes = [] - end - - attr_reader :name - - def name=( str ) - @name = str - @name = nil if str and str.empty? - end - - alias phrase name - alias phrase= name= - - attr_reader :routes - - def inspect - "#<#{self.class} #{address()}>" - end - - def local - return nil unless @local - return '""' if @local.size == 1 and @local[0].empty? - @local.map {|i| quote_atom(i) }.join('.') - end - - def domain - return nil unless @domain - join_domain(@domain) - end - - def spec - s = self.local - d = self.domain - if s and d - s + '@' + d - else - s - end - end - - alias address spec - - def ==( other ) - other.respond_to? :spec and self.spec == other.spec - end - - alias eql? == - - def hash - @local.hash ^ @domain.hash - end - - def dup - obj = self.class.new(@local.dup, @domain.dup) - obj.name = @name.dup if @name - obj.routes.replace @routes - obj - end - - include StrategyInterface - - def accept( strategy, dummy1 = nil, dummy2 = nil ) - unless @local - strategy.meta '<>' # empty return-path - return - end - - spec_p = (not @name and @routes.empty?) - if @name - strategy.phrase @name - strategy.space - end - tmp = spec_p ? '' : '<' - unless @routes.empty? - tmp << @routes.map {|i| '@' + i }.join(',') << ':' - end - tmp << self.spec - tmp << '>' unless spec_p - strategy.meta tmp - strategy.lwsp '' - end - - end - - - class AddressGroup - - include Enumerable - - def address_group? - true - end - - def initialize( name, addrs ) - @name = name - @addresses = addrs - end - - attr_reader :name - - def ==( other ) - other.respond_to? :to_a and @addresses == other.to_a - end - - alias eql? == - - def hash - map {|i| i.hash }.hash - end - - def []( idx ) - @addresses[idx] - end - - def size - @addresses.size - end - - def empty? - @addresses.empty? - end - - def each( &block ) - @addresses.each(&block) - end - - def to_a - @addresses.dup - end - - alias to_ary to_a - - def include?( a ) - @addresses.include? a - end - - def flatten - set = [] - @addresses.each do |a| - if a.respond_to? :flatten - set.concat a.flatten - else - set.push a - end - end - set - end - - def each_address( &block ) - flatten.each(&block) - end - - def add( a ) - @addresses.push a - end - - alias push add - - def delete( a ) - @addresses.delete a - end - - include StrategyInterface - - def accept( strategy, dummy1 = nil, dummy2 = nil ) - strategy.phrase @name - strategy.meta ':' - strategy.space - first = true - each do |mbox| - if first - first = false - else - strategy.meta ',' - end - strategy.space - mbox.accept strategy - end - strategy.meta ';' - strategy.lwsp '' - end - - end - -end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/facade.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/facade.rb deleted file mode 100755 index 1ecd64bf..00000000 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/facade.rb +++ /dev/null @@ -1,552 +0,0 @@ -# -# facade.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/utils' - - -module TMail - - class Mail - - def header_string( name, default = nil ) - h = @header[name.downcase] or return default - h.to_s - end - - ### - ### attributes - ### - - include TextUtils - - def set_string_array_attr( key, strs ) - strs.flatten! - if strs.empty? - @header.delete key.downcase - else - store key, strs.join(', ') - end - strs - end - private :set_string_array_attr - - def set_string_attr( key, str ) - if str - store key, str - else - @header.delete key.downcase - end - str - end - private :set_string_attr - - def set_addrfield( name, arg ) - if arg - h = HeaderField.internal_new(name, @config) - h.addrs.replace [arg].flatten - @header[name] = h - else - @header.delete name - end - arg - end - private :set_addrfield - - def addrs2specs( addrs ) - return nil unless addrs - list = addrs.map {|addr| - if addr.address_group? - then addr.map {|a| a.spec } - else addr.spec - end - }.flatten - return nil if list.empty? - list - end - private :addrs2specs - - - # - # date time - # - - def date( default = nil ) - if h = @header['date'] - h.date - else - default - end - end - - def date=( time ) - if time - store 'Date', time2str(time) - else - @header.delete 'date' - end - time - end - - def strftime( fmt, default = nil ) - if t = date - t.strftime(fmt) - else - default - end - end - - - # - # destination - # - - def to_addrs( default = nil ) - if h = @header['to'] - h.addrs - else - default - end - end - - def cc_addrs( default = nil ) - if h = @header['cc'] - h.addrs - else - default - end - end - - def bcc_addrs( default = nil ) - if h = @header['bcc'] - h.addrs - else - default - end - end - - def to_addrs=( arg ) - set_addrfield 'to', arg - end - - def cc_addrs=( arg ) - set_addrfield 'cc', arg - end - - def bcc_addrs=( arg ) - set_addrfield 'bcc', arg - end - - def to( default = nil ) - addrs2specs(to_addrs(nil)) || default - end - - def cc( default = nil ) - addrs2specs(cc_addrs(nil)) || default - end - - def bcc( default = nil ) - addrs2specs(bcc_addrs(nil)) || default - end - - def to=( *strs ) - set_string_array_attr 'To', strs - end - - def cc=( *strs ) - set_string_array_attr 'Cc', strs - end - - def bcc=( *strs ) - set_string_array_attr 'Bcc', strs - end - - - # - # originator - # - - def from_addrs( default = nil ) - if h = @header['from'] - h.addrs - else - default - end - end - - def from_addrs=( arg ) - set_addrfield 'from', arg - end - - def from( default = nil ) - addrs2specs(from_addrs(nil)) || default - end - - def from=( *strs ) - set_string_array_attr 'From', strs - end - - def friendly_from( default = nil ) - h = @header['from'] - a, = h.addrs - return default unless a - return a.phrase if a.phrase - return h.comments.join(' ') unless h.comments.empty? - a.spec - end - - - def reply_to_addrs( default = nil ) - if h = @header['reply-to'] - h.addrs - else - default - end - end - - def reply_to_addrs=( arg ) - set_addrfield 'reply-to', arg - end - - def reply_to( default = nil ) - addrs2specs(reply_to_addrs(nil)) || default - end - - def reply_to=( *strs ) - set_string_array_attr 'Reply-To', strs - end - - - def sender_addr( default = nil ) - f = @header['sender'] or return default - f.addr or return default - end - - def sender_addr=( addr ) - if addr - h = HeaderField.internal_new('sender', @config) - h.addr = addr - @header['sender'] = h - else - @header.delete 'sender' - end - addr - end - - def sender( default ) - f = @header['sender'] or return default - a = f.addr or return default - a.spec - end - - def sender=( str ) - set_string_attr 'Sender', str - end - - - # - # subject - # - - def subject( default = nil ) - if h = @header['subject'] - h.body - else - default - end - end - alias quoted_subject subject - - def subject=( str ) - set_string_attr 'Subject', str - end - - - # - # identity & threading - # - - def message_id( default = nil ) - if h = @header['message-id'] - h.id || default - else - default - end - end - - def message_id=( str ) - set_string_attr 'Message-Id', str - end - - def in_reply_to( default = nil ) - if h = @header['in-reply-to'] - h.ids - else - default - end - end - - def in_reply_to=( *idstrs ) - set_string_array_attr 'In-Reply-To', idstrs - end - - def references( default = nil ) - if h = @header['references'] - h.refs - else - default - end - end - - def references=( *strs ) - set_string_array_attr 'References', strs - end - - - # - # MIME headers - # - - def mime_version( default = nil ) - if h = @header['mime-version'] - h.version || default - else - default - end - end - - def mime_version=( m, opt = nil ) - if opt - if h = @header['mime-version'] - h.major = m - h.minor = opt - else - store 'Mime-Version', "#{m}.#{opt}" - end - else - store 'Mime-Version', m - end - m - end - - - def content_type( default = nil ) - if h = @header['content-type'] - h.content_type || default - else - default - end - end - - def main_type( default = nil ) - if h = @header['content-type'] - h.main_type || default - else - default - end - end - - def sub_type( default = nil ) - if h = @header['content-type'] - h.sub_type || default - else - default - end - end - - def set_content_type( str, sub = nil, param = nil ) - if sub - main, sub = str, sub - else - main, sub = str.split(%r, 2) - raise ArgumentError, "sub type missing: #{str.inspect}" unless sub - end - if h = @header['content-type'] - h.main_type = main - h.sub_type = sub - h.params.clear - else - store 'Content-Type', "#{main}/#{sub}" - end - @header['content-type'].params.replace param if param - - str - end - - alias content_type= set_content_type - - def type_param( name, default = nil ) - if h = @header['content-type'] - h[name] || default - else - default - end - end - - def charset( default = nil ) - if h = @header['content-type'] - h['charset'] or default - else - default - end - end - - def charset=( str ) - if str - if h = @header[ 'content-type' ] - h['charset'] = str - else - store 'Content-Type', "text/plain; charset=#{str}" - end - end - str - end - - - def transfer_encoding( default = nil ) - if h = @header['content-transfer-encoding'] - h.encoding || default - else - default - end - end - - def transfer_encoding=( str ) - set_string_attr 'Content-Transfer-Encoding', str - end - - alias encoding transfer_encoding - alias encoding= transfer_encoding= - alias content_transfer_encoding transfer_encoding - alias content_transfer_encoding= transfer_encoding= - - - def disposition( default = nil ) - if h = @header['content-disposition'] - h.disposition || default - else - default - end - end - - alias content_disposition disposition - - def set_disposition( str, params = nil ) - if h = @header['content-disposition'] - h.disposition = str - h.params.clear - else - store('Content-Disposition', str) - h = @header['content-disposition'] - end - h.params.replace params if params - end - - alias disposition= set_disposition - alias set_content_disposition set_disposition - alias content_disposition= set_disposition - - def disposition_param( name, default = nil ) - if h = @header['content-disposition'] - h[name] || default - else - default - end - end - - ### - ### utils - ### - - def create_reply - mail = TMail::Mail.parse('') - mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '') - mail.to_addrs = reply_addresses([]) - mail.in_reply_to = [message_id(nil)].compact - mail.references = references([]) + [message_id(nil)].compact - mail.mime_version = '1.0' - mail - end - - - def base64_encode - store 'Content-Transfer-Encoding', 'Base64' - self.body = Base64.folding_encode(self.body) - end - - def base64_decode - if /base64/i === self.transfer_encoding('') - store 'Content-Transfer-Encoding', '8bit' - self.body = Base64.decode(self.body, @config.strict_base64decode?) - end - end - - - def destinations( default = nil ) - ret = [] - %w( to cc bcc ).each do |nm| - if h = @header[nm] - h.addrs.each {|i| ret.push i.address } - end - end - ret.empty? ? default : ret - end - - def each_destination( &block ) - destinations([]).each do |i| - if Address === i - yield i - else - i.each(&block) - end - end - end - - alias each_dest each_destination - - - def reply_addresses( default = nil ) - reply_to_addrs(nil) or from_addrs(nil) or default - end - - def error_reply_addresses( default = nil ) - if s = sender(nil) - [s] - else - from_addrs(default) - end - end - - - def multipart? - main_type('').downcase == 'multipart' - end - - end # class Mail - -end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/info.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/info.rb deleted file mode 100755 index 5c115d58..00000000 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/info.rb +++ /dev/null @@ -1,35 +0,0 @@ -# -# info.rb -# -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -module TMail - - Version = '0.10.7' - Copyright = 'Copyright (c) 1998-2002 Minero Aoki' - -end diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/interface.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/interface.rb deleted file mode 100644 index 957e8997..00000000 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/interface.rb +++ /dev/null @@ -1,540 +0,0 @@ -=begin rdoc - -= Facade.rb Provides an interface to the TMail object - -=end -#-- -# Copyright (c) 1998-2003 Minero Aoki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. -#++ - -require 'tmail/utils' - -module TMail - - class Mail - - def header_string( name, default = nil ) - h = @header[name.downcase] or return default - h.to_s - end - - ### - ### attributes - ### - - include TextUtils - - def set_string_array_attr( key, strs ) - strs.flatten! - if strs.empty? - @header.delete key.downcase - else - store key, strs.join(', ') - end - strs - end - private :set_string_array_attr - - def set_string_attr( key, str ) - if str - store key, str - else - @header.delete key.downcase - end - str - end - private :set_string_attr - - def set_addrfield( name, arg ) - if arg - h = HeaderField.internal_new(name, @config) - h.addrs.replace [arg].flatten - @header[name] = h - else - @header.delete name - end - arg - end - private :set_addrfield - - def addrs2specs( addrs ) - return nil unless addrs - list = addrs.map {|addr| - if addr.address_group? - then addr.map {|a| a.spec } - else addr.spec - end - }.flatten - return nil if list.empty? - list - end - private :addrs2specs - - # - # date time - # - - def date( default = nil ) - if h = @header['date'] - h.date - else - default - end - end - - def date=( time ) - if time - store 'Date', time2str(time) - else - @header.delete 'date' - end - time - end - - def strftime( fmt, default = nil ) - if t = date - t.strftime(fmt) - else - default - end - end - - # - # destination - # - - def to_addrs( default = nil ) - if h = @header['to'] - h.addrs - else - default - end - end - - def cc_addrs( default = nil ) - if h = @header['cc'] - h.addrs - else - default - end - end - - def bcc_addrs( default = nil ) - if h = @header['bcc'] - h.addrs - else - default - end - end - - def to_addrs=( arg ) - set_addrfield 'to', arg - end - - def cc_addrs=( arg ) - set_addrfield 'cc', arg - end - - def bcc_addrs=( arg ) - set_addrfield 'bcc', arg - end - - def to( default = nil ) - addrs2specs(to_addrs(nil)) || default - end - - def cc( default = nil ) - addrs2specs(cc_addrs(nil)) || default - end - - def bcc( default = nil ) - addrs2specs(bcc_addrs(nil)) || default - end - - def to=( *strs ) - set_string_array_attr 'To', strs - end - - def cc=( *strs ) - set_string_array_attr 'Cc', strs - end - - def bcc=( *strs ) - set_string_array_attr 'Bcc', strs - end - - # - # originator - # - - def from_addrs( default = nil ) - if h = @header['from'] - h.addrs - else - default - end - end - - def from_addrs=( arg ) - set_addrfield 'from', arg - end - - def from( default = nil ) - addrs2specs(from_addrs(nil)) || default - end - - def from=( *strs ) - set_string_array_attr 'From', strs - end - - def friendly_from( default = nil ) - h = @header['from'] - a, = h.addrs - return default unless a - return a.phrase if a.phrase - return h.comments.join(' ') unless h.comments.empty? - a.spec - end - - - def reply_to_addrs( default = nil ) - if h = @header['reply-to'] - h.addrs - else - default - end - end - - def reply_to_addrs=( arg ) - set_addrfield 'reply-to', arg - end - - def reply_to( default = nil ) - addrs2specs(reply_to_addrs(nil)) || default - end - - def reply_to=( *strs ) - set_string_array_attr 'Reply-To', strs - end - - - def sender_addr( default = nil ) - f = @header['sender'] or return default - f.addr or return default - end - - def sender_addr=( addr ) - if addr - h = HeaderField.internal_new('sender', @config) - h.addr = addr - @header['sender'] = h - else - @header.delete 'sender' - end - addr - end - - def sender( default ) - f = @header['sender'] or return default - a = f.addr or return default - a.spec - end - - def sender=( str ) - set_string_attr 'Sender', str - end - - - # - # subject - # - - def subject( default = nil ) - if h = @header['subject'] - h.body - else - default - end - end - alias quoted_subject subject - - def subject=( str ) - set_string_attr 'Subject', str - end - - # - # identity & threading - # - - def message_id( default = nil ) - if h = @header['message-id'] - h.id || default - else - default - end - end - - def message_id=( str ) - set_string_attr 'Message-Id', str - end - - def in_reply_to( default = nil ) - if h = @header['in-reply-to'] - h.ids - else - default - end - end - - def in_reply_to=( *idstrs ) - set_string_array_attr 'In-Reply-To', idstrs - end - - def references( default = nil ) - if h = @header['references'] - h.refs - else - default - end - end - - def references=( *strs ) - set_string_array_attr 'References', strs - end - - # - # MIME headers - # - - def mime_version( default = nil ) - if h = @header['mime-version'] - h.version || default - else - default - end - end - - def mime_version=( m, opt = nil ) - if opt - if h = @header['mime-version'] - h.major = m - h.minor = opt - else - store 'Mime-Version', "#{m}.#{opt}" - end - else - store 'Mime-Version', m - end - m - end - - def content_type( default = nil ) - if h = @header['content-type'] - h.content_type || default - else - default - end - end - - def main_type( default = nil ) - if h = @header['content-type'] - h.main_type || default - else - default - end - end - - def sub_type( default = nil ) - if h = @header['content-type'] - h.sub_type || default - else - default - end - end - - def set_content_type( str, sub = nil, param = nil ) - if sub - main, sub = str, sub - else - main, sub = str.split(%r, 2) - raise ArgumentError, "sub type missing: #{str.inspect}" unless sub - end - if h = @header['content-type'] - h.main_type = main - h.sub_type = sub - h.params.clear - else - store 'Content-Type', "#{main}/#{sub}" - end - @header['content-type'].params.replace param if param - str - end - - alias content_type= set_content_type - - def type_param( name, default = nil ) - if h = @header['content-type'] - h[name] || default - else - default - end - end - - def charset( default = nil ) - if h = @header['content-type'] - h['charset'] or default - else - default - end - end - - def charset=( str ) - if str - if h = @header[ 'content-type' ] - h['charset'] = str - else - store 'Content-Type', "text/plain; charset=#{str}" - end - end - str - end - - def transfer_encoding( default = nil ) - if h = @header['content-transfer-encoding'] - h.encoding || default - else - default - end - end - - def transfer_encoding=( str ) - set_string_attr 'Content-Transfer-Encoding', str - end - - alias encoding transfer_encoding - alias encoding= transfer_encoding= - alias content_transfer_encoding transfer_encoding - alias content_transfer_encoding= transfer_encoding= - - def disposition( default = nil ) - if h = @header['content-disposition'] - h.disposition || default - else - default - end - end - - alias content_disposition disposition - - def set_disposition( str, params = nil ) - if h = @header['content-disposition'] - h.disposition = str - h.params.clear - else - store('Content-Disposition', str) - h = @header['content-disposition'] - end - h.params.replace params if params - end - - alias disposition= set_disposition - alias set_content_disposition set_disposition - alias content_disposition= set_disposition - - def disposition_param( name, default = nil ) - if h = @header['content-disposition'] - h[name] || default - else - default - end - end - - ### - ### utils - ### - - def create_reply - mail = TMail::Mail.parse('') - mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '') - mail.to_addrs = reply_addresses([]) - mail.in_reply_to = [message_id(nil)].compact - mail.references = references([]) + [message_id(nil)].compact - mail.mime_version = '1.0' - mail - end - - def base64_encode - store 'Content-Transfer-Encoding', 'Base64' - self.body = Base64.folding_encode(self.body) - end - - def base64_decode - if /base64/i === self.transfer_encoding('') - store 'Content-Transfer-Encoding', '8bit' - self.body = Base64.decode(self.body, @config.strict_base64decode?) - end - end - - def destinations( default = nil ) - ret = [] - %w( to cc bcc ).each do |nm| - if h = @header[nm] - h.addrs.each {|i| ret.push i.address } - end - end - ret.empty? ? default : ret - end - - def each_destination( &block ) - destinations([]).each do |i| - if Address === i - yield i - else - i.each(&block) - end - end - end - - alias each_dest each_destination - - def reply_addresses( default = nil ) - reply_to_addrs(nil) or from_addrs(nil) or default - end - - def error_reply_addresses( default = nil ) - if s = sender(nil) - [s] - else - from_addrs(default) - end - end - - def multipart? - main_type('').downcase == 'multipart' - end - - end # class Mail - -end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.y b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.y deleted file mode 100644 index 77a14577..00000000 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.y +++ /dev/null @@ -1,381 +0,0 @@ -# -# parser.y -# -# Copyright (c) 1998-2007 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the terms of -# the GNU Lesser General Public License version 2.1. -# - -class TMail::Parser - - options no_result_var - -rule - - content : DATETIME datetime { val[1] } - | RECEIVED received { val[1] } - | MADDRESS addrs_TOP { val[1] } - | RETPATH retpath { val[1] } - | KEYWORDS keys { val[1] } - | ENCRYPTED enc { val[1] } - | MIMEVERSION version { val[1] } - | CTYPE ctype { val[1] } - | CENCODING cencode { val[1] } - | CDISPOSITION cdisp { val[1] } - | ADDRESS addr_TOP { val[1] } - | MAILBOX mbox { val[1] } - - datetime : day DIGIT ATOM DIGIT hour zone - # 0 1 2 3 4 5 - # date month year - { - t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0) - (t + val[4] - val[5]).localtime - } - - day : /* none */ - | ATOM ',' - - hour : DIGIT ':' DIGIT - { - (val[0].to_i * 60 * 60) + - (val[2].to_i * 60) - } - | DIGIT ':' DIGIT ':' DIGIT - { - (val[0].to_i * 60 * 60) + - (val[2].to_i * 60) + - (val[4].to_i) - } - - zone : ATOM - { - timezone_string_to_unixtime(val[0]) - } - - received : from by via with id for received_datetime - { - val - } - - from : /* none */ - | FROM received_domain - { - val[1] - } - - by : /* none */ - | BY received_domain - { - val[1] - } - - received_domain - : domain - { - join_domain(val[0]) - } - | domain '@' domain - { - join_domain(val[2]) - } - | domain DOMLIT - { - join_domain(val[0]) - } - - via : /* none */ - | VIA ATOM - { - val[1] - } - - with : /* none */ - { - [] - } - | with WITH ATOM - { - val[0].push val[2] - val[0] - } - - id : /* none */ - | ID msgid - { - val[1] - } - | ID ATOM - { - val[1] - } - - for : /* none */ - | FOR received_addrspec - { - val[1] - } - - received_addrspec - : routeaddr - { - val[0].spec - } - | spec - { - val[0].spec - } - - received_datetime - : /* none */ - | ';' datetime - { - val[1] - } - - addrs_TOP : addrs - | group_bare - | addrs commas group_bare - - addr_TOP : mbox - | group - | group_bare - - retpath : addrs_TOP - | '<' '>' { [ Address.new(nil, nil) ] } - - addrs : addr - { - val - } - | addrs commas addr - { - val[0].push val[2] - val[0] - } - - addr : mbox - | group - - mboxes : mbox - { - val - } - | mboxes commas mbox - { - val[0].push val[2] - val[0] - } - - mbox : spec - | routeaddr - | addr_phrase routeaddr - { - val[1].phrase = Decoder.decode(val[0]) - val[1] - } - - group : group_bare ';' - - group_bare: addr_phrase ':' mboxes - { - AddressGroup.new(val[0], val[2]) - } - | addr_phrase ':' { AddressGroup.new(val[0], []) } - - addr_phrase - : local_head { val[0].join('.') } - | addr_phrase local_head { val[0] << ' ' << val[1].join('.') } - - routeaddr : '<' routes spec '>' - { - val[2].routes.replace val[1] - val[2] - } - | '<' spec '>' - { - val[1] - } - - routes : at_domains ':' - - at_domains: '@' domain { [ val[1].join('.') ] } - | at_domains ',' '@' domain { val[0].push val[3].join('.'); val[0] } - - spec : local '@' domain { Address.new( val[0], val[2] ) } - | local { Address.new( val[0], nil ) } - - local: local_head - | local_head '.' { val[0].push ''; val[0] } - - local_head: word - { val } - | local_head dots word - { - val[1].times do - val[0].push '' - end - val[0].push val[2] - val[0] - } - - domain : domword - { val } - | domain dots domword - { - val[1].times do - val[0].push '' - end - val[0].push val[2] - val[0] - } - - dots : '.' { 0 } - | '.' '.' { 1 } - - word : atom - | QUOTED - | DIGIT - - domword : atom - | DOMLIT - | DIGIT - - commas : ',' - | commas ',' - - msgid : '<' spec '>' - { - val[1] = val[1].spec - val.join('') - } - - keys : phrase { val } - | keys ',' phrase { val[0].push val[2]; val[0] } - - phrase : word - | phrase word { val[0] << ' ' << val[1] } - - enc : word - { - val.push nil - val - } - | word word - { - val - } - - version : DIGIT '.' DIGIT - { - [ val[0].to_i, val[2].to_i ] - } - - ctype : TOKEN '/' TOKEN params opt_semicolon - { - [ val[0].downcase, val[2].downcase, decode_params(val[3]) ] - } - | TOKEN params opt_semicolon - { - [ val[0].downcase, nil, decode_params(val[1]) ] - } - - params : /* none */ - { - {} - } - | params ';' TOKEN '=' QUOTED - { - val[0][ val[2].downcase ] = ('"' + val[4].to_s + '"') - val[0] - } - | params ';' TOKEN '=' TOKEN - { - val[0][ val[2].downcase ] = val[4] - val[0] - } - - cencode : TOKEN - { - val[0].downcase - } - - cdisp : TOKEN params opt_semicolon - { - [ val[0].downcase, decode_params(val[1]) ] - } - - opt_semicolon - : - | ';' - - atom : ATOM - | FROM - | BY - | VIA - | WITH - | ID - | FOR - -end - - ----- header -# -# parser.rb -# -# Copyright (c) 1998-2007 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the terms of -# the GNU Lesser General Public License version 2.1. -# - -require 'tmail/scanner' -require 'tmail/utils' - ----- inner - - include TextUtils - - def self.parse( ident, str, cmt = nil ) - new.parse(ident, str, cmt) - end - - MAILP_DEBUG = false - - def initialize - self.debug = MAILP_DEBUG - end - - def debug=( flag ) - @yydebug = flag && Racc_debug_parser - @scanner_debug = flag - end - - def debug - @yydebug - end - - def parse( ident, str, comments = nil ) - @scanner = Scanner.new(str, ident, comments) - @scanner.debug = @scanner_debug - @first = [ident, ident] - result = yyparse(self, :parse_in) - comments.map! {|c| to_kcode(c) } if comments - result - end - - private - - def parse_in( &block ) - yield @first - @scanner.scan(&block) - end - - def on_error( t, val, vstack ) - raise SyntaxError, "parse error on token #{racc_token2str t}" - end - diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/tmail.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/tmail.rb deleted file mode 100755 index 57ed3cc5..00000000 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/tmail.rb +++ /dev/null @@ -1 +0,0 @@ -require 'tmail' diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail.rb old mode 100755 new mode 100644 similarity index 83% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail.rb index 7de18501..18003659 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail.rb @@ -2,3 +2,4 @@ require 'tmail/version' require 'tmail/mail' require 'tmail/mailbox' require 'tmail/core_extensions' +require 'tmail/net' diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/address.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/address.rb new file mode 100644 index 00000000..fa8e5bcd --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/address.rb @@ -0,0 +1,426 @@ +=begin rdoc + += Address handling class + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/encode' +require 'tmail/parser' + + +module TMail + + # = Class Address + # + # Provides a complete handling library for email addresses. Can parse a string of an + # address directly or take in preformatted addresses themseleves. Allows you to add + # and remove phrases from the front of the address and provides a compare function for + # email addresses. + # + # == Parsing and Handling a Valid Address: + # + # Just pass the email address in as a string to Address.parse: + # + # email = TMail::Address.parse('Mikel Lindsaar ) + # #=> # + # email.address + # #=> "mikel@lindsaar.net" + # email.local + # #=> "mikel" + # email.domain + # #=> "lindsaar.net" + # email.name # Aliased as phrase as well + # #=> "Mikel Lindsaar" + # + # == Detecting an Invalid Address + # + # If you want to check the syntactical validity of an email address, just pass it to + # Address.parse and catch any SyntaxError: + # + # begin + # TMail::Mail.parse("mikel 2@@@@@ me .com") + # rescue TMail::SyntaxError + # puts("Invalid Email Address Detected") + # else + # puts("Address is valid") + # end + # #=> "Invalid Email Address Detected" + class Address + + include TextUtils #:nodoc: + + # Sometimes you need to parse an address, TMail can do it for you and provide you with + # a fairly robust method of detecting a valid address. + # + # Takes in a string, returns a TMail::Address object. + # + # Raises a TMail::SyntaxError on invalid email format + def Address.parse( str ) + Parser.parse :ADDRESS, special_quote_address(str) + end + + def Address.special_quote_address(str) #:nodoc: + # Takes a string which is an address and adds quotation marks to special + # edge case methods that the RACC parser can not handle. + # + # Right now just handles two edge cases: + # + # Full stop as the last character of the display name: + # Mikel L. + # Returns: + # "Mikel L." + # + # Unquoted @ symbol in the display name: + # mikel@me.com + # Returns: + # "mikel@me.com" + # + # Any other address not matching these patterns just gets returned as is. + case + # This handles the missing "" in an older version of Apple Mail.app + # around the display name when the display name contains a '@' + # like 'mikel@me.com ' + # Just quotes it to: '"mikel@me.com" ' + when str =~ /\A([^"].+@.+[^"])\s(<.*?>)\Z/ + return "\"#{$1}\" #{$2}" + # This handles cases where 'Mikel A. ' which is a trailing + # full stop before the address section. Just quotes it to + # '"Mikel A. " + when str =~ /\A(.*?\.)\s(<.*?>)\Z/ + return "\"#{$1}\" #{$2}" + else + str + end + end + + def address_group? #:nodoc: + false + end + + # Address.new(local, domain) + # + # Accepts: + # + # * local - Left of the at symbol + # + # * domain - Array of the domain split at the periods. + # + # For example: + # + # Address.new("mikel", ["lindsaar", "net"]) + # #=> "#" + def initialize( local, domain ) + if domain + domain.each do |s| + raise SyntaxError, 'empty word in domain' if s.empty? + end + end + + # This is to catch an unquoted "@" symbol in the local part of the + # address. Handles addresses like <"@"@me.com> and makes sure they + # stay like <"@"@me.com> (previously were becomming <@@me.com>) + if local && (local.join == '@' || local.join =~ /\A[^"].*?@.*?[^"]\Z/) + @local = "\"#{local.join}\"" + else + @local = local + end + + @domain = domain + @name = nil + @routes = [] + end + + # Provides the name or 'phrase' of the email address. + # + # For Example: + # + # email = TMail::Address.parse("Mikel Lindsaar ") + # email.name + # #=> "Mikel Lindsaar" + def name + @name + end + + # Setter method for the name or phrase of the email + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.name + # #=> nil + # email.name = "Mikel Lindsaar" + # email.to_s + # #=> "Mikel Lindsaar " + def name=( str ) + @name = str + @name = nil if str and str.empty? + end + + #:stopdoc: + alias phrase name + alias phrase= name= + #:startdoc: + + # This is still here from RFC 822, and is now obsolete per RFC2822 Section 4. + # + # "When interpreting addresses, the route portion SHOULD be ignored." + # + # It is still here, so you can access it. + # + # Routes return the route portion at the front of the email address, if any. + # + # For Example: + # email = TMail::Address.parse( "<@sa,@another:Mikel@me.com>") + # => # + # email.to_s + # => "<@sa,@another:Mikel@me.com>" + # email.routes + # => ["sa", "another"] + def routes + @routes + end + + def inspect #:nodoc: + "#<#{self.class} #{address()}>" + end + + # Returns the local part of the email address + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.local + # #=> "mikel" + def local + return nil unless @local + return '""' if @local.size == 1 and @local[0].empty? + # Check to see if it is an array before trying to map it + if @local.respond_to?(:map) + @local.map {|i| quote_atom(i) }.join('.') + else + quote_atom(@local) + end + end + + # Returns the domain part of the email address + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.local + # #=> "lindsaar.net" + def domain + return nil unless @domain + join_domain(@domain) + end + + # Returns the full specific address itself + # + # For Example: + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.address + # #=> "mikel@lindsaar.net" + def spec + s = self.local + d = self.domain + if s and d + s + '@' + d + else + s + end + end + + alias address spec + + # Provides == function to the email. Only checks the actual address + # and ignores the name/phrase component + # + # For Example + # + # addr1 = TMail::Address.parse("My Address ") + # #=> "#" + # addr2 = TMail::Address.parse("Another ") + # #=> "#" + # addr1 == addr2 + # #=> true + def ==( other ) + other.respond_to? :spec and self.spec == other.spec + end + + alias eql? == + + # Provides a unique hash value for this record against the local and domain + # parts, ignores the name/phrase value + # + # email = TMail::Address.parse("mikel@lindsaar.net") + # email.hash + # #=> 18767598 + def hash + @local.hash ^ @domain.hash + end + + # Duplicates a TMail::Address object returning the duplicate + # + # addr1 = TMail::Address.parse("mikel@lindsaar.net") + # addr2 = addr1.dup + # addr1.id == addr2.id + # #=> false + def dup + obj = self.class.new(@local.dup, @domain.dup) + obj.name = @name.dup if @name + obj.routes.replace @routes + obj + end + + include StrategyInterface #:nodoc: + + def accept( strategy, dummy1 = nil, dummy2 = nil ) #:nodoc: + unless @local + strategy.meta '<>' # empty return-path + return + end + + spec_p = (not @name and @routes.empty?) + if @name + strategy.phrase @name + strategy.space + end + tmp = spec_p ? '' : '<' + unless @routes.empty? + tmp << @routes.map {|i| '@' + i }.join(',') << ':' + end + tmp << self.spec + tmp << '>' unless spec_p + strategy.meta tmp + strategy.lwsp '' + end + + end + + + class AddressGroup + + include Enumerable + + def address_group? + true + end + + def initialize( name, addrs ) + @name = name + @addresses = addrs + end + + attr_reader :name + + def ==( other ) + other.respond_to? :to_a and @addresses == other.to_a + end + + alias eql? == + + def hash + map {|i| i.hash }.hash + end + + def []( idx ) + @addresses[idx] + end + + def size + @addresses.size + end + + def empty? + @addresses.empty? + end + + def each( &block ) + @addresses.each(&block) + end + + def to_a + @addresses.dup + end + + alias to_ary to_a + + def include?( a ) + @addresses.include? a + end + + def flatten + set = [] + @addresses.each do |a| + if a.respond_to? :flatten + set.concat a.flatten + else + set.push a + end + end + set + end + + def each_address( &block ) + flatten.each(&block) + end + + def add( a ) + @addresses.push a + end + + alias push add + + def delete( a ) + @addresses.delete a + end + + include StrategyInterface + + def accept( strategy, dummy1 = nil, dummy2 = nil ) + strategy.phrase @name + strategy.meta ':' + strategy.space + first = true + each do |mbox| + if first + first = false + else + strategy.meta ',' + end + strategy.space + mbox.accept strategy + end + strategy.meta ';' + strategy.lwsp '' + end + + end + +end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/attachments.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/attachments.rb similarity index 86% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/attachments.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/attachments.rb index a8b8017c..5dc5efae 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/attachments.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/attachments.rb @@ -1,6 +1,6 @@ =begin rdoc -= Attachment handling class += Attachment handling file =end @@ -17,8 +17,7 @@ module TMail end def attachment?(part) - (part['content-disposition'] && part['content-disposition'].disposition == "attachment") || - part.header['content-type'].main_type != "text" + part.disposition_is_attachment? || part.content_type_is_text? end def attachments diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/base64.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/base64.rb old mode 100755 new mode 100644 similarity index 97% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/base64.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/base64.rb index e99b6b0b..e294c629 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/base64.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/base64.rb @@ -1,9 +1,4 @@ -# = TITLE: -# -# Base64 -# -# = COPYRIGHT: -# +#-- # Copyright (c) 1998-2003 Minero Aoki # # Permission is hereby granted, free of charge, to any person obtaining @@ -27,10 +22,9 @@ # # Note: Originally licensed under LGPL v2+. Using MIT license for Rails # with permission of Minero Aoki. - -# +#++ +#:stopdoc: module TMail - module Base64 module_function @@ -48,5 +42,5 @@ module TMail end end - end +#:startdoc: diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/compat.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/compat.rb similarity index 70% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/compat.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/compat.rb index 9d2aa837..1275df79 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/compat.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/compat.rb @@ -1,17 +1,18 @@ -unless Enumerable.method_defined?(:map) - module Enumerable +#:stopdoc: +unless Enumerable.method_defined?(:map) + module Enumerable #:nodoc: alias map collect end end unless Enumerable.method_defined?(:select) - module Enumerable + module Enumerable #:nodoc: alias select find_all end end unless Enumerable.method_defined?(:reject) - module Enumerable + module Enumerable #:nodoc: def reject result = [] each do |i| @@ -23,7 +24,7 @@ unless Enumerable.method_defined?(:reject) end unless Enumerable.method_defined?(:sort_by) - module Enumerable + module Enumerable #:nodoc: def sort_by map {|i| [yield(i), i] }.sort.map {|val, i| i } end @@ -31,9 +32,10 @@ unless Enumerable.method_defined?(:sort_by) end unless File.respond_to?(:read) - def File.read(fname) + def File.read(fname) #:nodoc: File.open(fname) {|f| return f.read } end end +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/config.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/config.rb old mode 100755 new mode 100644 similarity index 97% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/config.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/config.rb index 4b253d2b..3a876dcd --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/config.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/config.rb @@ -1,8 +1,3 @@ -=begin rdoc - -= Configuration Class - -=end #-- # Copyright (c) 1998-2003 Minero Aoki # @@ -28,7 +23,7 @@ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails # with permission of Minero Aoki. #++ - +#:stopdoc: module TMail class Config @@ -69,3 +64,4 @@ module TMail end end +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/core_extensions.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/core_extensions.rb similarity index 65% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/core_extensions.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/core_extensions.rb index cc24e977..da62c33b 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/core_extensions.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/core_extensions.rb @@ -1,14 +1,9 @@ -=begin rdoc - -= Ruby on Rails Core Extensions - -provides .blank? - -=end -unless Object.respond_to?(:blank?) #:nodoc: - # Check first to see if we are in a Rails environment, no need to - # define these methods if we are +#:stopdoc: +unless Object.respond_to?(:blank?) class Object + # Check first to see if we are in a Rails environment, no need to + # define these methods if we are + # An object is blank if it's nil, empty, or a whitespace string. # For example, "", " ", nil, [], and {} are blank. # @@ -27,41 +22,42 @@ unless Object.respond_to?(:blank?) #:nodoc: end end - class NilClass #:nodoc: + class NilClass def blank? true end end - class FalseClass #:nodoc: + class FalseClass def blank? true end end - class TrueClass #:nodoc: + class TrueClass def blank? false end end - class Array #:nodoc: + class Array alias_method :blank?, :empty? end - class Hash #:nodoc: + class Hash alias_method :blank?, :empty? end - class String #:nodoc: + class String def blank? empty? || strip.empty? end end - class Numeric #:nodoc: + class Numeric def blank? false end end -end \ No newline at end of file +end +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/encode.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/encode.rb old mode 100755 new mode 100644 similarity index 62% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/encode.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/encode.rb index 0721a254..458dbbfe --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/encode.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/encode.rb @@ -1,41 +1,45 @@ -=begin rdoc - -= Text Encoding class - -=end #-- -# Copyright (c) 1998-2003 Minero Aoki +# = COPYRIGHT: # -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: +# Copyright (c) 1998-2003 Minero Aoki # -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. # -# Note: Originally licensed under LGPL v2+. Using MIT license for Rails -# with permission of Minero Aoki. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. #++ - +#:stopdoc: require 'nkf' -require 'tmail/base64.rb' +require 'tmail/base64' require 'tmail/stringio' require 'tmail/utils' +#:startdoc: module TMail + + #:stopdoc: + class << self + attr_accessor :KCODE + end + self.KCODE = 'NONE' module StrategyInterface @@ -52,27 +56,52 @@ module TMail end end module_function :create_dest - + + #:startdoc: + # Returns the TMail object encoded and ready to be sent via SMTP etc. + # You should call this before you are packaging up your email to + # correctly escape all the values that need escaping in the email, line + # wrap the email etc. + # + # It is also a good idea to call this before you marshal or serialize + # a TMail object. + # + # For Example: + # + # email = TMail::Load(my_email_file) + # email_to_send = email.encoded def encoded( eol = "\r\n", charset = 'j', dest = nil ) accept_strategy Encoder, eol, charset, dest end - + + # Returns the TMail object decoded and ready to be used by you, your + # program etc. + # + # You should call this before you are packaging up your email to + # correctly escape all the values that need escaping in the email, line + # wrap the email etc. + # + # For Example: + # + # email = TMail::Load(my_email_file) + # email_to_send = email.encoded def decoded( eol = "\n", charset = 'e', dest = nil ) # Turn the E-Mail into a string and return it with all # encoded characters decoded. alias for to_s accept_strategy Decoder, eol, charset, dest end - + alias to_s decoded - - def accept_strategy( klass, eol, charset, dest = nil ) + + def accept_strategy( klass, eol, charset, dest = nil ) #:nodoc: dest ||= '' accept klass.new( create_dest(dest), charset, eol ) dest end - + end + #:stopdoc: ### ### MIME B encoding decoder @@ -91,8 +120,8 @@ module TMail } def self.decode( str, encoding = nil ) - encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j') - opt = '-m' + encoding + encoding ||= (OUTPUT_ENCODING[TMail.KCODE] || 'j') + opt = '-mS' + encoding str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) } end @@ -121,7 +150,7 @@ module TMail def header_body( str ) @f << decode(str) end - + def space @f << ' ' end @@ -131,7 +160,7 @@ module TMail def lwsp( str ) @f << str end - + def meta( str ) @f << str end @@ -182,7 +211,8 @@ module TMail end SPACER = "\t" - MAX_LINE_LEN = 70 + MAX_LINE_LEN = 78 + RFC_2822_MAX_LENGTH = 998 OPTIONS = { 'EUC' => '-Ej -m0', @@ -193,8 +223,9 @@ module TMail def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil ) @f = StrategyInterface.create_dest(dest) - @opt = OPTIONS[$KCODE] + @opt = OPTIONS[TMail.KCODE] @eol = eol + @folded = false @preserve_quotes = true reset end @@ -202,7 +233,7 @@ module TMail def preserve_quotes=( bool ) @preserve_quotes end - + def preserve_quotes @preserve_quotes end @@ -308,22 +339,36 @@ module TMail def scanadd( str, force = false ) types = '' strs = [] - + if str.respond_to?(:encoding) + enc = str.encoding + str.force_encoding(Encoding::ASCII_8BIT) + end until str.empty? if m = /\A[^\e\t\r\n ]+/.match(str) types << (force ? 'j' : 'a') - strs.push m[0] - + if str.respond_to?(:encoding) + strs.push m[0].force_encoding(enc) + else + strs.push m[0] + end elsif m = /\A[\t\r\n ]+/.match(str) types << 's' - strs.push m[0] + if str.respond_to?(:encoding) + strs.push m[0].force_encoding(enc) + else + strs.push m[0] + end elsif m = /\A\e../.match(str) esc = m[0] str = m.post_match if esc != "\e(B" and m = /\A[^\e]+/.match(str) types << 'j' - strs.push m[0] + if str.respond_to?(:encoding) + strs.push m[0].force_encoding(enc) + else + strs.push m[0] + end end else @@ -367,18 +412,23 @@ module TMail end def concat_A_S( types, strs ) + if RUBY_VERSION < '1.9' + a = ?a; s = ?s + else + a = 'a'.ord; s = 's'.ord + end i = 0 types.each_byte do |t| case t - when ?a then add_text strs[i] - when ?s then add_lwsp strs[i] + when a then add_text strs[i] + when s then add_lwsp strs[i] else raise "TMail FATAL: unknown flag: #{t.chr}" end i += 1 end end - + METHOD_ID = { ?j => :extract_J, ?e => :extract_E, @@ -417,7 +467,13 @@ module TMail size = max_bytes(chunksize, str.size) - 6 size = (size % 2 == 0) ? (size) : (size - 1) return nil if size <= 0 - "\e$B#{str.slice!(0, size)}\e(B" + if str.respond_to?(:encoding) + enc = str.encoding + str.force_encoding(Encoding::ASCII_8BIT) + "\e$B#{str.slice!(0, size)}\e(B".force_encoding(enc) + else + "\e$B#{str.slice!(0, size)}\e(B" + end end def extract_A( chunksize, str ) @@ -451,31 +507,75 @@ module TMail # puts '---- lwsp -------------------------------------' # puts "+ #{lwsp.inspect}" fold if restsize() <= 0 - flush + flush(@folded) @lwsp = lwsp end - def flush + def flush(folded = false) # puts '---- flush ----' # puts "spc >>>#{@lwsp.inspect}<<<" # puts "txt >>>#{@text.inspect}<<<" @f << @lwsp << @text - @curlen += (@lwsp.size + @text.size) + if folded + @curlen = 0 + else + @curlen += (@lwsp.size + @text.size) + end @text = '' @lwsp = '' end def fold # puts '---- fold ----' - @f << @eol + unless @f.string =~ /^.*?:$/ + @f << @eol + @lwsp = SPACER + else + fold_header + @folded = true + end @curlen = 0 - @lwsp = SPACER + end + + def fold_header + # Called because line is too long - so we need to wrap. + # First look for whitespace in the text + # if it has text, fold there + # check the remaining text, if too long, fold again + # if it doesn't, then don't fold unless the line goes beyond 998 chars + + # Check the text to see if there is whitespace, or if not + @wrapped_text = [] + until @text.blank? + fold_the_string + end + @text = @wrapped_text.join("#{@eol}#{SPACER}") + end + + def fold_the_string + whitespace_location = @text =~ /\s/ || @text.length + # Is the location of the whitespace shorter than the RCF_2822_MAX_LENGTH? + # if there is no whitespace in the string, then this + unless mazsize(whitespace_location) <= 0 + @text.strip! + @wrapped_text << @text.slice!(0...whitespace_location) + # If it is not less, we have to wrap it destructively + else + slice_point = RFC_2822_MAX_LENGTH - @curlen - @lwsp.length + @text.strip! + @wrapped_text << @text.slice!(0...slice_point) + end end def restsize MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size) end - end + def mazsize(whitespace_location) + # Per RFC2822, the maximum length of a line is 998 chars + RFC_2822_MAX_LENGTH - (@curlen + @lwsp.size + whitespace_location) + end + end + #:startdoc: end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/header.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/header.rb old mode 100755 new mode 100644 similarity index 89% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/header.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/header.rb index 41c371f3..9153dcd7 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/header.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/header.rb @@ -1,11 +1,3 @@ -=begin rdoc - -= Header handling class - -=end -# RFC #822 ftp://ftp.isi.edu/in-notes/rfc822.txt -# -# #-- # Copyright (c) 1998-2003 Minero Aoki # @@ -38,9 +30,10 @@ require 'tmail/parser' require 'tmail/config' require 'tmail/utils' - +#:startdoc: module TMail + # Provides methods to handle and manipulate headers in the email class HeaderField include TextUtils @@ -54,8 +47,40 @@ module TMail klass.newobj body, conf end + # Returns a HeaderField object matching the header you specify in the "name" param. + # Requires an initialized TMail::Port to be passed in. + # + # The method searches the header of the Port you pass into it to find a match on + # the header line you pass. Once a match is found, it will unwrap the matching line + # as needed to return an initialized HeaderField object. + # + # If you want to get the Envelope sender of the email object, pass in "EnvelopeSender", + # if you want the From address of the email itself, pass in 'From'. + # + # This is because a mailbox doesn't have the : after the From that designates the + # beginning of the envelope sender (which can be different to the from address of + # the emial) + # + # Other fields can be passed as normal, "Reply-To", "Received" etc. + # + # Note: Change of behaviour in 1.2.1 => returns nil if it does not find the specified + # header field, otherwise returns an instantiated object of the correct header class + # + # For example: + # port = TMail::FilePort.new("/test/fixtures/raw_email_simple") + # h = TMail::HeaderField.new_from_port(port, "From") + # h.addrs.to_s #=> "Mikel Lindsaar " + # h = TMail::HeaderField.new_from_port(port, "EvelopeSender") + # h.addrs.to_s #=> "mike@anotherplace.com.au" + # h = TMail::HeaderField.new_from_port(port, "SomeWeirdHeaderField") + # h #=> nil def new_from_port( port, name, conf = DEFAULT_CONFIG ) - re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i') + if name == "EnvelopeSender" + name = "From" + re = Regexp.new('\A(From) ', 'i') + else + re = Regexp.new('\A(' + Regexp.quote(name) + '):', 'i') + end str = nil port.ropen {|f| f.each do |line| @@ -66,7 +91,7 @@ module TMail end end } - new(name, str, Config.to_config(conf)) + new(name, str, Config.to_config(conf)) if str end def internal_new( name, conf ) @@ -182,7 +207,11 @@ module TMail def comments ensure_parsed - @comments + if @comments[0] + [Decoder.decode(@comments[0])] + else + @comments + end end private diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/index.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/index.rb new file mode 100644 index 00000000..554e2fd6 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/index.rb @@ -0,0 +1,9 @@ +#:stopdoc: +# This is here for Rolls. +# Rolls uses this instead of lib/tmail.rb. + +require 'tmail/version' +require 'tmail/mail' +require 'tmail/mailbox' +require 'tmail/core_extensions' +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/interface.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/interface.rb new file mode 100644 index 00000000..a6d428d7 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/interface.rb @@ -0,0 +1,1130 @@ +=begin rdoc + += interface.rb Provides an interface to the TMail object + +=end +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +# TMail::Mail objects get accessed primarily through the methods in this file. +# +# + +require 'tmail/utils' + +module TMail + + class Mail + + # Allows you to query the mail object with a string to get the contents + # of the field you want. + # + # Returns a string of the exact contnts of the field + # + # mail.from = "mikel " + # mail.header_string("From") #=> "mikel " + def header_string( name, default = nil ) + h = @header[name.downcase] or return default + h.to_s + end + + #:stopdoc: + #-- + #== Attributes + + include TextUtils + + def set_string_array_attr( key, strs ) + strs.flatten! + if strs.empty? + @header.delete key.downcase + else + store key, strs.join(', ') + end + strs + end + private :set_string_array_attr + + def set_string_attr( key, str ) + if str + store key, str + else + @header.delete key.downcase + end + str + end + private :set_string_attr + + def set_addrfield( name, arg ) + if arg + h = HeaderField.internal_new(name, @config) + h.addrs.replace [arg].flatten + @header[name] = h + else + @header.delete name + end + arg + end + private :set_addrfield + + def addrs2specs( addrs ) + return nil unless addrs + list = addrs.map {|addr| + if addr.address_group? + then addr.map {|a| a.spec } + else addr.spec + end + }.flatten + return nil if list.empty? + list + end + private :addrs2specs + + #:startdoc: + + #== Date and Time methods + + # Returns the date of the email message as per the "date" header value or returns + # nil by default (if no date field exists). + # + # You can also pass whatever default you want into this method and it will return + # that instead of nil if there is no date already set. + def date( default = nil ) + if h = @header['date'] + h.date + else + default + end + end + + # Destructively sets the date of the mail object with the passed Time instance, + # returns a Time instance set to the date/time of the mail + # + # Example: + # + # now = Time.now + # mail.date = now + # mail.date #=> Sat Nov 03 18:47:50 +1100 2007 + # mail.date.class #=> Time + def date=( time ) + if time + store 'Date', time2str(time) + else + @header.delete 'date' + end + time + end + + # Returns the time of the mail message formatted to your taste using a + # strftime format string. If no date set returns nil by default or whatever value + # you pass as the second optional parameter. + # + # time = Time.now # (on Nov 16 2007) + # mail.date = time + # mail.strftime("%D") #=> "11/16/07" + def strftime( fmt, default = nil ) + if t = date + t.strftime(fmt) + else + default + end + end + + #== Destination methods + + # Return a TMail::Addresses instance for each entry in the "To:" field of the mail object header. + # + # If the "To:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.to_addrs #=> nil + # mail.to_addrs([]) #=> [] + # mail.to = "Mikel , another Mikel " + # mail.to_addrs #=> [#, #] + def to_addrs( default = nil ) + if h = @header['to'] + h.addrs + else + default + end + end + + # Return a TMail::Addresses instance for each entry in the "Cc:" field of the mail object header. + # + # If the "Cc:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc_addrs #=> nil + # mail.cc_addrs([]) #=> [] + # mail.cc = "Mikel , another Mikel " + # mail.cc_addrs #=> [#, #] + def cc_addrs( default = nil ) + if h = @header['cc'] + h.addrs + else + default + end + end + + # Return a TMail::Addresses instance for each entry in the "Bcc:" field of the mail object header. + # + # If the "Bcc:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc_addrs #=> nil + # mail.bcc_addrs([]) #=> [] + # mail.bcc = "Mikel , another Mikel " + # mail.bcc_addrs #=> [#, #] + def bcc_addrs( default = nil ) + if h = @header['bcc'] + h.addrs + else + default + end + end + + # Destructively set the to field of the "To:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.to = "Mikel , another Mikel " + # mail.to_addrs #=> [#, #] + def to_addrs=( arg ) + set_addrfield 'to', arg + end + + # Destructively set the to field of the "Cc:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc = "Mikel , another Mikel " + # mail.cc_addrs #=> [#, #] + def cc_addrs=( arg ) + set_addrfield 'cc', arg + end + + # Destructively set the to field of the "Bcc:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc = "Mikel , another Mikel " + # mail.bcc_addrs #=> [#, #] + def bcc_addrs=( arg ) + set_addrfield 'bcc', arg + end + + # Returns who the email is to as an Array of email addresses as opposed to an Array of + # TMail::Address objects which is what Mail#to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.to = "Mikel , another Mikel " + # mail.to #=> ["mikel@me.org", "mikel@you.org"] + def to( default = nil ) + addrs2specs(to_addrs(nil)) || default + end + + # Returns who the email cc'd as an Array of email addresses as opposed to an Array of + # TMail::Address objects which is what Mail#to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc = "Mikel , another Mikel " + # mail.cc #=> ["mikel@me.org", "mikel@you.org"] + def cc( default = nil ) + addrs2specs(cc_addrs(nil)) || default + end + + # Returns who the email bcc'd as an Array of email addresses as opposed to an Array of + # TMail::Address objects which is what Mail#to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc = "Mikel , another Mikel " + # mail.bcc #=> ["mikel@me.org", "mikel@you.org"] + def bcc( default = nil ) + addrs2specs(bcc_addrs(nil)) || default + end + + # Destructively sets the "To:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.to = ["mikel@abc.com", "Mikel "] + # mail.to #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['to'].to_s #=> "mikel@abc.com, Mikel " + def to=( *strs ) + set_string_array_attr 'To', strs + end + + # Destructively sets the "Cc:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.cc = ["mikel@abc.com", "Mikel "] + # mail.cc #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['cc'].to_s #=> "mikel@abc.com, Mikel " + def cc=( *strs ) + set_string_array_attr 'Cc', strs + end + + # Destructively sets the "Bcc:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.bcc = ["mikel@abc.com", "Mikel "] + # mail.bcc #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['bcc'].to_s #=> "mikel@abc.com, Mikel " + def bcc=( *strs ) + set_string_array_attr 'Bcc', strs + end + + #== Originator methods + + # Return a TMail::Addresses instance for each entry in the "From:" field of the mail object header. + # + # If the "From:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.from_addrs #=> nil + # mail.from_addrs([]) #=> [] + # mail.from = "Mikel , another Mikel " + # mail.from_addrs #=> [#, #] + def from_addrs( default = nil ) + if h = @header['from'] + h.addrs + else + default + end + end + + # Destructively set the to value of the "From:" header to equal the passed in string. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.from_addrs = "Mikel , another Mikel " + # mail.from_addrs #=> [#, #] + def from_addrs=( arg ) + set_addrfield 'from', arg + end + + # Returns who the email is from as an Array of email address strings instead to an Array of + # TMail::Address objects which is what Mail#from_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.from = "Mikel , another Mikel " + # mail.from #=> ["mikel@me.org", "mikel@you.org"] + def from( default = nil ) + addrs2specs(from_addrs(nil)) || default + end + + # Destructively sets the "From:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.from = ["mikel@abc.com", "Mikel "] + # mail.from #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['from'].to_s #=> "mikel@abc.com, Mikel " + def from=( *strs ) + set_string_array_attr 'From', strs + end + + # Returns the "friendly" human readable part of the address + # + # Example: + # + # mail = TMail::Mail.new + # mail.from = "Mikel Lindsaar " + # mail.friendly_from #=> "Mikel Lindsaar" + def friendly_from( default = nil ) + h = @header['from'] + a, = h.addrs + return default unless a + return a.phrase if a.phrase + return h.comments.join(' ') unless h.comments.empty? + a.spec + end + + # Return a TMail::Addresses instance for each entry in the "Reply-To:" field of the mail object header. + # + # If the "Reply-To:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to_addrs #=> nil + # mail.reply_to_addrs([]) #=> [] + # mail.reply_to = "Mikel , another Mikel " + # mail.reply_to_addrs #=> [#, #] + def reply_to_addrs( default = nil ) + if h = @header['reply-to'] + h.addrs.blank? ? default : h.addrs + else + default + end + end + + # Destructively set the to value of the "Reply-To:" header to equal the passed in argument. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to_addrs = "Mikel , another Mikel " + # mail.reply_to_addrs #=> [#, #] + def reply_to_addrs=( arg ) + set_addrfield 'reply-to', arg + end + + # Returns who the email is from as an Array of email address strings instead to an Array of + # TMail::Address objects which is what Mail#reply_to_addrs returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to = "Mikel , another Mikel " + # mail.reply_to #=> ["mikel@me.org", "mikel@you.org"] + def reply_to( default = nil ) + addrs2specs(reply_to_addrs(nil)) || default + end + + # Destructively sets the "Reply-To:" field to the passed array of strings (which should be valid + # email addresses) + # + # Example: + # + # mail = TMail::Mail.new + # mail.reply_to = ["mikel@abc.com", "Mikel "] + # mail.reply_to #=> ["mikel@abc.org", "mikel@xyz.org"] + # mail['reply_to'].to_s #=> "mikel@abc.com, Mikel " + def reply_to=( *strs ) + set_string_array_attr 'Reply-To', strs + end + + # Return a TMail::Addresses instance of the "Sender:" field of the mail object header. + # + # If the "Sender:" field does not exist, will return nil by default or the value you + # pass as the optional parameter. + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender #=> nil + # mail.sender([]) #=> [] + # mail.sender = "Mikel " + # mail.reply_to_addrs #=> [#] + def sender_addr( default = nil ) + f = @header['sender'] or return default + f.addr or return default + end + + # Destructively set the to value of the "Sender:" header to equal the passed in argument. + # + # TMail will parse your contents and turn each valid email address into a TMail::Address + # object before assigning it to the mail message. + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender_addrs = "Mikel , another Mikel " + # mail.sender_addrs #=> [#, #] + def sender_addr=( addr ) + if addr + h = HeaderField.internal_new('sender', @config) + h.addr = addr + @header['sender'] = h + else + @header.delete 'sender' + end + addr + end + + # Returns who the sender of this mail is as string instead to an Array of + # TMail::Address objects which is what Mail#sender_addr returns + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender = "Mikel " + # mail.sender #=> "mikel@me.org" + def sender( default = nil ) + f = @header['sender'] or return default + a = f.addr or return default + a.spec + end + + # Destructively sets the "Sender:" field to the passed string (which should be a valid + # email address) + # + # Example: + # + # mail = TMail::Mail.new + # mail.sender = "mikel@abc.com" + # mail.sender #=> "mikel@abc.org" + # mail['sender'].to_s #=> "mikel@abc.com" + def sender=( str ) + set_string_attr 'Sender', str + end + + #== Subject methods + + # Returns the subject of the mail instance. + # + # If the subject field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.subject #=> nil + # mail.subject("") #=> "" + # mail.subject = "Hello" + # mail.subject #=> "Hello" + def subject( default = nil ) + if h = @header['subject'] + h.body + else + default + end + end + alias quoted_subject subject + + # Destructively sets the passed string as the subject of the mail message. + # + # Example + # + # mail = TMail::Mail.new + # mail.subject #=> "This subject" + # mail.subject = "Another subject" + # mail.subject #=> "Another subject" + def subject=( str ) + set_string_attr 'Subject', str + end + + #== Message Identity & Threading Methods + + # Returns the message ID for this mail object instance. + # + # If the message_id field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.message_id #=> nil + # mail.message_id(TMail.new_message_id) #=> "<47404c5326d9c_2ad4fbb80161@baci.local.tmail>" + # mail.message_id = TMail.new_message_id + # mail.message_id #=> "<47404c5326d9c_2ad4fbb80161@baci.local.tmail>" + def message_id( default = nil ) + if h = @header['message-id'] + h.id || default + else + default + end + end + + # Destructively sets the message ID of the mail object instance to the passed in string + # + # Invalid message IDs are ignored (silently, unless configured otherwise) and result in + # a nil message ID. Left and right angle brackets are required. + # + # Example: + # + # mail = TMail::Mail.new + # mail.message_id = "<348F04F142D69C21-291E56D292BC@xxxx.net>" + # mail.message_id #=> "<348F04F142D69C21-291E56D292BC@xxxx.net>" + # mail.message_id = "this_is_my_badly_formatted_message_id" + # mail.message_id #=> nil + def message_id=( str ) + set_string_attr 'Message-Id', str + end + + # Returns the "In-Reply-To:" field contents as an array of this mail instance if it exists + # + # If the in_reply_to field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.in_reply_to #=> nil + # mail.in_reply_to([]) #=> [] + # TMail::Mail.load("../test/fixtures/raw_email_reply") + # mail.in_reply_to #=> ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def in_reply_to( default = nil ) + if h = @header['in-reply-to'] + h.ids + else + default + end + end + + # Destructively sets the value of the "In-Reply-To:" field of an email. + # + # Accepts an array of a single string of a message id + # + # Example: + # + # mail = TMail::Mail.new + # mail.in_reply_to = ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + # mail.in_reply_to #=> ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def in_reply_to=( *idstrs ) + set_string_array_attr 'In-Reply-To', idstrs + end + + # Returns the references of this email (prior messages relating to this message) + # as an array of message ID strings. Useful when you are trying to thread an + # email. + # + # If the references field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.references #=> nil + # mail.references([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email_reply") + # mail.references #=> ["<473FF3B8.9020707@xxx.org>", "<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def references( default = nil ) + if h = @header['references'] + h.refs + else + default + end + end + + # Destructively sets the value of the "References:" field of an email. + # + # Accepts an array of strings of message IDs + # + # Example: + # + # mail = TMail::Mail.new + # mail.references = ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + # mail.references #=> ["<348F04F142D69C21-291E56D292BC@xxxx.net>"] + def references=( *strs ) + set_string_array_attr 'References', strs + end + + #== MIME header methods + + # Returns the listed MIME version of this email from the "Mime-Version:" header field + # + # If the mime_version field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.mime_version #=> nil + # mail.mime_version([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.mime_version #=> "1.0" + def mime_version( default = nil ) + if h = @header['mime-version'] + h.version || default + else + default + end + end + + def mime_version=( m, opt = nil ) + if opt + if h = @header['mime-version'] + h.major = m + h.minor = opt + else + store 'Mime-Version', "#{m}.#{opt}" + end + else + store 'Mime-Version', m + end + m + end + + # Returns the current "Content-Type" of the mail instance. + # + # If the content_type field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.content_type #=> nil + # mail.content_type([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.content_type #=> "text/plain" + def content_type( default = nil ) + if h = @header['content-type'] + h.content_type || default + else + default + end + end + + # Returns the current main type of the "Content-Type" of the mail instance. + # + # If the content_type field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.main_type #=> nil + # mail.main_type([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.main_type #=> "text" + def main_type( default = nil ) + if h = @header['content-type'] + h.main_type || default + else + default + end + end + + # Returns the current sub type of the "Content-Type" of the mail instance. + # + # If the content_type field does not exist, returns nil by default or you can pass in as + # the parameter for what you want the default value to be. + # + # Example: + # + # mail = TMail::Mail.new + # mail.sub_type #=> nil + # mail.sub_type([]) #=> [] + # mail = TMail::Mail.load("../test/fixtures/raw_email") + # mail.sub_type #=> "plain" + def sub_type( default = nil ) + if h = @header['content-type'] + h.sub_type || default + else + default + end + end + + # Destructively sets the "Content-Type:" header field of this mail object + # + # Allows you to set the main type, sub type as well as parameters to the field. + # The main type and sub type need to be a string. + # + # The optional params hash can be passed with keys as symbols and values as a string, + # or strings as keys and values. + # + # Example: + # + # mail = TMail::Mail.new + # mail.set_content_type("text", "plain") + # mail.to_s #=> "Content-Type: text/plain\n\n" + # + # mail.set_content_type("text", "plain", {:charset => "EUC-KR", :format => "flowed"}) + # mail.to_s #=> "Content-Type: text/plain; charset=EUC-KR; format=flowed\n\n" + # + # mail.set_content_type("text", "plain", {"charset" => "EUC-KR", "format" => "flowed"}) + # mail.to_s #=> "Content-Type: text/plain; charset=EUC-KR; format=flowed\n\n" + def set_content_type( str, sub = nil, param = nil ) + if sub + main, sub = str, sub + else + main, sub = str.split(%r, 2) + raise ArgumentError, "sub type missing: #{str.inspect}" unless sub + end + if h = @header['content-type'] + h.main_type = main + h.sub_type = sub + h.params.clear + else + store 'Content-Type', "#{main}/#{sub}" + end + @header['content-type'].params.replace param if param + str + end + + alias content_type= set_content_type + + # Returns the named type parameter as a string, from the "Content-Type:" header. + # + # Example: + # + # mail = TMail::Mail.new + # mail.type_param("charset") #=> nil + # mail.type_param("charset", []) #=> [] + # mail.set_content_type("text", "plain", {:charset => "EUC-KR", :format => "flowed"}) + # mail.type_param("charset") #=> "EUC-KR" + # mail.type_param("format") #=> "flowed" + def type_param( name, default = nil ) + if h = @header['content-type'] + h[name] || default + else + default + end + end + + # Returns the character set of the email. Returns nil if no encoding set or returns + # whatever default you pass as a parameter - note passing the parameter does NOT change + # the mail object in any way. + # + # Example: + # + # mail = TMail::Mail.load("path_to/utf8_email") + # mail.charset #=> "UTF-8" + # + # mail = TMail::Mail.new + # mail.charset #=> nil + # mail.charset("US-ASCII") #=> "US-ASCII" + def charset( default = nil ) + if h = @header['content-type'] + h['charset'] or default + else + default + end + end + + # Destructively sets the character set used by this mail object to the passed string, you + # should note though that this does nothing to the mail body, just changes the header + # value, you will need to transliterate the body as well to match whatever you put + # in this header value if you are changing character sets. + # + # Example: + # + # mail = TMail::Mail.new + # mail.charset #=> nil + # mail.charset = "UTF-8" + # mail.charset #=> "UTF-8" + def charset=( str ) + if str + if h = @header[ 'content-type' ] + h['charset'] = str + else + store 'Content-Type', "text/plain; charset=#{str}" + end + end + str + end + + # Returns the transfer encoding of the email. Returns nil if no encoding set or returns + # whatever default you pass as a parameter - note passing the parameter does NOT change + # the mail object in any way. + # + # Example: + # + # mail = TMail::Mail.load("path_to/base64_encoded_email") + # mail.transfer_encoding #=> "base64" + # + # mail = TMail::Mail.new + # mail.transfer_encoding #=> nil + # mail.transfer_encoding("base64") #=> "base64" + def transfer_encoding( default = nil ) + if h = @header['content-transfer-encoding'] + h.encoding || default + else + default + end + end + + # Destructively sets the transfer encoding of the mail object to the passed string, you + # should note though that this does nothing to the mail body, just changes the header + # value, you will need to encode or decode the body as well to match whatever you put + # in this header value. + # + # Example: + # + # mail = TMail::Mail.new + # mail.transfer_encoding #=> nil + # mail.transfer_encoding = "base64" + # mail.transfer_encoding #=> "base64" + def transfer_encoding=( str ) + set_string_attr 'Content-Transfer-Encoding', str + end + + alias encoding transfer_encoding + alias encoding= transfer_encoding= + alias content_transfer_encoding transfer_encoding + alias content_transfer_encoding= transfer_encoding= + + # Returns the content-disposition of the mail object, returns nil or the passed + # default value if given + # + # Example: + # + # mail = TMail::Mail.load("path_to/raw_mail_with_attachment") + # mail.disposition #=> "attachment" + # + # mail = TMail::Mail.load("path_to/plain_simple_email") + # mail.disposition #=> nil + # mail.disposition(false) #=> false + def disposition( default = nil ) + if h = @header['content-disposition'] + h.disposition || default + else + default + end + end + + alias content_disposition disposition + + # Allows you to set the content-disposition of the mail object. Accepts a type + # and a hash of parameters. + # + # Example: + # + # mail.set_disposition("attachment", {:filename => "test.rb"}) + # mail.disposition #=> "attachment" + # mail['content-disposition'].to_s #=> "attachment; filename=test.rb" + def set_disposition( str, params = nil ) + if h = @header['content-disposition'] + h.disposition = str + h.params.clear + else + store('Content-Disposition', str) + h = @header['content-disposition'] + end + h.params.replace params if params + end + + alias disposition= set_disposition + alias set_content_disposition set_disposition + alias content_disposition= set_disposition + + # Returns the value of a parameter in an existing content-disposition header + # + # Example: + # + # mail.set_disposition("attachment", {:filename => "test.rb"}) + # mail['content-disposition'].to_s #=> "attachment; filename=test.rb" + # mail.disposition_param("filename") #=> "test.rb" + # mail.disposition_param("missing_param_key") #=> nil + # mail.disposition_param("missing_param_key", false) #=> false + # mail.disposition_param("missing_param_key", "Nothing to see here") #=> "Nothing to see here" + def disposition_param( name, default = nil ) + if h = @header['content-disposition'] + h[name] || default + else + default + end + end + + # Convert the Mail object's body into a Base64 encoded email + # returning the modified Mail object + def base64_encode! + store 'Content-Transfer-Encoding', 'Base64' + self.body = base64_encode + end + + # Return the result of encoding the TMail::Mail object body + # without altering the current body + def base64_encode + Base64.folding_encode(self.body) + end + + # Convert the Mail object's body into a Base64 decoded email + # returning the modified Mail object + def base64_decode! + if /base64/i === self.transfer_encoding('') + store 'Content-Transfer-Encoding', '8bit' + self.body = base64_decode + end + end + + # Returns the result of decoding the TMail::Mail object body + # without altering the current body + def base64_decode + Base64.decode(self.body, @config.strict_base64decode?) + end + + # Returns an array of each destination in the email message including to: cc: or bcc: + # + # Example: + # + # mail.to = "Mikel " + # mail.cc = "Trans " + # mail.bcc = "bob " + # mail.destinations #=> ["mikel@lindsaar.net", "t@t.com", "bob@me.com"] + def destinations( default = nil ) + ret = [] + %w( to cc bcc ).each do |nm| + if h = @header[nm] + h.addrs.each {|i| ret.push i.address } + end + end + ret.empty? ? default : ret + end + + # Yields a block of destination, yielding each as a string. + # (from the destinations example) + # mail.each_destination { |d| puts "#{d.class}: #{d}" } + # String: mikel@lindsaar.net + # String: t@t.com + # String: bob@me.com + def each_destination( &block ) + destinations([]).each do |i| + if Address === i + yield i + else + i.each(&block) + end + end + end + + alias each_dest each_destination + + # Returns an array of reply to addresses that the Mail object has, + # or if the Mail message has no reply-to, returns an array of the + # Mail objects from addresses. Else returns the default which can + # either be passed as a parameter or defaults to nil + # + # Example: + # mail.from = "Mikel " + # mail.reply_to = nil + # mail.reply_addresses #=> [""] + # + def reply_addresses( default = nil ) + reply_to_addrs(nil) or from_addrs(nil) or default + end + + # Returns the "sender" field as an array -> useful to find out who to + # send an error email to. + def error_reply_addresses( default = nil ) + if s = sender(nil) + [s] + else + from_addrs(default) + end + end + + # Returns true if the Mail object is a multipart message + def multipart? + main_type('').downcase == 'multipart' + end + + # Creates a new email in reply to self. Sets the In-Reply-To and + # References headers for you automagically. + # + # Example: + # mail = TMail::Mail.load("my_email") + # reply_email = mail.create_reply + # reply_email.class #=> TMail::Mail + # reply_email.references #=> [""] + # reply_email.in_reply_to #=> [""] + def create_reply + setup_reply create_empty_mail() + end + + # Creates a new email in reply to self. Sets the In-Reply-To and + # References headers for you automagically. + # + # Example: + # mail = TMail::Mail.load("my_email") + # forward_email = mail.create_forward + # forward_email.class #=> TMail::Mail + # forward_email.content_type #=> "multipart/mixed" + # forward_email.body #=> "Attachment: (unnamed)" + # forward_email.encoded #=> Returns the original email as a MIME attachment + def create_forward + setup_forward create_empty_mail() + end + + #:stopdoc: + private + + def create_empty_mail + self.class.new(StringPort.new(''), @config) + end + + def setup_reply( mail ) + if tmp = reply_addresses(nil) + mail.to_addrs = tmp + end + + mid = message_id(nil) + tmp = references(nil) || [] + tmp.push mid if mid + mail.in_reply_to = [mid] if mid + mail.references = tmp unless tmp.empty? + mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '') + mail.mime_version = '1.0' + mail + end + + def setup_forward( mail ) + m = Mail.new(StringPort.new('')) + m.body = decoded + m.set_content_type 'message', 'rfc822' + m.encoding = encoding('7bit') + mail.parts.push m + # call encoded to reparse the message + mail.encoded + mail + end + + #:startdoc: + end # class Mail + +end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/loader.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/loader.rb old mode 100755 new mode 100644 similarity index 52% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/loader.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/loader.rb index 79073154..6c0e2511 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/loader.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/loader.rb @@ -1 +1,3 @@ +#:stopdoc: require 'tmail/mailbox' +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mail.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mail.rb old mode 100755 new mode 100644 similarity index 68% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mail.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mail.rb index d10275b7..5a319907 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mail.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mail.rb @@ -29,6 +29,8 @@ # with permission of Minero Aoki. #++ + + require 'tmail/interface' require 'tmail/encode' require 'tmail/header' @@ -41,23 +43,89 @@ require 'socket' module TMail + # == Mail Class + # + # Accessing a TMail object done via the TMail::Mail class. As email can be fairly complex + # creatures, you will find a large amount of accessor and setter methods in this class! + # + # Most of the below methods handle the header, in fact, what TMail does best is handle the + # header of the email object. There are only a few methods that deal directly with the body + # of the email, such as base64_encode and base64_decode. + # + # === Using TMail inside your code + # + # The usual way is to install the gem (see the {README}[link:/README] on how to do this) and + # then put at the top of your class: + # + # require 'tmail' + # + # You can then create a new TMail object in your code with: + # + # @email = TMail::Mail.new + # + # Or if you have an email as a string, you can initialize a new TMail::Mail object and get it + # to parse that string for you like so: + # + # @email = TMail::Mail.parse(email_text) + # + # You can also read a single email off the disk, for example: + # + # @email = TMail::Mail.load('filename.txt') + # + # Also, you can read a mailbox (usual unix mbox format) and end up with an array of TMail + # objects by doing something like this: + # + # # Note, we pass true as the last variable to open the mailbox read only + # mailbox = TMail::UNIXMbox.new("mailbox", nil, true) + # @emails = [] + # mailbox.each_port { |m| @emails << TMail::Mail.new(m) } + # class Mail class << self + + # Opens an email that has been saved out as a file by itself. + # + # This function will read a file non-destructively and then parse + # the contents and return a TMail::Mail object. + # + # Does not handle multiple email mailboxes (like a unix mbox) for that + # use the TMail::UNIXMbox class. + # + # Example: + # mail = TMail::Mail.load('filename') + # def load( fname ) new(FilePort.new(fname)) end alias load_from load alias loadfrom load - + + # Parses an email from the supplied string and returns a TMail::Mail + # object. + # + # Example: + # require 'rubygems'; require 'tmail' + # email_string =< # bodyport=nil> + # mail.body + # #=> "Hello there Mikel!\n\n" def parse( str ) new(StringPort.new(str)) end end - def initialize( port = nil, conf = DEFAULT_CONFIG ) + def initialize( port = nil, conf = DEFAULT_CONFIG ) #:nodoc: @port = port || StringPort.new @config = Config.to_config(conf) @@ -73,6 +141,12 @@ module TMail } end + # Provides access to the port this email is using to hold it's data + # + # Example: + # mail = TMail::Mail.parse(email_string) + # mail.port + # #=> # attr_reader :port def inspect @@ -162,6 +236,14 @@ module TMail @header.dup end + # Returns a TMail::AddressHeader object of the field you are querying. + # Examples: + # @mail['from'] #=> # + # @mail['to'] #=> # + # + # You can get the string value of this by passing "to_s" to the query: + # Example: + # @mail['to'].to_s #=> "mikel@test.com.au" def []( key ) @header[key.downcase] end @@ -172,6 +254,19 @@ module TMail alias fetch [] + # Allows you to set or delete TMail header objects at will. + # Eamples: + # @mail = TMail::Mail.new + # @mail['to'].to_s # => 'mikel@test.com.au' + # @mail['to'] = 'mikel@elsewhere.org' + # @mail['to'].to_s # => 'mikel@elsewhere.org' + # @mail.encoded # => "To: mikel@elsewhere.org\r\n\r\n" + # @mail['to'] = nil + # @mail['to'].to_s # => nil + # @mail.encoded # => "\r\n" + # + # Note: setting mail[] = nil actualy deletes the header field in question from the object, + # it does not just set the value of the hash to nil def []=( key, val ) dkey = key.downcase @@ -203,7 +298,14 @@ module TMail end alias store []= - + + # Allows you to loop through each header in the TMail::Mail object in a block + # Example: + # @mail['to'] = 'mikel@elsewhere.org' + # @mail['from'] = 'me@me.com' + # @mail.each_header { |k,v| puts "#{k} = #{v}" } + # # => from = me@me.com + # # => to = mikel@elsewhere.org def each_header @header.each do |key, val| [val].flatten.each {|v| yield key, v } @@ -306,8 +408,8 @@ module TMail when /\AFrom (\S+)/ unixfrom = $1 - when /^charset=.*/ - + when /^charset=.*/ + else raise SyntaxError, "wrong mail header: '#{line.inspect}'" end @@ -350,10 +452,12 @@ module TMail end def quoted_body - parse_body - @body_port.ropen {|f| - return f.read - } + body_port.ropen {|f| return f.read } + end + + def quoted_body= str + body_port.wopen { |f| f.write str } + str end def body=( str ) @@ -375,8 +479,8 @@ module TMail str end - alias preamble body - alias preamble= body= + alias preamble quoted_body + alias preamble= quoted_body= def epilogue parse_body @@ -397,6 +501,18 @@ module TMail def each_part( &block ) parts().each(&block) end + + # Returns true if the content type of this part of the email is + # a disposition attachment + def disposition_is_attachment? + (self['content-disposition'] && self['content-disposition'].disposition == "attachment") + end + + # Returns true if this part's content main type is text, else returns false. + # By main type is meant "text/plain" is text. "text/html" is text + def content_type_is_text? + self.header['content-type'] && (self.header['content-type'].main_type != "text") + end private diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mailbox.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mailbox.rb old mode 100755 new mode 100644 similarity index 80% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mailbox.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mailbox.rb index bb7a460e..b0bc6a7f --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mailbox.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mailbox.rb @@ -150,9 +150,78 @@ module TMail class UNIXMbox + class << self + alias newobj new + end + + # Creates a new mailbox object that you can iterate through to collect the + # emails from with "each_port". + # + # You need to pass it a filename of a unix mailbox format file, the format of this + # file can be researched at this page at {wikipedia}[link:http://en.wikipedia.org/wiki/Mbox] + # + # ==== Parameters + # + # +filename+: The filename of the mailbox you want to open + # + # +tmpdir+: Can be set to override TMail using the system environment's temp dir. TMail will first + # use the temp dir specified by you (if any) or then the temp dir specified in the Environment's TEMP + # value then the value in the Environment's TMP value or failing all of the above, '/tmp' + # + # +readonly+: If set to false, each email you take from the mail box will be removed from the mailbox. + # default is *false* - ie, it *WILL* truncate your mailbox file to ZERO once it has read the emails out. + # + # ==== Options: + # + # None + # + # ==== Examples: + # + # # First show using readonly true: + # + # require 'ftools' + # File.size("../test/fixtures/mailbox") + # #=> 20426 + # + # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox", nil, true) + # #=> # + # + # mailbox.each_port do |port| + # mail = TMail::Mail.new(port) + # puts mail.subject + # end + # #Testing mailbox 1 + # #Testing mailbox 2 + # #Testing mailbox 3 + # #Testing mailbox 4 + # require 'ftools' + # File.size?("../test/fixtures/mailbox") + # #=> 20426 + # + # # Now show with readonly set to the default false + # + # mailbox = TMail::UNIXMbox.new("../test/fixtures/mailbox") + # #=> # + # + # mailbox.each_port do |port| + # mail = TMail::Mail.new(port) + # puts mail.subject + # end + # #Testing mailbox 1 + # #Testing mailbox 2 + # #Testing mailbox 3 + # #Testing mailbox 4 + # + # File.size?("../test/fixtures/mailbox") + # #=> nil + def UNIXMbox.new( filename, tmpdir = nil, readonly = false ) + tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp' + newobj(filename, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false) + end + def UNIXMbox.lock( fname ) begin - f = File.open(fname) + f = File.open(fname, 'r+') f.flock File::LOCK_EX yield f ensure @@ -161,15 +230,6 @@ module TMail end end - class << self - alias newobj new - end - - def UNIXMbox.new( fname, tmpdir = nil, readonly = false ) - tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp' - newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false) - end - def UNIXMbox.static_new( fname, dir, readonly = false ) newobj(fname, dir, readonly, true) end @@ -213,13 +273,13 @@ module TMail fromaddr(), TextUtils.time2str(File.mtime(port.filename)) end - def UNIXMbox.fromaddr + def UNIXMbox.fromaddr(port) h = HeaderField.new_from_port(port, 'Return-Path') || - HeaderField.new_from_port(port, 'From') or return 'nobody' + HeaderField.new_from_port(port, 'From') || + HeaderField.new_from_port(port, 'EnvelopeSender') or return 'nobody' a = h.addrs[0] or return 'nobody' a.spec end - private_class_method :fromaddr def close return if @closed diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/main.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/main.rb new file mode 100644 index 00000000..e5277279 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/main.rb @@ -0,0 +1,6 @@ +#:stopdoc: +require 'tmail/version' +require 'tmail/mail' +require 'tmail/mailbox' +require 'tmail/core_extensions' +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mbox.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mbox.rb old mode 100755 new mode 100644 similarity index 52% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mbox.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mbox.rb index 79073154..6c0e2511 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/mbox.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/mbox.rb @@ -1 +1,3 @@ +#:stopdoc: require 'tmail/mailbox' +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/net.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/net.rb old mode 100755 new mode 100644 similarity index 86% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/net.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/net.rb index 50b1dd95..65147228 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/net.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/net.rb @@ -1,8 +1,3 @@ -=begin rdoc - -= Net provides SMTP wrapping - -=end #-- # Copyright (c) 1998-2003 Minero Aoki # @@ -29,13 +24,14 @@ # with permission of Minero Aoki. #++ +#:stopdoc: require 'nkf' - +#:startdoc: module TMail class Mail - + def send_to( smtp ) do_send_to(smtp) do ready_to_send @@ -128,45 +124,10 @@ module TMail 'using C.T.Encoding with multipart mail is not permitted' end end - - def create_empty_mail - self.class.new(StringPort.new(''), @config) - end - - def create_reply - setup_reply create_empty_mail() - end - - def setup_reply( m ) - if tmp = reply_addresses(nil) - m.to_addrs = tmp - end - - mid = message_id(nil) - tmp = references(nil) || [] - tmp.push mid if mid - m.in_reply_to = [mid] if mid - m.references = tmp unless tmp.empty? - m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '') - - m - end - - def create_forward - setup_forward create_empty_mail() - end - - def setup_forward( mail ) - m = Mail.new(StringPort.new('')) - m.body = decoded - m.set_content_type 'message', 'rfc822' - m.encoding = encoding('7bit') - mail.parts.push m - end end - + #:stopdoc: class DeleteFields NOSEND_FIELDS = %w( @@ -190,8 +151,9 @@ module TMail end end + #:startdoc: - + #:stopdoc: class AddMessageId def initialize( fqdn = nil ) @@ -205,8 +167,9 @@ module TMail end end + #:startdoc: - + #:stopdoc: class AddDate def exec( mail ) @@ -214,8 +177,9 @@ module TMail end end + #:startdoc: - + #:stopdoc: class MimeEncodeAuto def initialize( s = nil, m = nil ) @@ -233,8 +197,9 @@ module TMail end end - + #:startdoc: + #:stopdoc: class MimeEncodeSingle def exec( mail ) @@ -260,8 +225,9 @@ module TMail end end - - + #:startdoc: + + #:stopdoc: class MimeEncodeMulti def exec( mail, top = true ) @@ -278,5 +244,5 @@ module TMail end end - + #:startdoc: end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/obsolete.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/obsolete.rb old mode 100755 new mode 100644 similarity index 94% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/obsolete.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/obsolete.rb index b871510b..22b0a126 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/obsolete.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/obsolete.rb @@ -2,6 +2,9 @@ = Obsolete methods that are depriciated +If you really want to see them, go to lib/tmail/obsolete.rb and view to your +heart's content. + =end #-- # Copyright (c) 1998-2003 Minero Aoki @@ -28,10 +31,9 @@ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails # with permission of Minero Aoki. #++ +#:stopdoc: +module TMail #:nodoc: -module TMail - - # mail.rb class Mail alias include? key? alias has_key? key? @@ -51,8 +53,6 @@ module TMail alias has_value? value? end - - # facade.rb class Mail def from_addr( default = nil ) addr, = from_addrs(nil) @@ -83,13 +83,11 @@ module TMail alias each_dest each_destination end - - # address.rb class Address alias route routes alias addr spec - def spec=( str ) + def spec=( str ) @local, @domain = str.split(/@/,2).map {|s| s.split(/\./) } end @@ -97,8 +95,6 @@ module TMail alias address= spec= end - - # mbox.rb class MhMailbox alias new_mail new_port alias each_mail each_port @@ -115,8 +111,6 @@ module TMail alias each_newmail each_new_port end - - # utils.rb extend TextUtils class << self @@ -135,3 +129,4 @@ module TMail end end # module TMail +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb old mode 100755 new mode 100644 similarity index 99% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb index 5deb0ff6..ab1a8284 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/parser.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb @@ -1,3 +1,4 @@ +#:stopdoc: # DO NOT MODIFY!!!! # This file is automatically generated by racc 1.4.5 # from racc grammer file "parser.y". diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/port.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/port.rb old mode 100755 new mode 100644 similarity index 100% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/port.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/port.rb diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/quoting.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/quoting.rb similarity index 82% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/quoting.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/quoting.rb index 0b2d11c3..cb9f4288 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/quoting.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/quoting.rb @@ -13,12 +13,12 @@ module TMail from_charset = sub_header("content-type", "charset") case (content_transfer_encoding || "7bit").downcase when "quoted-printable" - # the default charset is set to iso-8859-1 instead of 'us-ascii'. + # the default charset is set to iso-8859-1 instead of 'us-ascii'. # This is needed as many mailer do not set the charset but send in ISO. This is only used if no charset is set. if !from_charset.blank? && from_charset.downcase == 'us-ascii' from_charset = 'iso-8859-1' end - + Unquoter.unquote_quoted_printable_and_convert_to(quoted_body, to_charset, from_charset, true) when "base64" @@ -35,9 +35,9 @@ module TMail def body(to_charset = 'utf-8', &block) attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" } - + if multipart? - parts.collect { |part| + parts.collect { |part| header = part["content-type"] if part.multipart? @@ -81,13 +81,13 @@ module TMail end end end - + def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false) text = text.gsub(/_/, " ") unless preserve_underscores text = text.gsub(/\r\n|\r/, "\n") # normalize newlines convert_to(text.unpack("M*").first, to, from) end - + def unquote_base64_and_convert_to(text, to, from) convert_to(Base64.decode(text), to, from) end @@ -116,27 +116,3 @@ module TMail end end end - -if __FILE__ == $0 - require 'test/unit' - - class TC_Unquoter < Test::Unit::TestCase - def test_unquote_quoted_printable - a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?=" - b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') - assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b - end - - def test_unquote_base64 - a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?=" - b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') - assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b - end - - def test_unquote_without_charset - a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber" - b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') - assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b - end - end -end diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/require_arch.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/require_arch.rb new file mode 100644 index 00000000..b4fffb8a --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/require_arch.rb @@ -0,0 +1,58 @@ +#:stopdoc: +require 'rbconfig' + +# Attempts to require anative extension. +# Falls back to pure-ruby version, if it fails. +# +# This uses Config::CONFIG['arch'] from rbconfig. + +def require_arch(fname) + arch = Config::CONFIG['arch'] + begin + path = File.join("tmail", arch, fname) + require path + rescue LoadError => e + # try pre-built Windows binaries + if arch =~ /mswin/ + require File.join("tmail", 'mswin32', fname) + else + raise e + end + end +end + + +# def require_arch(fname) +# dext = Config::CONFIG['DLEXT'] +# begin +# if File.extname(fname) == dext +# path = fname +# else +# path = File.join("tmail","#{fname}.#{dext}") +# end +# require path +# rescue LoadError => e +# begin +# arch = Config::CONFIG['arch'] +# path = File.join("tmail", arch, "#{fname}.#{dext}") +# require path +# rescue LoadError +# case path +# when /i686/ +# path.sub!('i686', 'i586') +# when /i586/ +# path.sub!('i586', 'i486') +# when /i486/ +# path.sub!('i486', 'i386') +# else +# begin +# require fname + '.rb' +# rescue LoadError +# raise e +# end +# end +# retry +# end +# end +# end +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner.rb old mode 100755 new mode 100644 similarity index 72% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner.rb index 9216b430..a5d01396 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner.rb @@ -28,16 +28,22 @@ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails # with permission of Minero Aoki. #++ - +#:stopdoc: +#require 'tmail/require_arch' require 'tmail/utils' +require 'tmail/config' module TMail - require 'tmail/scanner_r.rb' + # NOTE: It woiuld be nice if these two libs could boith be called "tmailscanner", and + # the native extension would have precedence. However RubyGems boffs that up b/c + # it does not gaurantee load_path order. begin - raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT'] - require 'tmail/scanner_c.so' - Scanner = Scanner_C + raise LoadError, 'Turned off native extentions by user choice' if ENV['NORUBYEXT'] + require('tmail/tmailscanner') # c extension + Scanner = TMailScanner rescue LoadError - Scanner = Scanner_R + require 'tmail/scanner_r' + Scanner = TMailScanner end end +#:stopdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner_r.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner_r.rb old mode 100755 new mode 100644 similarity index 91% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner_r.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner_r.rb index d9169c53..f2075502 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/scanner_r.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/scanner_r.rb @@ -1,4 +1,3 @@ -# # scanner_r.rb # #-- @@ -26,15 +25,14 @@ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails # with permission of Minero Aoki. #++ - +#:stopdoc: require 'tmail/config' - module TMail - class Scanner_R + class TMailScanner - Version = '0.10.7' + Version = '1.2.3' Version.freeze MIME_HEADERS = { @@ -46,14 +44,13 @@ module TMail alnum = 'a-zA-Z0-9' atomsyms = %q[ _#!$%&`'*+-{|}~^/=? ].strip tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip - atomchars = alnum + Regexp.quote(atomsyms) tokenchars = alnum + Regexp.quote(tokensyms) iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B' - eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+' - sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+' - utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+' + eucstr = "(?:[\xa1-\xfe][\xa1-\xfe])+" + sjisstr = "(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+" + utf8str = "(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+" quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n @@ -107,7 +104,7 @@ module TMail @received = (scantype == :RECEIVED) @is_mime_header = MIME_HEADERS[scantype] - atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE] + atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[TMail.KCODE] @word_re = (MIME_HEADERS[scantype] ? token : atom) end @@ -147,34 +144,34 @@ module TMail if s = readstr(@word_re) if @is_mime_header - yield :TOKEN, s + yield [:TOKEN, s] else # atom if /\A\d+\z/ === s - yield :DIGIT, s + yield [:DIGIT, s] elsif @received - yield RECV_TOKEN[s.downcase] || :ATOM, s + yield [RECV_TOKEN[s.downcase] || :ATOM, s] else - yield :ATOM, s + yield [:ATOM, s] end end elsif skip(/\A"/) - yield :QUOTED, scan_quoted_word() + yield [:QUOTED, scan_quoted_word()] elsif skip(/\A\[/) - yield :DOMLIT, scan_domain_literal() + yield [:DOMLIT, scan_domain_literal()] elsif skip(/\A\(/) @comments.push scan_comment() else c = readchar() - yield c, c + yield [c, c] end end - yield false, '$' + yield [false, '$'] end def scan_quoted_word @@ -261,3 +258,4 @@ module TMail end end # module TMail +#:startdoc: \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/stringio.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb old mode 100755 new mode 100644 similarity index 99% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/stringio.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb index 3817850f..83573987 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/stringio.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/stringio.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 =begin rdoc = String handling class diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/utils.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/utils.rb old mode 100755 new mode 100644 similarity index 60% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/utils.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/utils.rb index 016330ff..dc594a42 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/utils.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/utils.rb @@ -1,8 +1,3 @@ -=begin rdoc - -= General Purpose TMail Utilities - -=end #-- # Copyright (c) 1998-2003 Minero Aoki # @@ -29,21 +24,73 @@ # with permission of Minero Aoki. #++ +# = TMail - The EMail Swiss Army Knife for Ruby +# +# The TMail library provides you with a very complete way to handle and manipulate EMails +# from within your Ruby programs. +# +# Used as the backbone for email handling by the Ruby on Rails and Nitro web frameworks as +# well as a bunch of other Ruby apps including the Ruby-Talk mailing list to newsgroup email +# gateway, it is a proven and reliable email handler that won't let you down. +# +# Originally created by Minero Aoki, TMail has been recently picked up by Mikel Lindsaar and +# is being actively maintained. Numerous backlogged bug fixes have been applied as well as +# Ruby 1.9 compatibility and a swath of documentation to boot. +# +# TMail allows you to treat an email totally as an object and allow you to get on with your +# own programming without having to worry about crafting the perfect email address validation +# parser, or assembling an email from all it's component parts. +# +# TMail handles the most complex part of the email - the header. It generates and parses +# headers and provides you with instant access to their innards through simple and logically +# named accessor and setter methods. +# +# TMail also provides a wrapper to Net/SMTP as well as Unix Mailbox handling methods to +# directly read emails from your unix mailbox, parse them and use them. +# +# Following is the comprehensive list of methods to access TMail::Mail objects. You can also +# check out TMail::Mail, TMail::Address and TMail::Headers for other lists. module TMail + # Provides an exception to throw on errors in Syntax within TMail's parsers class SyntaxError < StandardError; end - + # Provides a new email boundary to separate parts of the email. This is a random + # string based off the current time, so should be fairly unique. + # + # For Example: + # + # TMail.new_boundary + # #=> "mimepart_47bf656968207_25a8fbb80114" + # TMail.new_boundary + # #=> "mimepart_47bf66051de4_25a8fbb80240" def TMail.new_boundary 'mimepart_' + random_tag end + # Provides a new email message ID. You can use this to generate unique email message + # id's for your email so you can track them. + # + # Optionally takes a fully qualified domain name (default to the current hostname + # returned by Socket.gethostname) that will be appended to the message ID. + # + # For Example: + # + # email.message_id = TMail.new_message_id + # #=> "<47bf66845380e_25a8fbb80332@baci.local.tmail>" + # email.to_s + # #=> "Message-Id: <47bf668b633f1_25a8fbb80475@baci.local.tmail>\n\n" + # email.message_id = TMail.new_message_id("lindsaar.net") + # #=> "<47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>" + # email.to_s + # #=> "Message-Id: <47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>\n\n" def TMail.new_message_id( fqdn = nil ) fqdn ||= ::Socket.gethostname "<#{random_tag()}@#{fqdn}.tmail>" end - def TMail.random_tag + #:stopdoc: + def TMail.random_tag #:nodoc: @uniq += 1 t = Time.now sprintf('%x%x_%x%x%d%x', @@ -54,50 +101,55 @@ module TMail @uniq = 0 + #:startdoc: + + # Text Utils provides a namespace to define TOKENs, ATOMs, PHRASEs and CONTROL characters that + # are OK per RFC 2822. + # + # It also provides methods you can call to determine if a string is safe module TextUtils - # Defines characters per RFC that are OK for TOKENs, ATOMs, PHRASEs and CONTROL characters. - - aspecial = '()<>[]:;.\\,"' - tspecial = '()<>[];:\\,"/?=' - lwsp = " \t\r\n" - control = '\x00-\x1f\x7f-\xff' + aspecial = %Q|()<>[]:;.\\,"| + tspecial = %Q|()<>[];:\\,"/?=| + lwsp = %Q| \t\r\n| + control = %Q|\x00-\x1f\x7f-\xff| + + CONTROL_CHAR = /[#{control}]/n ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n - CONTROL_CHAR = /[#{control}]/n - + + # Returns true if the string supplied is free from characters not allowed as an ATOM def atom_safe?( str ) - # Returns true if the string supplied is free from characters not allowed as an ATOM not ATOM_UNSAFE === str end + # If the string supplied has ATOM unsafe characters in it, will return the string quoted + # in double quotes, otherwise returns the string unmodified def quote_atom( str ) - # If the string supplied has ATOM unsafe characters in it, will return the string quoted - # in double quotes, otherwise returns the string unmodified (ATOM_UNSAFE === str) ? dquote(str) : str end + # If the string supplied has PHRASE unsafe characters in it, will return the string quoted + # in double quotes, otherwise returns the string unmodified def quote_phrase( str ) - # If the string supplied has PHRASE unsafe characters in it, will return the string quoted - # in double quotes, otherwise returns the string unmodified (PHRASE_UNSAFE === str) ? dquote(str) : str end + # Returns true if the string supplied is free from characters not allowed as a TOKEN def token_safe?( str ) - # Returns true if the string supplied is free from characters not allowed as a TOKEN not TOKEN_UNSAFE === str end + # If the string supplied has TOKEN unsafe characters in it, will return the string quoted + # in double quotes, otherwise returns the string unmodified def quote_token( str ) - # If the string supplied has TOKEN unsafe characters in it, will return the string quoted - # in double quotes, otherwise returns the string unmodified (TOKEN_UNSAFE === str) ? dquote(str) : str end - def dquote( str ) - # Wraps supplied string in double quotes unless it is already wrapped - # Returns double quoted string + # Wraps supplied string in double quotes unless it is already wrapped + # Returns double quoted string + def dquote( str ) #:nodoc: unless str =~ /^".*?"$/ '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"' else @@ -106,12 +158,14 @@ module TMail end private :dquote + # Unwraps supplied string from inside double quotes + # Returns unquoted string def unquote( str ) - # Unwraps supplied string from inside double quotes - # Returns unquoted string str =~ /^"(.*?)"$/ ? $1 : str end + # Provides a method to join a domain name by it's parts and also makes it + # ATOM safe by quoting it as needed def join_domain( arr ) arr.map {|i| if /\A\[.*\]\z/ === i @@ -122,7 +176,7 @@ module TMail }.join('.') end - + #:stopdoc: ZONESTR_TABLE = { 'jst' => 9 * 60, 'eet' => 2 * 60, @@ -168,9 +222,10 @@ module TMail 'y' => 12 * 60, 'z' => 0 * 60 } + #:startdoc: + # Takes a time zone string from an EMail and converts it to Unix Time (seconds) def timezone_string_to_unixtime( str ) - # Takes a time zone string from an EMail and converts it to Unix Time (seconds) if m = /([\+\-])(\d\d?)(\d\d)/.match(str) sec = (m[2].to_i * 60 + m[3].to_i) * 60 m[1] == '-' ? -sec : sec @@ -181,7 +236,7 @@ module TMail end end - + #:stopdoc: WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG ) MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec TMailBUG ) @@ -201,7 +256,7 @@ module TMail MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/ - + def message_id?( str ) MESSAGE_ID === str end @@ -239,7 +294,7 @@ module TMail } def to_kcode( str ) - flag = NKF_FLAGS[$KCODE] or return str + flag = NKF_FLAGS[TMail.KCODE] or return str NKF.nkf(flag, str) end @@ -248,8 +303,7 @@ module TMail def decode_RFC2231( str ) m = RFC2231_ENCODED.match(str) or return str begin - NKF.nkf(NKF_FLAGS[$KCODE], - m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr }) + to_kcode(m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr }) rescue m.post_match.gsub(/%[\da-f]{2}/in, "") end @@ -263,7 +317,7 @@ module TMail preamble = $1 remainder = $2 if remainder =~ /;/ - remainder =~ /^(.*)(;.*)$/m + remainder =~ /^(.*?)(;.*)$/m boundary_text = $1 post = $2.chomp else @@ -275,6 +329,8 @@ module TMail end end end + #:startdoc: + end diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/version.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/version.rb similarity index 94% rename from vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/version.rb rename to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/version.rb index 14df4b06..95228497 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.1.0/tmail/version.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/version.rb @@ -27,11 +27,12 @@ # with permission of Minero Aoki. #++ -module TMail #:nodoc: - module VERSION #:nodoc: +#:stopdoc: +module TMail + module VERSION MAJOR = 1 - MINOR = 1 - TINY = 1 + MINOR = 2 + TINY = 3 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionmailer/lib/action_mailer/version.rb b/vendor/rails/actionmailer/lib/action_mailer/version.rb index 954a4727..c35b648b 100644 --- a/vendor/rails/actionmailer/lib/action_mailer/version.rb +++ b/vendor/rails/actionmailer/lib/action_mailer/version.rb @@ -1,8 +1,8 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 2 - MINOR = 0 - TINY = 2 + MINOR = 1 + TINY = 0 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionmailer/test/delivery_method_test.rb b/vendor/rails/actionmailer/test/delivery_method_test.rb index ebee2356..0731512e 100644 --- a/vendor/rails/actionmailer/test/delivery_method_test.rb +++ b/vendor/rails/actionmailer/test/delivery_method_test.rb @@ -1,4 +1,4 @@ -require "#{File.dirname(__FILE__)}/abstract_unit" +require 'abstract_unit' class DefaultDeliveryMethodMailer < ActionMailer::Base end diff --git a/vendor/rails/actionmailer/test/fixtures/path.with.dots/multipart_with_template_path_with_dots.rhtml b/vendor/rails/actionmailer/test/fixtures/path.with.dots/multipart_with_template_path_with_dots.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/raw_base64_decoded_string b/vendor/rails/actionmailer/test/fixtures/raw_base64_decoded_string deleted file mode 100644 index 99b00ea1..00000000 Binary files a/vendor/rails/actionmailer/test/fixtures/raw_base64_decoded_string and /dev/null differ diff --git a/vendor/rails/actionmailer/test/fixtures/raw_base64_encoded_string b/vendor/rails/actionmailer/test/fixtures/raw_base64_encoded_string deleted file mode 100644 index eacca525..00000000 --- a/vendor/rails/actionmailer/test/fixtures/raw_base64_encoded_string +++ /dev/null @@ -1 +0,0 @@ -R0lGODlhSwF3APcAAP/////39/j29f/v7vfv7u/v7//m5f7k4vfl5f/e3vff3/Lh4f/a2PXb2N/f3/fW1v/V1PDU0/TTz/fPzv/MzO/OzfHLx/fHxv/Fw//Cv+7ExO/EwMzMzPi/vfW8tPG8vO28t/+3tee9vfe1svC1tb+/v+e1tfCzr/+uq+izrO+urueurv+mpt+ureerpe6lpeilpf+ZzOaknd2lpK+vr/+Zme+dneadnOCdnOealv+Rjd+alu+VleaVlN2VlPeNi8yZmdeUk/CLi+aNjN+PjeaMh8GUlNaPjf+ChfiDg9+Kgs+NivCEhJmZmfd/etGKh+aDg/97e96Dg92Eeu99euZ8fNWBf/9zc917ePdzc+V5dtd8eo+Pj+51ctB9e+dzct91cL98fO9sbf9mZs9zbNZxceZsauhqZN1rZt9qatRtZexkZdBtYtdqan9/f8JubuhiX95kYdZkYcxmZt9hWqpsbN5cWd5aU+VXUNRZWbxgYOBSS8lYTtRTUsBZV8dUSd1LUaVaWtxKQ85LS2ZmZtVHRN9DO71KStlCOtBAPb5ERMRCQtg7M+U0PMk9PdQ5OZVHR98xMdgxMdsvKrI6OswzM7s2MNsqKk9PT7Y1K6o3N90nHtYpIdApKbQvL9ckLN4hIcwnI7MtIt4gG+gdHdYiGbspJtMgILIpKc4gF9ccGLUkH84bG80dEL8gIMIfGD8/P8QbG4sqKqwiGcwYD4QpKcYYEKchGc4TD7MaFsUUFNcQBr0WEuANDaYdEuAKAMQQB7wREYwdGqAXEDMzM74OCbMQEM4JANYFAK0PD8YJAOAAAMQHB6QQEKYPALsIEbwHB3oZGZsQCrwIAJoQELUICK4JCbYIALQHD5YQCcwAAJUPD44QEKwIAKUJCcICApYOAKYHAL4AAJsICH0QEJwHALYAAIMODpMICJQIAa4AAIwJAIwICKYAAHEODoQICIMJAJkAAHwICB8fH3MICIwAAIMAAHwAAHQAAGYAAA8PDwAAAAAAAAAAAAAAAAAAACH5BAUUADEALAAAAABLAXcAAAj/AGMIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzps2bOHPq3Mmzp8+fQIMKHUq0qNGjSJMqXcq0qdOnUKNKnUq1qtWrWLNq3cq1q9evYMOKHUu2rNmzaNOqXcu2LcUACBQ8mGDhwocPI07oTaHCBY4dO27MaNEggNvDVgVIkFFES5w7ghBdGlUqVStbzIABM9cuXjxUenAgQExaqQAFFzx4uIAAwIIbUBxDZnQJVOVWl61JQzfOs2dNQAyXHg4UwQcoey6p6sW8ERUFCHJUAfNYECNQmzaVCiZt27Zx1ML3/65XL1AE4uhzCrgARRAubdqQIVtGn36kDgRyaElDZ4+gRpG84o134xRooDTjbFOPPbJokN6DMgkAAhrvwQffMfL98kt9pFBAwA1ppHEGHowYI56BKI6DDjrc2MPgeRDGqJIAG+TBjDjiXHgMhhhmuOEyewhAAAxn9GFKNt8V6E2KBa74Djfv4AOJADJWWVIDZTCjjjniKDONMspos+OO8iGj4TK9hAAAAC4Yw00zSy4pzZIprojOO+fIg48RVvb5kQA5uNLOltMU+s2hYVooZob02bAmADPMQk0zyTRjqTRwwunNknbiiU8tCzTFQQlNuGGqG7DAQoOfQU3AhzqDqv9zjTnXiDMNoonqaOZ8VDwKgAaUTFppMpVmmqmKd+aJDxBNwbLPs9A+6warPgmwAy/txKqOrNfUeuuXuYqJIX1m+OqaHpZaYyml7DYT3jncnEMOOfQEIpxSHJQ6T7T7TEvtTgrMAWs73WxrjqzmFCrON2CCaSGZ8nVhLgAEPCGsN8l4k6k03bFzzjnukJOPLAU81ESqKKesMsqEuFECS85C6++/OFmgSLYFF3yNrNza+g24xzgM8TJQACAkAXEtQAARt1y86ZLboHPOO+TI4w490YjwkBv8du31s/oQ4kBKJUQ7M801fbBKPASrk3O33Ha78JfgJrpjfC8AYIFeLrj/0EMOI0AhKaUYe7MbNx5b7U7IfD7kAA2EdM1FCZRT7gYx/OrTREpmo22TCsnE003OPO886+nedln3hdrsYgECUFRRhRZgfIFGHEyYsYrTh79D9dXkuBPGRDTw+7JBNOjD7+Ynde75TDJ0NrrbbtOK8Om01vozol+Kqc0nCJzQBx3k03HHHnsUAgcepqzrLjXccDNvyCHXQZHxCHHQ9fElOf88TD7oTDgG6DbTlY5WCaOVzw6FKAuhAQBoYIQgJogIRDyCEZKYRCMMEYt1hSde8nKH1eRhv4ngDyFcixYmmiez/8GECNJTRzioV7oDzmoaCvTW9hIFggeMYhSSuMQm/2oDiiKqQhWbmAQrvIGOeKCjHu9wkT3kcY976OF+0eKfQRzQNRZKy4UuOYL0skVDA5oOgdi7RqGmYasuiaMTAlDCMlRRijqWgo6pyKNlWlEKW7SDPAuyRxWriI8rmjCLCtkXIvvXQjCuJIDxGOAMc3YwM6LxkrVKWBvFUQQBPEIbpWiFKnBzGVvgwha20AUwUAmMdgzylfi4xxuwCC0tGiRmtTSJ/xx5kuhFUpJlrOEZ04jGbtVKFwoQAXxYqUpbaKYYxQAGNJlRDGq2Q4qxvAc+5HEEWj7LlgXB5Td12UhemuQE3vglAUlnRmFisnSzAgMA2mCOZ0qzGNDAJz6hMf8NaBjTVq7MpjbPgQNv7gOcBMFctEq2EMoxxAEl4EBC/OcALmAiVYRYlTk3YoHQSXKd1GunJU/Hs4MdLBYKWIApzAHNfvbTmNlD4Ja2JA51aJOQ1NDaIXM50WjN4yCp2l9BCAEL5fGrp190gDihpblbfm2cBHmq5xpgiXo446PUY6cwt7ozk26raC2oJPbMQdaZetWr6qipPfDB1ls0wKAIFUjxdjmQpUJ1IJczarSQ2q8m6NVrmGCoQLigSH7NAxNjI8jlMkcItAVAD/FwhmSBOT2dcfWy22pHJVpDhkGd1WBmnaloD4ajeOQDH6hQAFwTIk59CNYgXDihQZpwVIT/RAtz82hCYjnghr8SA3l/bWxCkveswHpuB54px1WdMTrSve2y7SzYtlIAAARYwrOh/SxZt8tdmi7IE6PZ6V0LElumSlQhhNBrXGMgTr7uQ7gF4UBwDVI2aK1QIc6CxfMaELpyxKMc4WAuc527LZEKM2fdaEcbBACAFYB2u9ndFqwGli3fADIeiiDAamHr0/MqpL7jLUgKn8VX+BpEvotMaLQSixDlrbdKBPBDPXzTDuWObsBZNUc3DGzAnLXDFa0RQGclPDAK+8bCgLRHIKXYDj8MYMMDoQEu9eGG1yYExAdNyIj3wdezGWTL+i0Ibcs523381nMuiMeMadwNyTaX/8Ai1bGO3ZbgbnhgTQKAbIWPzOcjzxiQ5FHyPerRjjlUhF/ESNltuWDlD6f4y3u1LZkNUoC/slgglYbWTxHiLObRTAGouMeRy5Gtdgi4uVkNKc+6MefRZUsKvgoC2zrT51p7BtBLZhsZDv1U80YEy+vdcpcXEjloccEgxX6WRgvCRX08zwpKHnWp23yNATu31ameXjzyQABfaaAZpe4MrW2N5CXbI1te4DW0WmYq3zba0Tw9iLAl/UWFjLm4W4xWmIfaL89VYBj24HONp33qyuYYwXSORyUWYDQhCWABlIiHkcXNtj4DWskuInQ7nqDuu2L5vRABtpYjfRC60ve2QP9dsUGUd2lqvWHQaj4yAck4YOZmO9udcYUEKBYBCVhAAxfww6zDHe6K+znQUpx1QaEcgy3vw9MMETkKSW4Qk1ed6gO5N8gJQtv70swCwlDyn31DalLjrM1oR3jBZjjAeBjjAgBoAAlUAAMY2EAFcmAbhcVdaj4nWYoab8cMOp5lgrTWww19tIixHtVJl5zxAimsaw1feJp5wUVqJs+Rwz0watscwYNquzE+oLce9GAIQ4ACFISQhnAQvcJFvzXSk17qFhDelpmGFjHeffJ4Qxpaw15IbX//LE/rb9Of9gSu15zc19847YMiei5AAIATgEELswMDGNJgBjPkYuhGt/j/7DNeam/oVLyVH8jHCUGlxPt+8cCnd7+FD/kYcFH3A4ncsdG2gm7EIx0Xxny+8VGSJGAEGA7xkAsbIAA3YAdoYAa3EwflAweuMGvh53d/R3vZMgsVcHsHkWzKFnWKp1j1FwNWRxAFoG8JgQnRIlHNxntVIgBlUA3JIA2Zp3mbR3QyFGBu5mrxsAoaQADtUT53cD7ngz6d4Bl7Jn7jp2ThhgpvxXQqxlQt13shRoLx93j1dmXRYmIFMVfP0lhc44WsggCDEAy5YAzjAIABt2b+xTYARoBuJll/tAoSQABpIAlF6B8TFBkVZAiSQHG19mfm5iJE5wmhIoUDgWL4B29X/4hXJXiCA1FeIZgQkpdpiEctFVAJrOAKuVCD8SB2ZKeEZRduy1UO9WAJDSAAdLAJFFRBjIBBGTQJkxAJ1UBuyydFAdd37UAJ4YV+60WJYeiI6Qd/JCZ/XmYQLPgsyDd10OIsZ4Y2JvAIndAJntgM44GD/8VnZUdqzuAZf6AABLAHvYAIskiLk1BERVQKozAJxYCLF6eLr3cIGqaIlBcty5ZvxpYQIBh8CJGC0AJ1B3F/0SKQ1OIChVAIlRAK15hOF8aNo6aEZEAACoAIv3AJRLSOlGFHdbQJtjCIuKaLAQcrQ+cHFiFbA/lX+pCJjfcs+yZmw3d1zKgQIxaNCrGMYP8Dg1biAn3QB4OwkA15gzHXZ/5VD94QBABQAZKADD/EjhxZR6mgCnnER7owbuW2IIX4eoVWEfoTLfuXEMK4D/oQVzj5dGX2VwYpEPzylfFlVPpQhQcBhlvnOSagBnKQBwrJkLkAJzc4dkdnCRoAAB9wCrvwlHrECqSEG6iESlYZc/EoRelAdGxjaBPBAQrFVGl5j11YhR9XZTFQAFL2XlumOY3WNQbJAYq0kg9RWHDJKiKABWXQBngpCXoJimL3kAsyDmXQbSqAC4U5lZZBC8JJC4vJSs6EC/53a30pkvawDukgcUPXDukGEU1wmV+jD7DAkvZ3nS8ZAyCYOZvjdC7/iYJgg0uYcDwO0FvPQgzaOXL70J00EwFEgAVYIJs/aY2rUCnlcJtqpmSUQAJrcgNiskelVJyspBn2BAy3SIiP6SLOuQ7gBys+EBHi+TVxVaHHOBAF8J1hmFixhZ2pggluAHX1dWwlUJbRQmU6OZDF9zwE0ABDQgRSAAZt4JOV0AmxkJ/NgIpINwxPYABrogTwMZyrhAvF4EwIKk3UpE/QwAzQ4JgN6iLpsA7gMHQTNng5wQFElSpu0Jpc+JIF0ARbCgsZtaIIUTbNSDMbUAQS0AA40ANSUJ95MAiJ0AmhsAp7GYr2MA5vUAF4VgYLg6QIGk2ESk3QkE+Fwk8u1ZdN/2gP4PCg0akO1rACU1EAZkoRzsKW/zIAOWAHQyAAIrADcBqbc1qnsVAN2XILehCEa4IAcvANzOBMhAoM+VSrLqWoa4RA0/BHUdqc6fCrzhl9o2MM50caZTN5nqMAWjBBCiAhcDoFbSAHP6kKuOAKg2AFfuorTxAP9RRNtKoM/ISr1dAtmFRW4sCrWCmSVPqrUxqZg2IN6mAKDjIczpKM1CIBdoAIvXADRhMBK+ADM9oGbbAFObAB3fYoAvBwyqd33YKrMDUrEIZWZdWg+LCuUwoOU/qu6oANphCFpFE883CpMbIBiCAJy8BteIYAC7AA0GEuQrIAD9AAMKAO4YczMf+lXRG2JaFYiPCAsewKrO1gDdeADdWwcGvhV+/FUJmWmaxCAqDACbvQChYwMQiLNA1QARtAAi5AAnlQdONGRp8VYSZlDnoqkuzqnGdbDtWADUTrCL94Frn3ngLBgvBJMypwG99ABC5rtVgLAnR3AznQAzywCpK5hHy3JWWFs5zBnBZ7to+qtkQbDIPwZGnxcVwQOW8JRi9QStCACgxntRKgAX77Ai8QuKkXO2gwYTQLexdIY4hbVqOlDubGro8KrBgLodXwDLorB2tBkCnanv/SA0hqIksgABYAAlpbuj1QBLFDOxD4BYkATK/XuhdGRuLwWfUwSD+LttubDrkbDMH/UAZs8Z2q6UhQEE3GkAzb4Al5YbqqpwVfsH1xIIHk8wrRqZVLmHmAxzbcRbZVdLaOe7FTag26AL5Y0BYlsKWMFhG8JbIywQGaKhEQxQVMGxFYwE/Fsg1twAOxoX1pML92sIfogwgJVmp7F3tHt2T4sItp5Ur14LO167Nnuw7WwAu6oAtSsBFMeyqnko9UwYL2ioI8bCov9mtDfCpkWYwRUVHv1X4XUQbXUA2Esw2oQAVfML/mc4R8iAiGEAombMJFhsI3qItstcJr5rPci7YWuw7OcMOxkAMa4QZpioKZWgJjeBUcAAsRvEUuZsdz6RAFUALvRlvEUDmRc6HIWhEp/7jHE6EG5jAsTCQNfXAGWvyKsigJwYC/EhZ9+TvGLjJI+JAPhEal3CvDtAsPzhALuhALKpARmYZf+8BQkQO8TVEAu9cQZjYQmBMRxaOdZWNic9wRZVPEDiEArwLJ3sANroAHliwJQaSOoFDCZATGm5y/GUjGQ9eu22vK61AOsRALrNDKGEEILAi8+oB8XEPMSeEAmEDLiziXRUWhznYQsUWiI8E1GoEAi9AOlJIxA7INjGAIGISR6tiRrYC/ZJRZA4OB5sZWEld0pPyzv/qozqkLrNAJH4ARWprOCMFFXocJK1mvqJm5G0oMiSZYeWzSbkAIHLCh71kAzqJRNJBoxP9wuTFAA/tCOfsyLb2lD6uSx/2iVHKL0/MnEKhCDPPQ0pEDCzC9D6tSMhuKCfPAngdBW/vHRTNz1En9mUQ11RyQx/qAnV6Xf/twXtPi0kwd03KcuUYNC0j91SKdX1xH0yadz57QDpBcINwQC5FQ0E9ZCsWA0PibwiqMDw9davBaDg96u7U7peugC6dQCROAEYFVNkFcPP6iP565D7l1yJXGPBclEPLFPLtcMvVKW9PiVxKVgvoVyO+5KiwoNqz9mSaYWwUQW+cZA7ssEORcMsRgXKdd1IRgovuwx4ccAyUACzPT27q9QsON3MWtlkG82xywQqbdV/0CmoXH3L9dMpz/fWzH3XQhC91kSBELcF39jI3jwA7NsAm2YUfA2QonLNh+l3mDlA/3wMlkFA7VUA2Kzb2065zBcAqIoFoWQQPHpj/l3XTFbcdUNhD7sEIgDdP7VjYSNQ/w5dZ1FbKxVQJc5GlbVzbMwzUMlWgQLlxlo1GRIxB+VeL3BQscXnlOLdqc9p4wvsAx0OIC8dsmqFHnpT8+DOEgCs8x/jK0JVE63txqKVzF8zJcdDyLnM+KEA8ZkzFNwg2vUBlTqZiMKdh9x9BLlg/ntrrTti3Y8AzhsA7OicbAmgunUAhvOxH7JrcpZyq69c6e9uHqd1C0xWJhMxB/nn/Ipz/ME1uJpeHS/y3aZsngDJUqkSdctHU8gV4Q85BbCpHLczt5GM7iWVbpUGfoB/HLn4kJnjbpvD3Pm57jWUboRs1l3ol8w6wRBHAz/cxEK3IOzVAKwymcxaALtgANg/3lfubJa1UPJ0w91kC0zxAM1rAO8CDRA8wJgEC5FIEqKHPOLRbMR551+8BivUwIyMdFVx3dA4Hh7bft3jnPMRDosa7q5yXVED4tWI0q7SwQ4h7qyrPVJzaXZcMF8w4L9Y7c+X5e6YUQ9WzU53XvCbVC/x7w6O7oMZDqDL4Rj8XPhWMnLFKc0oSg8+21Vhmlh23C8Mq2/b3sBazN67oO3vAJdnAvEjzWzsKiY/9N1iRIEAUP8TddeE0eVcvt6rodZgpP4rzt84clEClO3oJMEDt/EGGK7WX2lZj9y0mPgn61aSb+gWVt71FWjP0m9a+14kue6HNrkxjhBfxcKcjCIu/QDEa68dTEDLzId5LJqCIZ8mRUMCRfDeALvjf832c7Dp/wBRZBCFo0y8hT1Ib3kvhc7o2F84s/8TxfV3PuL7089pKPaeQeWyVz9JgmUY+v9OdVPFjPP7PM+Z/JATTAAWsi+iY43T5f8wQR66ZfABIF8UAu9hHPfhsRBO2gMdiI8eeADtHUpOF6DXIf99Y8foRG5ogtqUT7vbwQ/RYdC96g5rZ7ClVQESVA9lz/E+ThXRB//OSDNd5CXwL6EI2IPhCYcGa0FWac//jYeeJGX3gsCOhADwtjk/5Rxf4zv+OxPFgAsY9QjBj6YBF0AMvBPmIAADTBFKPEPjcELRIssO/gRYKwiHF0s68EQYMIFcYQSJCLSIKYNhKiyFHmTJoELYzzlmzcOHQ9370j5w0YtGtEr6lrlzRpPKXtmMaDGq/eVHtV6zFtmlSdta3VvBYLFjaYrli6WJ1Ktg5eunXpTgmpGdcBTH2EBMQoQGjePn00LM7dC8vvRQ77mnBsos8NoZN49c3DxGWeXZIDCesjBqvJXkIOVjrouDEGsX2YShQ+HCOkRUKYDyeeBwsW/4fKM0MScgOrAEcasPbxhRV7cAzYsmnfZrx7oT7TF7n4Frj7Yt27Fpvsm/f6sfEYqAnSwD6QA+bZ+uqCjpv+ooBD7ZpJ6+kTKLti06aZw48Vq1L9TqFOraeq/bJqp5utsKnmmWfAGqssVh48hZNg7FkrHVd4UC9DDTeMwYES0LOogBIKWGg4mkTcTUT1VFRvxBBP01BE2jh8cUaMYPzLRRpl4gBEGWviwEYUOxxpx7h8iMebbeL76R153LHmG3XwQ6qp/pxSJyqpAmRKnSqV8rIba7BB0BhoiuGFF7J0MeusU06JJJZ17FnHlRGMxDNPgoqMQS89/wQ0UEEHJTRDBP8oiYcadtj5SR6g3GFHHHO8zHIpS51aSksAtYQKzG6qwUbBYJ7RRc01H3TzlE8+aSSUdOxZpYNCZyWosIqu45NWXXfltdc8W5BqmyadfFIea6i8FMsro5oKnnXUctbZZ9NJJ55y4gnHG2usMSasst58E5RLPomk3EYSkcaXDXwNtABYMGuOXXnnpbfXAMIIkJtG5XnSnXEkRWq/AZ/671mDDzY4WngWZthgasHBKRlj0mTFlVM6kcSQSlY5oV6PPwY5ZJD1qOqcc/il50lyzBEnWUsJnupZadVCuGaaGcYZ53XYySYbX25ZpZJHBHlBZKOPRjppPMM45557yHHnSXr/3PGmZYExZVZmm7c+OJ2eex5mmJ+BXsUSoQvBA441xMgCjUQwVDpuuedOWoRA8MGbHr3deccc/vyDii2ta0bn62zC9uXnVUTJJBNLFFHkjz/4oEPtMca4IvMoNtc8jjkGiWIAukcnvfReBWghDFmi0VtvgqMax/Cwb6FdFMYbX2QRyfngQw425mCDDTLUIB6NM9a4PHnlL8/8iijAAF6OMSgwvXrrr8eTAAuA0EMTYeSh56pmckHFFEccGaSPPvKwIw465CA+fuKHnz94NtDAf43jl2cec8yb3xwYhje8MaAAewdEYAItooEjvMGBZAjDE6wghjWkIQ1msGAa4mDB/zNo0INxaAP85KcG/OHPDCcUQwqzkEK2sS0LWdCc5jaHBS+AYQplwEIWahAABfbQh3MLwAzmQAYvPOEJSzCiFdaGwQxakIkXbKIF29CGMrTBhF8QwxfM8AUucrELX6RCF5xABSeUMQlRcAISopCEKVhhCm/EwhbSmIAf1tGOIAsADuZghSP00YhH1IIYvgiGNHyhkIcEwxcSmcg0gKEMjixDJCWphS9QUguXrEIXqkAFTnIyCU5IQiiRgIQkKOEJSkClEqaghB9EIQN3hGUse2WCOShhB0ToYy6P8EUtdIEKWgCDJStZyUta8pJYAMMW4rgFLzRTmZusQjSrAAUqMP/BmtYUQhKY8ANu4rIIRPgmEYigBB4goQayRGc6A6WAKtiSCEEggg+CcAR4ZqGTvyymFqqQT0zqUwtxBOgWBGoFglpBClBgAhQUWgQmFEEIDxUCD36gAx74YAc9yAERdpCDHuzABzXQgQ4MoE6SlnRDJyjCDXrgg5X6QJ5BEIITmDBGmepTmjeNJhRyWgUp9NSnPX2CFP6ohCEMQQhFFUIPeLBUHtQgBziQQQ5kcAMZVLWqO7ABSKlnUq529SIDeEEObNDRju7ArDgIZVpDmVCFttWtbj1oEaDwTSWI065HeGcQWMpUpfLABjawqgxc4ALBDtawLGBBDULgVcZytQH/L7ABDGxwA8pSdgY50AE3NcvNhspVrkUFrVFDC1oiDGGlpzVranGAg7/+9QUvMGwKXCDbFNTWtidAAQtQILrG9hadE4DBa18AA+LKYAYzAGlIlatc0Cb1qDwYAnSlq9QeVLe6ObgBdm+w2tUe97gueIEKZltbE6TgBCYAQXrVa4IRhCAELKCjb+V7RwuoILyvJW5+a7Bf/va3BkrNAQ8CDGCmLrW1gJ3sDWAgA+LOAAYzaAEMWjDhFZxAvRfG8AZAsIEOZKADKNjqfEXcwweowMQmhoF4W/ACxLbYxYidbIwPfGDICle4hnXBClww4QmbgAQk8AB6QfCBDRTZyEW2/4AGknwBDGCgwzwccZQPiIAfV/kEJ1iBCVCwZS53mcs1fq0NbGxj+4rXxCtQQZazbAI2e6ADb+6ACDSgZCVbwM4VsACe82yBCVDAzxkYqZQFXb0AfIAEhvbAj9HrXkY3utEjKPN9y3xiE1/5yj5mM5uHDGc40xnPFaiABEQtgQiQmtQPkAAFJAABCsR30K8e3QVUsIEP1HrTTcZ1rnU9ghOQwNJXHoGvq0yCIX8gvSIg8psv0IELNPsCFMgzqCMw7Qc8oAHXxja2GfCABDwAArAG99wS8IIOWOACRN4ABVjtZ3a3u92GPvShaz3vWtP6yM328wQuMAF+81vV045AA/8Cfm0FFLzgCDB4wROgAAbwNtwPN1oARjCCC1TgAkpmwAESkAAGdNzjH2dAAijAYWZ3QAMXMPLFL2DnfbO7396WQLUfsG2CN2ABClhAzhGwc57v3AAIOMABFOBwiBf9YxRAQQcmIIEKPGAATzfAAAwwdaobIOhXnwCfVz4Bi2e93+1etcwZ0ICxM0ABC094z3s+AAIggO0GIADcox5oo9edXgfoAAYoYG0DOAQAAQA84J8+eMIbQAITQPXhZe5txo+d42cv+NUlz/O2I4AAl8c8Adi+ec6z3e6fl9cA2q0Av5fe9KcHgAGqne2Nt37jChD61as+dalL/fKb1/zmBTBdgN0LIAC+/z3vBwBl0BdfVwmAAAMgcADUN9/0CJd80GdPdalHnfCEz33vf7994AM/8N8nvvHFP6gBZDwBAzC94AkfgOZX3/rWv378nx6A4Q8f8N4PvEMAP37+6ykgADs= \ No newline at end of file diff --git a/vendor/rails/actionmailer/test/fixtures/second_mailer/share.rhtml b/vendor/rails/actionmailer/test/fixtures/second_mailer/share.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/templates/signed_up.rhtml b/vendor/rails/actionmailer/test/fixtures/templates/signed_up.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.yaml.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.yaml.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up_with_url.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up_with_url.rhtml deleted file mode 100644 index e69de29b..00000000 diff --git a/vendor/rails/actionmailer/test/mail_helper_test.rb b/vendor/rails/actionmailer/test/mail_helper_test.rb index 70e5cb81..e94aeff0 100644 --- a/vendor/rails/actionmailer/test/mail_helper_test.rb +++ b/vendor/rails/actionmailer/test/mail_helper_test.rb @@ -1,4 +1,4 @@ -require "#{File.dirname(__FILE__)}/abstract_unit" +require 'abstract_unit' module MailerHelper def person_name diff --git a/vendor/rails/actionmailer/test/mail_render_test.rb b/vendor/rails/actionmailer/test/mail_render_test.rb index 475735e7..fbcd1887 100644 --- a/vendor/rails/actionmailer/test/mail_render_test.rb +++ b/vendor/rails/actionmailer/test/mail_render_test.rb @@ -1,4 +1,4 @@ -require "#{File.dirname(__FILE__)}/abstract_unit" +require 'abstract_unit' class RenderMailer < ActionMailer::Base def inline_template(recipient) diff --git a/vendor/rails/actionmailer/test/mail_service_test.rb b/vendor/rails/actionmailer/test/mail_service_test.rb index eb408f96..e5ecb0e2 100755 --- a/vendor/rails/actionmailer/test/mail_service_test.rb +++ b/vendor/rails/actionmailer/test/mail_service_test.rb @@ -1,4 +1,5 @@ -require "#{File.dirname(__FILE__)}/abstract_unit" +# encoding: utf-8 +require 'abstract_unit' class FunkyPathMailer < ActionMailer::Base self.template_root = "#{File.dirname(__FILE__)}/fixtures/path.with.dots" @@ -39,6 +40,15 @@ class TestMailer < ActionMailer::Base body "Nothing to see here." end + def different_reply_to(recipient) + recipients recipient + subject "testing reply_to" + from "system@loudthinking.com" + sent_on Time.local(2008, 5, 23) + reply_to "atraver@gmail.com" + body "Nothing to see here." + end + def iso_charset(recipient) @recipients = recipient @subject = "testing isø charsets" @@ -444,6 +454,31 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded end + def test_reply_to + expected = new_mail + + expected.to = @recipient + expected.subject = "testing reply_to" + expected.body = "Nothing to see here." + expected.from = "system@loudthinking.com" + expected.reply_to = "atraver@gmail.com" + expected.date = Time.local 2008, 5, 23 + + created = nil + assert_nothing_raised do + created = TestMailer.create_different_reply_to @recipient + end + assert_not_nil created + assert_equal expected.encoded, created.encoded + + assert_nothing_raised do + TestMailer.deliver_different_reply_to @recipient + end + + assert_not_nil ActionMailer::Base.deliveries.first + assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded + end + def test_iso_charset expected = new_mail( "iso-8859-1" ) expected.to = @recipient @@ -534,7 +569,8 @@ class ActionMailerTest < Test::Unit::TestCase def test_delivery_logs_sent_mail mail = TestMailer.create_signed_up(@recipient) logger = mock() - logger.expects(:info).with("Sent mail:\n #{mail.encoded}") + logger.expects(:info).with("Sent mail to #{@recipient}") + logger.expects(:debug).with("\n#{mail.encoded}") TestMailer.logger = logger TestMailer.deliver_signed_up(@recipient) end @@ -766,23 +802,23 @@ EOF def test_implicitly_multipart_messages mail = TestMailer.create_implicitly_multipart_example(@recipient) - assert_equal 6, mail.parts.length + assert_equal 3, mail.parts.length assert_equal "1.0", mail.mime_version assert_equal "multipart/alternative", mail.content_type assert_equal "text/yaml", mail.parts[0].content_type assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset") - assert_equal "text/plain", mail.parts[2].content_type + assert_equal "text/plain", mail.parts[1].content_type + assert_equal "utf-8", mail.parts[1].sub_header("content-type", "charset") + assert_equal "text/html", mail.parts[2].content_type assert_equal "utf-8", mail.parts[2].sub_header("content-type", "charset") - assert_equal "text/html", mail.parts[4].content_type - assert_equal "utf-8", mail.parts[4].sub_header("content-type", "charset") end def test_implicitly_multipart_messages_with_custom_order mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"]) - assert_equal 6, mail.parts.length + assert_equal 3, mail.parts.length assert_equal "text/html", mail.parts[0].content_type - assert_equal "text/plain", mail.parts[2].content_type - assert_equal "text/yaml", mail.parts[4].content_type + assert_equal "text/plain", mail.parts[1].content_type + assert_equal "text/yaml", mail.parts[2].content_type end def test_implicitly_multipart_messages_with_charset @@ -838,7 +874,11 @@ EOF fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8") mail = TMail::Mail.parse(fixture) attachment = mail.attachments.last - assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename + + expected = "01 Quien Te Dij\212at. Pitbull.mp3" + expected.force_encoding(Encoding::ASCII_8BIT) if expected.respond_to?(:force_encoding) + + assert_equal expected, attachment.original_filename end def test_wrong_mail_header diff --git a/vendor/rails/actionmailer/test/quoting_test.rb b/vendor/rails/actionmailer/test/quoting_test.rb index 14981571..13a859a5 100644 --- a/vendor/rails/actionmailer/test/quoting_test.rb +++ b/vendor/rails/actionmailer/test/quoting_test.rb @@ -1,12 +1,12 @@ -require "#{File.dirname(__FILE__)}/abstract_unit" +# encoding: utf-8 +require 'abstract_unit' require 'tmail' require 'tempfile' class QuotingTest < Test::Unit::TestCase - # Move some tests from TMAIL here def test_unquote_quoted_printable - a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?=" + a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?=" b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b end @@ -18,31 +18,34 @@ class QuotingTest < Test::Unit::TestCase end def test_unquote_without_charset - a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber" + a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber" b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b - end - + end + def test_unqoute_multiple - a ="=?utf-8?q?Re=3A_=5B12=5D_=23137=3A_Inkonsistente_verwendung_von_=22Hin?==?utf-8?b?enVmw7xnZW4i?=" + a ="=?utf-8?q?Re=3A_=5B12=5D_=23137=3A_Inkonsistente_verwendung_von_=22Hin?==?utf-8?b?enVmw7xnZW4i?=" b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') assert_equal "Re: [12] #137: Inkonsistente verwendung von \"Hinzuf\303\274gen\"", b end - + def test_unqoute_in_the_middle - a ="Re: Photos =?ISO-8859-1?Q?Brosch=FCre_Rand?=" + a ="Re: Photos =?ISO-8859-1?Q?Brosch=FCre_Rand?=" b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8') assert_equal "Re: Photos Brosch\303\274re Rand", b end - + def test_unqoute_iso - a ="=?ISO-8859-1?Q?Brosch=FCre_Rand?=" + a ="=?ISO-8859-1?Q?Brosch=FCre_Rand?=" b = TMail::Unquoter.unquote_and_convert_to(a, 'iso-8859-1') - assert_equal "Brosch\374re Rand", b + expected = "Brosch\374re Rand" + expected.force_encoding 'iso-8859-1' if expected.respond_to?(:force_encoding) + assert_equal expected, b end - + def test_quote_multibyte_chars original = "\303\246 \303\270 and \303\245" + original.force_encoding('ASCII-8BIT') if original.respond_to?(:force_encoding) result = execute_in_sandbox(<<-CODE) $:.unshift(File.dirname(__FILE__) + "/../lib/") @@ -56,8 +59,8 @@ class QuotingTest < Test::Unit::TestCase unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil) assert_equal unquoted, original end - - + + # test an email that has been created using \r\n newlines, instead of # \n newlines. def test_email_quoted_with_0d0a @@ -69,19 +72,8 @@ class QuotingTest < Test::Unit::TestCase mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject")) assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject end - - def test_decode - encoded, decoded = expected_base64_strings - assert_equal decoded, TMail::Base64.decode(encoded) - end - - def test_encode - encoded, decoded = expected_base64_strings - assert_equal encoded.length, TMail::Base64.encode(decoded).length - end - + private - # This whole thing *could* be much simpler, but I don't think Tempfile, # popen and others exist on all platforms (like Windows). def execute_in_sandbox(code) @@ -103,9 +95,4 @@ class QuotingTest < Test::Unit::TestCase File.delete(test_name) rescue nil File.delete(res_name) rescue nil end - - def expected_base64_strings - [ File.read("#{File.dirname(__FILE__)}/fixtures/raw_base64_encoded_string"), File.read("#{File.dirname(__FILE__)}/fixtures/raw_base64_decoded_string") ] - end end - diff --git a/vendor/rails/actionmailer/test/test_helper_test.rb b/vendor/rails/actionmailer/test/test_helper_test.rb index eb17e3e8..f8913e54 100644 --- a/vendor/rails/actionmailer/test/test_helper_test.rb +++ b/vendor/rails/actionmailer/test/test_helper_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/abstract_unit' +require 'abstract_unit' class TestHelperMailer < ActionMailer::Base def test @@ -9,7 +9,6 @@ class TestHelperMailer < ActionMailer::Base end class TestHelperMailerTest < ActionMailer::TestCase - def test_setup_sets_right_action_mailer_options assert_equal :test, ActionMailer::Base.delivery_method assert ActionMailer::Base.perform_deliveries @@ -115,3 +114,16 @@ class TestHelperMailerTest < ActionMailer::TestCase assert_match /0 .* but 1/, error.message end end + +class AnotherTestHelperMailerTest < ActionMailer::TestCase + tests TestHelperMailer + + def setup + @test_var = "a value" + end + + def test_setup_shouldnt_conflict_with_mailer_setup + assert @expected.is_a?(TMail::Mail) + assert_equal 'a value', @test_var + end +end diff --git a/vendor/rails/actionmailer/test/tmail_test.rb b/vendor/rails/actionmailer/test/tmail_test.rb index b40d8945..718990e7 100644 --- a/vendor/rails/actionmailer/test/tmail_test.rb +++ b/vendor/rails/actionmailer/test/tmail_test.rb @@ -1,4 +1,4 @@ -require "#{File.dirname(__FILE__)}/abstract_unit" +require 'abstract_unit' class TMailMailTest < Test::Unit::TestCase def test_body diff --git a/vendor/rails/actionmailer/test/url_test.rb b/vendor/rails/actionmailer/test/url_test.rb index 56478696..71286bd1 100644 --- a/vendor/rails/actionmailer/test/url_test.rb +++ b/vendor/rails/actionmailer/test/url_test.rb @@ -1,4 +1,4 @@ -require "#{File.dirname(__FILE__)}/abstract_unit" +require 'abstract_unit' class TestMailer < ActionMailer::Base @@ -73,4 +73,4 @@ class ActionMailerUrlTest < Test::Unit::TestCase assert_not_nil ActionMailer::Base.deliveries.first assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG index d8a1cc75..cb684a92 100644 --- a/vendor/rails/actionpack/CHANGELOG +++ b/vendor/rails/actionpack/CHANGELOG @@ -1,3 +1,195 @@ +*2.1.0 (May 31st, 2008)* + +* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing] + +* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139] + +* Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136] + +* Change the request forgery protection to go by Content-Type instead of request.format so that you can't bypass it by POSTing to "#{request.uri}.xml" [rick] +* InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing] + +* select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing] + +* Fixed that TextHelper#text_field would corrypt when raw HTML was used as the value (mchenryc, Kevin Glowacz) [#80] + +* Added ActionController::TestCase#rescue_action_in_public! to control whether the action under test should use the regular rescue_action path instead of simply raising the exception inline (great for error testing) [DHH] + +* Reduce number of instance variables being copied from controller to view. [Pratik] + +* select_datetime and select_time default to Time.zone.now when config.time_zone is set [Geoff Buesing] + +* datetime_select defaults to Time.zone.now when config.time_zone is set [Geoff Buesing] + +* Remove ActionController::Base#view_controller_internals flag. [Pratik] + +* Add conditional options to caches_page method. [Paul Horsfall] + +* Move missing template logic to ActionView. [Pratik] + +* Introduce ActionView::InlineTemplate class. [Pratik] + +* Automatically parse posted JSON content for Mime::JSON requests. [rick] + + POST /posts + {"post": {"title": "Breaking News"}} + + def create + @post = Post.create params[:post] + # ... + end + +* add json_escape ERB util to escape html entities in json strings that are output in HTML pages. [rick] + +* Provide a helper proxy to access helper methods from outside views. Closes #10839 [Josh Peek] + e.g. ApplicationController.helpers.simple_format(text) + +* Improve documentation. [Xavier Noria, leethal, jerome] + +* Ensure RJS redirect_to doesn't html-escapes string argument. Closes #8546 [josh, eventualbuddha, Pratik] + +* Support render :partial => collection of heterogeneous elements. #11491 [Zach Dennis] + +* Avoid remote_ip spoofing. [Brian Candler] + +* Added support for regexp flags like ignoring case in the :requirements part of routes declarations #11421 [NeilW] + +* Fixed that ActionController::Base#read_multipart would fail if boundary was exactly 10240 bytes #10886 [ariejan] + +* Fixed HTML::Tokenizer (used in sanitize helper) didn't handle unclosed CDATA tags #10071 [esad, packagethief] + +* Improve documentation. [Radar, Jan De Poorter, chuyeow, xaviershay, danger, miloops, Xavier Noria, Sunny Ripert] + +* Fixed that FormHelper#radio_button would produce invalid ids #11298 [harlancrystal] + +* Added :confirm option to submit_tag #11415 [miloops] + +* Fixed NumberHelper#number_with_precision to properly round in a way that works equally on Mac, Windows, Linux (closes #11409, #8275, #10090, #8027) [zhangyuanyi] + +* Allow the #simple_format text_helper to take an html_options hash for each paragraph. #2448 [Francois Beausoleil, thechrisoshow] + +* Fix regression from filter refactoring where re-adding a skipped filter resulted in it being called twice. [rick] + +* Refactor filters to use Active Support callbacks. #11235 [Josh Peek] + +* Fixed that polymorphic routes would modify the input array #11363 [thomas.lee] + +* Added :format option to NumberHelper#number_to_currency to enable better localization support #11149 [lylo] + +* Fixed that TextHelper#excerpt would include one character too many #11268 [Irfy] + +* Fix more obscure nested parameter hash parsing bug. #10797 [thomas.lee] + +* Added ActionView::Helpers::register_javascript/stylesheet_expansion to make it easier for plugin developers to inject multiple assets. #10350 [lotswholetime] + +* Fix nested parameter hash parsing bug. #10797 [thomas.lee] + +* Allow using named routes in ActionController::TestCase before any request has been made. Closes #11273 [alloy] + +* Fixed that sweepers defined by cache_sweeper will be added regardless of the perform_caching setting. Instead, control whether the sweeper should be run with the perform_caching setting. This makes testing easier when you want to turn perform_caching on/off [DHH] + +* Make MimeResponds::Responder#any work without explicit types. Closes #11140 [jaw6] + +* Better error message for type conflicts when parsing params. Closes #7962 [spicycode, matt] + +* Remove unused ActionController::Base.template_class. Closes #10787 [Pratik] + +* Moved template handlers related code from ActionView::Base to ActionView::Template. [Pratik] + +* Tests for div_for and content_tag_for helpers. Closes #11223 [thechrisoshow] + +* Allow file uploads in Integration Tests. Closes #11091 [RubyRedRick] + +* Refactor partial rendering into a PartialTemplate class. [Pratik] + +* Added that requests with JavaScript as the priority mime type in the accept header and no format extension in the parameters will be treated as though their format was :js when it comes to determining which template to render. This makes it possible for JS requests to automatically render action.js.rjs files without an explicit respond_to block [DHH] + +* Tests for distance_of_time_in_words with TimeWithZone instances. Closes #10914 [ernesto.jimenez] + +* Remove support for multivalued (e.g., '&'-delimited) cookies. [Jamis Buck] + +* Fix problem with render :partial collections, records, and locals. #11057 [lotswholetime] + +* Added support for naming concrete classes in sweeper declarations [DHH] + +* Remove ERB trim variables from trace template in case ActionView::Base.erb_trim_mode is changed in the application. #10098 [tpope, kampers] + +* Fix typo in form_helper documentation. #10650 [xaviershay, kampers] + +* Fix bug with setting Request#format= after the getter has cached the value. #10889 [cch1] + +* Correct inconsistencies in RequestForgeryProtection docs. #11032 [mislav] + +* Introduce a Template class to ActionView. #11024 [lifofifo] + +* Introduce the :index option for form_for and fields_for to simplify multi-model forms (see http://railscasts.com/episodes/75). #9883 [rmm5t] + +* Introduce map.resources :cards, :as => 'tarjetas' to use a custom resource name in the URL: cards_path == '/tarjetas'. #10578 [blj] + +* TestSession supports indifferent access. #7372 [tamc, Arsen7, mhackett, julik, jean.helou] + +* Make assert_routing aware of the HTTP method used. #8039 [mpalmer] + e.g. assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }) + +* Make map.root accept a single symbol as an argument to declare an alias. #10818 [bscofield] + + e.g. map.dashboard '/dashboard', :controller=>'dashboard' + map.root :dashboard + +* Handle corner case with image_tag when passed 'messed up' image names. #9018 [duncanbeevers, mpalmer] + +* Add label_tag helper for generating elements. #10802 [DefV] + +* Introduce TemplateFinder to handle view paths and lookups. #10800 [Pratik Naik] + +* Performance: optimize route recognition. Large speedup for apps with many resource routes. #10835 [oleganza] + +* Make render :partial recognise form builders and use the _form partial. #10814 [djanowski] + +* Allow users to declare other namespaces when using the atom feed helpers. #10304 [david.calavera] + +* Introduce send_file :x_sendfile => true to send an X-Sendfile response header. [Jeremy Kemper] + +* Fixed ActionView::Helpers::ActiveRecordHelper::form for when protect_from_forgery is used #10739 [jeremyevans] + +* Provide nicer access to HTTP Headers. Instead of request.env["HTTP_REFERRER"] you can now use request.headers["Referrer"]. [Koz] + +* UrlWriter respects relative_url_root. #10748 [Cheah Chu Yeow] + +* The asset_host block takes the controller request as an optional second argument. Example: use a single asset host for SSL requests. #10549 [Cheah Chu Yeow, Peter B, Tom Taylor] + +* Support render :text => nil. #6684 [tjennings, PotatoSalad, Cheah Chu Yeow] + +* assert_response failures include the exception message. #10688 [Seth Rasmussen] + +* All fragment cache keys are now by default prefixed with the "views/" namespace [DHH] + +* Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [DHH] + +* Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [DHH] + +* Made fragment caching in views work for rjs and builder as well #6642 [zsombor] + +* Fixed rendering of partials with layout when done from site layout #9209 [antramm] + +* Fix atom_feed_helper to comply with the atom spec. Closes #10672 [xaviershay] + + * The tags created do not contain a date (http://feedvalidator.org/docs/error/InvalidTAG.html) + * IDs are not guaranteed unique + * A default self link was not provided, contrary to the documentation + * NOTE: This changes tags for existing atom entries, but at least they validate now. + +* Correct indentation in tests. Closes #10671 [l.guidi] + +* Fix that auto_link looks for ='s in url paths (Amazon urls have them). Closes #10640 [bgreenlee] + +* Ensure that test case setup is run even if overridden. #10382 [Josh Peek] + +* Fix HTML Sanitizer to allow trailing spaces in CSS style attributes. Closes #10566 [wesley.moxam] + +* Add :default option to time_zone_select. #10590 [Matt Aimonetti] + + *2.0.2* (December 16th, 2007) * Added delete_via_redirect and put_via_redirect to integration testing #10497 [philodespotos] diff --git a/vendor/rails/actionpack/MIT-LICENSE b/vendor/rails/actionpack/MIT-LICENSE index 007cc942..13c90d46 100644 --- a/vendor/rails/actionpack/MIT-LICENSE +++ b/vendor/rails/actionpack/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2007 David Heinemeier Hansson +Copyright (c) 2004-2008 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/vendor/rails/actionpack/README b/vendor/rails/actionpack/README index 72f0f624..2746c3cc 100644 --- a/vendor/rails/actionpack/README +++ b/vendor/rails/actionpack/README @@ -97,7 +97,7 @@ A short rundown of the major features: class WeblogController < ActionController::Base before_filter :authenticate, :cache, :audit - after_filter { |c| c.response.body = GZip::compress(c.response.body) } + after_filter { |c| c.response.body = Gzip::compress(c.response.body) } after_filter LocalizeFilter def index diff --git a/vendor/rails/actionpack/Rakefile b/vendor/rails/actionpack/Rakefile index 1b1d9a17..b37f756c 100644 --- a/vendor/rails/actionpack/Rakefile +++ b/vendor/rails/actionpack/Rakefile @@ -4,7 +4,7 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require 'rake/contrib/rubyforgepublisher' +require 'rake/contrib/sshpublisher' require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version') PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' @@ -27,9 +27,9 @@ task :test => [:test_action_pack, :test_active_record_integration] Rake::TestTask.new(:test_action_pack) { |t| t.libs << "test" -# make sure we include the controller tests (c*) first as on some systems +# make sure we include the tests in alphabetical order as on some systems # this will not happen automatically and the tests (as a whole) will error - t.test_files=Dir.glob( "test/c*/**/*_test.rb" ) + Dir.glob( "test/[ft]*/*_test.rb" ) + t.test_files=Dir.glob( "test/[cft]*/**/*_test.rb" ).sort # t.pattern = 'test/*/*_test.rb' t.verbose = true } @@ -76,7 +76,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' @@ -144,6 +144,7 @@ end desc "Publish the release files to RubyForge." task :release => [ :package ] do require 'rubyforge' + require 'rake/contrib/rubyforgepublisher' packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" } diff --git a/vendor/rails/actionpack/lib/action_controller.rb b/vendor/rails/actionpack/lib/action_controller.rb index e7a9eba5..810a5fb9 100755 --- a/vendor/rails/actionpack/lib/action_controller.rb +++ b/vendor/rails/actionpack/lib/action_controller.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -55,9 +55,9 @@ require 'action_controller/http_authentication' require 'action_controller/components' require 'action_controller/record_identifier' require 'action_controller/request_forgery_protection' +require 'action_controller/headers' require 'action_view' -ActionController::Base.template_class = ActionView::Base ActionController::Base.class_eval do include ActionController::Flash diff --git a/vendor/rails/actionpack/lib/action_controller/assertions/model_assertions.rb b/vendor/rails/actionpack/lib/action_controller/assertions/model_assertions.rb index 0b431305..d25214bb 100644 --- a/vendor/rails/actionpack/lib/action_controller/assertions/model_assertions.rb +++ b/vendor/rails/actionpack/lib/action_controller/assertions/model_assertions.rb @@ -1,7 +1,8 @@ module ActionController module Assertions module ModelAssertions - # Ensures that the passed record is valid by ActiveRecord standards and returns any error messages if it is not. + # Ensures that the passed record is valid by Active Record standards and + # returns any error messages if it is not. # # ==== Examples # diff --git a/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb b/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb index 42bd7fb3..c5fc6c79 100644 --- a/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb +++ b/vendor/rails/actionpack/lib/action_controller/assertions/response_assertions.rb @@ -33,7 +33,13 @@ module ActionController elsif type.is_a?(Symbol) && @response.response_code == ActionController::StatusCodes::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else - assert_block(build_message(message, "Expected response to be a , but was ", type, @response.response_code)) { false } + if @response.error? + exception = @response.template.instance_variable_get(:@exception) + exception_message = exception && exception.message + assert_block(build_message(message, "Expected response to be a , but was \n", type, @response.response_code, exception_message.to_s)) { false } + else + assert_block(build_message(message, "Expected response to be a , but was ", type, @response.response_code)) { false } + end end end end diff --git a/vendor/rails/actionpack/lib/action_controller/assertions/routing_assertions.rb b/vendor/rails/actionpack/lib/action_controller/assertions/routing_assertions.rb index 9bff2832..491b72d5 100644 --- a/vendor/rails/actionpack/lib/action_controller/assertions/routing_assertions.rb +++ b/vendor/rails/actionpack/lib/action_controller/assertions/routing_assertions.rb @@ -59,7 +59,7 @@ module ActionController end end - # Asserts that the provided options can be used to generate the provided path. This is the inverse of #assert_recognizes. + # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+. # The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures. # @@ -96,8 +96,8 @@ module ActionController end # Asserts that path and options match both ways; in other words, it verifies that path generates - # options and then that options generates path. This essentially combines #assert_recognizes - # and #assert_generates into one step. + # options and then that options generates path. This essentially combines +assert_recognizes+ + # and +assert_generates+ into one step. # # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The # +message+ parameter allows you to specify a custom error message to display upon failure. @@ -114,6 +114,9 @@ module ActionController # # # Tests a route, providing a defaults hash # assert_routing 'controller/action/9', {:id => "9", :item => "square"}, {:controller => "controller", :action => "action"}, {}, {:item => "square"} + # + # # Tests a route with a HTTP method + # assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" }) def assert_routing(path, options, defaults={}, extras={}, message=nil) assert_recognizes(options, path, extras, message) @@ -122,7 +125,7 @@ module ActionController options[:controller] = "/#{controller}" end - assert_generates(path, options, defaults, extras, message) + assert_generates(path.is_a?(Hash) ? path[:path] : path, options, defaults, extras, message) end private @@ -140,4 +143,4 @@ module ActionController end end end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb b/vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb index 573405c0..d3594e71 100644 --- a/vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb +++ b/vendor/rails/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -12,20 +12,20 @@ module ActionController NO_STRIP = %w{pre script style textarea} end - # Adds the #assert_select method for use in Rails functional + # Adds the +assert_select+ method for use in Rails functional # test cases, which can be used to make assertions on the response HTML of a controller - # action. You can also call #assert_select within another #assert_select to + # action. You can also call +assert_select+ within another +assert_select+ to # make assertions on elements selected by the enclosing assertion. # - # Use #css_select to select elements without making an assertions, either + # Use +css_select+ to select elements without making an assertions, either # from the response HTML or elements selected by the enclosing assertion. # # In addition to HTML responses, you can make the following assertions: - # * #assert_select_rjs -- Assertions on HTML content of RJS update and + # * +assert_select_rjs+ - Assertions on HTML content of RJS update and # insertion operations. - # * #assert_select_encoded -- Assertions on HTML encoded inside XML, + # * +assert_select_encoded+ - Assertions on HTML encoded inside XML, # for example for dealing with feed item descriptions. - # * #assert_select_email -- Assertions on the HTML body of an e-mail. + # * +assert_select_email+ - Assertions on the HTML body of an e-mail. # # Also see HTML::Selector to learn how to use selectors. module SelectorAssertions @@ -44,8 +44,8 @@ module ActionController # base element and any of its children. Returns an empty array if no # match is found. # - # The selector may be a CSS selector expression (+String+), an expression - # with substitution values (+Array+) or an HTML::Selector object. + # The selector may be a CSS selector expression (String), an expression + # with substitution values (Array) or an HTML::Selector object. # # ==== Examples # # Selects all div tags @@ -114,8 +114,8 @@ module ActionController # starting from (and including) that element and all its children in # depth-first order. # - # If no element if specified, calling #assert_select will select from the - # response HTML. Calling #assert_select inside an #assert_select block will + # If no element if specified, calling +assert_select+ will select from the + # response HTML. Calling #assert_select inside an +assert_select+ block will # run the assertion for each element selected by the enclosing assertion. # # ==== Example @@ -130,33 +130,33 @@ module ActionController # assert_select "li" # end # - # The selector may be a CSS selector expression (+String+), an expression + # The selector may be a CSS selector expression (String), an expression # with substitution values, or an HTML::Selector object. # # === Equality Tests # # The equality test may be one of the following: - # * true -- Assertion is true if at least one element selected. - # * false -- Assertion is true if no element selected. - # * String/Regexp -- Assertion is true if the text value of at least + # * true - Assertion is true if at least one element selected. + # * false - Assertion is true if no element selected. + # * String/Regexp - Assertion is true if the text value of at least # one element matches the string or regular expression. - # * Integer -- Assertion is true if exactly that number of + # * Integer - Assertion is true if exactly that number of # elements are selected. - # * Range -- Assertion is true if the number of selected + # * Range - Assertion is true if the number of selected # elements fit the range. # If no equality test specified, the assertion is true if at least one # element selected. # # To perform more than one equality tests, use a hash with the following keys: - # * :text -- Narrow the selection to elements that have this text + # * :text - Narrow the selection to elements that have this text # value (string or regexp). - # * :html -- Narrow the selection to elements that have this HTML + # * :html - Narrow the selection to elements that have this HTML # content (string or regexp). - # * :count -- Assertion is true if the number of selected elements + # * :count - Assertion is true if the number of selected elements # is equal to this value. - # * :minimum -- Assertion is true if the number of selected + # * :minimum - Assertion is true if the number of selected # elements is at least this value. - # * :maximum -- Assertion is true if the number of selected + # * :maximum - Assertion is true if the number of selected # elements is at most this value. # # If the method is called with a block, once all equality tests are @@ -263,12 +263,15 @@ module ActionController if match_with = equals[:text] matches.delete_if do |match| text = "" + text.force_encoding(match_with.encoding) if text.respond_to?(:force_encoding) stack = match.children.reverse while node = stack.pop if node.tag? stack.concat node.children.reverse else - text << node.content + content = node.content + content.force_encoding(match_with.encoding) if content.respond_to?(:force_encoding) + text << content end end text.strip! unless NO_STRIP.include?(match.name) @@ -353,16 +356,16 @@ module ActionController # # === Using blocks # - # Without a block, #assert_select_rjs merely asserts that the response + # Without a block, +assert_select_rjs+ merely asserts that the response # contains one or more RJS statements that replace or update content. # - # With a block, #assert_select_rjs also selects all elements used in + # With a block, +assert_select_rjs+ also selects all elements used in # these statements and passes them to the block. Nested assertions are # supported. # - # Calling #assert_select_rjs with no arguments and using nested asserts + # Calling +assert_select_rjs+ with no arguments and using nested asserts # asserts that the HTML content is returned by one or more RJS statements. - # Using #assert_select directly makes the same assertion on the content, + # Using +assert_select+ directly makes the same assertion on the content, # but without distinguishing whether the content is returned in an HTML # or JavaScript. # @@ -598,7 +601,7 @@ module ActionController RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/ end - # #assert_select and #css_select call this to obtain the content in the HTML + # +assert_select+ and +css_select+ call this to obtain the content in the HTML # page, or from all the RJS statements, depending on the type of response. def response_from_page_or_rjs() content_type = @response.content_type diff --git a/vendor/rails/actionpack/lib/action_controller/assertions/tag_assertions.rb b/vendor/rails/actionpack/lib/action_controller/assertions/tag_assertions.rb index 4ac48952..90ba3668 100644 --- a/vendor/rails/actionpack/lib/action_controller/assertions/tag_assertions.rb +++ b/vendor/rails/actionpack/lib/action_controller/assertions/tag_assertions.rb @@ -91,7 +91,7 @@ module ActionController # :descendant => { :tag => "span", # :child => /hello world/ } # - # Please note: #assert_tag and #assert_no_tag only work + # Please note: +assert_tag+ and +assert_no_tag+ only work # with well-formed XHTML. They recognize a few tags as implicitly self-closing # (like br and hr and such) but will not work correctly with tags # that allow optional closing tags (p, li, td). You must explicitly @@ -104,8 +104,8 @@ module ActionController end end - # Identical to #assert_tag, but asserts that a matching tag does _not_ - # exist. (See #assert_tag for a full discussion of the syntax.) + # Identical to +assert_tag+, but asserts that a matching tag does _not_ + # exist. (See +assert_tag+ for a full discussion of the syntax.) # # === Examples # # Assert that there is not a "div" containing a "p" diff --git a/vendor/rails/actionpack/lib/action_controller/base.rb b/vendor/rails/actionpack/lib/action_controller/base.rb index ff77e036..a036600c 100755 --- a/vendor/rails/actionpack/lib/action_controller/base.rb +++ b/vendor/rails/actionpack/lib/action_controller/base.rb @@ -5,6 +5,7 @@ require 'action_controller/routing' require 'action_controller/resources' require 'action_controller/url_rewriter' require 'action_controller/status_codes' +require 'action_view' require 'drb' require 'set' @@ -15,9 +16,6 @@ module ActionController #:nodoc: class SessionRestoreError < ActionControllerError #:nodoc: end - class MissingTemplate < ActionControllerError #:nodoc: - end - class RenderError < ActionControllerError #:nodoc: end @@ -106,7 +104,7 @@ module ActionController #:nodoc: # end # # Actions, by default, render a template in the app/views directory corresponding to the name of the controller and action - # after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the + # after executing code in the action. For example, the +index+ action of the GuestBookController would render the # template app/views/guestbook/index.erb by default after populating the @entries instance variable. # # Unlike index, the sign action will not render a template. After performing its main purpose (creating a @@ -120,10 +118,10 @@ module ActionController #:nodoc: # # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters. # This value should hold the name of the action to be performed. Once the action has been identified, the remaining - # request parameters, the session (if one is available), and the full request with all the http headers are made available to + # request parameters, the session (if one is available), and the full request with all the HTTP headers are made available to # the action through instance variables. Then the action is performed. # - # The full request object is available with the request accessor and is primarily used to query for http headers. These queries + # The full request object is available with the request accessor and is primarily used to query for HTTP headers. These queries # are made by accessing the environment hash, like this: # # def server_ip @@ -161,28 +159,34 @@ module ActionController #:nodoc: # # Hello #{session[:person]} # - # For removing objects from the session, you can either assign a single key to nil, like session[:person] = nil, or you can - # remove the entire session with reset_session. + # For removing objects from the session, you can either assign a single key to +nil+: # - # Sessions are stored in a browser cookie that's cryptographically signed, but unencrypted, by default. This prevents - # the user from tampering with the session but also allows him to see its contents. + # # removes :person from session + # session[:person] = nil # - # Do not put secret information in session! + # or you can remove the entire session with +reset_session+. + # + # Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted. + # This prevents the user from tampering with the session but also allows him to see its contents. + # + # Do not put secret information in cookie-based sessions! # # Other options for session storage are: # - # ActiveRecordStore: sessions are stored in your database, which works better than PStore with multiple app servers and, - # unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set + # * ActiveRecordStore - Sessions are stored in your database, which works better than PStore with multiple app servers and, + # unlike CookieStore, hides your session contents from the user. To use ActiveRecordStore, set # - # config.action_controller.session_store = :active_record_store + # config.action_controller.session_store = :active_record_store # - # in your environment.rb and run rake db:sessions:create. + # in your config/environment.rb and run rake db:sessions:create. # - # MemCacheStore: sessions are stored as entries in your memcached cache. Set the session store type in environment.rb: + # * MemCacheStore - Sessions are stored as entries in your memcached cache. + # Set the session store type in config/environment.rb: # - # config.action_controller.session_store = :mem_cache_store + # config.action_controller.session_store = :mem_cache_store # - # This assumes that memcached has been installed and configured properly. See the MemCacheStore docs for more information. + # This assumes that memcached has been installed and configured properly. + # See the MemCacheStore docs for more information. # # == Responses # @@ -256,14 +260,10 @@ module ActionController #:nodoc: include StatusCodes - # Determines whether the view has access to controller internals @request, @response, @session, and @template. - # By default, it does. - @@view_controller_internals = true - cattr_accessor :view_controller_internals - - # Protected instance variable cache - @@protected_variables_cache = nil - cattr_accessor :protected_variables_cache + # Controller specific instance variables which will not be accessible inside views. + @@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller + @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params + @_flash @_response) # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, # and images to a dedicated asset server away from the main web server. Example: @@ -283,17 +283,18 @@ module ActionController #:nodoc: @@debug_routes = true cattr_accessor :debug_routes - # Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex - # around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications - # may not be. Turned off by default. + # Indicates to Mongrel or Webrick whether to allow concurrent action + # processing. Your controller actions and any other code they call must + # also behave well when called from concurrent threads. Turned off by + # default. @@allow_concurrency = false cattr_accessor :allow_concurrency # Modern REST web services often need to submit complex data to the web application. - # The param_parsers hash lets you register handlers which will process the http body and add parameters to the - # params hash. These handlers are invoked for post and put requests. + # The @@param_parsers hash lets you register handlers which will process the HTTP body and add parameters to the + # params hash. These handlers are invoked for POST and PUT requests. # - # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instantiated + # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instantiated # in the params. This allows XML requests to mask themselves as regular form submissions, so you can have one # action serve both regular forms and web service requests. # @@ -306,7 +307,7 @@ module ActionController #:nodoc: # # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the # root node for such requests. The new default is to keep the root, such that "David" results - # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can + # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can # re-register XmlSimple as application/xml handler ike this: # # ActionController::Base.param_parsers[Mime::XML] = @@ -315,30 +316,30 @@ module ActionController #:nodoc: # A YAML parser is also available and can be turned on with: # # ActionController::Base.param_parsers[Mime::YAML] = :yaml - @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form, + @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form, Mime::URL_ENCODED_FORM => :url_encoded_form, - Mime::XML => :xml_simple } + Mime::XML => :xml_simple, + Mime::JSON => :json } cattr_accessor :param_parsers # Controls the default charset for all renders. @@default_charset = "utf-8" cattr_accessor :default_charset - + # The logger is used for generating information on the action run-time (including benchmarking) if available. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. cattr_accessor :logger - # Determines which template class should be used by ActionController. - cattr_accessor :template_class - - # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates. - cattr_accessor :ignore_missing_templates - # Controls the resource action separator @@resource_action_separator = "/" cattr_accessor :resource_action_separator - - # Sets the token parameter name for RequestForgery. Calling #protect_from_forgery sets it to :authenticity_token by default + + # Allow to override path names for default resources' actions + @@resources_path_names = { :new => 'new', :edit => 'edit' } + cattr_accessor :resources_path_names + + # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ + # sets it to :authenticity_token by default. cattr_accessor :request_forgery_protection_token # Indicates whether or not optimise the generated named @@ -428,10 +429,11 @@ module ActionController #:nodoc: def view_paths=(value) @view_paths = value + ActionView::TemplateFinder.process_view_paths(value) end # Adds a view_path to the front of the view_paths array. - # If the current class has no view paths, copy them from + # If the current class has no view paths, copy them from # the superclass. This change will be visible for all future requests. # # ArticleController.prepend_view_path("views/default") @@ -440,10 +442,11 @@ module ActionController #:nodoc: def prepend_view_path(path) @view_paths = superclass.view_paths.dup if @view_paths.nil? view_paths.unshift(*path) + ActionView::TemplateFinder.process_view_paths(path) end - + # Adds a view_path to the end of the view_paths array. - # If the current class has no view paths, copy them from + # If the current class has no view paths, copy them from # the superclass. This change will be visible for all future requests. # # ArticleController.append_view_path("views/default") @@ -452,8 +455,9 @@ module ActionController #:nodoc: def append_view_path(path) @view_paths = superclass.view_paths.dup if @view_paths.nil? view_paths.push(*path) + ActionView::TemplateFinder.process_view_paths(path) end - + # Replace sensitive parameter data from the request log. # Filters parameters that have any of the arguments as a substring. # Looks in all subhashes of the param hash for keys to filter. @@ -500,6 +504,7 @@ module ActionController #:nodoc: filtered_parameters end + protected :filter_parameters end # Don't render layouts for templates with the given extensions. @@ -534,23 +539,23 @@ module ActionController #:nodoc: # Returns a URL that has been rewritten according to the options hash and the defined Routes. # (For doing a complete redirect, use redirect_to). - #   + # # url_for is used to: - #   - # All keys given to url_for are forwarded to the Route module, save for the following: - # * :anchor -- specifies the anchor name to be appended to the path. For example, + # + # All keys given to +url_for+ are forwarded to the Route module, save for the following: + # * :anchor - Specifies the anchor name to be appended to the path. For example, # url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments' # will produce "/posts/show/10#comments". - # * :only_path -- if true, returns the relative URL (omitting the protocol, host name, and port) (false by default) - # * :trailing_slash -- if true, adds a trailing slash, as in "/archive/2005/". Note that this + # * :only_path - If true, returns the relative URL (omitting the protocol, host name, and port) (false by default). + # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2005/". Note that this # is currently not recommended since it breaks caching. - # * :host -- overrides the default (current) host if provided. - # * :protocol -- overrides the default (current) protocol if provided. - # * :port -- optionally specify the port to connect to. - # * :user -- Inline HTTP authentication (only plucked out if :password is also present). - # * :password -- Inline HTTP authentication (only plucked out if :user is also present). - # * :skip_relative_url_root -- if true, the url is not constructed using the relative_url_root of the request so the path - # will include the web server relative installation directory. + # * :host - Overrides the default (current) host if provided. + # * :protocol - Overrides the default (current) protocol if provided. + # * :port - Optionally specify the port to connect to. + # * :user - Inline HTTP authentication (only plucked out if :password is also present). + # * :password - Inline HTTP authentication (only plucked out if :user is also present). + # * :skip_relative_url_root - If true, the url is not constructed using the +relative_url_root+ + # of the request so the path will include the web server relative installation directory. # # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the and a query string. # Routes composes a query string as the key/value pairs not included in the . @@ -601,7 +606,7 @@ module ActionController #:nodoc: # url_for :controller => 'posts', :action => nil # # If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the - # :overwrite_params options. Say for your posts you have different views for showing and printing them. + # :overwrite_params options. Say for your posts you have different views for showing and printing them. # Then, in the show view, you get the URL for the print view like this # # url_for :overwrite_params => { :action => 'print' } @@ -639,14 +644,14 @@ module ActionController #:nodoc: end self.view_paths = [] - + # View load paths for controller. def view_paths - (@template || self.class).view_paths + @template.finder.view_paths end - + def view_paths=(value) - (@template || self.class).view_paths = value + @template.finder.view_paths = value # Mutex needed end # Adds a view_path to the front of the view_paths array. @@ -656,9 +661,9 @@ module ActionController #:nodoc: # self.prepend_view_path(["views/default", "views/custom"]) # def prepend_view_path(path) - (@template || self.class).prepend_view_path(path) + @template.finder.prepend_view_path(path) # Mutex needed end - + # Adds a view_path to the end of the view_paths array. # This change affects the current request only. # @@ -666,7 +671,7 @@ module ActionController #:nodoc: # self.append_view_path(["views/default", "views/custom"]) # def append_view_path(path) - (@template || self.class).append_view_path(path) + @template.finder.append_view_path(path) # Mutex needed end protected @@ -772,7 +777,7 @@ module ActionController #:nodoc: # # placed in "app/views/layouts/special.r(html|xml)" # render :text => "Hi there!", :layout => "special" # - # The :text option can also accept a Proc object, which can be used to manually control the page generation. This should + # The :text option can also accept a Proc object, which can be used to manually control the page generation. This should # generally be avoided, as it violates the separation between code and content, and because almost everything that can be # done with this method can also be done more cleanly using one of the other rendering methods, most notably templates. # @@ -826,19 +831,21 @@ module ActionController #:nodoc: # # === Rendering with status and location headers # - # All renders take the :status and :location options and turn them into headers. They can even be used together: + # All renders take the :status and :location options and turn them into headers. They can even be used together: # # render :xml => post.to_xml, :status => :created, :location => post_url(post) - def render(options = nil, &block) #:doc: + def render(options = nil, extra_options = {}, &block) #:doc: raise DoubleRenderError, "Can only render or redirect once per action" if performed? if options.nil? return render_for_file(default_template_name, nil, true) + elsif !extra_options.is_a?(Hash) + raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}" else if options == :update - options = { :update => true } + options = extra_options.merge({ :update => true }) elsif !options.is_a?(Hash) - raise RenderError, "You called render with invalid options : #{options}" + raise RenderError, "You called render with invalid options : #{options.inspect}" end end @@ -850,8 +857,8 @@ module ActionController #:nodoc: response.headers["Location"] = url_for(location) end - if text = options[:text] - render_for_text(text, options[:status]) + if options.has_key?(:text) + render_for_text(options[:text], options[:status]) else if file = options[:file] @@ -862,15 +869,16 @@ module ActionController #:nodoc: elsif inline = options[:inline] add_variables_to_assigns - render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status]) + tmpl = ActionView::InlineTemplate.new(@template, options[:inline], options[:locals], options[:type]) + render_for_text(@template.render_template(tmpl), options[:status]) elsif action_name = options[:action] template = default_template_name(action_name.to_s) if options[:layout] && !template_exempt_from_layout?(template) - render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true) + render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true) else render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true) - end + end elsif xml = options[:xml] response.content_type ||= Mime::XML @@ -888,12 +896,12 @@ module ActionController #:nodoc: if collection = options[:collection] render_for_text( - @template.send!(:render_partial_collection, partial, collection, + @template.send!(:render_partial_collection, partial, collection, options[:spacer_template], options[:locals]), options[:status] ) else render_for_text( - @template.send!(:render_partial, partial, + @template.send!(:render_partial, partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status] ) end @@ -904,7 +912,7 @@ module ActionController #:nodoc: generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block) response.content_type = Mime::JS - render_for_text(generator.to_s) + render_for_text(generator.to_s, options[:status]) elsif options[:nothing] # Safari doesn't pass the headers of the return if the response is zero length @@ -997,7 +1005,7 @@ module ActionController #:nodoc: # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set # by this method. - def default_url_options(options) #:doc: + def default_url_options(options = nil) end # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: @@ -1017,7 +1025,7 @@ module ActionController #:nodoc: # redirect_to articles_url # redirect_to :back # - # The redirection happens as a "302 Moved" header unless otherwise specified. + # The redirection happens as a "302 Moved" header unless otherwise specified. # # Examples: # redirect_to post_url(@post), :status=>:found @@ -1028,16 +1036,17 @@ module ActionController #:nodoc: # When using redirect_to :back, if there is no referrer, # RedirectBackError will be raised. You may specify some fallback # behavior for this case by rescuing RedirectBackError. - def redirect_to(options = {}, response_status = {}) #:doc: - - if options.is_a?(Hash) && options[:status] - status = options.delete(:status) - elsif response_status[:status] - status = response_status[:status] - else - status = 302 + def redirect_to(options = {}, response_status = {}) #:doc: + raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? + + if options.is_a?(Hash) && options[:status] + status = options.delete(:status) + elsif response_status[:status] + status = response_status[:status] + else + status = 302 end - + case options when %r{^\w+://.*} raise DoubleRenderError if performed? @@ -1095,7 +1104,6 @@ module ActionController #:nodoc: private def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc: add_variables_to_assigns - assert_existence_of_template_file(template_path) if use_full_path logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger render_for_text(@template.render_file(template_path, use_full_path, locals), status) end @@ -1112,13 +1120,9 @@ module ActionController #:nodoc: response.body = text.is_a?(Proc) ? text : text.to_s end end - - def initialize_template_class(response) - unless @@template_class - raise "You must assign a template class through ActionController.template_class= before processing a request" - end - response.template = ActionView::Base.new(view_paths, {}, self) + def initialize_template_class(response) + response.template = ActionView::Base.new(self.class.view_paths, {}, self) response.template.extend self.class.master_helper_module response.redirected_to = nil @performed_render = @performed_redirect = false @@ -1195,7 +1199,6 @@ module ActionController #:nodoc: def add_variables_to_assigns unless @variables_added add_instance_variables_to_assigns - add_class_variables_to_assigns if view_controller_internals @variables_added = true end end @@ -1209,30 +1212,11 @@ module ActionController #:nodoc: end def add_instance_variables_to_assigns - @@protected_variables_cache ||= Set.new(protected_instance_variables) - instance_variables.each do |var| - next if @@protected_variables_cache.include?(var) + (instance_variable_names - @@protected_view_variables).each do |var| @assigns[var[1..-1]] = instance_variable_get(var) end end - def add_class_variables_to_assigns - %w(view_paths logger template_class ignore_missing_templates).each do |cvar| - @assigns[cvar] = self.send(cvar) - end - end - - def protected_instance_variables - if view_controller_internals - %w(@assigns @performed_redirect @performed_render) - else - %w(@assigns @performed_redirect @performed_render - @_request @request @_response @response @_params @params - @_session @session @_cookies @cookies - @template @request_origin @parent_controller) - end - end - def request_origin # this *needs* to be cached! # otherwise you'd get different results if calling it more than once @@ -1248,7 +1232,7 @@ module ActionController #:nodoc: end def template_exists?(template_name = default_template_name) - @template.file_exists?(template_name) + @template.finder.file_exists?(template_name) end def template_public?(template_name = default_template_name) @@ -1256,20 +1240,11 @@ module ActionController #:nodoc: end def template_exempt_from_layout?(template_name = default_template_name) - extension = @template && @template.pick_template_extension(template_name) + extension = @template && @template.finder.pick_template_extension(template_name) name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name @@exempt_from_layout.any? { |ext| name_with_extension =~ ext } end - def assert_existence_of_template_file(template_name) - unless template_exists?(template_name) || ignore_missing_templates - full_template_path = template_name.include?('.') ? template_name : "#{template_name}.#{@template.template_format}.erb" - display_paths = view_paths.join(':') - template_type = (template_name =~ /layouts/i) ? 'layout' : 'template' - raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}") - end - end - def default_template_name(action_name = self.action_name) if action_name action_name = action_name.to_s diff --git a/vendor/rails/actionpack/lib/action_controller/benchmarking.rb b/vendor/rails/actionpack/lib/action_controller/benchmarking.rb index 5201d31b..98b0325b 100644 --- a/vendor/rails/actionpack/lib/action_controller/benchmarking.rb +++ b/vendor/rails/actionpack/lib/action_controller/benchmarking.rb @@ -41,14 +41,14 @@ module ActionController #:nodoc: end protected - def render_with_benchmark(options = nil, deprecated_status = nil, &block) + def render_with_benchmark(options = nil, extra_options = {}, &block) unless logger - render_without_benchmark(options, &block) + render_without_benchmark(options, extra_options, &block) else db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? render_output = nil - @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, &block) }.real + @rendering_runtime = Benchmark::realtime{ render_output = render_without_benchmark(options, extra_options, &block) } if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? @db_rt_before_render = db_runtime diff --git a/vendor/rails/actionpack/lib/action_controller/caching.rb b/vendor/rails/actionpack/lib/action_controller/caching.rb index e3e48f66..c4063dfb 100644 --- a/vendor/rails/actionpack/lib/action_controller/caching.rb +++ b/vendor/rails/actionpack/lib/action_controller/caching.rb @@ -2,6 +2,13 @@ require 'fileutils' require 'uri' require 'set' +require 'action_controller/caching/pages' +require 'action_controller/caching/actions' +require 'action_controller/caching/sql_cache' +require 'action_controller/caching/sweeping' +require 'action_controller/caching/fragments' + + module ActionController #:nodoc: # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment. @@ -9,675 +16,57 @@ module ActionController #:nodoc: # You can read more about each approach and the sweeping assistance by clicking the modules below. # # Note: To turn off all caching and sweeping, set Base.perform_caching = false. + # + # + # == Caching stores + # + # All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching. This setting only + # affects action and fragment caching as page caching is always written to disk. + # + # Configuration examples (MemoryStore is the default): + # + # ActionController::Base.cache_store = :memory_store + # ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" + # ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" + # ActionController::Base.cache_store = :mem_cache_store, "localhost" + # ActionController::Base.cache_store = MyOwnStore.new("parameter") module Caching def self.included(base) #:nodoc: base.class_eval do - include Pages, Actions, Fragments + @@cache_store = nil + cattr_reader :cache_store - if defined? ActiveRecord - include Sweeping, SqlCache + # Defines the storage option for cached fragments + def self.cache_store=(store_option) + @@cache_store = ActiveSupport::Cache.lookup_store(store_option) end + include Pages, Actions, Fragments + include Sweeping, SqlCache if defined?(ActiveRecord) + @@perform_caching = true cattr_accessor :perform_caching + + def self.cache_configured? + perform_caching && cache_store + end end end - # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server - # can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically - # generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors - # are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit - # for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates. - # - # Specifying which actions to cache is done through the caches class method: - # - # class WeblogController < ActionController::Base - # caches_page :show, :new - # end - # - # This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic - # generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to - # the Action Pack to generate it. - # - # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache - # is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends: - # - # class WeblogController < ActionController::Base - # def update - # List.update(params[:list][:id], params[:list]) - # expire_page :action => "show", :id => params[:list][:id] - # redirect_to :action => "show", :id => params[:list][:id] - # end - # end - # - # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be - # expired. - # - # == Setting the cache directory - # - # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root". - # For Rails, this directory has already been set to RAILS_ROOT + "/public". - # - # == Setting the cache extension - # - # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want - # something else, like .php or .shtml, just set Base.page_cache_extension. - module Pages - def self.included(base) #:nodoc: - base.extend(ClassMethods) - base.class_eval do - @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "" - cattr_accessor :page_cache_directory - - @@page_cache_extension = '.html' - cattr_accessor :page_cache_extension - end - end - - module ClassMethods - # Expires the page that was cached with the +path+ as a key. Example: - # expire_page "/lists/show" - def expire_page(path) - return unless perform_caching - - benchmark "Expired page: #{page_cache_file(path)}" do - File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path)) - end - end - - # Manually cache the +content+ in the key determined by +path+. Example: - # cache_page "I'm the cached content", "/lists/show" - def cache_page(content, path) - return unless perform_caching - - benchmark "Cached page: #{page_cache_file(path)}" do - FileUtils.makedirs(File.dirname(page_cache_path(path))) - File.open(page_cache_path(path), "wb+") { |f| f.write(content) } - end - end - - # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that - # matches the triggering url. - def caches_page(*actions) - return unless perform_caching - actions = actions.map(&:to_s) - after_filter { |c| c.cache_page if actions.include?(c.action_name) } - end - - private - def page_cache_file(path) - name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/')) - name << page_cache_extension unless (name.split('/').last || name).include? '.' - return name - end - - def page_cache_path(path) - page_cache_directory + page_cache_file(path) - end - end - - # Expires the page that was cached with the +options+ as a key. Example: - # expire_page :controller => "lists", :action => "show" - def expire_page(options = {}) - return unless perform_caching - - if options.is_a?(Hash) - if options[:action].is_a?(Array) - options[:action].dup.each do |action| - self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action))) - end - else - self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true))) - end + protected + # Convenience accessor + def cache(key, options = {}, &block) + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) else - self.class.expire_page(options) - end - end - - # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used - # If no options are provided, the requested url is used. Example: - # cache_page "I'm the cached content", :controller => "lists", :action => "show" - def cache_page(content = nil, options = nil) - return unless perform_caching && caching_allowed - - path = case options - when Hash - url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])) - when String - options - else - request.path - end - - self.class.cache_page(content || response.body, path) - end - - private - def caching_allowed - request.get? && response.headers['Status'].to_i == 200 - end - end - - # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching, - # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which - # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example: - # - # class ListsController < ApplicationController - # before_filter :authenticate, :except => :public - # caches_page :public - # caches_action :show, :feed - # end - # - # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the - # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches. - # - # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both - # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named - # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and - # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern. - # - # Different representations of the same resource, e.g. http://david.somewhere.com/lists and http://david.somewhere.com/lists.xml - # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that :action => 'lists' is not the same - # as :action => 'list', :format => :xml. - # - # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy - # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. - # - # class ListsController < ApplicationController - # before_filter :authenticate, :except => :public - # caches_page :public - # caches_action :show, :cache_path => { :project => 1 } - # caches_action :show, :cache_path => Proc.new { |controller| - # controller.params[:user_id] ? - # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) : - # controller.send(:list_url, c.params[:id]) } - # end - module Actions - def self.included(base) #:nodoc: - base.extend(ClassMethods) - base.class_eval do - attr_accessor :rendered_action_cache, :action_cache_path - alias_method_chain :protected_instance_variables, :action_caching - end - end - - module ClassMethods - # Declares that +actions+ should be cached. - # See ActionController::Caching::Actions for details. - def caches_action(*actions) - return unless perform_caching - around_filter(ActionCacheFilter.new(*actions)) - end - end - - def protected_instance_variables_with_action_caching - protected_instance_variables_without_action_caching + %w(@action_cache_path) - end - - def expire_action(options = {}) - return unless perform_caching - if options[:action].is_a?(Array) - options[:action].dup.each do |action| - expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }))) - end - else - expire_fragment(ActionCachePath.path_for(self, options)) - end - end - - class ActionCacheFilter #:nodoc: - def initialize(*actions, &block) - @options = actions.extract_options! - @actions = Set.new actions - end - - def before(controller) - return unless @actions.include?(controller.action_name.intern) - cache_path = ActionCachePath.new(controller, path_options_for(controller, @options)) - if cache = controller.read_fragment(cache_path.path) - controller.rendered_action_cache = true - set_content_type!(controller, cache_path.extension) - controller.send!(:render_for_text, cache) - false - else - controller.action_cache_path = cache_path - end - end - - def after(controller) - return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller) - controller.write_fragment(controller.action_cache_path.path, controller.response.body) - end - - private - def set_content_type!(controller, extension) - controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension - end - - def path_options_for(controller, options) - ((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {} - end - - def caching_allowed(controller) - controller.request.get? && controller.response.headers['Status'].to_i == 200 - end - end - - class ActionCachePath - attr_reader :path, :extension - - class << self - def path_for(controller, options) - new(controller, options).path - end - end - - def initialize(controller, options = {}) - @extension = extract_extension(controller.request.path) - path = controller.url_for(options).split('://').last - normalize!(path) - add_extension!(path, @extension) - @path = URI.unescape(path) - end - - private - def normalize!(path) - path << 'index' if path[-1] == ?/ - end - - def add_extension!(path, extension) - path << ".#{extension}" if extension - end - - def extract_extension(file_path) - # Don't want just what comes after the last '.' to accommodate multi part extensions - # such as tar.gz. - file_path[/^[^.]+\.(.+)$/, 1] - end - end - end - - # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when - # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple - # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like: - # - # Hello <%= @name %> - # <% cache do %> - # All the topics in the system: - # <%= render :partial => "topic", :collection => Topic.find(:all) %> - # <% end %> - # - # This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would - # be able to invalidate it using expire_fragment(:controller => "topics", :action => "list"). - # - # This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using - # caches_action, so we also have the option to qualify the name of the cached fragment with something like: - # - # <% cache(:action => "list", :action_suffix => "all_topics") do %> - # - # That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a - # different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique - # cache names that we can refer to when we need to expire the cache. - # - # The expiration call for this example is: - # - # expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics") - # - # == Fragment stores - # - # By default, cached fragments are stored in memory. The available store options are: - # - # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and allows all - # processes running from the same application directory to access the cached content. - # * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its - # own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take - # up a lot of memory since each process keeps all the caches in memory. - # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache - # around for all processes, but requires that you run and manage a separate DRb process. - # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead. - # Requires the ruby-memcache library: gem install ruby-memcache. - # - # Configuration examples (MemoryStore is the default): - # - # ActionController::Base.fragment_cache_store = :memory_store - # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" - # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" - # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost" - # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter") - module Fragments - def self.included(base) #:nodoc: - base.class_eval do - @@fragment_cache_store = MemoryStore.new - cattr_reader :fragment_cache_store - - # Defines the storage option for cached fragments - def self.fragment_cache_store=(store_option) - store, *parameters = *([ store_option ].flatten) - @@fragment_cache_store = if store.is_a?(Symbol) - store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize) - store_class = ActionController::Caching::Fragments.const_get(store_class_name) - store_class.new(*parameters) - else - store - end - end - end - end - - # Given a name (as described in expire_fragment), returns a key suitable for use in reading, - # writing, or expiring a cached fragment. If the name is a hash, the generated name is the return - # value of url_for on that hash (without the protocol). - def fragment_cache_key(name) - name.is_a?(Hash) ? url_for(name).split("://").last : name - end - - # Called by CacheHelper#cache - def cache_erb_fragment(block, name = {}, options = nil) - unless perform_caching then block.call; return end - - buffer = eval(ActionView::Base.erb_variable, block.binding) - - if cache = read_fragment(name, options) - buffer.concat(cache) - else - pos = buffer.length - block.call - write_fragment(name, buffer[pos..-1], options) - end - end - - # Writes content to the location signified by name (see expire_fragment for acceptable formats) - def write_fragment(name, content, options = nil) - return unless perform_caching - - key = fragment_cache_key(name) - self.class.benchmark "Cached fragment: #{key}" do - fragment_cache_store.write(key, content, options) - end - content - end - - # Reads a cached fragment from the location signified by name (see expire_fragment for acceptable formats) - def read_fragment(name, options = nil) - return unless perform_caching - - key = fragment_cache_key(name) - self.class.benchmark "Fragment read: #{key}" do - fragment_cache_store.read(key, options) - end - end - - # Name can take one of three forms: - # * String: This would normally take the form of a path like "pages/45/notes" - # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 } - # * Regexp: Will destroy all the matched fragments, example: - # %r{pages/\d*/notes} - # Ensure you do not specify start and finish in the regex (^$) because - # the actual filename matched looks like ./cache/filename/path.cache - # Regexp expiration is only supported on caches that can iterate over - # all keys (unlike memcached). - def expire_fragment(name, options = nil) - return unless perform_caching - - key = fragment_cache_key(name) - - if key.is_a?(Regexp) - self.class.benchmark "Expired fragments matching: #{key.source}" do - fragment_cache_store.delete_matched(key, options) - end - else - self.class.benchmark "Expired fragment: #{key}" do - fragment_cache_store.delete(key, options) - end + yield end end - class UnthreadedMemoryStore #:nodoc: - def initialize #:nodoc: - @data = {} - end - - def read(name, options=nil) #:nodoc: - @data[name] - end - - def write(name, value, options=nil) #:nodoc: - @data[name] = value - end - - def delete(name, options=nil) #:nodoc: - @data.delete(name) - end - - def delete_matched(matcher, options=nil) #:nodoc: - @data.delete_if { |k,v| k =~ matcher } - end + private + def cache_configured? + self.class.cache_configured? end - - module ThreadSafety #:nodoc: - def read(name, options=nil) #:nodoc: - @mutex.synchronize { super } - end - - def write(name, value, options=nil) #:nodoc: - @mutex.synchronize { super } - end - - def delete(name, options=nil) #:nodoc: - @mutex.synchronize { super } - end - - def delete_matched(matcher, options=nil) #:nodoc: - @mutex.synchronize { super } - end - end - - class MemoryStore < UnthreadedMemoryStore #:nodoc: - def initialize #:nodoc: - super - if ActionController::Base.allow_concurrency - @mutex = Mutex.new - MemoryStore.module_eval { include ThreadSafety } - end - end - end - - class DRbStore < MemoryStore #:nodoc: - attr_reader :address - - def initialize(address = 'druby://localhost:9192') - super() - @address = address - @data = DRbObject.new(nil, address) - end - end - - begin - require_library_or_gem 'memcache' - class MemCacheStore < MemoryStore #:nodoc: - attr_reader :addresses - - def initialize(*addresses) - super() - addresses = addresses.flatten - addresses = ["localhost"] if addresses.empty? - @addresses = addresses - @data = MemCache.new(*addresses) - end - end - rescue LoadError - # MemCache wasn't available so neither can the store be - end - - class UnthreadedFileStore #:nodoc: - attr_reader :cache_path - - def initialize(cache_path) - @cache_path = cache_path - end - - def write(name, value, options = nil) #:nodoc: - ensure_cache_path(File.dirname(real_file_path(name))) - File.open(real_file_path(name), "wb+") { |f| f.write(value) } - rescue => e - Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger - end - - def read(name, options = nil) #:nodoc: - File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil - end - - def delete(name, options) #:nodoc: - File.delete(real_file_path(name)) - rescue SystemCallError => e - # If there's no cache, then there's nothing to complain about - end - - def delete_matched(matcher, options) #:nodoc: - search_dir(@cache_path) do |f| - if f =~ matcher - begin - File.delete(f) - rescue SystemCallError => e - # If there's no cache, then there's nothing to complain about - end - end - end - end - - private - def real_file_path(name) - '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')] - end - - def ensure_cache_path(path) - FileUtils.makedirs(path) unless File.exist?(path) - end - - def search_dir(dir, &callback) - Dir.foreach(dir) do |d| - next if d == "." || d == ".." - name = File.join(dir, d) - if File.directory?(name) - search_dir(name, &callback) - else - callback.call name - end - end - end - end - - class FileStore < UnthreadedFileStore #:nodoc: - def initialize(cache_path) - super(cache_path) - if ActionController::Base.allow_concurrency - @mutex = Mutex.new - FileStore.module_eval { include ThreadSafety } - end - end - end - end - - # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. - # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: - # - # class ListSweeper < ActionController::Caching::Sweeper - # observe List, Item - # - # def after_save(record) - # list = record.is_a?(List) ? record : record.list - # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id) - # expire_action(:controller => "lists", :action => "all") - # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) } - # end - # end - # - # The sweeper is assigned in the controllers that wish to have its job performed using the cache_sweeper class method: - # - # class ListsController < ApplicationController - # caches_action :index, :show, :public, :feed - # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ] - # end - # - # In the example above, four actions are cached and three actions are responsible for expiring those caches. - module Sweeping - def self.included(base) #:nodoc: - base.extend(ClassMethods) - end - - module ClassMethods #:nodoc: - def cache_sweeper(*sweepers) - return unless perform_caching - configuration = sweepers.extract_options! - sweepers.each do |sweeper| - ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) - sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance - - if sweeper_instance.is_a?(Sweeper) - around_filter(sweeper_instance, :only => configuration[:only]) - else - after_filter(sweeper_instance, :only => configuration[:only]) - end - end - end - end - end - - if defined?(ActiveRecord) and defined?(ActiveRecord::Observer) - class Sweeper < ActiveRecord::Observer #:nodoc: - attr_accessor :controller - - def before(controller) - self.controller = controller - callback(:before) - end - - def after(controller) - callback(:after) - # Clean up, so that the controller can be collected after this request - self.controller = nil - end - - protected - # gets the action cache path for the given options. - def action_path_for(options) - ActionController::Caching::Actions::ActionCachePath.path_for(controller, options) - end - - # Retrieve instance variables set in the controller. - def assigns(key) - controller.instance_variable_get("@#{key}") - end - - private - def callback(timing) - controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" - action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}" - - send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true) - send!(action_callback_method_name) if respond_to?(action_callback_method_name, true) - end - - def method_missing(method, *arguments) - return if @controller.nil? - @controller.send!(method, *arguments) - end - end - end - - module SqlCache - def self.included(base) #:nodoc: - if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) - base.alias_method_chain :perform_action, :caching - end - end - - def perform_action_with_caching - ActiveRecord::Base.cache do - perform_action_without_caching - end - end - end end -end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller/caching/actions.rb b/vendor/rails/actionpack/lib/action_controller/caching/actions.rb new file mode 100644 index 00000000..1ef9e60a --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/caching/actions.rb @@ -0,0 +1,144 @@ +require 'set' + +module ActionController #:nodoc: + module Caching + # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching, + # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which + # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example: + # + # class ListsController < ApplicationController + # before_filter :authenticate, :except => :public + # caches_page :public + # caches_action :index, :show, :feed + # end + # + # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the + # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches. + # + # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both + # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named + # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and + # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern. + # + # Different representations of the same resource, e.g. http://david.somewhere.com/lists and http://david.somewhere.com/lists.xml + # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that :action => 'lists' is not the same + # as :action => 'list', :format => :xml. + # + # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy + # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. + # + # And you can also use :if to pass a Proc that specifies when the action should be cached. + # + # class ListsController < ApplicationController + # before_filter :authenticate, :except => :public + # caches_page :public + # caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request + # caches_action :show, :cache_path => { :project => 1 } + # caches_action :feed, :cache_path => Proc.new { |controller| + # controller.params[:user_id] ? + # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) : + # controller.send(:list_url, c.params[:id]) } + # end + # + module Actions + def self.included(base) #:nodoc: + base.extend(ClassMethods) + base.class_eval do + attr_accessor :rendered_action_cache, :action_cache_path + end + end + + module ClassMethods + # Declares that +actions+ should be cached. + # See ActionController::Caching::Actions for details. + def caches_action(*actions) + return unless cache_configured? + options = actions.extract_options! + around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options)) + end + end + + protected + def expire_action(options = {}) + return unless cache_configured? + + if options[:action].is_a?(Array) + options[:action].dup.each do |action| + expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }))) + end + else + expire_fragment(ActionCachePath.path_for(self, options)) + end + end + + class ActionCacheFilter #:nodoc: + def initialize(options, &block) + @options = options + end + + def before(controller) + cache_path = ActionCachePath.new(controller, path_options_for(controller, @options)) + if cache = controller.read_fragment(cache_path.path) + controller.rendered_action_cache = true + set_content_type!(controller, cache_path.extension) + controller.send!(:render_for_text, cache) + false + else + controller.action_cache_path = cache_path + end + end + + def after(controller) + return if controller.rendered_action_cache || !caching_allowed(controller) + controller.write_fragment(controller.action_cache_path.path, controller.response.body) + end + + private + def set_content_type!(controller, extension) + controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension + end + + def path_options_for(controller, options) + ((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {} + end + + def caching_allowed(controller) + controller.request.get? && controller.response.headers['Status'].to_i == 200 + end + end + + class ActionCachePath + attr_reader :path, :extension + + class << self + def path_for(controller, options) + new(controller, options).path + end + end + + def initialize(controller, options = {}) + @extension = extract_extension(controller.request.path) + path = controller.url_for(options).split('://').last + normalize!(path) + add_extension!(path, @extension) + @path = URI.unescape(path) + end + + private + def normalize!(path) + path << 'index' if path[-1] == ?/ + end + + def add_extension!(path, extension) + path << ".#{extension}" if extension + end + + def extract_extension(file_path) + # Don't want just what comes after the last '.' to accommodate multi part extensions + # such as tar.gz. + file_path[/^[^.]+\.(.+)$/, 1] + end + end + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/caching/fragments.rb b/vendor/rails/actionpack/lib/action_controller/caching/fragments.rb new file mode 100644 index 00000000..e4f5de44 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/caching/fragments.rb @@ -0,0 +1,138 @@ +module ActionController #:nodoc: + module Caching + # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when + # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple + # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like: + # + # Hello <%= @name %> + # <% cache do %> + # All the topics in the system: + # <%= render :partial => "topic", :collection => Topic.find(:all) %> + # <% end %> + # + # This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would + # be able to invalidate it using expire_fragment(:controller => "topics", :action => "list"). + # + # This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using + # caches_action, so we also have the option to qualify the name of the cached fragment with something like: + # + # <% cache(:action => "list", :action_suffix => "all_topics") do %> + # + # That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a + # different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique + # cache names that we can refer to when we need to expire the cache. + # + # The expiration call for this example is: + # + # expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics") + module Fragments + def self.included(base) #:nodoc: + base.class_eval do + class << self + def fragment_cache_store=(store_option) #:nodoc: + ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=') + self.cache_store = store_option + end + + def fragment_cache_store #:nodoc: + ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store') + cache_store + end + end + + def fragment_cache_store=(store_option) #:nodoc: + ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=') + self.cache_store = store_option + end + + def fragment_cache_store #:nodoc: + ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store') + cache_store + end + end + end + + # Given a key (as described in expire_fragment), returns a key suitable for use in reading, + # writing, or expiring a cached fragment. If the key is a hash, the generated key is the return + # value of url_for on that hash (without the protocol). All keys are prefixed with "views/" and uses + # ActiveSupport::Cache.expand_cache_key for the expansion. + def fragment_cache_key(key) + ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) + end + + def fragment_for(block, name = {}, options = nil) #:nodoc: + unless perform_caching then block.call; return end + + buffer = yield + + if cache = read_fragment(name, options) + buffer.concat(cache) + else + pos = buffer.length + block.call + write_fragment(name, buffer[pos..-1], options) + end + end + + # Writes content to the location signified by key (see expire_fragment for acceptable formats) + def write_fragment(key, content, options = nil) + return unless cache_configured? + + key = fragment_cache_key(key) + + self.class.benchmark "Cached fragment miss: #{key}" do + cache_store.write(key, content, options) + end + + content + end + + # Reads a cached fragment from the location signified by key (see expire_fragment for acceptable formats) + def read_fragment(key, options = nil) + return unless cache_configured? + + key = fragment_cache_key(key) + + self.class.benchmark "Cached fragment hit: #{key}" do + cache_store.read(key, options) + end + end + + # Check if a cached fragment from the location signified by key exists (see expire_fragment for acceptable formats) + def fragment_exist?(key, options = nil) + return unless cache_configured? + + key = fragment_cache_key(key) + + self.class.benchmark "Cached fragment exists?: #{key}" do + cache_store.exist?(key, options) + end + end + + # Name can take one of three forms: + # * String: This would normally take the form of a path like "pages/45/notes" + # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 } + # * Regexp: Will destroy all the matched fragments, example: + # %r{pages/\d*/notes} + # Ensure you do not specify start and finish in the regex (^$) because + # the actual filename matched looks like ./cache/filename/path.cache + # Regexp expiration is only supported on caches that can iterate over + # all keys (unlike memcached). + def expire_fragment(key, options = nil) + return unless cache_configured? + + key = key.is_a?(Regexp) ? key : fragment_cache_key(key) + + if key.is_a?(Regexp) + self.class.benchmark "Expired fragments matching: #{key.source}" do + cache_store.delete_matched(key, options) + end + else + self.class.benchmark "Expired fragment: #{key}" do + cache_store.delete(key, options) + end + end + end + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/caching/pages.rb b/vendor/rails/actionpack/lib/action_controller/caching/pages.rb new file mode 100644 index 00000000..a70ed72f --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/caching/pages.rb @@ -0,0 +1,154 @@ +require 'fileutils' +require 'uri' + +module ActionController #:nodoc: + module Caching + # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server + # can serve without going through Action Pack. This is the fastest way to cache your content as opposed to going dynamically + # through the process of generating the content. Unfortunately, this incredible speed-up is only available to stateless pages + # where all visitors are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are + # a great fit for this approach, but account-based systems where people log in and manipulate their own data are often less + # likely candidates. + # + # Specifying which actions to cache is done through the caches_page class method: + # + # class WeblogController < ActionController::Base + # caches_page :show, :new + # end + # + # This will generate cache files such as weblog/show/5.html and weblog/new.html, + # which match the URLs used to trigger the dynamic generation. This is how the web server is able + # pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it. + # + # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache + # is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends: + # + # class WeblogController < ActionController::Base + # def update + # List.update(params[:list][:id], params[:list]) + # expire_page :action => "show", :id => params[:list][:id] + # redirect_to :action => "show", :id => params[:list][:id] + # end + # end + # + # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be + # expired. + # + # == Setting the cache directory + # + # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root". + # For Rails, this directory has already been set to Rails.public_path (which is usually set to RAILS_ROOT + "/public"). Changing + # this setting can be useful to avoid naming conflicts with files in public/, but doing so will likely require configuring your + # web server to look in the new location for cached files. + # + # == Setting the cache extension + # + # Most Rails requests do not have an extension, such as /weblog/new. In these cases, the page caching mechanism will add one in + # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is .html. + # If you want something else, like .php or .shtml, just set Base.page_cache_extension. In cases where a request already has an + # extension, such as .xml or .rss, page caching will not add an extension. This allows it to work well with RESTful apps. + module Pages + def self.included(base) #:nodoc: + base.extend(ClassMethods) + base.class_eval do + @@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" + cattr_accessor :page_cache_directory + + @@page_cache_extension = '.html' + cattr_accessor :page_cache_extension + end + end + + module ClassMethods + # Expires the page that was cached with the +path+ as a key. Example: + # expire_page "/lists/show" + def expire_page(path) + return unless perform_caching + + benchmark "Expired page: #{page_cache_file(path)}" do + File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path)) + end + end + + # Manually cache the +content+ in the key determined by +path+. Example: + # cache_page "I'm the cached content", "/lists/show" + def cache_page(content, path) + return unless perform_caching + + benchmark "Cached page: #{page_cache_file(path)}" do + FileUtils.makedirs(File.dirname(page_cache_path(path))) + File.open(page_cache_path(path), "wb+") { |f| f.write(content) } + end + end + + # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that + # matches the triggering url. + # + # Usage: + # + # # cache the index action + # caches_page :index + # + # # cache the index action except for JSON requests + # caches_page :index, :if => Proc.new { |c| !c.request.format.json? } + def caches_page(*actions) + return unless perform_caching + options = actions.extract_options! + after_filter({:only => actions}.merge(options)) { |c| c.cache_page } + end + + private + def page_cache_file(path) + name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/')) + name << page_cache_extension unless (name.split('/').last || name).include? '.' + return name + end + + def page_cache_path(path) + page_cache_directory + page_cache_file(path) + end + end + + # Expires the page that was cached with the +options+ as a key. Example: + # expire_page :controller => "lists", :action => "show" + def expire_page(options = {}) + return unless perform_caching + + if options.is_a?(Hash) + if options[:action].is_a?(Array) + options[:action].dup.each do |action| + self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action))) + end + else + self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true))) + end + else + self.class.expire_page(options) + end + end + + # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used + # If no options are provided, the requested url is used. Example: + # cache_page "I'm the cached content", :controller => "lists", :action => "show" + def cache_page(content = nil, options = nil) + return unless perform_caching && caching_allowed + + path = case options + when Hash + url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])) + when String + options + else + request.path + end + + self.class.cache_page(content || response.body, path) + end + + private + def caching_allowed + request.get? && response.headers['Status'].to_i == 200 + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb b/vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb new file mode 100644 index 00000000..139be610 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/caching/sql_cache.rb @@ -0,0 +1,18 @@ +module ActionController #:nodoc: + module Caching + module SqlCache + def self.included(base) #:nodoc: + if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) + base.alias_method_chain :perform_action, :caching + end + end + + protected + def perform_action_with_caching + ActiveRecord::Base.cache do + perform_action_without_caching + end + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb b/vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb new file mode 100644 index 00000000..61559e9e --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/caching/sweeping.rb @@ -0,0 +1,97 @@ +module ActionController #:nodoc: + module Caching + # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. + # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: + # + # class ListSweeper < ActionController::Caching::Sweeper + # observe List, Item + # + # def after_save(record) + # list = record.is_a?(List) ? record : record.list + # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id) + # expire_action(:controller => "lists", :action => "all") + # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) } + # end + # end + # + # The sweeper is assigned in the controllers that wish to have its job performed using the cache_sweeper class method: + # + # class ListsController < ApplicationController + # caches_action :index, :show, :public, :feed + # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ] + # end + # + # In the example above, four actions are cached and three actions are responsible for expiring those caches. + # + # You can also name an explicit class in the declaration of a sweeper, which is needed if the sweeper is in a module: + # + # class ListsController < ApplicationController + # caches_action :index, :show, :public, :feed + # cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ] + # end + module Sweeping + def self.included(base) #:nodoc: + base.extend(ClassMethods) + end + + module ClassMethods #:nodoc: + def cache_sweeper(*sweepers) + configuration = sweepers.extract_options! + + sweepers.each do |sweeper| + ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) + sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance + + if sweeper_instance.is_a?(Sweeper) + around_filter(sweeper_instance, :only => configuration[:only]) + else + after_filter(sweeper_instance, :only => configuration[:only]) + end + end + end + end + end + + if defined?(ActiveRecord) and defined?(ActiveRecord::Observer) + class Sweeper < ActiveRecord::Observer #:nodoc: + attr_accessor :controller + + def before(controller) + self.controller = controller + callback(:before) if controller.perform_caching + end + + def after(controller) + callback(:after) if controller.perform_caching + # Clean up, so that the controller can be collected after this request + self.controller = nil + end + + protected + # gets the action cache path for the given options. + def action_path_for(options) + ActionController::Caching::Actions::ActionCachePath.path_for(controller, options) + end + + # Retrieve instance variables set in the controller. + def assigns(key) + controller.instance_variable_get("@#{key}") + end + + private + def callback(timing) + controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" + action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}" + + send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true) + send!(action_callback_method_name) if respond_to?(action_callback_method_name, true) + end + + def method_missing(method, *arguments) + return if @controller.nil? + @controller.send!(method, *arguments) + end + end + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/cgi_ext/cookie.rb b/vendor/rails/actionpack/lib/action_controller/cgi_ext/cookie.rb index 07d2f08d..009ddd1c 100644 --- a/vendor/rails/actionpack/lib/action_controller/cgi_ext/cookie.rb +++ b/vendor/rails/actionpack/lib/action_controller/cgi_ext/cookie.rb @@ -6,25 +6,24 @@ class CGI #:nodoc: attr_accessor :name, :value, :path, :domain, :expires attr_reader :secure, :http_only - # Create a new CGI::Cookie object. + # Creates a new CGI::Cookie object. # # The contents of the cookie can be specified as a +name+ and one # or more +value+ arguments. Alternatively, the contents can # be specified as a single hash argument. The possible keywords of # this hash are as follows: # - # name:: the name of the cookie. Required. - # value:: the cookie's value or list of values. - # path:: the path for which this cookie applies. Defaults to the - # base directory of the CGI script. - # domain:: the domain for which this cookie applies. - # expires:: the time at which this cookie expires, as a +Time+ object. - # secure:: whether this cookie is a secure cookie or not (default to - # false). Secure cookies are only transmitted to HTTPS - # servers. - # http_only:: whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP - # More details: http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx - # Defaults to false. + # * :name - The name of the cookie. Required. + # * :value - The cookie's value or list of values. + # * :path - The path for which this cookie applies. Defaults to the + # base directory of the CGI script. + # * :domain - The domain for which this cookie applies. + # * :expires - The time at which this cookie expires, as a Time object. + # * :secure - Whether this cookie is a secure cookie or not (defaults to + # +false+). Secure cookies are only transmitted to HTTPS servers. + # * :http_only - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP. + # More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+. + # # These keywords correspond to attributes of the cookie object. def initialize(name = '', *value) if name.kind_of?(String) @@ -37,7 +36,7 @@ class CGI #:nodoc: @path = nil else @name = name['name'] - @value = Array(name['value']) + @value = (name['value'].kind_of?(String) ? [name['value']] : Array(name['value'])).delete_if(&:blank?) @domain = name['domain'] @expires = name['expires'] @secure = name['secure'] || false @@ -56,17 +55,17 @@ class CGI #:nodoc: super(@value) end - # Set whether the Cookie is a secure cookie or not. + # Sets whether the Cookie is a secure cookie or not. def secure=(val) @secure = val == true end - # Set whether the Cookie is an HTTP only cookie or not. + # Sets whether the Cookie is an HTTP only cookie or not. def http_only=(val) @http_only = val == true end - # Convert the Cookie to its string representation. + # Converts the Cookie to its string representation. def to_s buf = '' buf << @name << '=' @@ -79,23 +78,28 @@ class CGI #:nodoc: buf end - # Parse a raw cookie string into a hash of cookie-name=>Cookie + # FIXME: work around broken 1.8.7 DelegateClass#respond_to? + def respond_to?(method, include_private = false) + return true if super(method) + return __getobj__.respond_to?(method, include_private) + end + + # Parses a raw cookie string into a hash of cookie-name => cookie-object # pairs. # # cookies = CGI::Cookie::parse("raw_cookie_string") - # # { "name1" => cookie1, "name2" => cookie2, ... } + # # => { "name1" => cookie1, "name2" => cookie2, ... } # def self.parse(raw_cookie) cookies = Hash.new([]) if raw_cookie - raw_cookie.split(/[;,]\s?/).each do |pairs| - name, values = pairs.split('=',2) - next unless name and values + raw_cookie.split(/;\s?/).each do |pairs| + name, value = pairs.split('=',2) + next unless name and value name = CGI::unescape(name) - values = values.split('&').collect!{|v| CGI::unescape(v) } unless cookies.has_key?(name) - cookies[name] = new(name, *values) + cookies[name] = new(name, CGI::unescape(value)) end end end diff --git a/vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb b/vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb index b0ca63ef..5e9b6784 100644 --- a/vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb +++ b/vendor/rails/actionpack/lib/action_controller/cgi_ext/stdinput.rb @@ -16,6 +16,7 @@ module ActionController def initialize_with_stdinput(type = nil, stdinput = $stdin) @stdinput = stdinput + @stdinput.set_encoding(Encoding::BINARY) if @stdinput.respond_to?(:set_encoding) initialize_without_stdinput(type || 'query') end end diff --git a/vendor/rails/actionpack/lib/action_controller/cgi_process.rb b/vendor/rails/actionpack/lib/action_controller/cgi_process.rb index 6a802aa8..8bc5e4c3 100644 --- a/vendor/rails/actionpack/lib/action_controller/cgi_process.rb +++ b/vendor/rails/actionpack/lib/action_controller/cgi_process.rb @@ -3,7 +3,7 @@ require 'action_controller/session/cookie_store' module ActionController #:nodoc: class Base - # Process a request extracted from an CGI object and return a response. Pass false as session_options to disable + # Process a request extracted from a CGI object and return a response. Pass false as session_options to disable # sessions (large performance increase if sessions are not needed). The session_options are the same as for CGI::Session: # # * :database_manager - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore @@ -15,9 +15,9 @@ module ActionController #:nodoc: # * :new_session - if true, force creation of a new session. If not set, a new session is only created if none currently # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set, # an ArgumentError is raised. - # * :session_expires - the time the current session expires, as a +Time+ object. If not set, the session will continue + # * :session_expires - the time the current session expires, as a Time object. If not set, the session will continue # indefinitely. - # * :session_domain - the hostname domain for which this session is valid. If not set, defaults to the hostname of the + # * :session_domain - the hostname domain for which this session is valid. If not set, defaults to the hostname of the # server. # * :session_secure - if +true+, this session will only work over HTTPS. # * :session_path - the path for which this session applies. Defaults to the directory of the CGI script. @@ -34,7 +34,8 @@ module ActionController #:nodoc: class CgiRequest < AbstractRequest #:nodoc: attr_accessor :cgi, :session_options - class SessionFixationAttempt < StandardError; end #:nodoc: + class SessionFixationAttempt < StandardError #:nodoc: + end DEFAULT_SESSION_OPTIONS = { :database_manager => CGI::Session::CookieStore, # store data in cookie @@ -64,6 +65,7 @@ module ActionController #:nodoc: # variable is already set, wrap it in a StringIO. def body if raw_post = env['RAW_POST_DATA'] + raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding) StringIO.new(raw_post) else @cgi.stdinput diff --git a/vendor/rails/actionpack/lib/action_controller/components.rb b/vendor/rails/actionpack/lib/action_controller/components.rb index 7f7ecfff..8275bd38 100644 --- a/vendor/rails/actionpack/lib/action_controller/components.rb +++ b/vendor/rails/actionpack/lib/action_controller/components.rb @@ -39,12 +39,7 @@ module ActionController #:nodoc: base.class_eval do include InstanceMethods extend ClassMethods - - helper do - def render_component(options) - @controller.send!(:render_component_as_string, options) - end - end + helper HelperMethods # If this controller was instantiated to process a component request, # +parent_controller+ points to the instantiator of this controller. @@ -67,6 +62,12 @@ module ActionController #:nodoc: end end + module HelperMethods + def render_component(options) + @controller.send!(:render_component_as_string, options) + end + end + module InstanceMethods # Extracts the action_name from the request parameters and performs that action. def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc: diff --git a/vendor/rails/actionpack/lib/action_controller/cookies.rb b/vendor/rails/actionpack/lib/action_controller/cookies.rb index 19847c69..a4cddbce 100644 --- a/vendor/rails/actionpack/lib/action_controller/cookies.rb +++ b/vendor/rails/actionpack/lib/action_controller/cookies.rb @@ -1,31 +1,38 @@ module ActionController #:nodoc: - # Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request, - # the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object - # itself back -- just the value it holds). Examples for writing: + # Cookies are read and written through ActionController#cookies. # - # cookies[:user_name] = "david" # => Will set a simple session cookie + # The cookies being read are the ones received along with the request, the cookies + # being written will be sent out with the response. Reading a cookie does not get + # the cookie object itself back, just the value it holds. + # + # Examples for writing: + # + # # Sets a simple session cookie. + # cookies[:user_name] = "david" + # + # # Sets a cookie that expires in 1 hour. # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now } - # # => Will set a cookie that expires in 1 hour # # Examples for reading: # # cookies[:user_name] # => "david" - # cookies.size # => 2 + # cookies.size # => 2 # # Example for deleting: # # cookies.delete :user_name # - # All the option symbols for setting cookies are: + # The option symbols for setting cookies are: # - # * value - the cookie's value or list of values (as an array). - # * path - the path for which this cookie applies. Defaults to the root of the application. - # * domain - the domain for which this cookie applies. - # * expires - the time at which this cookie expires, as a +Time+ object. - # * secure - whether this cookie is a secure cookie or not (default to false). - # Secure cookies are only transmitted to HTTPS servers. - # * http_only - whether this cookie is accessible via scripting or only HTTP (defaults to false). - + # * :value - The cookie's value or list of values (as an array). + # * :path - The path for which this cookie applies. Defaults to the root + # of the application. + # * :domain - The domain for which this cookie applies. + # * :expires - The time at which this cookie expires, as a Time object. + # * :secure - Whether this cookie is a only transmitted to HTTPS servers. + # Default is +false+. + # * :http_only - Whether this cookie is accessible via scripting or + # only HTTP. Defaults to +false+. module Cookies def self.included(base) base.helper_method :cookies @@ -45,8 +52,7 @@ module ActionController #:nodoc: update(@cookies) end - # Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using cookies[]= - # (for simple name/value cookies without options). + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. def [](name) cookie = @cookies[name.to_s] if cookie && cookie.respond_to?(:value) @@ -54,6 +60,8 @@ module ActionController #:nodoc: end end + # Sets the cookie named +name+. The second argument may be the very cookie + # value, or a hash of options as documented above. def []=(name, options) if options.is_a?(Hash) options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options } @@ -66,14 +74,18 @@ module ActionController #:nodoc: end # Removes the cookie on the client machine by setting the value to an empty string - # and setting its expiration date into the past. Like []=, you can pass in an options - # hash to delete cookies with extra data such as a +path+. + # and setting its expiration date into the past. Like []=, you can pass in + # an options hash to delete cookies with extra data such as a :path. def delete(name, options = {}) options.stringify_keys! set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0))) end private + # Builds a CGI::Cookie object and adds the cookie to the response headers. + # + # The path of the cookie defaults to "/" if there's none in +options+, and + # everything is passed to the CGI::Cookie constructor. def set_cookie(options) #:doc: options["path"] = "/" unless options["path"] cookie = CGI::Cookie.new(options) diff --git a/vendor/rails/actionpack/lib/action_controller/dispatcher.rb b/vendor/rails/actionpack/lib/action_controller/dispatcher.rb index c8656e4b..6e1e7a26 100644 --- a/vendor/rails/actionpack/lib/action_controller/dispatcher.rb +++ b/vendor/rails/actionpack/lib/action_controller/dispatcher.rb @@ -2,27 +2,39 @@ module ActionController # Dispatches requests to the appropriate controller and takes care of # reloading the app after each request when Dependencies.load? is true. class Dispatcher + @@guard = Mutex.new + class << self + def define_dispatcher_callbacks(cache_classes) + unless cache_classes + # Development mode callbacks + before_dispatch :reload_application + after_dispatch :cleanup_application + end + + # Common callbacks + to_prepare :load_application_controller do + begin + require_dependency 'application' unless defined?(::ApplicationController) + rescue LoadError => error + raise unless error.message =~ /application\.rb/ + end + end + + if defined?(ActiveRecord) + before_dispatch { ActiveRecord::Base.verify_active_connections! } + to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers } + end + + after_dispatch :flush_logger if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush) + end + # Backward-compatible class method takes CGI-specific args. Deprecated # in favor of Dispatcher.new(output, request, response).dispatch. def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout) new(output).dispatch_cgi(cgi, session_options) end - # Declare a block to be called before each dispatch. - # Run in the order declared. - def before_dispatch(*method_names, &block) - callbacks[:before].concat method_names - callbacks[:before] << block if block_given? - end - - # Declare a block to be called after each dispatch. - # Run in reverse of the order declared. - def after_dispatch(*method_names, &block) - callbacks[:after].concat method_names - callbacks[:after] << block if block_given? - end - # Add a preparation callback. Preparation callbacks are run before every # request in development mode, and before the first request in production # mode. @@ -32,16 +44,9 @@ module ActionController # existing callback. Passing an identifier is a suggested practice if the # code adding a preparation block may be reloaded. def to_prepare(identifier = nil, &block) - # Already registered: update the existing callback - if identifier - if callback = callbacks[:prepare].assoc(identifier) - callback[1] = block - else - callbacks[:prepare] << [identifier, block] - end - else - callbacks[:prepare] << block - end + @prepare_dispatch_callbacks ||= ActiveSupport::Callbacks::CallbackChain.new + callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier) + @prepare_dispatch_callbacks | callback end # If the block raises, send status code as a last-ditch response. @@ -86,37 +91,26 @@ module ActionController end cattr_accessor :error_file_path - self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT + self.error_file_path = Rails.public_path if defined?(Rails.public_path) - cattr_accessor :callbacks - self.callbacks = Hash.new { |h, k| h[k] = [] } - - cattr_accessor :unprepared - self.unprepared = true - - - before_dispatch :reload_application - before_dispatch :prepare_application - after_dispatch :flush_logger - after_dispatch :cleanup_application - - if defined? ActiveRecord - to_prepare :activerecord_instantiate_observers do - ActiveRecord::Base.instantiate_observers - end - end + include ActiveSupport::Callbacks + define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch def initialize(output, request = nil, response = nil) @output, @request, @response = output, request, response end def dispatch - run_callbacks :before - handle_request - rescue Exception => exception - failsafe_rescue exception - ensure - run_callbacks :after, :reverse_each + @@guard.synchronize do + begin + run_callbacks :before_dispatch + handle_request + rescue Exception => exception + failsafe_rescue exception + ensure + run_callbacks :after_dispatch, :enumerator => :reverse_each + end + end end def dispatch_cgi(cgi, session_options) @@ -130,39 +124,23 @@ module ActionController end def reload_application - if Dependencies.load? - Routing::Routes.reload - self.unprepared = true - end - end + # Run prepare callbacks before every request in development mode + run_callbacks :prepare_dispatch - def prepare_application(force = false) - begin - require_dependency 'application' unless defined?(::ApplicationController) - rescue LoadError => error - raise unless error.message =~ /application\.rb/ - end - - ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord) - - if unprepared || force - run_callbacks :prepare - self.unprepared = false - end + Routing::Routes.reload + ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading end # Cleanup the application by clearing out loaded classes so they can # be reloaded on the next request without restarting the server. - def cleanup_application(force = false) - if Dependencies.load? || force - ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) - Dependencies.clear - ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) - end + def cleanup_application + ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) + Dependencies.clear + ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) end def flush_logger - RAILS_DEFAULT_LOGGER.flush if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:flush) + RAILS_DEFAULT_LOGGER.flush end protected @@ -171,17 +149,6 @@ module ActionController @controller.process(@request, @response).out(@output) end - def run_callbacks(kind, enumerator = :each) - callbacks[kind].send!(enumerator) do |callback| - case callback - when Proc; callback.call(self) - when String, Symbol; send!(callback) - when Array; callback[1].call(self) - else raise ArgumentError, "Unrecognized callback #{callback.inspect}" - end - end - end - def failsafe_rescue(exception) self.class.failsafe_response(@output, '500 Internal Server Error', exception) do if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base diff --git a/vendor/rails/actionpack/lib/action_controller/filters.rb b/vendor/rails/actionpack/lib/action_controller/filters.rb index d7fb2761..60d92d9b 100644 --- a/vendor/rails/actionpack/lib/action_controller/filters.rb +++ b/vendor/rails/actionpack/lib/action_controller/filters.rb @@ -100,10 +100,10 @@ module ActionController #:nodoc: # # Around filters wrap an action, executing code both before and after. # They may be declared as method references, blocks, or objects responding - # to #filter or to both #before and #after. + # to +filter+ or to both +before+ and +after+. # - # To use a method as an around_filter, pass a symbol naming the Ruby method. - # Yield (or block.call) within the method to run the action. + # To use a method as an +around_filter+, pass a symbol naming the Ruby method. + # Yield (or block.call) within the method to run the action. # # around_filter :catch_exceptions # @@ -115,9 +115,9 @@ module ActionController #:nodoc: # raise # end # - # To use a block as an around_filter, pass a block taking as args both + # To use a block as an +around_filter+, pass a block taking as args both # the controller and the action block. You can't call yield directly from - # an around_filter block; explicitly call the action block instead: + # an +around_filter+ block; explicitly call the action block instead: # # around_filter do |controller, action| # logger.debug "before #{controller.action_name}" @@ -125,9 +125,9 @@ module ActionController #:nodoc: # logger.debug "after #{controller.action_name}" # end # - # To use a filter object with around_filter, pass an object responding - # to :filter or both :before and :after. With a filter method, yield to - # the block as above: + # To use a filter object with +around_filter+, pass an object responding + # to :filter or both :before and :after. With a + # filter method, yield to the block as above: # # around_filter BenchmarkingFilter # @@ -137,7 +137,7 @@ module ActionController #:nodoc: # end # end # - # With before and after methods: + # With +before+ and +after+ methods: # # around_filter Authorizer.new # @@ -154,9 +154,9 @@ module ActionController #:nodoc: # end # end # - # If the filter has before and after methods, the before method will be - # called before the action. If before renders or redirects, the filter chain is - # halted and after will not be run. See Filter Chain Halting below for + # If the filter has +before+ and +after+ methods, the +before+ method will be + # called before the action. If +before+ renders or redirects, the filter chain is + # halted and +after+ will not be run. See Filter Chain Halting below for # an example. # # == Filter chain skipping @@ -191,8 +191,9 @@ module ActionController #:nodoc: # == Filter conditions # # Filters may be limited to specific actions by declaring the actions to - # include or exclude. Both options accept single actions (:only => :index) - # or arrays of actions (:except => [:foo, :bar]). + # include or exclude. Both options accept single actions + # (:only => :index) or arrays of actions + # (:except => [:foo, :bar]). # # class Journal < ActionController::Base # # Require authentication for edit and delete. @@ -214,7 +215,7 @@ module ActionController #:nodoc: # # before_filter and around_filter may halt the request # before a controller action is run. This is useful, for example, to deny - # access to unauthenticated users or to redirect from http to https. + # access to unauthenticated users or to redirect from HTTP to HTTPS. # Simply call render or redirect. After filters will not be executed if the filter # chain is halted. # @@ -240,298 +241,47 @@ module ActionController #:nodoc: # . / # #after (actual filter code is run, unless the around filter does not yield) # - # If #around returns before yielding, #after will still not be run. The #before - # filter and controller action will not be run. If #before renders or redirects, - # the second half of #around and will still run but #after and the - # action will not. If #around fails to yield, #after will not be run. - module ClassMethods - # The passed filters will be appended to the filter_chain and - # will execute before the action on this controller is performed. - def append_before_filter(*filters, &block) - append_filter_to_chain(filters, :before, &block) + # If +around+ returns before yielding, +after+ will still not be run. The +before+ + # filter and controller action will not be run. If +before+ renders or redirects, + # the second half of +around+ and will still run but +after+ and the + # action will not. If +around+ fails to yield, +after+ will not be run. + + class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc: + def append_filter_to_chain(filters, filter_type, &block) + pos = find_filter_append_position(filters, filter_type) + update_filter_chain(filters, filter_type, pos, &block) end - # The passed filters will be prepended to the filter_chain and - # will execute before the action on this controller is performed. - def prepend_before_filter(*filters, &block) - prepend_filter_to_chain(filters, :before, &block) + def prepend_filter_to_chain(filters, filter_type, &block) + pos = find_filter_prepend_position(filters, filter_type) + update_filter_chain(filters, filter_type, pos, &block) end - # Shorthand for append_before_filter since it's the most common. - alias :before_filter :append_before_filter - - # The passed filters will be appended to the array of filters - # that run _after_ actions on this controller are performed. - def append_after_filter(*filters, &block) - append_filter_to_chain(filters, :after, &block) + def create_filters(filters, filter_type, &block) + filters, conditions = extract_options(filters, &block) + filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) } + filters end - # The passed filters will be prepended to the array of filters - # that run _after_ actions on this controller are performed. - def prepend_after_filter(*filters, &block) - prepend_filter_to_chain(filters, :after, &block) + def skip_filter_in_chain(*filters, &test) + filters, conditions = extract_options(filters) + filters.each do |filter| + if callback = find(filter) then delete(callback) end + end if conditions.empty? + update_filter_in_chain(filters, :skip => conditions, &test) end - # Shorthand for append_after_filter since it's the most common. - alias :after_filter :append_after_filter - - - # If you append_around_filter A.new, B.new, the filter chain looks like - # - # B#before - # A#before - # # run the action - # A#after - # B#after - # - # With around filters which yield to the action block, #before and #after - # are the code before and after the yield. - def append_around_filter(*filters, &block) - filters, conditions = extract_conditions(filters, &block) - filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter| - append_filter_to_chain([filter, conditions]) - end - end - - # If you prepend_around_filter A.new, B.new, the filter chain looks like: - # - # A#before - # B#before - # # run the action - # B#after - # A#after - # - # With around filters which yield to the action block, #before and #after - # are the code before and after the yield. - def prepend_around_filter(*filters, &block) - filters, conditions = extract_conditions(filters, &block) - filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter| - prepend_filter_to_chain([filter, conditions]) - end - end - - # Shorthand for append_around_filter since it's the most common. - alias :around_filter :append_around_filter - - # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference - # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out - # of many sub-controllers need a different hierarchy. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_before_filter(*filters) - skip_filter_in_chain(*filters, &:before?) - end - - # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference - # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out - # of many sub-controllers need a different hierarchy. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_after_filter(*filters) - skip_filter_in_chain(*filters, &:after?) - end - - # Removes the specified filters from the filter chain. This only works for method reference (symbol) - # filters, not procs. This method is different from skip_after_filter and skip_before_filter in that - # it will match any before, after or yielding around filter. - # - # You can control the actions to skip the filter for with the :only and :except options, - # just like when you apply the filters. - def skip_filter(*filters) - skip_filter_in_chain(*filters) - end - - # Returns an array of Filter objects for this controller. - def filter_chain - read_inheritable_attribute("filter_chain") || [] - end - - # Returns all the before filters for this class and all its ancestors. - # This method returns the actual filter that was assigned in the controller to maintain existing functionality. - def before_filters #:nodoc: - filter_chain.select(&:before?).map(&:filter) - end - - # Returns all the after filters for this class and all its ancestors. - # This method returns the actual filter that was assigned in the controller to maintain existing functionality. - def after_filters #:nodoc: - filter_chain.select(&:after?).map(&:filter) - end - - # Returns a mapping between filters and the actions that may run them. - def included_actions #:nodoc: - @included_actions ||= read_inheritable_attribute("included_actions") || {} - end - - # Returns a mapping between filters and actions that may not run them. - def excluded_actions #:nodoc: - @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {} - end - - # Find a filter in the filter_chain where the filter method matches the _filter_ param - # and (optionally) the passed block evaluates to true (mostly used for testing before? - # and after? on the filter). Useful for symbol filters. - # - # The object of type Filter is passed to the block when yielded, not the filter itself. - def find_filter(filter, &block) #:nodoc: - filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first - end - - # Returns true if the filter is excluded from the given action - def filter_excluded_from_action?(filter,action) #:nodoc: - case - when ia = included_actions[filter] - !ia.include?(action) - when ea = excluded_actions[filter] - ea.include?(action) - end - end - - # Filter class is an abstract base class for all filters. Handles all of the included/excluded actions but - # contains no logic for calling the actual filters. - class Filter #:nodoc: - attr_reader :filter, :included_actions, :excluded_actions - - def initialize(filter) - @filter = filter - end - - def type - :around - end - - def before? - type == :before - end - - def after? - type == :after - end - - def around? - type == :around - end - - def run(controller) - raise ActionControllerError, 'No filter type: Nothing to do here.' - end - - def call(controller, &block) - run(controller) - end - end - - # Abstract base class for filter proxies. FilterProxy objects are meant to mimic the behaviour of the old - # before_filter and after_filter by moving the logic into the filter itself. - class FilterProxy < Filter #:nodoc: - def filter - @filter.filter - end - end - - class BeforeFilterProxy < FilterProxy #:nodoc: - def type - :before - end - - def run(controller) - # only filters returning false are halted. - @filter.call(controller) - if controller.send!(:performed?) - controller.send!(:halt_filter_chain, @filter, :rendered_or_redirected) - end - end - - def call(controller) - yield unless run(controller) - end - end - - class AfterFilterProxy < FilterProxy #:nodoc: - def type - :after - end - - def run(controller) - @filter.call(controller) - end - - def call(controller) - yield - run(controller) - end - end - - class SymbolFilter < Filter #:nodoc: - def call(controller, &block) - controller.send!(@filter, &block) - end - end - - class ProcFilter < Filter #:nodoc: - def call(controller) - @filter.call(controller) - rescue LocalJumpError # a yield from a proc... no no bad dog. - raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.') - end - end - - class ProcWithCallFilter < Filter #:nodoc: - def call(controller, &block) - @filter.call(controller, block) - rescue LocalJumpError # a yield from a proc... no no bad dog. - raise(ActionControllerError, 'Cannot yield from a Proc type filter. The Proc must take two arguments and execute #call on the second argument.') - end - end - - class MethodFilter < Filter #:nodoc: - def call(controller, &block) - @filter.call(controller, &block) - end - end - - class ClassFilter < Filter #:nodoc: - def call(controller, &block) - @filter.filter(controller, &block) - end - end - - class ClassBeforeFilter < Filter #:nodoc: - def call(controller, &block) - @filter.before(controller) - end - end - - class ClassAfterFilter < Filter #:nodoc: - def call(controller, &block) - @filter.after(controller) - end - end - - protected - def append_filter_to_chain(filters, filter_type = :around, &block) - pos = find_filter_append_position(filters, filter_type) - update_filter_chain(filters, filter_type, pos, &block) - end - - def prepend_filter_to_chain(filters, filter_type = :around, &block) - pos = find_filter_prepend_position(filters, filter_type) - update_filter_chain(filters, filter_type, pos, &block) - end - + private def update_filter_chain(filters, filter_type, pos, &block) new_filters = create_filters(filters, filter_type, &block) - new_chain = filter_chain.insert(pos, new_filters).flatten - write_inheritable_attribute('filter_chain', new_chain) + insert(pos, new_filters).flatten! end def find_filter_append_position(filters, filter_type) # appending an after filter puts it at the end of the call chain # before and around filters go before the first after filter in the chain unless filter_type == :after - filter_chain.each_with_index do |f,i| + each_with_index do |f,i| return i if f.after? end end @@ -542,7 +292,7 @@ module ActionController #:nodoc: # prepending a before or around filter puts it at the front of the call chain # after filters go before the first after filter in the chain if filter_type == :after - filter_chain.each_with_index do |f,i| + each_with_index do |f,i| return i if f.after? end return -1 @@ -550,126 +300,261 @@ module ActionController #:nodoc: return 0 end - def create_filters(filters, filter_type, &block) #:nodoc: - filters, conditions = extract_conditions(filters, &block) - filters.map! { |filter| find_or_create_filter(filter, filter_type) } - update_conditions(filters, conditions) - filters - end + def find_or_create_filter(filter, filter_type, options = {}) + update_filter_in_chain([filter], options) - def find_or_create_filter(filter, filter_type) - if found_filter = find_filter(filter) { |f| f.type == filter_type } + if found_filter = find(filter) { |f| f.type == filter_type } found_filter else - f = class_for_filter(filter, filter_type).new(filter) - # apply proxy to filter if necessary + filter_kind = case + when filter.respond_to?(:before) && filter_type == :before + :before + when filter.respond_to?(:after) && filter_type == :after + :after + else + :filter + end + case filter_type when :before - BeforeFilterProxy.new(f) + BeforeFilter.new(filter_kind, filter, options) when :after - AfterFilterProxy.new(f) + AfterFilter.new(filter_kind, filter, options) else - f + AroundFilter.new(filter_kind, filter, options) end end end - # The determination of the filter type was once done at run time. - # This method is here to extract as much logic from the filter run time as possible - def class_for_filter(filter, filter_type) #:nodoc: - case - when filter.is_a?(Symbol) - SymbolFilter - when filter.respond_to?(:call) - if filter.is_a?(Method) - MethodFilter - elsif filter.arity == 1 - ProcFilter - else - ProcWithCallFilter - end - when filter.respond_to?(:filter) - ClassFilter - when filter.respond_to?(:before) && filter_type == :before - ClassBeforeFilter - when filter.respond_to?(:after) && filter_type == :after - ClassAfterFilter - else - raise(ActionControllerError, 'A filter must be a Symbol, Proc, Method, or object responding to filter, after or before.') - end - end - - def extract_conditions(*filters, &block) #:nodoc: - filters.flatten! - conditions = filters.extract_options! - filters << block if block_given? - return filters, conditions - end - - def update_conditions(filters, conditions) - return if conditions.empty? - if conditions[:only] - write_inheritable_hash('included_actions', condition_hash(filters, conditions[:only])) - elsif conditions[:except] - write_inheritable_hash('excluded_actions', condition_hash(filters, conditions[:except])) - end - end - - def condition_hash(filters, *actions) - actions = actions.flatten.map(&:to_s) - filters.inject({}) { |h,f| h.update( f => (actions.blank? ? nil : actions)) } - end - - def skip_filter_in_chain(*filters, &test) #:nodoc: - filters, conditions = extract_conditions(filters) - filters.map! { |f| block_given? ? find_filter(f, &test) : find_filter(f) } + def update_filter_in_chain(filters, options, &test) + filters.map! { |f| block_given? ? find(f, &test) : find(f) } filters.compact! - if conditions.empty? - delete_filters_in_chain(filters) + map! do |filter| + if filters.include?(filter) + new_filter = filter.dup + new_filter.options.merge!(options) + new_filter + else + filter + end + end + end + end + + class Filter < ActiveSupport::Callbacks::Callback #:nodoc: + def before? + self.class == BeforeFilter + end + + def after? + self.class == AfterFilter + end + + def around? + self.class == AroundFilter + end + + private + def should_not_skip?(controller) + if options[:skip] + !included_in_action?(controller, options[:skip]) else - remove_actions_from_included_actions!(filters,conditions[:only] || []) - conditions[:only], conditions[:except] = conditions[:except], conditions[:only] - update_conditions(filters,conditions) + true end end - def remove_actions_from_included_actions!(filters,*actions) - actions = actions.flatten.map(&:to_s) - updated_hash = filters.inject(read_inheritable_attribute('included_actions')||{}) do |hash,filter| - ia = (hash[filter] || []) - actions - ia.empty? ? hash.delete(filter) : hash[filter] = ia - hash + def included_in_action?(controller, options) + if options[:only] + Array(options[:only]).map(&:to_s).include?(controller.action_name) + elsif options[:except] + !Array(options[:except]).map(&:to_s).include?(controller.action_name) + else + true end - write_inheritable_attribute('included_actions', updated_hash) end - def delete_filters_in_chain(filters) #:nodoc: - write_inheritable_attribute('filter_chain', filter_chain.reject { |f| filters.include?(f) }) + def should_run_callback?(controller) + should_not_skip?(controller) && included_in_action?(controller, options) && super + end + end + + class AroundFilter < Filter #:nodoc: + def type + :around + end + + def call(controller, &block) + if should_run_callback?(controller) + method = filter_responds_to_before_and_after? ? around_proc : self.method + + # For around_filter do |controller, action| + if method.is_a?(Proc) && method.arity == 2 + evaluate_method(method, controller, block) + else + evaluate_method(method, controller, &block) + end + else + block.call + end + end + + private + def filter_responds_to_before_and_after? + method.respond_to?(:before) && method.respond_to?(:after) end - def filter_responds_to_before_and_after(filter) #:nodoc: - filter.respond_to?(:before) && filter.respond_to?(:after) - end - - def proxy_before_and_after_filter(filter) #:nodoc: - return filter unless filter_responds_to_before_and_after(filter) + def around_proc Proc.new do |controller, action| - filter.before(controller) + method.before(controller) if controller.send!(:performed?) - controller.send!(:halt_filter_chain, filter, :rendered_or_redirected) + controller.send!(:halt_filter_chain, method, :rendered_or_redirected) else begin action.call ensure - filter.after(controller) + method.after(controller) end end end end end + class BeforeFilter < Filter #:nodoc: + def type + :before + end + + def call(controller, &block) + super + if controller.send!(:performed?) + controller.send!(:halt_filter_chain, method, :rendered_or_redirected) + end + end + end + + class AfterFilter < Filter #:nodoc: + def type + :after + end + end + + module ClassMethods + # The passed filters will be appended to the filter_chain and + # will execute before the action on this controller is performed. + def append_before_filter(*filters, &block) + filter_chain.append_filter_to_chain(filters, :before, &block) + end + + # The passed filters will be prepended to the filter_chain and + # will execute before the action on this controller is performed. + def prepend_before_filter(*filters, &block) + filter_chain.prepend_filter_to_chain(filters, :before, &block) + end + + # Shorthand for append_before_filter since it's the most common. + alias :before_filter :append_before_filter + + # The passed filters will be appended to the array of filters + # that run _after_ actions on this controller are performed. + def append_after_filter(*filters, &block) + filter_chain.append_filter_to_chain(filters, :after, &block) + end + + # The passed filters will be prepended to the array of filters + # that run _after_ actions on this controller are performed. + def prepend_after_filter(*filters, &block) + filter_chain.prepend_filter_to_chain(filters, :after, &block) + end + + # Shorthand for append_after_filter since it's the most common. + alias :after_filter :append_after_filter + + # If you append_around_filter A.new, B.new, the filter chain looks like + # + # B#before + # A#before + # # run the action + # A#after + # B#after + # + # With around filters which yield to the action block, +before+ and +after+ + # are the code before and after the yield. + def append_around_filter(*filters, &block) + filter_chain.append_filter_to_chain(filters, :around, &block) + end + + # If you prepend_around_filter A.new, B.new, the filter chain looks like: + # + # A#before + # B#before + # # run the action + # B#after + # A#after + # + # With around filters which yield to the action block, +before+ and +after+ + # are the code before and after the yield. + def prepend_around_filter(*filters, &block) + filter_chain.prepend_filter_to_chain(filters, :around, &block) + end + + # Shorthand for +append_around_filter+ since it's the most common. + alias :around_filter :append_around_filter + + # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference + # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out + # of many sub-controllers need a different hierarchy. + # + # You can control the actions to skip the filter for with the :only and :except options, + # just like when you apply the filters. + def skip_before_filter(*filters) + filter_chain.skip_filter_in_chain(*filters, &:before?) + end + + # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference + # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out + # of many sub-controllers need a different hierarchy. + # + # You can control the actions to skip the filter for with the :only and :except options, + # just like when you apply the filters. + def skip_after_filter(*filters) + filter_chain.skip_filter_in_chain(*filters, &:after?) + end + + # Removes the specified filters from the filter chain. This only works for method reference (symbol) + # filters, not procs. This method is different from skip_after_filter and skip_before_filter in that + # it will match any before, after or yielding around filter. + # + # You can control the actions to skip the filter for with the :only and :except options, + # just like when you apply the filters. + def skip_filter(*filters) + filter_chain.skip_filter_in_chain(*filters) + end + + # Returns an array of Filter objects for this controller. + def filter_chain + if chain = read_inheritable_attribute('filter_chain') + return chain + else + write_inheritable_attribute('filter_chain', FilterChain.new) + return filter_chain + end + end + + # Returns all the before filters for this class and all its ancestors. + # This method returns the actual filter that was assigned in the controller to maintain existing functionality. + def before_filters #:nodoc: + filter_chain.select(&:before?).map(&:method) + end + + # Returns all the after filters for this class and all its ancestors. + # This method returns the actual filter that was assigned in the controller to maintain existing functionality. + def after_filters #:nodoc: + filter_chain.select(&:after?).map(&:method) + end + end + module InstanceMethods # :nodoc: def self.included(base) base.class_eval do @@ -679,89 +564,80 @@ module ActionController #:nodoc: end protected + def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: + @before_filter_chain_aborted = false + process_without_filters(request, response, method, *arguments) + end - def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: - @before_filter_chain_aborted = false - process_without_filters(request, response, method, *arguments) - end - - def perform_action_with_filters - call_filters(self.class.filter_chain, 0, 0) - end + def perform_action_with_filters + call_filters(self.class.filter_chain, 0, 0) + end private - - def call_filters(chain, index, nesting) - index = run_before_filters(chain, index, nesting) - aborted = @before_filter_chain_aborted - perform_action_without_filters unless performed? || aborted - return index if nesting != 0 || aborted - run_after_filters(chain, index) - end - - def skip_excluded_filters(chain, index) - while (filter = chain[index]) && self.class.filter_excluded_from_action?(filter, action_name) - index = index.next + def call_filters(chain, index, nesting) + index = run_before_filters(chain, index, nesting) + aborted = @before_filter_chain_aborted + perform_action_without_filters unless performed? || aborted + return index if nesting != 0 || aborted + run_after_filters(chain, index) end - [filter, index] - end - def run_before_filters(chain, index, nesting) - while chain[index] - filter, index = skip_excluded_filters(chain, index) - break unless filter # end of call chain reached + def run_before_filters(chain, index, nesting) + while chain[index] + filter, index = chain[index], index + break unless filter # end of call chain reached - case filter.type - when :before - filter.run(self) # invoke before filter - index = index.next - break if @before_filter_chain_aborted - when :around - yielded = false + case filter + when BeforeFilter + filter.call(self) # invoke before filter + index = index.next + break if @before_filter_chain_aborted + when AroundFilter + yielded = false - filter.call(self) do - yielded = true - # all remaining before and around filters will be run in this call - index = call_filters(chain, index.next, nesting.next) + filter.call(self) do + yielded = true + # all remaining before and around filters will be run in this call + index = call_filters(chain, index.next, nesting.next) + end + + halt_filter_chain(filter, :did_not_yield) unless yielded + + break + else + break # no before or around filters left + end + end + + index + end + + def run_after_filters(chain, index) + seen_after_filter = false + + while chain[index] + filter, index = chain[index], index + break unless filter # end of call chain reached + + case filter + when AfterFilter + seen_after_filter = true + filter.call(self) # invoke after filter + else + # implementation error or someone has mucked with the filter chain + raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter end - halt_filter_chain(filter, :did_not_yield) unless yielded - - break - else - break # no before or around filters left - end - end - - index - end - - def run_after_filters(chain, index) - seen_after_filter = false - - while chain[index] - filter, index = skip_excluded_filters(chain, index) - break unless filter # end of call chain reached - - case filter.type - when :after - seen_after_filter = true - filter.run(self) # invoke after filter - else - # implementation error or someone has mucked with the filter chain - raise ActionControllerError, "filter #{filter.inspect} was in the wrong place!" if seen_after_filter + index = index.next end - index = index.next + index.next end - index.next - end - - def halt_filter_chain(filter, reason) - @before_filter_chain_aborted = true - logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger - end + def halt_filter_chain(filter, reason) + @before_filter_chain_aborted = true + logger.info "Filter chain halted as [#{filter.inspect}] #{reason}." if logger + end end end end diff --git a/vendor/rails/actionpack/lib/action_controller/flash.rb b/vendor/rails/actionpack/lib/action_controller/flash.rb index 692168f2..0148fb5c 100644 --- a/vendor/rails/actionpack/lib/action_controller/flash.rb +++ b/vendor/rails/actionpack/lib/action_controller/flash.rb @@ -28,7 +28,6 @@ module ActionController #:nodoc: base.class_eval do include InstanceMethods alias_method_chain :assign_shortcuts, :flash - alias_method_chain :process_cleanup, :flash alias_method_chain :reset_session, :flash end end @@ -166,11 +165,7 @@ module ActionController #:nodoc: def assign_shortcuts_with_flash(request, response) #:nodoc: assign_shortcuts_without_flash(request, response) flash(:refresh) - end - - def process_cleanup_with_flash - flash.sweep if @_session - process_cleanup_without_flash + flash.sweep if @_session && !component_request? end end end diff --git a/vendor/rails/actionpack/lib/action_controller/headers.rb b/vendor/rails/actionpack/lib/action_controller/headers.rb new file mode 100644 index 00000000..7239438c --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/headers.rb @@ -0,0 +1,31 @@ +module ActionController + module Http + class Headers < ::Hash + + def initialize(constructor = {}) + if constructor.is_a?(Hash) + super() + update(constructor) + else + super(constructor) + end + end + + def [](header_name) + if include?(header_name) + super + else + super(normalize_header(header_name)) + end + end + + + private + # Takes an HTTP header name and returns it in the + # format + def normalize_header(header_name) + "HTTP_#{header_name.upcase.gsub(/-/, '_')}" + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller/helpers.rb b/vendor/rails/actionpack/lib/action_controller/helpers.rb index 81b8ff97..ce5e8be5 100644 --- a/vendor/rails/actionpack/lib/action_controller/helpers.rb +++ b/vendor/rails/actionpack/lib/action_controller/helpers.rb @@ -20,7 +20,7 @@ module ActionController #:nodoc: end # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, - # +numbers+ and +ActiveRecord+ objects, to name a few. These helpers are available to all templates + # +numbers+ and Active Record objects, to name a few. These helpers are available to all templates # by default. # # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to @@ -32,7 +32,7 @@ module ActionController #:nodoc: # controller which inherits from it. # # ==== Examples - # The +to_s+ method from the +Time+ class can be wrapped in a helper method to display a custom message if + # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if # the Time object is blank: # # module FormattedTimeHelper @@ -41,7 +41,7 @@ module ActionController #:nodoc: # end # end # - # +FormattedTimeHelper+ can now be included in a controller, using the +helper+ class method: + # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: # # class EventsController < ActionController::Base # helper FormattedTimeHelper @@ -74,22 +74,22 @@ module ActionController #:nodoc: # The +helper+ class method can take a series of helper module names, a block, or both. # - # * *args: One or more +Modules+, +Strings+ or +Symbols+, or the special symbol :all. + # * *args: One or more modules, strings or symbols, or the special symbol :all. # * &block: A block defining helper methods. # # ==== Examples - # When the argument is a +String+ or +Symbol+, the method will provide the "_helper" suffix, require the file + # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file # and include the module in the template class. The second form illustrates how to include custom helpers # when working with namespaced controllers, or other cases where the file containing the helper definition is not # in one of Rails' standard load paths: # helper :foo # => requires 'foo_helper' and includes FooHelper # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper # - # When the argument is a +Module+, it will be included directly in the template class. + # When the argument is a module it will be included directly in the template class. # helper FooHelper # => includes FooHelper # # When the argument is the symbol :all, the controller will include all helpers from - # app/helpers/**/*.rb under +RAILS_ROOT+. + # app/helpers/**/*.rb under RAILS_ROOT. # helper :all # # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available @@ -143,11 +143,19 @@ module ActionController #:nodoc: # Declare a controller method as a helper. For example, the following # makes the +current_user+ controller method available to the view: # class ApplicationController < ActionController::Base - # helper_method :current_user + # helper_method :current_user, :logged_in? + # # def current_user - # @current_user ||= User.find(session[:user]) + # @current_user ||= User.find_by_id(session[:user]) # end + # + # def logged_in? + # current_user != nil + # end # end + # + # In a view: + # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> def helper_method(*methods) methods.flatten.each do |method| master_helper_module.module_eval <<-end_eval @@ -167,6 +175,15 @@ module ActionController #:nodoc: attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } end + # Provides a proxy to access helpers methods from outside the view. + def helpers + unless @helper_proxy + @helper_proxy = ActionView::Base.new + @helper_proxy.extend master_helper_module + else + @helper_proxy + end + end private def default_helper_module! diff --git a/vendor/rails/actionpack/lib/action_controller/http_authentication.rb b/vendor/rails/actionpack/lib/action_controller/http_authentication.rb index 18a503c3..31db012e 100644 --- a/vendor/rails/actionpack/lib/action_controller/http_authentication.rb +++ b/vendor/rails/actionpack/lib/action_controller/http_authentication.rb @@ -1,5 +1,3 @@ -require 'base64' - module ActionController module HttpAuthentication # Makes it dead easy to do HTTP Basic authentication. @@ -72,7 +70,7 @@ module ActionController # # On shared hosts, Apache sometimes doesn't pass authentication headers to # FCGI instances. If your environment matches this description and you cannot - # authenticate, try this rule in public/.htaccess (replace the plain one): + # authenticate, try this rule in your Apache setup: # # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] module Basic @@ -110,11 +108,11 @@ module ActionController end def decode_credentials(request) - Base64.decode64(authorization(request).split.last || '') + ActiveSupport::Base64.decode64(authorization(request).split.last || '') end def encode_credentials(user_name, password) - "Basic #{Base64.encode64("#{user_name}:#{password}")}" + "Basic #{ActiveSupport::Base64.encode64("#{user_name}:#{password}")}" end def authentication_request(controller, realm) diff --git a/vendor/rails/actionpack/lib/action_controller/integration.rb b/vendor/rails/actionpack/lib/action_controller/integration.rb index 25fb1b93..bd69d02e 100644 --- a/vendor/rails/actionpack/lib/action_controller/integration.rb +++ b/vendor/rails/actionpack/lib/action_controller/integration.rb @@ -1,6 +1,7 @@ -require 'dispatcher' require 'stringio' require 'uri' + +require 'action_controller/dispatcher' require 'action_controller/test_process' module ActionController @@ -54,7 +55,10 @@ module ActionController # A running counter of the number of requests processed. attr_accessor :request_count - # Create and initialize a new +Session+ instance. + class MultiPartNeededException < Exception + end + + # Create and initialize a new Session instance. def initialize reset! end @@ -132,25 +136,25 @@ module ActionController end # Performs a GET request, following any subsequent redirect. - # See #request_via_redirect() for more information. + # See +request_via_redirect+ for more information. def get_via_redirect(path, parameters = nil, headers = nil) request_via_redirect(:get, path, parameters, headers) end # Performs a POST request, following any subsequent redirect. - # See #request_via_redirect() for more information. + # See +request_via_redirect+ for more information. def post_via_redirect(path, parameters = nil, headers = nil) request_via_redirect(:post, path, parameters, headers) end # Performs a PUT request, following any subsequent redirect. - # See #request_via_redirect() for more information. + # See +request_via_redirect+ for more information. def put_via_redirect(path, parameters = nil, headers = nil) request_via_redirect(:put, path, parameters, headers) end # Performs a DELETE request, following any subsequent redirect. - # See #request_via_redirect() for more information. + # See +request_via_redirect+ for more information. def delete_via_redirect(path, parameters = nil, headers = nil) request_via_redirect(:delete, path, parameters, headers) end @@ -162,12 +166,12 @@ module ActionController # Performs a GET request with the given parameters. The parameters may # be +nil+, a Hash, or a string that is appropriately encoded - # (application/x-www-form-urlencoded or multipart/form-data). The headers - # should be a hash. The keys will automatically be upcased, with the + # (application/x-www-form-urlencoded or multipart/form-data). + # The headers should be a hash. The keys will automatically be upcased, with the # prefix 'HTTP_' added if needed. # - # You can also perform POST, PUT, DELETE, and HEAD requests with #post, - # #put, #delete, and #head. + # You can also perform POST, PUT, DELETE, and HEAD requests with +post+, + # +put+, +delete+, and +head+. def get(path, parameters = nil, headers = nil) process :get, path, parameters, headers end @@ -224,6 +228,8 @@ module ActionController super + stdinput.set_encoding(Encoding::BINARY) if stdinput.respond_to?(:set_encoding) + stdinput.force_encoding(Encoding::BINARY) if stdinput.respond_to?(:force_encoding) @stdinput = stdinput.is_a?(IO) ? stdinput : StringIO.new(stdinput || '') end end @@ -276,7 +282,7 @@ module ActionController ActionController::Base.clear_last_instantiation! cgi = StubCGI.new(env, data) - Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput) + ActionController::Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput) @result = cgi.stdoutput.string @request_count += 1 @@ -293,15 +299,19 @@ module ActionController parse_result return status + rescue MultiPartNeededException + boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1" + status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"})) + return status end # Parses the result of the response and extracts the various values, # like cookies, status, headers, etc. def parse_result - headers, result_body = @result.split(/\r\n\r\n/, 2) + response_headers, result_body = @result.split(/\r\n\r\n/, 2) @headers = Hash.new { |h,k| h[k] = [] } - headers.each_line do |line| + response_headers.to_s.each_line do |line| key, value = line.strip.split(/:\s*/, 2) @headers[key.downcase] << value end @@ -311,7 +321,7 @@ module ActionController @cookies[name] = value end - @status, @status_message = @headers["status"].first.split(/ /) + @status, @status_message = @headers["status"].first.to_s.split(/ /) @status = @status.to_i end @@ -341,7 +351,9 @@ module ActionController # Convert the given parameters to a request string. The parameters may # be a string, +nil+, or a Hash. def requestify(parameters, prefix=nil) - if Hash === parameters + if TestUploadedFile === parameters + raise MultiPartNeededException + elsif Hash === parameters return nil if parameters.empty? parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&") elsif Array === parameters @@ -352,6 +364,47 @@ module ActionController "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}" end end + + def multipart_requestify(params, first=true) + returning Hash.new do |p| + params.each do |key, value| + k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]" + if Hash === value + multipart_requestify(value, false).each do |subkey, subvalue| + p[k + subkey] = subvalue + end + else + p[k] = value + end + end + end + end + + def multipart_body(params, boundary) + multipart_requestify(params).map do |key, value| + if value.respond_to?(:original_filename) + File.open(value.path) do |f| + f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding) + + <<-EOF +--#{boundary}\r +Content-Disposition: form-data; name="#{key}"; filename="#{CGI.escape(value.original_filename)}"\r +Content-Type: #{value.content_type}\r +Content-Length: #{File.stat(value.path).size}\r +\r +#{f.read}\r +EOF + end + else +<<-EOF +--#{boundary}\r +Content-Disposition: form-data; name="#{key}"\r +\r +#{value}\r +EOF + end + end.join("")+"--#{boundary}--\r" + end end # A module used to extend ActionController::Base, so that integration tests diff --git a/vendor/rails/actionpack/lib/action_controller/layout.rb b/vendor/rails/actionpack/lib/action_controller/layout.rb index 63a68c10..b5b59f2d 100644 --- a/vendor/rails/actionpack/lib/action_controller/layout.rb +++ b/vendor/rails/actionpack/lib/action_controller/layout.rb @@ -29,18 +29,20 @@ module ActionController #:nodoc: # # // The header part of this layout # <%= yield %> - # // The footer part of this layout --> + # // The footer part of this layout # # And then you have content pages that look like this: # # hello world # - # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout, - # like this: + # At rendering time, the content page is computed and then inserted in the layout, like this: # # // The header part of this layout # hello world - # // The footer part of this layout --> + # // The footer part of this layout + # + # NOTE: The old notation for rendering the view from a layout was to expose the magic @content_for_layout instance + # variable. The preferred notation now is to use yield, as documented above. # # == Accessing shared variables # @@ -63,15 +65,15 @@ module ActionController #:nodoc: # == Automatic layout assignment # # If there is a template in app/views/layouts/ with the same name as the current controller then it will be automatically - # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named + # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named # app/views/layouts/weblog.erb or app/views/layouts/weblog.builder exists then it will be automatically set as # the layout for your WeblogController. You can create a layout with the name application.erb or application.builder - # and this will be set as the default controller if there is no layout with the same name as the current controller and there is + # and this will be set as the default controller if there is no layout with the same name as the current controller and there is # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout. # assignment. So an Admin::WeblogController will look for a template named app/views/layouts/admin/weblog.erb. # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set. # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child - # class has a layout with the same name. + # class has a layout with the same name. # # == Inheritance for layouts # @@ -111,7 +113,7 @@ module ActionController #:nodoc: # logged_in? ? "writer_layout" : "reader_layout" # end # - # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing + # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing # is logged in or not. # # If you want to use an inline method, such as a proc, do something like this: @@ -124,48 +126,45 @@ module ActionController #:nodoc: # class WeblogController < ActionController::Base # layout "weblog_standard" # - # If no directory is specified for the template name, the template will by default be looked for in +app/views/layouts/+. + # If no directory is specified for the template name, the template will by default be looked for in app/views/layouts/. # Otherwise, it will be looked up relative to the template root. # # == Conditional layouts # # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering - # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The + # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The # :only and :except options can be passed to the layout call. For example: # # class WeblogController < ActionController::Base # layout "weblog_standard", :except => :rss - # + # # # ... # # end # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout + # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout # around the rendered view. # - # Both the :only and :except condition can accept an arbitrary number of method references, so + # Both the :only and :except condition can accept an arbitrary number of method references, so # #:except => [ :rss, :text_only ] is valid, as is :except => :rss. # # == Using a different layout in the action render call - # + # # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. - # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller. - # This is possible using the render method. It's just a bit more manual work as you'll have to supply fully - # qualified template and layout names as this example shows: + # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. + # You can do this by passing a :layout option to the render call. For example: # # class WeblogController < ActionController::Base + # layout "weblog_standard" + # # def help - # render :action => "help/index", :layout => "help" + # render :action => "help", :layout => "help" # end # end # - # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout - # as the third. - # - # NOTE: The old notation for rendering the view from a layout was to expose the magic @content_for_layout instance - # variable. The preferred notation now is to use yield, as documented above. + # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. module ClassMethods - # If a layout is specified, all rendered actions will have their result rendered + # If a layout is specified, all rendered actions will have their result rendered # when the layout yields. This layout can itself depend on instance variables assigned during action # performance and have access to them as any normal template would. def layout(template_name, conditions = {}, auto = false) @@ -177,21 +176,19 @@ module ActionController #:nodoc: def layout_conditions #:nodoc: @layout_conditions ||= read_inheritable_attribute("layout_conditions") end - + def default_layout(format) #:nodoc: - layout = read_inheritable_attribute("layout") + layout = read_inheritable_attribute("layout") return layout unless read_inheritable_attribute("auto_layout") @default_layout ||= {} @default_layout[format] ||= default_layout_with_format(format, layout) @default_layout[format] end - + def layout_list #:nodoc: - view_paths.collect do |path| - Dir["#{path}/layouts/**/*"] - end.flatten + Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } end - + private def inherited_with_layout(child) inherited_without_layout(child) @@ -208,13 +205,7 @@ module ActionController #:nodoc: def normalize_conditions(conditions) conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})} end - - def layout_directory_exists_cache - @@layout_directory_exists_cache ||= Hash.new do |h, dirname| - h[dirname] = File.directory? dirname - end - end - + def default_layout_with_format(format, layout) list = layout_list if list.grep(%r{layouts/#{layout}\.#{format}(\.[a-z][0-9a-z]*)+$}).empty? @@ -236,7 +227,7 @@ module ActionController #:nodoc: when Symbol then send!(layout) when Proc then layout.call(self) end - + # Explicitly passed layout names with slashes are looked up relative to the template root, # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from. @@ -250,16 +241,14 @@ module ActionController #:nodoc: end protected - def render_with_a_layout(options = nil, &block) #:nodoc: + def render_with_a_layout(options = nil, extra_options = {}, &block) #:nodoc: template_with_options = options.is_a?(Hash) - - if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options)) - assert_existence_of_template_file(layout) + if (layout = pick_layout(template_with_options, options)) && apply_layout?(template_with_options, options) options = options.merge :layout => false if template_with_options logger.info("Rendering template within #{layout}") if logger - content_for_layout = render_with_no_layout(options, &block) + content_for_layout = render_with_no_layout(options, extra_options, &block) erase_render_results add_variables_to_assigns @template.instance_variable_set("@content_for_layout", content_for_layout) @@ -267,7 +256,7 @@ module ActionController #:nodoc: status = template_with_options ? options[:status] : nil render_for_text(@template.render_file(layout, true), status) else - render_with_no_layout(options, &block) + render_with_no_layout(options, extra_options, &block) end end @@ -279,7 +268,7 @@ module ActionController #:nodoc: end def candidate_for_layout?(options) - (options.has_key?(:layout) && options[:layout] != false) || + (options.has_key?(:layout) && options[:layout] != false) || options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing).compact.empty? && !template_exempt_from_layout?(options[:template] || default_template_name(options[:action])) end @@ -305,7 +294,7 @@ module ActionController #:nodoc: when only = conditions[:only] only.include?(action_name) when except = conditions[:except] - !except.include?(action_name) + !except.include?(action_name) else true end @@ -313,14 +302,9 @@ module ActionController #:nodoc: true end end - - # Does a layout directory for this class exist? - # we cache this info in a class level hash + def layout_directory?(layout_name) - view_paths.find do |path| - next unless template_path = Dir[File.join(path, 'layouts', layout_name) + ".*"].first - self.class.send!(:layout_directory_exists_cache)[File.dirname(template_path)] - end + @template.finder.find_template_extension_from_handler(File.join('layouts', layout_name)) end end end diff --git a/vendor/rails/actionpack/lib/action_controller/mime_responds.rb b/vendor/rails/actionpack/lib/action_controller/mime_responds.rb index 4ba4e626..1dbd8b9e 100644 --- a/vendor/rails/actionpack/lib/action_controller/mime_responds.rb +++ b/vendor/rails/actionpack/lib/action_controller/mime_responds.rb @@ -92,7 +92,7 @@ module ActionController #:nodoc: # with the remaining data. # # Note that you can define your own XML parameter parser which would allow you to describe multiple entities - # in a single request (i.e., by wrapping them all in a single root note), but if you just go with the flow + # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow # and accept Rails' defaults, life will be much easier. # # If you need to use a MIME type which isn't supported by default, you can register your own handlers in @@ -125,7 +125,7 @@ module ActionController #:nodoc: @order << mime_type - @responses[mime_type] = Proc.new do + @responses[mime_type] ||= Proc.new do @response.template.template_format = mime_type.to_sym @response.content_type = mime_type.to_s block_given? ? block.call : @controller.send(:render, :action => @controller.action_name) @@ -133,7 +133,11 @@ module ActionController #:nodoc: end def any(*args, &block) - args.each { |type| send(type, &block) } + if args.any? + args.each { |type| send(type, &block) } + else + custom(@mime_type_priority.first, &block) + end end def method_missing(symbol, &block) diff --git a/vendor/rails/actionpack/lib/action_controller/mime_type.rb b/vendor/rails/actionpack/lib/action_controller/mime_type.rb index ec9d2eea..fa123f78 100644 --- a/vendor/rails/actionpack/lib/action_controller/mime_type.rb +++ b/vendor/rails/actionpack/lib/action_controller/mime_type.rb @@ -17,6 +17,10 @@ module Mime # end # end class Type + @@html_types = Set.new [:html, :all] + @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] + cattr_reader :html_types, :unverifiable_types + # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: attr_accessor :order, :name, :q @@ -71,8 +75,11 @@ module Mime # keep track of creation order to keep the subsequent sort stable list = [] accept_header.split(/,/).each_with_index do |header, index| - params = header.split(/;\s*q=/) - list << AcceptItem.new(index, *params) unless params.empty? + params, q = header.split(/;\s*q=/) + if params + params.strip! + list << AcceptItem.new(index, params, q) unless params.empty? + end end list.sort! @@ -97,7 +104,7 @@ module Mime list[text_xml].name = Mime::XML.to_s end - # Look for more specific xml-based types and sort them ahead of app/xml + # Look for more specific XML-based types and sort them ahead of app/xml if app_xml idx = app_xml @@ -145,14 +152,26 @@ module Mime end def ==(mime_type) - (@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type + return false if mime_type.blank? + (@synonyms + [ self ]).any? do |synonym| + synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym + end end - + + # Returns true if Action Pack should check requests using this Mime Type for possible request forgery. See + # ActionController::RequestForgerProtection. + def verify_request? + !@@unverifiable_types.include?(to_sym) + end + + def html? + @@html_types.include?(to_sym) || @string =~ /html/ + end + private def method_missing(method, *args) if method.to_s =~ /(\w+)\?$/ - mime_type = $1.downcase.to_sym - mime_type == @symbol || (mime_type == :html && @symbol == :all) + $1.downcase.to_sym == to_sym else super end diff --git a/vendor/rails/actionpack/lib/action_controller/mime_types.rb b/vendor/rails/actionpack/lib/action_controller/mime_types.rb index 71706b4c..01a266d3 100644 --- a/vendor/rails/actionpack/lib/action_controller/mime_types.rb +++ b/vendor/rails/actionpack/lib/action_controller/mime_types.rb @@ -17,4 +17,4 @@ Mime::Type.register "multipart/form-data", :multipart_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.ietf.org/rfc/rfc4627.txt -Mime::Type.register "application/json", :json, %w( text/x-json ) +Mime::Type.register "application/json", :json, %w( text/x-json ) \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb b/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb index 94aefc9a..509fa6a0 100644 --- a/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/vendor/rails/actionpack/lib/action_controller/polymorphic_routes.rb @@ -1,8 +1,80 @@ module ActionController + # Polymorphic URL helpers are methods for smart resolution to a named route call when + # given an Active Record model instance. They are to be used in combination with + # ActionController::Resources. + # + # These methods are useful when you want to generate correct URL or path to a RESTful + # resource without having to know the exact type of the record in question. + # + # Nested resources and/or namespaces are also supported, as illustrated in the example: + # + # polymorphic_url([:admin, @article, @comment]) + # + # results in: + # + # admin_article_comment_url(@article, @comment) + # + # == Usage within the framework + # + # Polymorphic URL helpers are used in a number of places throughout the Rails framework: + # + # * url_for, so you can use it with a record as the argument, e.g. + # url_for(@article); + # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write + # form_for(@article) without having to specify :url parameter for the form + # action; + # * redirect_to (which, in fact, uses url_for) so you can write + # redirect_to(post) in your controllers; + # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs + # for feed entries. + # + # == Prefixed polymorphic helpers + # + # In addition to polymorphic_url and polymorphic_path methods, a + # number of prefixed helpers are available as a shorthand to :action => "..." + # in options. Those are: + # + # * edit_polymorphic_url, edit_polymorphic_path + # * new_polymorphic_url, new_polymorphic_path + # * formatted_polymorphic_url, formatted_polymorphic_path + # + # Example usage: + # + # edit_polymorphic_path(@post) # => "/posts/1/edit" + # formatted_polymorphic_path([@post, :pdf]) # => "/posts/1.pdf" module PolymorphicRoutes + # Constructs a call to a named RESTful route for the given record and returns the + # resulting URL string. For example: + # + # # calls post_url(post) + # polymorphic_url(post) # => "http://example.com/posts/1" + # + # ==== Options + # + # * :action - Specifies the action prefix for the named route: + # :new, :edit, or :formatted. Default is no prefix. + # * :routing_type - Allowed values are :path or :url. + # Default is :url. + # + # ==== Examples + # + # # an Article record + # polymorphic_url(record) # same as article_url(record) + # + # # a Comment record + # polymorphic_url(record) # same as comment_url(record) + # + # # it recognizes new records and maps to the collection + # record = Comment.new + # polymorphic_url(record) # same as comments_url() + # def polymorphic_url(record_or_hash_or_array, options = {}) - record = extract_record(record_or_hash_or_array) + if record_or_hash_or_array.kind_of?(Array) + record_or_hash_or_array = record_or_hash_or_array.dup + end + record = extract_record(record_or_hash_or_array) + format = extract_format(record_or_hash_or_array, options) namespace = extract_namespace(record_or_hash_or_array) args = case record_or_hash_or_array @@ -11,9 +83,11 @@ module ActionController else [ record_or_hash_or_array ] end + args << format if format + inflection = case - when options[:action] == "new" + when options[:action].to_s == "new" args.pop :singular when record.respond_to?(:new_record?) && record.new_record? @@ -27,8 +101,11 @@ module ActionController send!(named_route, *args) end - def polymorphic_path(record_or_hash_or_array) - polymorphic_url(record_or_hash_or_array, :routing_type => :path) + # Returns the path component of a URL for the given record. It uses + # polymorphic_url with :routing_type => :path. + def polymorphic_path(record_or_hash_or_array, options = {}) + options[:routing_type] = :path + polymorphic_url(record_or_hash_or_array, options) end %w(edit new formatted).each do |action| @@ -43,26 +120,29 @@ module ActionController EOT end - private def action_prefix(options) options[:action] ? "#{options[:action]}_" : "" end def routing_type(options) - "#{options[:routing_type] || "url"}" + options[:routing_type] || :url end def build_named_route_call(records, namespace, inflection, options = {}) - records = Array.new([extract_record(records)]) unless records.is_a?(Array) - base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_" - - method_root = records.reverse.inject(base_segment) do |string, name| - segment = "#{RecordIdentifier.send!("singular_class_name", name)}_" - segment << string + unless records.is_a?(Array) + record = extract_record(records) + route = '' + else + record = records.pop + route = records.inject("") do |string, parent| + string << "#{RecordIdentifier.send!("singular_class_name", parent)}_" + end end - action_prefix(options) + namespace + method_root + routing_type(options) + route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_" + + action_prefix(options) + namespace + route + routing_type(options).to_s end def extract_record(record_or_hash_or_array) @@ -73,12 +153,22 @@ module ActionController end end + def extract_format(record_or_hash_or_array, options) + if options[:action].to_s == "formatted" && record_or_hash_or_array.is_a?(Array) + record_or_hash_or_array.pop + elsif options[:format] + options[:format] + else + nil + end + end + def extract_namespace(record_or_hash_or_array) returning "" do |namespace| if record_or_hash_or_array.is_a?(Array) record_or_hash_or_array.delete_if do |record_or_namespace| if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol) - namespace << "#{record_or_namespace.to_s}_" + namespace << "#{record_or_namespace}_" end end end diff --git a/vendor/rails/actionpack/lib/action_controller/record_identifier.rb b/vendor/rails/actionpack/lib/action_controller/record_identifier.rb index bdf2753a..643ff7e5 100644 --- a/vendor/rails/actionpack/lib/action_controller/record_identifier.rb +++ b/vendor/rails/actionpack/lib/action_controller/record_identifier.rb @@ -33,11 +33,17 @@ module ActionController # Returns plural/singular for a record or class. Example: # - # partial_path(post) # => "posts/post" - # partial_path(Person) # => "people/person" - def partial_path(record_or_class) + # partial_path(post) # => "posts/post" + # partial_path(Person) # => "people/person" + # partial_path(Person, "admin/games") # => "admin/people/person" + def partial_path(record_or_class, controller_path = nil) klass = class_from_record_or_class(record_or_class) - "#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + + if controller_path && controller_path.include?("/") + "#{File.dirname(controller_path)}/#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + else + "#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + end end # The DOM class convention is to use the singular form of an object or class. Examples: @@ -53,15 +59,15 @@ module ActionController [ prefix, singular_class_name(record_or_class) ].compact * '_' end - # The DOM class convention is to use the singular form of an object or class with the id following an underscore. + # The DOM id convention is to use the singular form of an object or class with the id following an underscore. # If no id is found, prefix with "new_" instead. Examples: # - # dom_class(Post.new(:id => 45)) # => "post_45" - # dom_class(Post.new) # => "new_post" + # dom_id(Post.new(:id => 45)) # => "post_45" + # dom_id(Post.new) # => "new_post" # # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: # - # dom_class(Post.new(:id => 45), :edit) # => "edit_post_45" + # dom_id(Post.new(:id => 45), :edit) # => "edit_post_45" def dom_id(record, prefix = nil) prefix ||= 'new' unless record.id [ prefix, singular_class_name(record), record.id ].compact * '_' diff --git a/vendor/rails/actionpack/lib/action_controller/request.rb b/vendor/rails/actionpack/lib/action_controller/request.rb index 19948da9..a35b9041 100755 --- a/vendor/rails/actionpack/lib/action_controller/request.rb +++ b/vendor/rails/actionpack/lib/action_controller/request.rb @@ -15,7 +15,7 @@ module ActionController # such as { 'RAILS_ENV' => 'production' }. attr_reader :env - # The true HTTP request method as a lowercase symbol, such as :get. + # The true HTTP request method as a lowercase symbol, such as :get. # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS. def request_method @request_method ||= begin @@ -28,41 +28,43 @@ module ActionController end end - # The HTTP request method as a lowercase symbol, such as :get. - # Note, HEAD is returned as :get since the two are functionally + # The HTTP request method as a lowercase symbol, such as :get. + # Note, HEAD is returned as :get since the two are functionally # equivalent from the application's perspective. def method request_method == :head ? :get : request_method end - # Is this a GET (or HEAD) request? Equivalent to request.method == :get + # Is this a GET (or HEAD) request? Equivalent to request.method == :get. def get? method == :get end - # Is this a POST request? Equivalent to request.method == :post + # Is this a POST request? Equivalent to request.method == :post. def post? request_method == :post end - # Is this a PUT request? Equivalent to request.method == :put + # Is this a PUT request? Equivalent to request.method == :put. def put? request_method == :put end - # Is this a DELETE request? Equivalent to request.method == :delete + # Is this a DELETE request? Equivalent to request.method == :delete. def delete? request_method == :delete end - # Is this a HEAD request? request.method sees HEAD as :get, so check the - # HTTP method directly. + # Is this a HEAD request? request.method sees HEAD as :get, + # so check the HTTP method directly. def head? request_method == :head end + # Provides acccess to the request's HTTP headers, for example: + # request.headers["Content-Type"] # => "text/plain" def headers - @env + @headers ||= ActionController::Http::Headers.new(@env) end def content_length @@ -111,7 +113,7 @@ module ActionController # end def format=(extension) parameters[:format] = extension.to_s - format + @format = Mime::Type.lookup_by_extension(parameters[:format]) end # Returns true if the request's "X-Requested-With" header contains @@ -122,26 +124,41 @@ module ActionController end alias xhr? :xml_http_request? + # Which IP addresses are "trusted proxies" that can be stripped from + # the right-hand-side of X-Forwarded-For + TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i + # Determine originating IP address. REMOTE_ADDR is the standard # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or - # HTTP_X_FORWARDED_FOR are set by proxies so check for these before - # falling back to REMOTE_ADDR. HTTP_X_FORWARDED_FOR may be a comma- - # delimited list in the case of multiple chained proxies; the first is - # the originating IP. - # - # Security note: do not use if IP spoofing is a concern for your - # application. Since remote_ip checks HTTP headers for addresses forwarded - # by proxies, the client may send any IP. remote_addr can't be spoofed but - # also doesn't work behind a proxy, since it's always the proxy's IP. + # HTTP_X_FORWARDED_FOR are set by proxies so check for these if + # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma- + # delimited list in the case of multiple chained proxies; the last + # address which is not trusted is the originating IP. + def remote_ip - return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP' + if TRUSTED_PROXIES !~ @env['REMOTE_ADDR'] + return @env['REMOTE_ADDR'] + end + + if @env.include? 'HTTP_CLIENT_IP' + if @env.include? 'HTTP_X_FORWARDED_FOR' + # We don't know which came from the proxy, and which from the user + raise ActionControllerError.new(< 1 && TRUSTED_PROXIES =~ remote_ips.last.strip + remote_ips.pop end - return remote_ips.first.strip unless remote_ips.empty? + return remote_ips.last.strip end @env['REMOTE_ADDR'] @@ -385,6 +402,14 @@ module ActionController body.blank? ? {} : Hash.from_xml(body).with_indifferent_access when :yaml YAML.load(body) + when :json + if body.blank? + {} + else + data = ActiveSupport::JSON.decode(body) + data = {:_json => data} unless data.is_a?(Hash) + data.with_indifferent_access + end else {} end @@ -441,8 +466,8 @@ module ActionController parser.result end - def parse_multipart_form_parameters(body, boundary, content_length, env) - parse_request_parameters(read_multipart(body, boundary, content_length, env)) + def parse_multipart_form_parameters(body, boundary, body_size, env) + parse_request_parameters(read_multipart(body, boundary, body_size, env)) end def extract_multipart_boundary(content_type_with_parameters) @@ -473,7 +498,7 @@ module ActionController when Array value.map { |v| get_typed_value(v) } else - if value.is_a?(UploadedFile) + if value.respond_to? :original_filename # Uploaded file if value.original_filename value @@ -490,23 +515,28 @@ module ActionController end end - MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n EOL = "\015\012" - def read_multipart(body, boundary, content_length, env) + def read_multipart(body, boundary, body_size, env) params = Hash.new([]) boundary = "--" + boundary - quoted_boundary = Regexp.quote(boundary, "n") + quoted_boundary = Regexp.quote(boundary) buf = "" bufsize = 10 * 1024 boundary_end="" # start multipart/form-data body.binmode if defined? body.binmode + case body + when File + body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding) + when StringIO + body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding) + end boundary_size = boundary.size + EOL.size - content_length -= boundary_size + body_size -= boundary_size status = body.read(boundary_size) if nil == status raise EOFError, "no content body" @@ -517,7 +547,7 @@ module ActionController loop do head = nil content = - if 10240 < content_length + if 10240 < body_size UploadedTempfile.new("CGI") else UploadedStringIO.new @@ -539,24 +569,24 @@ module ActionController buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = "" end - c = if bufsize < content_length + c = if bufsize < body_size body.read(bufsize) else - body.read(content_length) + body.read(body_size) end if c.nil? || c.empty? raise EOFError, "bad content body" end buf.concat(c) - content_length -= c.size + body_size -= c.size end buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do content.print $1 if "--" == $2 - content_length = -1 + body_size = -1 end - boundary_end = $2.dup + boundary_end = $2.dup "" end @@ -583,17 +613,16 @@ module ActionController else params[name] = [content] end - break if buf.size == 0 - break if content_length == -1 + break if body_size == -1 end raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/ - begin + begin body.rewind if body.respond_to?(:rewind) - rescue Errno::ESPIPE + rescue Errno::ESPIPE # Handles exceptions raised by input streams that cannot be rewound # such as when using plain CGI under Apache - end + end params end @@ -672,6 +701,7 @@ module ActionController else top << {key => value}.with_indifferent_access push top.last + value = top[key] end else top << value @@ -679,7 +709,8 @@ module ActionController elsif top.is_a? Hash key = CGI.unescape(key) parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) - return top[key] ||= value + top[key] ||= value + return top[key] else raise ArgumentError, "Don't know what to do: top is #{top.inspect}" end @@ -688,7 +719,7 @@ module ActionController end def type_conflict!(klass, value) - raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value." + raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)" end end diff --git a/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb b/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb index 75f9c0b2..02c9d59d 100644 --- a/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/vendor/rails/actionpack/lib/action_controller/request_forgery_protection.rb @@ -13,33 +13,46 @@ module ActionController #:nodoc: base.extend(ClassMethods) end + # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a + # forged link from another site, is done by embedding a token based on the session (which an attacker wouldn't know) in all + # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only + # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication + # scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway. + # + # This is turned on with the protect_from_forgery method, which will check the token and raise an + # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in + # production by editing public/422.html. A call to this method in ApplicationController is generated by default in post-Rails 2.0 + # applications. + # + # The token parameter is named authenticity_token by default. If you are generating an HTML form manually (without the + # use of Rails' form_for, form_tag or other helpers), you have to include a hidden field named like that and + # set its value to what is returned by form_authenticity_token. Same applies to manually constructed Ajax requests. To + # make the token available through a global variable to scripts on a certain page, you could add something like this to a view: + # + # <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> + # + # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to + # config/environments/test.rb: + # + # # Disable request forgery protection in test environment + # config.action_controller.allow_forgery_protection = false + # + # == Learn more about CSRF (Cross-Site Request Forgery) attacks + # + # Here are some resources: + # * http://isc.sans.org/diary.html?storyid=1750 + # * http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application. + # There are a few guidelines you should follow: + # + # * Keep your GET requests safe and idempotent. More reading material: + # * http://www.xml.com/pub/a/2002/04/24/deviant.html + # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 + # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session" + # module ClassMethods - # Protect a controller's actions from CSRF attacks by ensuring that all forms are coming from the current web application, not - # a forged link from another site. This is done by embedding a token based on the session (which an attacker wouldn't know) in - # all forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only - # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication - # scheme there anyway). Also, GET requests are not protected as these should be indempotent anyway. - # - # You turn this on with the #protect_from_forgery method, which will perform the check and raise - # an ActionController::InvalidAuthenticityToken if the token doesn't match what was expected. And it will add - # a _authenticity_token parameter to all forms that are automatically generated by Rails. You can customize the error message - # given through public/422.html. - # - # Learn more about CSRF (Cross-Site Request Forgery) attacks: - # - # * http://isc.sans.org/diary.html?storyid=1750 - # * http://en.wikipedia.org/wiki/Cross-site_request_forgery - # - # Keep in mind, this is NOT a silver-bullet, plug 'n' play, warm security blanket for your rails application. - # There are a few guidelines you should follow: - # - # * Keep your GET requests safe and idempotent. More reading material: - # * http://www.xml.com/pub/a/2002/04/24/deviant.html - # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 - # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session" - # - # If you need to construct a request yourself, but still want to take advantage of forgery protection, you can grab the - # authenticity_token using the form_authenticity_token helper method and make it part of the parameters yourself. + # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked. # # Example: # @@ -54,18 +67,12 @@ module ActionController #:nodoc: # skip_before_filter :verify_authenticity_token # end # - # If you are upgrading from Rails 1.x, disable forgery protection to - # simplify your tests. Add this to config/environments/test.rb: - # - # # Disable request forgery protection in test environment - # config.action_controller.allow_forgery_protection = false - # # Valid Options: # - # * :only/:except - passed to the before_filter call. Set which actions are verified. - # * :secret - Custom salt used to generate the form_authenticity_token. + # * :only/:except - Passed to the before_filter call. Set which actions are verified. + # * :secret - Custom salt used to generate the form_authenticity_token. # Leave this off if you are using the cookie session store. - # * :digest - Message digest used for hashing. Defaults to 'SHA1' + # * :digest - Message digest used for hashing. Defaults to 'SHA1'. def protect_from_forgery(options = {}) self.request_forgery_protection_token ||= :authenticity_token before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except) @@ -92,17 +99,18 @@ module ActionController #:nodoc: end def verifiable_request_format? - request.format.html? || request.format.js? + request.content_type.nil? || request.content_type.verify_request? end - # Sets the token value for the current session. Pass a :secret option in #protect_from_forgery to add a custom salt to the hash. + # Sets the token value for the current session. Pass a :secret option + # in +protect_from_forgery+ to add a custom salt to the hash. def form_authenticity_token - @form_authenticity_token ||= if request_forgery_protection_options[:secret] + @form_authenticity_token ||= if !session.respond_to?(:session_id) + raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session." + elsif request_forgery_protection_options[:secret] authenticity_token_from_session_id elsif session.respond_to?(:dbman) && session.dbman.respond_to?(:generate_digest) authenticity_token_from_cookie_session - elsif session.nil? - raise InvalidAuthenticityToken, "Request Forgery Protection requires a valid session. Use #allow_forgery_protection to disable it, or use a valid session." else raise InvalidAuthenticityToken, "No :secret given to the #protect_from_forgery call. Set that or use a session store capable of generating its own keys (Cookie Session Store)." end diff --git a/vendor/rails/actionpack/lib/action_controller/request_profiler.rb b/vendor/rails/actionpack/lib/action_controller/request_profiler.rb index 62f6e665..a6471d0c 100755 --- a/vendor/rails/actionpack/lib/action_controller/request_profiler.rb +++ b/vendor/rails/actionpack/lib/action_controller/request_profiler.rb @@ -13,20 +13,21 @@ module ActionController def initialize(script_path) @quiet = false - define_run_method(File.read(script_path)) + define_run_method(script_path) reset! end - def benchmark(n) + def benchmark(n, profiling = false) @quiet = true print ' ' + result = Benchmark.realtime do n.times do |i| - run - print i % 10 == 0 ? 'x' : '.' - $stdout.flush + run(profiling) + print_progress(i) end end + puts result ensure @@ -38,8 +39,33 @@ module ActionController end private - def define_run_method(script) - instance_eval "def run; #{script}; end", __FILE__, __LINE__ + def define_run_method(script_path) + script = File.read(script_path) + + source = <<-end_source + def run(profiling = false) + if profiling + RubyProf.resume do + #{script} + end + else + #{script} + end + + old_request_count = request_count + reset! + self.request_count = old_request_count + end + end_source + + instance_eval source, script_path, 1 + end + + def print_progress(i) + print "\n " if i % 60 == 0 + print ' ' if i % 10 == 0 + print '.' + $stdout.flush end end @@ -72,21 +98,22 @@ module ActionController def profile(sandbox) load_ruby_prof - results = RubyProf.profile { benchmark(sandbox) } + benchmark(sandbox, true) + results = RubyProf.stop show_profile_results results results end - def benchmark(sandbox) + def benchmark(sandbox, profiling = false) sandbox.request_count = 0 - elapsed = sandbox.benchmark(options[:n]).to_f + elapsed = sandbox.benchmark(options[:n], profiling).to_f count = sandbox.request_count.to_i puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed] end def warmup(sandbox) - Benchmark.realtime { sandbox.run } + Benchmark.realtime { sandbox.run(false) } end def default_options @@ -98,8 +125,9 @@ module ActionController OptionParser.new do |opt| opt.banner = "USAGE: #{$0} [options] [session script path]" - opt.on('-n', '--times [0000]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i } + opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v } opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v } + opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v } opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v } opt.on('-h', '--help', 'Show this help') { puts opt; exit } @@ -116,8 +144,11 @@ module ActionController protected def load_ruby_prof begin + gem 'ruby-prof', '>= 0.6.1' require 'ruby-prof' - #RubyProf.measure_mode = RubyProf::ALLOCATED_OBJECTS + if mode = options[:measure] + RubyProf.measure_mode = RubyProf.const_get(mode.upcase) + end rescue LoadError abort '`gem install ruby-prof` to use the profiler' end diff --git a/vendor/rails/actionpack/lib/action_controller/rescue.rb b/vendor/rails/actionpack/lib/action_controller/rescue.rb index b91115a9..40ef4ea0 100644 --- a/vendor/rails/actionpack/lib/action_controller/rescue.rb +++ b/vendor/rails/actionpack/lib/action_controller/rescue.rb @@ -26,7 +26,7 @@ module ActionController #:nodoc: DEFAULT_RESCUE_TEMPLATE = 'diagnostics' DEFAULT_RESCUE_TEMPLATES = { - 'ActionController::MissingTemplate' => 'missing_template', + 'ActionView::MissingTemplate' => 'missing_template', 'ActionController::RoutingError' => 'routing_error', 'ActionController::UnknownAction' => 'unknown_action', 'ActionView::TemplateError' => 'template_error' @@ -58,33 +58,35 @@ module ActionController #:nodoc: # Rescue exceptions raised in controller actions. # # rescue_from receives a series of exception classes or class - # names, and a trailing :with option with the name of a method or a Proc - # object to be called to handle them. Alternatively a block can be given. + # names, and a trailing :with option with the name of a method + # or a Proc object to be called to handle them. Alternatively a block can + # be given. # # Handlers that take one argument will be called with the exception, so # that the exception can be inspected when dealing with it. # # Handlers are inherited. They are searched from right to left, from # bottom to top, and up the hierarchy. The handler of the first class for - # which exception.is_a?(klass) holds true is the one invoked, if any. + # which exception.is_a?(klass) holds true is the one invoked, if + # any. # - # class ApplicationController < ActionController::Base - # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception - # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors + # class ApplicationController < ActionController::Base + # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception + # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors # - # rescue_from 'MyAppError::Base' do |exception| - # render :xml => exception, :status => 500 + # rescue_from 'MyAppError::Base' do |exception| + # render :xml => exception, :status => 500 + # end + # + # protected + # def deny_access + # ... + # end + # + # def show_errors(exception) + # exception.record.new_record? ? ... + # end # end - # - # protected - # def deny_access - # ... - # end - # - # def show_errors(exception) - # exception.record.new_record? ? ... - # end - # end def rescue_from(*klasses, &block) options = klasses.extract_options! unless options.has_key?(:with) @@ -153,7 +155,7 @@ module ActionController #:nodoc: # If the file doesn't exist, the body of the response will be left empty. def render_optional_error_file(status_code) status = interpret_status(status_code) - path = "#{RAILS_ROOT}/public/#{status[0,3]}.html" + path = "#{Rails.public_path}/#{status[0,3]}.html" if File.exist?(path) render :file => path, :status => status else @@ -165,7 +167,7 @@ module ActionController #:nodoc: # method if you wish to redefine the meaning of a local request to # include remote IP addresses or other criteria. def local_request? #:doc: - request.remote_addr == LOCALHOST and request.remote_ip == LOCALHOST + request.remote_addr == LOCALHOST && request.remote_ip == LOCALHOST end # Render detailed diagnostics for unhandled exceptions rescued from @@ -197,10 +199,8 @@ module ActionController #:nodoc: private def perform_action_with_rescue #:nodoc: perform_action_without_rescue - rescue Exception => exception # errors from action performed - return if rescue_action_with_handler(exception) - - rescue_action(exception) + rescue Exception => exception + rescue_action_with_handler(exception) || rescue_action(exception) end def rescues_path(template_name) diff --git a/vendor/rails/actionpack/lib/action_controller/resources.rb b/vendor/rails/actionpack/lib/action_controller/resources.rb index 8ec6b84a..9fb1f9fa 100644 --- a/vendor/rails/actionpack/lib/action_controller/resources.rb +++ b/vendor/rails/actionpack/lib/action_controller/resources.rb @@ -44,13 +44,14 @@ module ActionController module Resources class Resource #:nodoc: attr_reader :collection_methods, :member_methods, :new_methods - attr_reader :path_prefix, :name_prefix + attr_reader :path_prefix, :name_prefix, :path_segment attr_reader :plural, :singular attr_reader :options def initialize(entities, options) @plural ||= entities @singular ||= options[:singular] || plural.to_s.singularize + @path_segment = options.delete(:as) || @plural @options = options @@ -75,11 +76,13 @@ module ActionController end def path - @path ||= "#{path_prefix}/#{plural}" + @path ||= "#{path_prefix}/#{path_segment}" end def new_path - @new_path ||= "#{path}/new" + new_action = self.options[:path_names][:new] if self.options[:path_names] + new_action ||= Base.resources_path_names[:new] + @new_path ||= "#{path}/#{new_action}" end def member_path @@ -188,7 +191,7 @@ module ActionController # end # end # - # Along with the routes themselves, #resources generates named routes for use in + # Along with the routes themselves, +resources+ generates named routes for use in # controllers and views. map.resources :messages produces the following named routes and helpers: # # Named Route Helpers @@ -205,7 +208,7 @@ module ActionController # edit_message edit_message_url(id), hash_for_edit_message_url(id), # edit_message_path(id), hash_for_edit_message_path(id) # - # You can use these helpers instead of #url_for or methods that take #url_for parameters. For example: + # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example: # # redirect_to :controller => 'messages', :action => 'index' # # and @@ -226,17 +229,53 @@ module ActionController # # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %> # - # The #resources method accepts the following options to customize the resulting routes: - # * :collection - add named routes for other actions that operate on the collection. + # or + # + # <% form_for @message do |f| %> + # + # which takes into account whether @message is a new record or not and generates the + # path and method accordingly. + # + # The +resources+ method accepts the following options to customize the resulting routes: + # * :collection - Add named routes for other actions that operate on the collection. # Takes a hash of #{action} => #{method}, where method is :get/:post/:put/:delete - # or :any if the method does not matter. These routes map to a URL like /messages/rss, with a route of rss_messages_url. - # * :member - same as :collection, but for actions that operate on a specific member. - # * :new - same as :collection, but for actions that operate on the new resource action. - # * :controller - specify the controller name for the routes. - # * :singular - specify the singular name used in the member routes. - # * :requirements - set custom routing parameter requirements. - # * :conditions - specify custom routing recognition conditions. Resources sets the :method value for the method-specific routes. - # * :path_prefix - set a prefix to the routes with required route variables. + # or :any if the method does not matter. These routes map to a URL like /messages/rss, with a route of +rss_messages_url+. + # * :member - Same as :collection, but for actions that operate on a specific member. + # * :new - Same as :collection, but for actions that operate on the new resource action. + # * :controller - Specify the controller name for the routes. + # * :singular - Specify the singular name used in the member routes. + # * :requirements - Set custom routing parameter requirements. + # * :conditions - Specify custom routing recognition conditions. Resources sets the :method value for the method-specific routes. + # * :as - Specify a different resource name to use in the URL path. For example: + # # products_path == '/productos' + # map.resources :products, :as => 'productos' do |product| + # # product_reviews_path(product) == '/productos/1234/comentarios' + # product.resources :product_reviews, :as => 'comentarios' + # end + # + # * :has_one - Specify nested resources, this is a shorthand for mapping singleton resources beneath the current. + # * :has_many - Same has :has_one, but for plural resources. + # + # You may directly specify the routing association with +has_one+ and +has_many+ like: + # + # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments] + # + # This is the same as: + # + # map.resources :notes do |notes| + # notes.resource :author + # notes.resources :comments + # notes.resources :attachments + # end + # + # * :path_names - Specify different names for the 'new' and 'edit' actions. For example: + # # new_products_path == '/productos/nuevo' + # map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' } + # + # You can also set default action names from an environment, like this: + # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } + # + # * :path_prefix - Set a prefix to the routes with required route variables. # # Weblog comments usually belong to a post, so you might use resources like: # @@ -249,7 +288,7 @@ module ActionController # article.resources :comments # end # - # The comment resources work the same, but must now include a value for :article_id. + # The comment resources work the same, but must now include a value for :article_id. # # article_comments_url(@article) # article_comment_url(@article, @comment) @@ -257,13 +296,13 @@ module ActionController # article_comments_url(:article_id => @article) # article_comment_url(:article_id => @article, :id => @comment) # - # * :name_prefix - define a prefix for all generated routes, usually ending in an underscore. + # * :name_prefix - Define a prefix for all generated routes, usually ending in an underscore. # Use this if you have named routes that may clash. # # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_' # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' # - # You may also use :name_prefix to override the generic named routes in a nested resource: + # You may also use :name_prefix to override the generic named routes in a nested resource: # # map.resources :articles do |article| # article.resources :comments, :name_prefix => nil @@ -304,7 +343,7 @@ module ActionController # # --> GET /categories/7/messages/1 # # has named route "category_message" # - # The #resources method sets HTTP method restrictions on the routes it generates. For example, making an + # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an # HTTP POST on new_message_url will raise a RoutingError exception. The default route in # config/routes.rb overrides this and allows invalid HTTP methods for resource routes. def resources(*entities, &block) @@ -325,7 +364,7 @@ module ActionController # # See map.resources for general conventions. These are the main differences: # * A singular name is given to map.resource. The default controller name is still taken from the plural name. - # * To specify a custom plural name, use the :plural option. There is no :singular option. + # * To specify a custom plural name, use the :plural option. There is no :singular option. # * No default index route is created for the singleton resource controller. # * When nesting singleton resources, only the singular name is used as the path prefix (example: 'account/messages/1') # @@ -367,7 +406,7 @@ module ActionController # end # end # - # Along with the routes themselves, #resource generates named routes for + # Along with the routes themselves, +resource+ generates named routes for # use in controllers and views. map.resource :account produces # these named routes and helpers: # @@ -485,8 +524,12 @@ module ActionController resource.member_methods.each do |method, actions| actions.each do |action| action_options = action_options_for(action, resource, method) - map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}", action_options) - map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action}.:format",action_options) + + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= Base.resources_path_names[action] || action + + map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options) + map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options) end end diff --git a/vendor/rails/actionpack/lib/action_controller/routing.rb b/vendor/rails/actionpack/lib/action_controller/routing.rb index edd4683f..6aa26651 100644 --- a/vendor/rails/actionpack/lib/action_controller/routing.rb +++ b/vendor/rails/actionpack/lib/action_controller/routing.rb @@ -1,53 +1,13 @@ require 'cgi' require 'uri' require 'action_controller/polymorphic_routes' -require 'action_controller/routing_optimisation' - -class Object - def to_param - to_s - end -end - -class TrueClass - def to_param - self - end -end - -class FalseClass - def to_param - self - end -end - -class NilClass - def to_param - self - end -end - -class Regexp #:nodoc: - def number_of_captures - Regexp.new("|#{source}").match('').captures.length - end - - class << self - def optionalize(pattern) - case unoptionalize(pattern) - when /\A(.|\(.*\))\Z/ then "#{pattern}?" - else "(?:#{pattern})?" - end - end - - def unoptionalize(pattern) - [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp| - return $1 if regexp =~ pattern - end - return pattern - end - end -end +require 'action_controller/routing/optimisations' +require 'action_controller/routing/routing_ext' +require 'action_controller/routing/route' +require 'action_controller/routing/segments' +require 'action_controller/routing/builder' +require 'action_controller/routing/route_set' +require 'action_controller/routing/recognition_optimisation' module ActionController # == Routing @@ -55,7 +15,7 @@ module ActionController # The routing module provides URL rewriting in native Ruby. It's a way to # redirect incoming requests to controllers and actions. This replaces # mod_rewrite rules. Best of all, Rails' Routing works with any web server. - # Routes are defined in routes.rb in your RAILS_ROOT/config directory. + # Routes are defined in config/routes.rb. # # Consider the following route, installed by Rails when you generate your # application: @@ -63,7 +23,8 @@ module ActionController # map.connect ':controller/:action/:id' # # This route states that it expects requests to consist of a - # :controller followed by an :action that in turn is fed some :id. + # :controller followed by an :action that in turn is fed + # some :id. # # Suppose you get an incoming request for /blog/edit/22, you'll end up # with: @@ -76,35 +37,35 @@ module ActionController # Think of creating routes as drawing a map for your requests. The map tells # them where to go based on some predefined pattern: # - # ActionController::Routing::Routes.draw do |map| - # Pattern 1 tells some request to go to one place - # Pattern 2 tell them to go to another - # ... - # end + # ActionController::Routing::Routes.draw do |map| + # Pattern 1 tells some request to go to one place + # Pattern 2 tell them to go to another + # ... + # end # # The following symbols are special: # # :controller maps to your controller name # :action maps to an action with your controllers # - # Other names simply map to a parameter as in the case of +:id+. + # Other names simply map to a parameter as in the case of :id. # # == Route priority # # Not all routes are created equally. Routes have priority defined by the - # order of appearance of the routes in the routes.rb file. The priority goes + # order of appearance of the routes in the config/routes.rb file. The priority goes # from top to bottom. The last route in that file is at the lowest priority # and will be applied last. If no route matches, 404 is returned. # # Within blocks, the empty pattern is at the highest priority. # In practice this works out nicely: # - # ActionController::Routing::Routes.draw do |map| - # map.with_options :controller => 'blog' do |blog| - # blog.show '', :action => 'list' - # end - # map.connect ':controller/:action/:view' - # end + # ActionController::Routing::Routes.draw do |map| + # map.with_options :controller => 'blog' do |blog| + # blog.show '', :action => 'list' + # end + # map.connect ':controller/:action/:view' + # end # # In this case, invoking blog controller (with an URL like '/blog/') # without parameters will activate the 'list' action by default. @@ -115,14 +76,15 @@ module ActionController # Hash at the end of your mapping to set any default parameters. # # Example: - # ActionController::Routing:Routes.draw do |map| - # map.connect ':controller/:action/:id', :controller => 'blog' - # end + # + # ActionController::Routing:Routes.draw do |map| + # map.connect ':controller/:action/:id', :controller => 'blog' + # end # # This sets up +blog+ as the default controller if no other is specified. # This means visiting '/' would invoke the blog controller. # - # More formally, you can define defaults in a route with the +:defaults+ key. + # More formally, you can define defaults in a route with the :defaults key. # # map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' } # @@ -133,6 +95,7 @@ module ActionController # for the full URL and +name_of_route_path+ for the URI path. # # Example: + # # # In routes.rb # map.login 'login', :controller => 'accounts', :action => 'login' # @@ -155,6 +118,12 @@ module ActionController # root_url # => 'http://www.example.com/' # root_path # => '' # + # You can also specify an already-defined named route in your map.root call: + # + # # In routes.rb + # map.new_session :controller => 'sessions', :action => 'new' + # map.root :new_session + # # Note: when using +with_options+, the route is simply named after the # method you call on the block parameter rather than map. # @@ -172,33 +141,52 @@ module ActionController # # Routes can generate pretty URLs. For example: # - # map.connect 'articles/:year/:month/:day', - # :controller => 'articles', - # :action => 'find_by_date', - # :year => /\d{4}/, - # :month => /\d{1,2}/, - # :day => /\d{1,2}/ + # map.connect 'articles/:year/:month/:day', + # :controller => 'articles', + # :action => 'find_by_date', + # :year => /\d{4}/, + # :month => /\d{1,2}/, + # :day => /\d{1,2}/ # - # # Using the route above, the url below maps to: - # # params = {:year => '2005', :month => '11', :day => '06'} - # # http://localhost:3000/articles/2005/11/06 + # Using the route above, the URL "http://localhost:3000/articles/2005/11/06" + # maps to + # + # params = {:year => '2005', :month => '11', :day => '06'} # # == Regular Expressions and parameters # You can specify a regular expression to define a format for a parameter. # - # map.geocode 'geocode/:postalcode', :controller => 'geocode', - # :action => 'show', :postalcode => /\d{5}(-\d{4})?/ + # map.geocode 'geocode/:postalcode', :controller => 'geocode', + # :action => 'show', :postalcode => /\d{5}(-\d{4})?/ # # or, more formally: # # map.geocode 'geocode/:postalcode', :controller => 'geocode', # :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ } # + # Formats can include the 'ignorecase' and 'extended syntax' regular + # expression modifiers: + # + # map.geocode 'geocode/:postalcode', :controller => 'geocode', + # :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i + # + # map.geocode 'geocode/:postalcode', :controller => 'geocode', + # :action => 'show',:requirements => { + # :postalcode => /# Postcode format + # \d{5} #Prefix + # (-\d{4})? #Suffix + # /x + # } + # + # Using the multiline match modifier will raise an ArgumentError. + # Encoding regular expression modifiers are silently ignored. The + # match will always use the default encoding or ASCII. + # # == Route globbing # # Specifying *[string] as part of a rule like: # - # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?' + # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?' # # will glob all remaining parts of the route that were not recognized earlier. This idiom # must appear at the end of the path. The globbed values are in params[:path] in @@ -226,10 +214,10 @@ module ActionController # # You can reload routes if you feel you must: # - # ActionController::Routing::Routes.reload + # ActionController::Routing::Routes.reload # # This will clear all named routes and reload routes.rb if the file has been modified from - # last load. To absolutely force reloading, use +reload!+. + # last load. To absolutely force reloading, use reload!. # # == Testing Routes # @@ -237,19 +225,19 @@ module ActionController # # === +assert_routing+ # - # def test_movie_route_properly_splits - # opts = {:controller => "plugin", :action => "checkout", :id => "2"} - # assert_routing "plugin/checkout/2", opts - # end + # def test_movie_route_properly_splits + # opts = {:controller => "plugin", :action => "checkout", :id => "2"} + # assert_routing "plugin/checkout/2", opts + # end # # +assert_routing+ lets you test whether or not the route properly resolves into options. # # === +assert_recognizes+ # - # def test_route_has_options - # opts = {:controller => "plugin", :action => "show", :id => "12"} - # assert_recognizes opts, "/plugins/show/12" - # end + # def test_route_has_options + # opts = {:controller => "plugin", :action => "show", :id => "12"} + # assert_recognizes opts, "/plugins/show/12" + # end # # Note the subtle difference between the two: +assert_routing+ tests that # a URL fits options while +assert_recognizes+ tests that a URL @@ -257,16 +245,16 @@ module ActionController # # In tests you can simply pass the URL or named route to +get+ or +post+. # - # def send_to_jail - # get '/jail' - # assert_response :success - # assert_template "jail/front" - # end + # def send_to_jail + # get '/jail' + # assert_response :success + # assert_template "jail/front" + # end # - # def goes_to_login - # get login_url - # #... - # end + # def goes_to_login + # get login_url + # #... + # end # # == View a list of all your routes # @@ -289,6 +277,9 @@ module ActionController end class << self + # Expects an array of controller names as the first argument. + # Executes the passed block with only the named controllers named available. + # This method is used in internal Rails testing. def with_controllers(names) prior_controllers = @possible_controllers use_controllers! names @@ -297,6 +288,10 @@ module ActionController use_controllers! prior_controllers end + # Returns an array of paths, cleaned of double-slashes and relative path references. + # * "\\\" and "//" become "\\" or "/". + # * "/foo/bar/../config" becomes "/foo/config". + # The returned array is sorted by length, descending. def normalize_paths(paths) # do the hokey-pokey of path normalization... paths = paths.collect do |path| @@ -306,8 +301,8 @@ module ActionController gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it # eliminate .. paths where possible - re = %r{\w+[/\\]\.\.[/\\]} - path.gsub!(%r{\w+[/\\]\.\.[/\\]}, "") while path.match(re) + re = %r{[^/\\]+[/\\]\.\.[/\\]} + path.gsub!(re, "") while path.match(re) path end @@ -315,6 +310,7 @@ module ActionController paths = paths.uniq.sort_by { |path| - path.length } end + # Returns the array of controller names currently available to ActionController::Routing. def possible_controllers unless @possible_controllers @possible_controllers = [] @@ -339,10 +335,28 @@ module ActionController @possible_controllers end + # Replaces the internal list of controllers available to ActionController::Routing with the passed argument. + # ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ]) def use_controllers!(controller_names) @possible_controllers = controller_names end + # Returns a controller path for a new +controller+ based on a +previous+ controller path. + # Handles 4 scenarios: + # + # * stay in the previous controller: + # controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion" + # + # * stay in the previous namespace: + # controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts" + # + # * forced move to the root namespace: + # controller_relative_to( "/posts", "groups/discussion" ) # => "posts" + # + # * previous namespace is root: + # controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts" + # + def controller_relative_to(controller, previous) if controller.nil? then previous elsif controller[0] == ?/ then controller[1..-1] @@ -351,1142 +365,12 @@ module ActionController end end end - - class Route #:nodoc: - attr_accessor :segments, :requirements, :conditions, :optimise - - def initialize - @segments = [] - @requirements = {} - @conditions = {} - @optimise = true - end - - # Indicates whether the routes should be optimised with the string interpolation - # version of the named routes methods. - def optimise? - @optimise && ActionController::Base::optimise_named_routes - end - - def segment_keys - segments.collect do |segment| - segment.key if segment.respond_to? :key - end.compact - end - - # Write and compile a +generate+ method for this Route. - def write_generation - # Build the main body of the generation - body = "expired = false\n#{generation_extraction}\n#{generation_structure}" - - # If we have conditions that must be tested first, nest the body inside an if - body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements - args = "options, hash, expire_on = {}" - - # Nest the body inside of a def block, and then compile it. - raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - - # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash - # are the same as the keys that were recalled from the previous request. Thus, - # we can use the expire_on.keys to determine which keys ought to be used to build - # the query string. (Never use keys from the recalled request when building the - # query string.) - - method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - - method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - raw_method - end - - # Build several lines of code that extract values from the options hash. If any - # of the values are missing or rejected then a return will be executed. - def generation_extraction - segments.collect do |segment| - segment.extraction_code - end.compact * "\n" - end - - # Produce a condition expression that will check the requirements of this route - # upon generation. - def generation_requirements - requirement_conditions = requirements.collect do |key, req| - if req.is_a? Regexp - value_regexp = Regexp.new "\\A#{req.source}\\Z" - "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]" - else - "hash[:#{key}] == #{req.inspect}" - end - end - requirement_conditions * ' && ' unless requirement_conditions.empty? - end - - def generation_structure - segments.last.string_structure segments[0..-2] - end - - # Write and compile a +recognize+ method for this Route. - def write_recognition - # Create an if structure to extract the params from a match if it occurs. - body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" - body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" - - # Build the method declaration and compile it - method_decl = "def recognize(path, env={})\n#{body}\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - method_decl - end - - # Plugins may override this method to add other conditions, like checks on - # host, subdomain, and so forth. Note that changes here only affect route - # recognition, not generation. - def recognition_conditions - result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] - result << "conditions[:method] === env[:method]" if conditions[:method] - result - end - - # Build the regular expression pattern that will match this route. - def recognition_pattern(wrap = true) - pattern = '' - segments.reverse_each do |segment| - pattern = segment.build_pattern pattern - end - wrap ? ("\\A" + pattern + "\\Z") : pattern - end - - # Write the code to extract the parameters from a matched route. - def recognition_extraction - next_capture = 1 - extraction = segments.collect do |segment| - x = segment.match_extraction(next_capture) - next_capture += Regexp.new(segment.regexp_chunk).number_of_captures - x - end - extraction.compact - end - - # Write the real generation implementation and then resend the message. - def generate(options, hash, expire_on = {}) - write_generation - generate options, hash, expire_on - end - - def generate_extras(options, hash, expire_on = {}) - write_generation - generate_extras options, hash, expire_on - end - - # Generate the query string with any extra keys in the hash and append - # it to the given path, returning the new path. - def append_query_string(path, hash, query_keys=nil) - return nil unless path - query_keys ||= extra_keys(hash) - "#{path}#{build_query_string(hash, query_keys)}" - end - - # Determine which keys in the given hash are "extra". Extra keys are - # those that were not used to generate a particular route. The extra - # keys also do not include those recalled from the prior request, nor - # do they include any keys that were implied in the route (like a - # :controller that is required, but not explicitly used in the text of - # the route.) - def extra_keys(hash, recall={}) - (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys - end - - # Build a query string from the keys of the given hash. If +only_keys+ - # is given (as an array), only the keys indicated will be used to build - # the query string. The query string will correctly build array parameter - # values. - def build_query_string(hash, only_keys = nil) - elements = [] - - (only_keys || hash.keys).each do |key| - if value = hash[key] - elements << value.to_query(key) - end - end - - elements.empty? ? '' : "?#{elements.sort * '&'}" - end - - # Write the real recognition implementation and then resend the message. - def recognize(path, environment={}) - write_recognition - recognize path, environment - end - - # A route's parameter shell contains parameter values that are not in the - # route's path, but should be placed in the recognized hash. - # - # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route: - # - # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ - # - def parameter_shell - @parameter_shell ||= returning({}) do |shell| - requirements.each do |key, requirement| - shell[key] = requirement unless requirement.is_a? Regexp - end - end - end - - # Return an array containing all the keys that are used in this route. This - # includes keys that appear inside the path, and keys that have requirements - # placed upon them. - def significant_keys - @significant_keys ||= returning [] do |sk| - segments.each { |segment| sk << segment.key if segment.respond_to? :key } - sk.concat requirements.keys - sk.uniq! - end - end - - # Return a hash of key/value pairs representing the keys in the route that - # have defaults, or which are specified by non-regexp requirements. - def defaults - @defaults ||= returning({}) do |hash| - segments.each do |segment| - next unless segment.respond_to? :default - hash[segment.key] = segment.default unless segment.default.nil? - end - requirements.each do |key,req| - next if Regexp === req || req.nil? - hash[key] = req - end - end - end - - def matches_controller_and_action?(controller, action) - unless defined? @matching_prepared - @controller_requirement = requirement_for(:controller) - @action_requirement = requirement_for(:action) - @matching_prepared = true - end - - (@controller_requirement.nil? || @controller_requirement === controller) && - (@action_requirement.nil? || @action_requirement === action) - end - - def to_s - @to_s ||= begin - segs = segments.inject("") { |str,s| str << s.to_s } - "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect] - end - end - - protected - def requirement_for(key) - return requirements[key] if requirements.key? key - segments.each do |segment| - return segment.regexp if segment.respond_to?(:key) && segment.key == key - end - nil - end - - end - - class Segment #:nodoc: - RESERVED_PCHAR = ':@&=+$,;' - UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze - - attr_accessor :is_optional - alias_method :optional?, :is_optional - - def initialize - self.is_optional = false - end - - def extraction_code - nil - end - - # Continue generating string for the prior segments. - def continue_string_structure(prior_segments) - if prior_segments.empty? - interpolation_statement(prior_segments) - else - new_priors = prior_segments[0..-2] - prior_segments.last.string_structure(new_priors) - end - end - - def interpolation_chunk - URI.escape(value, UNSAFE_PCHAR) - end - - # Return a string interpolation statement for this segment and those before it. - def interpolation_statement(prior_segments) - chunks = prior_segments.collect { |s| s.interpolation_chunk } - chunks << interpolation_chunk - "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}" - end - - def string_structure(prior_segments) - optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments) - end - - # Return an if condition that is true if all the prior segments can be generated. - # If there are no optional segments before this one, then nil is returned. - def all_optionals_available_condition(prior_segments) - optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact - optional_locals.empty? ? nil : " if #{optional_locals * ' && '}" - end - - # Recognition - - def match_extraction(next_capture) - nil - end - - # Warning - - # Returns true if this segment is optional? because of a default. If so, then - # no warning will be emitted regarding this segment. - def optionality_implied? - false - end - end - - class StaticSegment < Segment #:nodoc: - attr_accessor :value, :raw - alias_method :raw?, :raw - - def initialize(value = nil) - super() - self.value = value - end - - def interpolation_chunk - raw? ? value : super - end - - def regexp_chunk - chunk = Regexp.escape(value) - optional? ? Regexp.optionalize(chunk) : chunk - end - - def build_pattern(pattern) - escaped = Regexp.escape(value) - if optional? && ! pattern.empty? - "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" - elsif optional? - Regexp.optionalize escaped - else - escaped + pattern - end - end - - def to_s - value - end - end - - class DividerSegment < StaticSegment #:nodoc: - def initialize(value = nil) - super(value) - self.raw = true - self.is_optional = true - end - - def optionality_implied? - true - end - end - - class DynamicSegment < Segment #:nodoc: - attr_accessor :key, :default, :regexp - - def initialize(key = nil, options = {}) - super() - self.key = key - self.default = options[:default] if options.key? :default - self.is_optional = true if options[:optional] || options.key?(:default) - end - - def to_s - ":#{key}" - end - - # The local variable name that the value of this segment will be extracted to. - def local_name - "#{key}_value" - end - - def extract_value - "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}" - end - def value_check - if default # Then we know it won't be nil - "#{value_regexp.inspect} =~ #{local_name}" if regexp - elsif optional? - # If we have a regexp check that the value is not given, or that it matches. - # If we have no regexp, return nil since we do not require a condition. - "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp - else # Then it must be present, and if we have a regexp, it must match too. - "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" - end - end - def expiry_statement - "expired, hash = true, options if !expired && expire_on[:#{key}]" - end - - def extraction_code - s = extract_value - vc = value_check - s << "\nreturn [nil,nil] unless #{vc}" if vc - s << "\n#{expiry_statement}" - end - - def interpolation_chunk(value_code = "#{local_name}") - "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}" - end - - def string_structure(prior_segments) - if optional? # We have a conditional to do... - # If we should not appear in the url, just write the code for the prior - # segments. This occurs if our value is the default value, or, if we are - # optional, if we have nil as our value. - "if #{local_name} == #{default.inspect}\n" + - continue_string_structure(prior_segments) + - "\nelse\n" + # Otherwise, write the code up to here - "#{interpolation_statement(prior_segments)}\nend" - else - interpolation_statement(prior_segments) - end - end - - def value_regexp - Regexp.new "\\A#{regexp.source}\\Z" if regexp - end - def regexp_chunk - regexp ? "(#{regexp.source})" : "([^#{Routing::SEPARATORS.join}]+)" - end - - def build_pattern(pattern) - chunk = regexp_chunk - chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 - pattern = "#{chunk}#{pattern}" - optional? ? Regexp.optionalize(pattern) : pattern - end - def match_extraction(next_capture) - # All non code-related keys (such as :id, :slug) are URI-unescaped as - # path parameters. - default_value = default ? default.inspect : nil - %[ - value = if (m = match[#{next_capture}]) - URI.unescape(m) - else - #{default_value} - end - params[:#{key}] = value if value - ] - end - - def optionality_implied? - [:action, :id].include? key - end - - end - - class ControllerSegment < DynamicSegment #:nodoc: - def regexp_chunk - possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } - "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" - end - - # Don't URI.escape the controller name since it may contain slashes. - def interpolation_chunk(value_code = "#{local_name}") - "\#{#{value_code}.to_s}" - end - - # Make sure controller names like Admin/Content are correctly normalized to - # admin/content - def extract_value - "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase" - end - - def match_extraction(next_capture) - if default - "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'" - else - "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]" - end - end - end - - class PathSegment < DynamicSegment #:nodoc: - RESERVED_PCHAR = "#{Segment::RESERVED_PCHAR}/" - UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze - - def interpolation_chunk(value_code = "#{local_name}") - "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::PathSegment::UNSAFE_PCHAR)}" - end - - def default - '' - end - - def default=(path) - raise RoutingError, "paths cannot have non-empty default values" unless path.blank? - end - - def match_extraction(next_capture) - "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}" - end - - def regexp_chunk - regexp || "(.*)" - end - - def optionality_implied? - true - end - - class Result < ::Array #:nodoc: - def to_s() join '/' end - def self.new_escaped(strings) - new strings.collect {|str| URI.unescape str} - end - end - end - - class RouteBuilder #:nodoc: - attr_accessor :separators, :optional_separators - - def initialize - self.separators = Routing::SEPARATORS - self.optional_separators = %w( / ) - end - - def separator_pattern(inverted = false) - "[#{'^' if inverted}#{Regexp.escape(separators.join)}]" - end - - def interval_regexp - Regexp.new "(.*?)(#{separators.source}|$)" - end - - # Accepts a "route path" (a string defining a route), and returns the array - # of segments that corresponds to it. Note that the segment array is only - # partially initialized--the defaults and requirements, for instance, need - # to be set separately, via the #assign_route_options method, and the - # #optional? method for each segment will not be reliable until after - # #assign_route_options is called, as well. - def segments_for_route_path(path) - rest, segments = path, [] - - until rest.empty? - segment, rest = segment_for rest - segments << segment - end - segments - end - - # A factory method that returns a new segment instance appropriate for the - # format of the given string. - def segment_for(string) - segment = case string - when /\A:(\w+)/ - key = $1.to_sym - case key - when :controller then ControllerSegment.new(key) - else DynamicSegment.new key - end - when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) - when /\A\?(.*?)\?/ - returning segment = StaticSegment.new($1) do - segment.is_optional = true - end - when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) - when Regexp.new(separator_pattern) then - returning segment = DividerSegment.new($&) do - segment.is_optional = (optional_separators.include? $&) - end - end - [segment, $~.post_match] - end - - # Split the given hash of options into requirement and default hashes. The - # segments are passed alongside in order to distinguish between default values - # and requirements. - def divide_route_options(segments, options) - options = options.dup - - if options[:namespace] - options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}" - options.delete(:path_prefix) - options.delete(:name_prefix) - options.delete(:namespace) - end - - requirements = (options.delete(:requirements) || {}).dup - defaults = (options.delete(:defaults) || {}).dup - conditions = (options.delete(:conditions) || {}).dup - - path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact - options.each do |key, value| - hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements - hash[key] = value - end - - [defaults, requirements, conditions] - end - - # Takes a hash of defaults and a hash of requirements, and assigns them to - # the segments. Any unused requirements (which do not correspond to a segment) - # are returned as a hash. - def assign_route_options(segments, defaults, requirements) - route_requirements = {} # Requirements that do not belong to a segment - - segment_named = Proc.new do |key| - segments.detect { |segment| segment.key == key if segment.respond_to?(:key) } - end - - requirements.each do |key, requirement| - segment = segment_named[key] - if segment - raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp) - if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} - raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" - end - segment.regexp = requirement - else - route_requirements[key] = requirement - end - end - - defaults.each do |key, default| - segment = segment_named[key] - raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment - segment.is_optional = true - segment.default = default.to_param if default - end - - assign_default_route_options(segments) - ensure_required_segments(segments) - route_requirements - end - - # Assign default options, such as 'index' as a default for :action. This - # method must be run *after* user supplied requirements and defaults have - # been applied to the segments. - def assign_default_route_options(segments) - segments.each do |segment| - next unless segment.is_a? DynamicSegment - case segment.key - when :action - if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index' - segment.default ||= 'index' - segment.is_optional = true - end - when :id - if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ '' - segment.is_optional = true - end - end - end - end - - # Makes sure that there are no optional segments that precede a required - # segment. If any are found that precede a required segment, they are - # made required. - def ensure_required_segments(segments) - allow_optional = true - segments.reverse_each do |segment| - allow_optional &&= segment.optional? - if !allow_optional && segment.optional? - unless segment.optionality_implied? - warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required." - end - segment.is_optional = false - elsif allow_optional && segment.respond_to?(:default) && segment.default - # if a segment has a default, then it is optional - segment.is_optional = true - end - end - end - - # Construct and return a route with the given path and options. - def build(path, options) - # Wrap the path with slashes - path = "/#{path}" unless path[0] == ?/ - path = "#{path}/" unless path[-1] == ?/ - - path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix] - - segments = segments_for_route_path(path) - defaults, requirements, conditions = divide_route_options(segments, options) - requirements = assign_route_options(segments, defaults, requirements) - - route = Route.new - - route.segments = segments - route.requirements = requirements - route.conditions = conditions - - if !route.significant_keys.include?(:action) && !route.requirements[:action] - route.requirements[:action] = "index" - route.significant_keys << :action - end - - # Routes cannot use the current string interpolation method - # if there are user-supplied :requirements as the interpolation - # code won't raise RoutingErrors when generating - if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION - route.optimise = false - end - - if !route.significant_keys.include?(:controller) - raise ArgumentError, "Illegal route: the :controller must be specified!" - end - - route - end - end - - class RouteSet #:nodoc: - # Mapper instances are used to build routes. The object passed to the draw - # block in config/routes.rb is a Mapper instance. - # - # Mapper instances have relatively few instance methods, in order to avoid - # clashes with named routes. - class Mapper #:doc: - def initialize(set) #:nodoc: - @set = set - end - - # Create an unnamed route with the provided +path+ and +options+. See - # ActionController::Routing for an introduction to routes. - def connect(path, options = {}) - @set.add_route(path, options) - end - - # Creates a named route called "root" for matching the root level request. - def root(options = {}) - named_route("root", '', options) - end - - def named_route(name, path, options = {}) #:nodoc: - @set.add_named_route(name, path, options) - end - - # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. - # Example: - # - # map.namespace(:admin) do |admin| - # admin.resources :products, - # :has_many => [ :tags, :images, :variants ] - # end - # - # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. - # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for - # Admin::TagsController. - def namespace(name, options = {}, &block) - if options[:namespace] - with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) - else - with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block) - end - end - - def method_missing(route_name, *args, &proc) #:nodoc: - super unless args.length >= 1 && proc.nil? - @set.add_named_route(route_name, *args) - end - end - - # A NamedRouteCollection instance is a collection of named routes, and also - # maintains an anonymous module that can be used to install helpers for the - # named routes. - class NamedRouteCollection #:nodoc: - include Enumerable - include ActionController::Routing::Optimisation - attr_reader :routes, :helpers - - def initialize - clear! - end - - def clear! - @routes = {} - @helpers = [] - - @module ||= Module.new - @module.instance_methods.each do |selector| - @module.class_eval { remove_method selector } - end - end - - def add(name, route) - routes[name.to_sym] = route - define_named_route_methods(name, route) - end - - def get(name) - routes[name.to_sym] - end - - alias []= add - alias [] get - alias clear clear! - - def each - routes.each { |name, route| yield name, route } - self - end - - def names - routes.keys - end - - def length - routes.length - end - - def reset! - old_routes = routes.dup - clear! - old_routes.each do |name, route| - add(name, route) - end - end - - def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false) - reset! if regenerate - Array(destinations).each do |dest| - dest.send! :include, @module - end - end - - private - def url_helper_name(name, kind = :url) - :"#{name}_#{kind}" - end - - def hash_access_name(name, kind = :url) - :"hash_for_#{name}_#{kind}" - end - - def define_named_route_methods(name, route) - {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts| - hash = route.defaults.merge(:use_route => name).merge(opts) - define_hash_access route, name, kind, hash - define_url_helper route, name, kind, hash - end - end - - def define_hash_access(route, name, kind, options) - selector = hash_access_name(name, kind) - @module.module_eval <<-end_eval # We use module_eval to avoid leaks - def #{selector}(options = nil) - options ? #{options.inspect}.merge(options) : #{options.inspect} - end - protected :#{selector} - end_eval - helpers << selector - end - - def define_url_helper(route, name, kind, options) - selector = url_helper_name(name, kind) - # The segment keys used for positional paramters - - hash_access_method = hash_access_name(name, kind) - - # allow ordered parameters to be associated with corresponding - # dynamic segments, so you can do - # - # foo_url(bar, baz, bang) - # - # instead of - # - # foo_url(:bar => bar, :baz => baz, :bang => bang) - # - # Also allow options hash, so you can do - # - # foo_url(bar, baz, bang, :sort_by => 'baz') - # - @module.module_eval <<-end_eval # We use module_eval to avoid leaks - def #{selector}(*args) - #{generate_optimisation_block(route, kind)} - - opts = if args.empty? || Hash === args.first - args.first || {} - else - options = args.last.is_a?(Hash) ? args.pop : {} - args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| - h[k] = v - h - end - options.merge(args) - end - - url_for(#{hash_access_method}(opts)) - end - protected :#{selector} - end_eval - helpers << selector - end - end - - attr_accessor :routes, :named_routes - - def initialize - self.routes = [] - self.named_routes = NamedRouteCollection.new - end - - # Subclasses and plugins may override this method to specify a different - # RouteBuilder instance, so that other route DSL's can be created. - def builder - @builder ||= RouteBuilder.new - end - - def draw - clear! - yield Mapper.new(self) - install_helpers - end - - def clear! - routes.clear - named_routes.clear - @combined_regexp = nil - @routes_by_controller = nil - end - - def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) - Array(destinations).each { |d| d.module_eval { include Helpers } } - named_routes.install(destinations, regenerate_code) - end - - def empty? - routes.empty? - end - - def load! - Routing.use_controllers! nil # Clear the controller cache so we may discover new ones - clear! - load_routes! - install_helpers - end - - # reload! will always force a reload whereas load checks the timestamp first - alias reload! load! - - def reload - if @routes_last_modified && defined?(RAILS_ROOT) - mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime - # if it hasn't been changed, then just return - return if mtime == @routes_last_modified - # if it has changed then record the new time and fall to the load! below - @routes_last_modified = mtime - end - load! - end - - def load_routes! - if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes - load File.join("#{RAILS_ROOT}/config/routes.rb") - @routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime - else - add_route ":controller/:action/:id" - end - end - - def add_route(path, options = {}) - route = builder.build(path, options) - routes << route - route - end - - def add_named_route(name, path, options = {}) - # TODO - is options EVER used? - name = options[:name_prefix] + name.to_s if options[:name_prefix] - named_routes[name.to_sym] = add_route(path, options) - end - - def options_as_params(options) - # If an explicit :controller was given, always make :action explicit - # too, so that action expiry works as expected for things like - # - # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) - # - # (the above is from the unit tests). In the above case, because the - # controller was explicitly given, but no action, the action is implied to - # be "index", not the recalled action of "show". - # - # great fun, eh? - - options_as_params = options.clone - options_as_params[:action] ||= 'index' if options[:controller] - options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action] - options_as_params - end - - def build_expiry(options, recall) - recall.inject({}) do |expiry, (key, recalled_value)| - expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param) - expiry - end - end - - # Generate the path indicated by the arguments, and return an array of - # the keys that were not used to generate it. - def extra_keys(options, recall={}) - generate_extras(options, recall).last - end - - def generate_extras(options, recall={}) - generate(options, recall, :generate_extras) - end - - def generate(options, recall = {}, method=:generate) - named_route_name = options.delete(:use_route) - generate_all = options.delete(:generate_all) - if named_route_name - named_route = named_routes[named_route_name] - options = named_route.parameter_shell.merge(options) - end - - options = options_as_params(options) - expire_on = build_expiry(options, recall) - - if options[:controller] - options[:controller] = options[:controller].to_s - end - # if the controller has changed, make sure it changes relative to the - # current controller module, if any. In other words, if we're currently - # on admin/get, and the new controller is 'set', the new controller - # should really be admin/set. - if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/ - old_parts = recall[:controller].split('/') - new_parts = options[:controller].split('/') - parts = old_parts[0..-(new_parts.length + 1)] + new_parts - options[:controller] = parts.join('/') - end - - # drop the leading '/' on the controller name - options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/ - merged = recall.merge(options) - - if named_route - path = named_route.generate(options, merged, expire_on) - if path.nil? - raise_named_route_error(options, named_route, named_route_name) - else - return path - end - else - merged[:action] ||= 'index' - options[:action] ||= 'index' - - controller = merged[:controller] - action = merged[:action] - - raise RoutingError, "Need controller and action!" unless controller && action - - if generate_all - # Used by caching to expire all paths for a resource - return routes.collect do |route| - route.send!(method, options, merged, expire_on) - end.compact - end - - # don't use the recalled keys when determining which routes to check - routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }] - - routes.each do |route| - results = route.send!(method, options, merged, expire_on) - return results if results && (!results.is_a?(Array) || results.first) - end - end - - raise RoutingError, "No route matches #{options.inspect}" - end - - # try to give a helpful error message when named route generation fails - def raise_named_route_error(options, named_route, named_route_name) - diff = named_route.requirements.diff(options) - unless diff.empty? - raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}" - else - required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) } - required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment - raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?" - end - end - - def recognize(request) - params = recognize_path(request.path, extract_request_environment(request)) - request.path_parameters = params.with_indifferent_access - "#{params[:controller].camelize}Controller".constantize - end - - def recognize_path(path, environment={}) - routes.each do |route| - result = route.recognize(path, environment) and return result - end - - allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } } - - if environment[:method] && !HTTP_METHODS.include?(environment[:method]) - raise NotImplemented.new(*allows) - elsif !allows.empty? - raise MethodNotAllowed.new(*allows) - else - raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}" - end - end - - def routes_by_controller - @routes_by_controller ||= Hash.new do |controller_hash, controller| - controller_hash[controller] = Hash.new do |action_hash, action| - action_hash[action] = Hash.new do |key_hash, keys| - key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) - end - end - end - end - - def routes_for(options, merged, expire_on) - raise "Need controller and action!" unless controller && action - controller = merged[:controller] - merged = options if expire_on[:controller] - action = merged[:action] || 'index' - - routes_by_controller[controller][action][merged.keys] - end - - def routes_for_controller_and_action(controller, action) - selected = routes.select do |route| - route.matches_controller_and_action? controller, action - end - (selected.length == routes.length) ? routes : selected - end - - def routes_for_controller_and_action_and_keys(controller, action, keys) - selected = routes.select do |route| - route.matches_controller_and_action? controller, action - end - selected.sort_by do |route| - (keys - route.significant_keys).length - end - end - - # Subclasses and plugins may override this method to extract further attributes - # from the request, for use by route conditions and such. - def extract_request_environment(request) - { :method => request.method } - end - end + Routes = RouteSet.new ::Inflector.module_eval do + # Ensures that routes are reloaded when Rails inflections are updated. def inflections_with_route_reloading(&block) returning(inflections_without_route_reloading(&block)) { ActionController::Routing::Routes.reload! if block_given? diff --git a/vendor/rails/actionpack/lib/action_controller/routing/builder.rb b/vendor/rails/actionpack/lib/action_controller/routing/builder.rb new file mode 100644 index 00000000..4740113e --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/routing/builder.rb @@ -0,0 +1,204 @@ +module ActionController + module Routing + class RouteBuilder #:nodoc: + attr_accessor :separators, :optional_separators + + def initialize + self.separators = Routing::SEPARATORS + self.optional_separators = %w( / ) + end + + def separator_pattern(inverted = false) + "[#{'^' if inverted}#{Regexp.escape(separators.join)}]" + end + + def interval_regexp + Regexp.new "(.*?)(#{separators.source}|$)" + end + + def multiline_regexp?(expression) + expression.options & Regexp::MULTILINE == Regexp::MULTILINE + end + + # Accepts a "route path" (a string defining a route), and returns the array + # of segments that corresponds to it. Note that the segment array is only + # partially initialized--the defaults and requirements, for instance, need + # to be set separately, via the +assign_route_options+ method, and the + # optional? method for each segment will not be reliable until after + # +assign_route_options+ is called, as well. + def segments_for_route_path(path) + rest, segments = path, [] + + until rest.empty? + segment, rest = segment_for rest + segments << segment + end + segments + end + + # A factory method that returns a new segment instance appropriate for the + # format of the given string. + def segment_for(string) + segment = case string + when /\A:(\w+)/ + key = $1.to_sym + case key + when :controller then ControllerSegment.new(key) + else DynamicSegment.new key + end + when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) + when /\A\?(.*?)\?/ + returning segment = StaticSegment.new($1) do + segment.is_optional = true + end + when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) + when Regexp.new(separator_pattern) then + returning segment = DividerSegment.new($&) do + segment.is_optional = (optional_separators.include? $&) + end + end + [segment, $~.post_match] + end + + # Split the given hash of options into requirement and default hashes. The + # segments are passed alongside in order to distinguish between default values + # and requirements. + def divide_route_options(segments, options) + options = options.dup + + if options[:namespace] + options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}" + options.delete(:path_prefix) + options.delete(:name_prefix) + options.delete(:namespace) + end + + requirements = (options.delete(:requirements) || {}).dup + defaults = (options.delete(:defaults) || {}).dup + conditions = (options.delete(:conditions) || {}).dup + + path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact + options.each do |key, value| + hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements + hash[key] = value + end + + [defaults, requirements, conditions] + end + + # Takes a hash of defaults and a hash of requirements, and assigns them to + # the segments. Any unused requirements (which do not correspond to a segment) + # are returned as a hash. + def assign_route_options(segments, defaults, requirements) + route_requirements = {} # Requirements that do not belong to a segment + + segment_named = Proc.new do |key| + segments.detect { |segment| segment.key == key if segment.respond_to?(:key) } + end + + requirements.each do |key, requirement| + segment = segment_named[key] + if segment + raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp) + if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" + end + if multiline_regexp?(requirement) + raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" + end + segment.regexp = requirement + else + route_requirements[key] = requirement + end + end + + defaults.each do |key, default| + segment = segment_named[key] + raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment + segment.is_optional = true + segment.default = default.to_param if default + end + + assign_default_route_options(segments) + ensure_required_segments(segments) + route_requirements + end + + # Assign default options, such as 'index' as a default for :action. This + # method must be run *after* user supplied requirements and defaults have + # been applied to the segments. + def assign_default_route_options(segments) + segments.each do |segment| + next unless segment.is_a? DynamicSegment + case segment.key + when :action + if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index' + segment.default ||= 'index' + segment.is_optional = true + end + when :id + if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ '' + segment.is_optional = true + end + end + end + end + + # Makes sure that there are no optional segments that precede a required + # segment. If any are found that precede a required segment, they are + # made required. + def ensure_required_segments(segments) + allow_optional = true + segments.reverse_each do |segment| + allow_optional &&= segment.optional? + if !allow_optional && segment.optional? + unless segment.optionality_implied? + warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required." + end + segment.is_optional = false + elsif allow_optional && segment.respond_to?(:default) && segment.default + # if a segment has a default, then it is optional + segment.is_optional = true + end + end + end + + # Construct and return a route with the given path and options. + def build(path, options) + # Wrap the path with slashes + path = "/#{path}" unless path[0] == ?/ + path = "#{path}/" unless path[-1] == ?/ + + path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix] + + segments = segments_for_route_path(path) + defaults, requirements, conditions = divide_route_options(segments, options) + requirements = assign_route_options(segments, defaults, requirements) + + route = Route.new + + route.segments = segments + route.requirements = requirements + route.conditions = conditions + + if !route.significant_keys.include?(:action) && !route.requirements[:action] + route.requirements[:action] = "index" + route.significant_keys << :action + end + + # Routes cannot use the current string interpolation method + # if there are user-supplied :requirements as the interpolation + # code won't raise RoutingErrors when generating + if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION + route.optimise = false + end + + if !route.significant_keys.include?(:controller) + raise ArgumentError, "Illegal route: the :controller must be specified!" + end + + route + end + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/routing_optimisation.rb b/vendor/rails/actionpack/lib/action_controller/routing/optimisations.rb similarity index 73% rename from vendor/rails/actionpack/lib/action_controller/routing_optimisation.rb rename to vendor/rails/actionpack/lib/action_controller/routing/optimisations.rb index ba4aeb4e..cd4a423e 100644 --- a/vendor/rails/actionpack/lib/action_controller/routing_optimisation.rb +++ b/vendor/rails/actionpack/lib/action_controller/routing/optimisations.rb @@ -1,11 +1,11 @@ module ActionController module Routing # Much of the slow performance from routes comes from the - # complexity of expiry, :requirements matching, defaults providing + # complexity of expiry, :requirements matching, defaults providing # and figuring out which url pattern to use. With named routes # we can avoid the expense of finding the right route. So if # they've provided the right number of arguments, and have no - # :requirements, we can just build up a string and return it. + # :requirements, we can just build up a string and return it. # # To support building optimisations for other common cases, the # generation code is separated into several classes @@ -41,28 +41,29 @@ module ActionController end end - # Temporarily disabled :url optimisation pending proper solution to + # Temporarily disabled :url optimisation pending proper solution to # Issues around request.host etc. def applicable? true end end - # Given a route: - # map.person '/people/:id' + # Given a route # - # If the user calls person_url(@person), we can simply + # map.person '/people/:id' + # + # If the user calls person_url(@person), we can simply # return a string like "/people/#{@person.to_param}" - # rather than triggering the expensive logic in url_for + # rather than triggering the expensive logic in +url_for+. class PositionalArguments < Optimiser def guard_condition number_of_arguments = route.segment_keys.size # if they're using foo_url(:id=>2) it's one # argument, but we don't want to generate /foos/id2 if number_of_arguments == 1 - "defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)" + "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == 1 && !args.first.is_a?(Hash)" else - "defined?(request) && request && args.size == #{number_of_arguments}" + "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{number_of_arguments}" end end @@ -77,7 +78,7 @@ module ActionController elements << '#{request.relative_url_root if request.relative_url_root}' - # The last entry in route.segments appears to # *always* be a + # The last entry in route.segments appears to *always* be a # 'divider segment' for '/' but we have assertions to ensure that # we don't include the trailing slashes, so skip them. (route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment| @@ -97,7 +98,7 @@ module ActionController # argument class PositionalArgumentsWithAdditionalParams < PositionalArguments def guard_condition - "defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)" + "(!defined?(default_url_options) || default_url_options.blank?) && defined?(request) && request && args.size == #{route.segment_keys.size + 1} && !args.last.has_key?(:anchor) && !args.last.has_key?(:port) && !args.last.has_key?(:host)" end # This case uses almost the same code as positional arguments, @@ -106,7 +107,7 @@ module ActionController super.insert(-2, '?#{args.last.to_query}') end - # To avoid generating http://localhost/?host=foo.example.com we + # To avoid generating "http://localhost/?host=foo.example.com" we # can't use this optimisation on routes without any segments def applicable? super && route.segment_keys.size > 0 diff --git a/vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb new file mode 100644 index 00000000..cf8f5232 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/routing/recognition_optimisation.rb @@ -0,0 +1,158 @@ +module ActionController + module Routing + # BEFORE: 0.191446860631307 ms/url + # AFTER: 0.029847304022858 ms/url + # Speed up: 6.4 times + # + # Route recognition is slow due to one-by-one iterating over + # a whole routeset (each map.resources generates at least 14 routes) + # and matching weird regexps on each step. + # + # We optimize this by skipping all URI segments that 100% sure can't + # be matched, moving deeper in a tree of routes (where node == segment) + # until first possible match is accured. In such case, we start walking + # a flat list of routes, matching them with accurate matcher. + # So, first step: search a segment tree for the first relevant index. + # Second step: iterate routes starting with that index. + # + # How tree is walked? We can do a recursive tests, but it's smarter: + # We just create a tree of if-s and elsif-s matching segments. + # + # We have segments of 3 flavors: + # 1) nil (no segment, route finished) + # 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg") + # 3) const (like "/posts", "/comments") + # 4) dynamic ("/:id", "file.:size.:extension") + # + # We split incoming string into segments and iterate over them. + # When segment is nil, we drop immediately, on a current node index. + # When segment is equal to some const, we step into branch. + # If none constants matched, we step into 'dynamic' branch (it's a last). + # If we can't match anything, we drop to last index on a level. + # + # Note: we maintain the original routes order, so we finish building + # steps on a first dynamic segment. + # + # + # Example. Given the routes: + # 0 /posts/ + # 1 /posts/:id + # 2 /posts/:id/comments + # 3 /posts/blah + # 4 /users/ + # 5 /users/:id + # 6 /users/:id/profile + # + # request_uri = /users/123 + # + # There will be only 4 iterations: + # 1) segm test for /posts prefix, skip all /posts/* routes + # 2) segm test for /users/ + # 3) segm test for /users/:id + # (jump to list index = 5) + # 4) full test for /users/:id => here we are! + + class RouteSet + def recognize_path(path, environment={}) + result = recognize_optimized(path, environment) and return result + + # Route was not recognized. Try to find out why (maybe wrong verb). + allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } } + + if environment[:method] && !HTTP_METHODS.include?(environment[:method]) + raise NotImplemented.new(*allows) + elsif !allows.empty? + raise MethodNotAllowed.new(*allows) + else + raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}" + end + end + + def recognize_optimized(path, env) + write_recognize_optimized + recognize_optimized(path, env) + end + + def write_recognize_optimized + tree = segment_tree(routes) + body = generate_code(tree) + instance_eval %{ + def recognize_optimized(path, env) + segments = to_plain_segments(path) + index = #{body} + return nil unless index + while index < routes.size + result = routes[index].recognize(path, env) and return result + index += 1 + end + nil + end + }, __FILE__, __LINE__ + end + + def segment_tree(routes) + tree = [0] + + i = -1 + routes.each do |route| + i += 1 + # not fast, but runs only once + segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s }) + + node = tree + segments.each do |seg| + seg = :dynamic if seg && seg[0] == ?: + node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg + node = node[node.size - 1][1] + end + end + tree + end + + def generate_code(list, padding=' ', level = 0) + # a digit + return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0]) + + body = padding + "(seg = segments[#{level}]; \n" + + i = 0 + was_nil = false + list.each do |item| + if Array === item + i += 1 + start = (i == 1) + final = (i == list.size) + tag, sub = item + if tag == :dynamic + body += padding + "#{start ? 'if' : 'elsif'} true\n" + body += generate_code(sub, padding + " ", level + 1) + break + elsif tag == nil && !was_nil + was_nil = true + body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n" + body += generate_code(sub, padding + " ", level + 1) + else + body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n" + body += generate_code(sub, padding + " ", level + 1) + end + end + end + body += padding + "else\n" + body += padding + " #{list[0]}\n" + body += padding + "end)\n" + body + end + + # this must be really fast + def to_plain_segments(str) + str = str.dup + str.sub!(/^\/+/,'') + str.sub!(/\/+$/,'') + segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also + segments << nil + segments + end + + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/routing/route.rb b/vendor/rails/actionpack/lib/action_controller/routing/route.rb new file mode 100644 index 00000000..a0d108ba --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/routing/route.rb @@ -0,0 +1,240 @@ +module ActionController + module Routing + class Route #:nodoc: + attr_accessor :segments, :requirements, :conditions, :optimise + + def initialize + @segments = [] + @requirements = {} + @conditions = {} + @optimise = true + end + + # Indicates whether the routes should be optimised with the string interpolation + # version of the named routes methods. + def optimise? + @optimise && ActionController::Base::optimise_named_routes + end + + def segment_keys + segments.collect do |segment| + segment.key if segment.respond_to? :key + end.compact + end + + # Write and compile a +generate+ method for this Route. + def write_generation + # Build the main body of the generation + body = "expired = false\n#{generation_extraction}\n#{generation_structure}" + + # If we have conditions that must be tested first, nest the body inside an if + body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements + args = "options, hash, expire_on = {}" + + # Nest the body inside of a def block, and then compile it. + raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend" + instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" + + # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash + # are the same as the keys that were recalled from the previous request. Thus, + # we can use the expire_on.keys to determine which keys ought to be used to build + # the query string. (Never use keys from the recalled request when building the + # query string.) + + method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend" + instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" + + method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend" + instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" + raw_method + end + + # Build several lines of code that extract values from the options hash. If any + # of the values are missing or rejected then a return will be executed. + def generation_extraction + segments.collect do |segment| + segment.extraction_code + end.compact * "\n" + end + + # Produce a condition expression that will check the requirements of this route + # upon generation. + def generation_requirements + requirement_conditions = requirements.collect do |key, req| + if req.is_a? Regexp + value_regexp = Regexp.new "\\A#{req.to_s}\\Z" + "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]" + else + "hash[:#{key}] == #{req.inspect}" + end + end + requirement_conditions * ' && ' unless requirement_conditions.empty? + end + + def generation_structure + segments.last.string_structure segments[0..-2] + end + + # Write and compile a +recognize+ method for this Route. + def write_recognition + # Create an if structure to extract the params from a match if it occurs. + body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" + body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" + + # Build the method declaration and compile it + method_decl = "def recognize(path, env={})\n#{body}\nend" + instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" + method_decl + end + + # Plugins may override this method to add other conditions, like checks on + # host, subdomain, and so forth. Note that changes here only affect route + # recognition, not generation. + def recognition_conditions + result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] + result << "conditions[:method] === env[:method]" if conditions[:method] + result + end + + # Build the regular expression pattern that will match this route. + def recognition_pattern(wrap = true) + pattern = '' + segments.reverse_each do |segment| + pattern = segment.build_pattern pattern + end + wrap ? ("\\A" + pattern + "\\Z") : pattern + end + + # Write the code to extract the parameters from a matched route. + def recognition_extraction + next_capture = 1 + extraction = segments.collect do |segment| + x = segment.match_extraction(next_capture) + next_capture += Regexp.new(segment.regexp_chunk).number_of_captures + x + end + extraction.compact + end + + # Write the real generation implementation and then resend the message. + def generate(options, hash, expire_on = {}) + write_generation + generate options, hash, expire_on + end + + def generate_extras(options, hash, expire_on = {}) + write_generation + generate_extras options, hash, expire_on + end + + # Generate the query string with any extra keys in the hash and append + # it to the given path, returning the new path. + def append_query_string(path, hash, query_keys=nil) + return nil unless path + query_keys ||= extra_keys(hash) + "#{path}#{build_query_string(hash, query_keys)}" + end + + # Determine which keys in the given hash are "extra". Extra keys are + # those that were not used to generate a particular route. The extra + # keys also do not include those recalled from the prior request, nor + # do they include any keys that were implied in the route (like a + # :controller that is required, but not explicitly used in the + # text of the route.) + def extra_keys(hash, recall={}) + (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys + end + + # Build a query string from the keys of the given hash. If +only_keys+ + # is given (as an array), only the keys indicated will be used to build + # the query string. The query string will correctly build array parameter + # values. + def build_query_string(hash, only_keys = nil) + elements = [] + + (only_keys || hash.keys).each do |key| + if value = hash[key] + elements << value.to_query(key) + end + end + + elements.empty? ? '' : "?#{elements.sort * '&'}" + end + + # Write the real recognition implementation and then resend the message. + def recognize(path, environment={}) + write_recognition + recognize path, environment + end + + # A route's parameter shell contains parameter values that are not in the + # route's path, but should be placed in the recognized hash. + # + # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route: + # + # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ + # + def parameter_shell + @parameter_shell ||= returning({}) do |shell| + requirements.each do |key, requirement| + shell[key] = requirement unless requirement.is_a? Regexp + end + end + end + + # Return an array containing all the keys that are used in this route. This + # includes keys that appear inside the path, and keys that have requirements + # placed upon them. + def significant_keys + @significant_keys ||= returning [] do |sk| + segments.each { |segment| sk << segment.key if segment.respond_to? :key } + sk.concat requirements.keys + sk.uniq! + end + end + + # Return a hash of key/value pairs representing the keys in the route that + # have defaults, or which are specified by non-regexp requirements. + def defaults + @defaults ||= returning({}) do |hash| + segments.each do |segment| + next unless segment.respond_to? :default + hash[segment.key] = segment.default unless segment.default.nil? + end + requirements.each do |key,req| + next if Regexp === req || req.nil? + hash[key] = req + end + end + end + + def matches_controller_and_action?(controller, action) + unless defined? @matching_prepared + @controller_requirement = requirement_for(:controller) + @action_requirement = requirement_for(:action) + @matching_prepared = true + end + + (@controller_requirement.nil? || @controller_requirement === controller) && + (@action_requirement.nil? || @action_requirement === action) + end + + def to_s + @to_s ||= begin + segs = segments.inject("") { |str,s| str << s.to_s } + "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect] + end + end + + protected + def requirement_for(key) + return requirements[key] if requirements.key? key + segments.each do |segment| + return segment.regexp if segment.respond_to?(:key) && segment.key == key + end + nil + end + + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/routing/route_set.rb b/vendor/rails/actionpack/lib/action_controller/routing/route_set.rb new file mode 100644 index 00000000..5bc13cf2 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/routing/route_set.rb @@ -0,0 +1,435 @@ +module ActionController + module Routing + class RouteSet #:nodoc: + # Mapper instances are used to build routes. The object passed to the draw + # block in config/routes.rb is a Mapper instance. + # + # Mapper instances have relatively few instance methods, in order to avoid + # clashes with named routes. + class Mapper #:doc: + def initialize(set) #:nodoc: + @set = set + end + + # Create an unnamed route with the provided +path+ and +options+. See + # ActionController::Routing for an introduction to routes. + def connect(path, options = {}) + @set.add_route(path, options) + end + + # Creates a named route called "root" for matching the root level request. + def root(options = {}) + if options.is_a?(Symbol) + if source_route = @set.named_routes.routes[options] + options = source_route.defaults.merge({ :conditions => source_route.conditions }) + end + end + named_route("root", '', options) + end + + def named_route(name, path, options = {}) #:nodoc: + @set.add_named_route(name, path, options) + end + + # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. + # Example: + # + # map.namespace(:admin) do |admin| + # admin.resources :products, + # :has_many => [ :tags, :images, :variants ] + # end + # + # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. + # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for + # Admin::TagsController. + def namespace(name, options = {}, &block) + if options[:namespace] + with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) + else + with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block) + end + end + + def method_missing(route_name, *args, &proc) #:nodoc: + super unless args.length >= 1 && proc.nil? + @set.add_named_route(route_name, *args) + end + end + + # A NamedRouteCollection instance is a collection of named routes, and also + # maintains an anonymous module that can be used to install helpers for the + # named routes. + class NamedRouteCollection #:nodoc: + include Enumerable + include ActionController::Routing::Optimisation + attr_reader :routes, :helpers + + def initialize + clear! + end + + def clear! + @routes = {} + @helpers = [] + + @module ||= Module.new + @module.instance_methods.each do |selector| + @module.class_eval { remove_method selector } + end + end + + def add(name, route) + routes[name.to_sym] = route + define_named_route_methods(name, route) + end + + def get(name) + routes[name.to_sym] + end + + alias []= add + alias [] get + alias clear clear! + + def each + routes.each { |name, route| yield name, route } + self + end + + def names + routes.keys + end + + def length + routes.length + end + + def reset! + old_routes = routes.dup + clear! + old_routes.each do |name, route| + add(name, route) + end + end + + def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false) + reset! if regenerate + Array(destinations).each do |dest| + dest.send! :include, @module + end + end + + private + def url_helper_name(name, kind = :url) + :"#{name}_#{kind}" + end + + def hash_access_name(name, kind = :url) + :"hash_for_#{name}_#{kind}" + end + + def define_named_route_methods(name, route) + {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts| + hash = route.defaults.merge(:use_route => name).merge(opts) + define_hash_access route, name, kind, hash + define_url_helper route, name, kind, hash + end + end + + def define_hash_access(route, name, kind, options) + selector = hash_access_name(name, kind) + @module.module_eval <<-end_eval # We use module_eval to avoid leaks + def #{selector}(options = nil) + options ? #{options.inspect}.merge(options) : #{options.inspect} + end + protected :#{selector} + end_eval + helpers << selector + end + + def define_url_helper(route, name, kind, options) + selector = url_helper_name(name, kind) + # The segment keys used for positional paramters + + hash_access_method = hash_access_name(name, kind) + + # allow ordered parameters to be associated with corresponding + # dynamic segments, so you can do + # + # foo_url(bar, baz, bang) + # + # instead of + # + # foo_url(:bar => bar, :baz => baz, :bang => bang) + # + # Also allow options hash, so you can do + # + # foo_url(bar, baz, bang, :sort_by => 'baz') + # + @module.module_eval <<-end_eval # We use module_eval to avoid leaks + def #{selector}(*args) + #{generate_optimisation_block(route, kind)} + + opts = if args.empty? || Hash === args.first + args.first || {} + else + options = args.extract_options! + args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| + h[k] = v + h + end + options.merge(args) + end + + url_for(#{hash_access_method}(opts)) + end + protected :#{selector} + end_eval + helpers << selector + end + end + + attr_accessor :routes, :named_routes, :configuration_file + + def initialize + self.routes = [] + self.named_routes = NamedRouteCollection.new + end + + # Subclasses and plugins may override this method to specify a different + # RouteBuilder instance, so that other route DSL's can be created. + def builder + @builder ||= RouteBuilder.new + end + + def draw + clear! + yield Mapper.new(self) + install_helpers + end + + def clear! + routes.clear + named_routes.clear + @combined_regexp = nil + @routes_by_controller = nil + # This will force routing/recognition_optimization.rb + # to refresh optimisations. + @compiled_recognize_optimized = nil + end + + def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) + Array(destinations).each { |d| d.module_eval { include Helpers } } + named_routes.install(destinations, regenerate_code) + end + + def empty? + routes.empty? + end + + def load! + Routing.use_controllers! nil # Clear the controller cache so we may discover new ones + clear! + load_routes! + install_helpers + end + + # reload! will always force a reload whereas load checks the timestamp first + alias reload! load! + + def reload + if @routes_last_modified && configuration_file + mtime = File.stat(configuration_file).mtime + # if it hasn't been changed, then just return + return if mtime == @routes_last_modified + # if it has changed then record the new time and fall to the load! below + @routes_last_modified = mtime + end + load! + end + + def load_routes! + if configuration_file + load configuration_file + @routes_last_modified = File.stat(configuration_file).mtime + else + add_route ":controller/:action/:id" + end + end + + def add_route(path, options = {}) + route = builder.build(path, options) + routes << route + route + end + + def add_named_route(name, path, options = {}) + # TODO - is options EVER used? + name = options[:name_prefix] + name.to_s if options[:name_prefix] + named_routes[name.to_sym] = add_route(path, options) + end + + def options_as_params(options) + # If an explicit :controller was given, always make :action explicit + # too, so that action expiry works as expected for things like + # + # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) + # + # (the above is from the unit tests). In the above case, because the + # controller was explicitly given, but no action, the action is implied to + # be "index", not the recalled action of "show". + # + # great fun, eh? + + options_as_params = options.clone + options_as_params[:action] ||= 'index' if options[:controller] + options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action] + options_as_params + end + + def build_expiry(options, recall) + recall.inject({}) do |expiry, (key, recalled_value)| + expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param) + expiry + end + end + + # Generate the path indicated by the arguments, and return an array of + # the keys that were not used to generate it. + def extra_keys(options, recall={}) + generate_extras(options, recall).last + end + + def generate_extras(options, recall={}) + generate(options, recall, :generate_extras) + end + + def generate(options, recall = {}, method=:generate) + named_route_name = options.delete(:use_route) + generate_all = options.delete(:generate_all) + if named_route_name + named_route = named_routes[named_route_name] + options = named_route.parameter_shell.merge(options) + end + + options = options_as_params(options) + expire_on = build_expiry(options, recall) + + if options[:controller] + options[:controller] = options[:controller].to_s + end + # if the controller has changed, make sure it changes relative to the + # current controller module, if any. In other words, if we're currently + # on admin/get, and the new controller is 'set', the new controller + # should really be admin/set. + if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/ + old_parts = recall[:controller].split('/') + new_parts = options[:controller].split('/') + parts = old_parts[0..-(new_parts.length + 1)] + new_parts + options[:controller] = parts.join('/') + end + + # drop the leading '/' on the controller name + options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/ + merged = recall.merge(options) + + if named_route + path = named_route.generate(options, merged, expire_on) + if path.nil? + raise_named_route_error(options, named_route, named_route_name) + else + return path + end + else + merged[:action] ||= 'index' + options[:action] ||= 'index' + + controller = merged[:controller] + action = merged[:action] + + raise RoutingError, "Need controller and action!" unless controller && action + + if generate_all + # Used by caching to expire all paths for a resource + return routes.collect do |route| + route.send!(method, options, merged, expire_on) + end.compact + end + + # don't use the recalled keys when determining which routes to check + routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }] + + routes.each do |route| + results = route.send!(method, options, merged, expire_on) + return results if results && (!results.is_a?(Array) || results.first) + end + end + + raise RoutingError, "No route matches #{options.inspect}" + end + + # try to give a helpful error message when named route generation fails + def raise_named_route_error(options, named_route, named_route_name) + diff = named_route.requirements.diff(options) + unless diff.empty? + raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}" + else + required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) } + required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment + raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?" + end + end + + def recognize(request) + params = recognize_path(request.path, extract_request_environment(request)) + request.path_parameters = params.with_indifferent_access + "#{params[:controller].camelize}Controller".constantize + end + + def recognize_path(path, environment={}) + raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path." + end + + def routes_by_controller + @routes_by_controller ||= Hash.new do |controller_hash, controller| + controller_hash[controller] = Hash.new do |action_hash, action| + action_hash[action] = Hash.new do |key_hash, keys| + key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) + end + end + end + end + + def routes_for(options, merged, expire_on) + raise "Need controller and action!" unless controller && action + controller = merged[:controller] + merged = options if expire_on[:controller] + action = merged[:action] || 'index' + + routes_by_controller[controller][action][merged.keys] + end + + def routes_for_controller_and_action(controller, action) + selected = routes.select do |route| + route.matches_controller_and_action? controller, action + end + (selected.length == routes.length) ? routes : selected + end + + def routes_for_controller_and_action_and_keys(controller, action, keys) + selected = routes.select do |route| + route.matches_controller_and_action? controller, action + end + selected.sort_by do |route| + (keys - route.significant_keys).length + end + end + + # Subclasses and plugins may override this method to extract further attributes + # from the request, for use by route conditions and such. + def extract_request_environment(request) + { :method => request.method } + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller/routing/routing_ext.rb b/vendor/rails/actionpack/lib/action_controller/routing/routing_ext.rb new file mode 100644 index 00000000..2ad20ee6 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/routing/routing_ext.rb @@ -0,0 +1,46 @@ + +class Object + def to_param + to_s + end +end + +class TrueClass + def to_param + self + end +end + +class FalseClass + def to_param + self + end +end + +class NilClass + def to_param + self + end +end + +class Regexp #:nodoc: + def number_of_captures + Regexp.new("|#{source}").match('').captures.length + end + + class << self + def optionalize(pattern) + case unoptionalize(pattern) + when /\A(.|\(.*\))\Z/ then "#{pattern}?" + else "(?:#{pattern})?" + end + end + + def unoptionalize(pattern) + [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp| + return $1 if regexp =~ pattern + end + return pattern + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/routing/segments.rb b/vendor/rails/actionpack/lib/action_controller/routing/segments.rb new file mode 100644 index 00000000..864e0680 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_controller/routing/segments.rb @@ -0,0 +1,283 @@ +module ActionController + module Routing + class Segment #:nodoc: + RESERVED_PCHAR = ':@&=+$,;' + UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze + + attr_accessor :is_optional + alias_method :optional?, :is_optional + + def initialize + self.is_optional = false + end + + def extraction_code + nil + end + + # Continue generating string for the prior segments. + def continue_string_structure(prior_segments) + if prior_segments.empty? + interpolation_statement(prior_segments) + else + new_priors = prior_segments[0..-2] + prior_segments.last.string_structure(new_priors) + end + end + + def interpolation_chunk + URI.escape(value, UNSAFE_PCHAR) + end + + # Return a string interpolation statement for this segment and those before it. + def interpolation_statement(prior_segments) + chunks = prior_segments.collect { |s| s.interpolation_chunk } + chunks << interpolation_chunk + "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}" + end + + def string_structure(prior_segments) + optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments) + end + + # Return an if condition that is true if all the prior segments can be generated. + # If there are no optional segments before this one, then nil is returned. + def all_optionals_available_condition(prior_segments) + optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact + optional_locals.empty? ? nil : " if #{optional_locals * ' && '}" + end + + # Recognition + + def match_extraction(next_capture) + nil + end + + # Warning + + # Returns true if this segment is optional? because of a default. If so, then + # no warning will be emitted regarding this segment. + def optionality_implied? + false + end + end + + class StaticSegment < Segment #:nodoc: + attr_accessor :value, :raw + alias_method :raw?, :raw + + def initialize(value = nil) + super() + self.value = value + end + + def interpolation_chunk + raw? ? value : super + end + + def regexp_chunk + chunk = Regexp.escape(value) + optional? ? Regexp.optionalize(chunk) : chunk + end + + def build_pattern(pattern) + escaped = Regexp.escape(value) + if optional? && ! pattern.empty? + "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" + elsif optional? + Regexp.optionalize escaped + else + escaped + pattern + end + end + + def to_s + value + end + end + + class DividerSegment < StaticSegment #:nodoc: + def initialize(value = nil) + super(value) + self.raw = true + self.is_optional = true + end + + def optionality_implied? + true + end + end + + class DynamicSegment < Segment #:nodoc: + attr_accessor :key, :default, :regexp + + def initialize(key = nil, options = {}) + super() + self.key = key + self.default = options[:default] if options.key? :default + self.is_optional = true if options[:optional] || options.key?(:default) + end + + def to_s + ":#{key}" + end + + # The local variable name that the value of this segment will be extracted to. + def local_name + "#{key}_value" + end + + def extract_value + "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}" + end + def value_check + if default # Then we know it won't be nil + "#{value_regexp.inspect} =~ #{local_name}" if regexp + elsif optional? + # If we have a regexp check that the value is not given, or that it matches. + # If we have no regexp, return nil since we do not require a condition. + "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp + else # Then it must be present, and if we have a regexp, it must match too. + "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" + end + end + def expiry_statement + "expired, hash = true, options if !expired && expire_on[:#{key}]" + end + + def extraction_code + s = extract_value + vc = value_check + s << "\nreturn [nil,nil] unless #{vc}" if vc + s << "\n#{expiry_statement}" + end + + def interpolation_chunk(value_code = "#{local_name}") + "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}" + end + + def string_structure(prior_segments) + if optional? # We have a conditional to do... + # If we should not appear in the url, just write the code for the prior + # segments. This occurs if our value is the default value, or, if we are + # optional, if we have nil as our value. + "if #{local_name} == #{default.inspect}\n" + + continue_string_structure(prior_segments) + + "\nelse\n" + # Otherwise, write the code up to here + "#{interpolation_statement(prior_segments)}\nend" + else + interpolation_statement(prior_segments) + end + end + + def value_regexp + Regexp.new "\\A#{regexp.to_s}\\Z" if regexp + end + + def regexp_chunk + if regexp + if regexp_has_modifiers? + "(#{regexp.to_s})" + else + "(#{regexp.source})" + end + else + "([^#{Routing::SEPARATORS.join}]+)" + end + end + + def build_pattern(pattern) + chunk = regexp_chunk + chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 + pattern = "#{chunk}#{pattern}" + optional? ? Regexp.optionalize(pattern) : pattern + end + + def match_extraction(next_capture) + # All non code-related keys (such as :id, :slug) are URI-unescaped as + # path parameters. + default_value = default ? default.inspect : nil + %[ + value = if (m = match[#{next_capture}]) + URI.unescape(m) + else + #{default_value} + end + params[:#{key}] = value if value + ] + end + + def optionality_implied? + [:action, :id].include? key + end + + def regexp_has_modifiers? + regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 + end + + end + + class ControllerSegment < DynamicSegment #:nodoc: + def regexp_chunk + possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } + "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" + end + + # Don't URI.escape the controller name since it may contain slashes. + def interpolation_chunk(value_code = "#{local_name}") + "\#{#{value_code}.to_s}" + end + + # Make sure controller names like Admin/Content are correctly normalized to + # admin/content + def extract_value + "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase" + end + + def match_extraction(next_capture) + if default + "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'" + else + "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]" + end + end + end + + class PathSegment < DynamicSegment #:nodoc: + def interpolation_chunk(value_code = "#{local_name}") + "\#{#{value_code}}" + end + + def extract_value + "#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}" + end + + def default + '' + end + + def default=(path) + raise RoutingError, "paths cannot have non-empty default values" unless path.blank? + end + + def match_extraction(next_capture) + "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}" + end + + def regexp_chunk + regexp || "(.*)" + end + + def optionality_implied? + true + end + + class Result < ::Array #:nodoc: + def to_s() join '/' end + def self.new_escaped(strings) + new strings.collect {|str| URI.unescape str} + end + end + end + end +end diff --git a/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb b/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb index 14747c50..1e8eb57a 100644 --- a/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb +++ b/vendor/rails/actionpack/lib/action_controller/session/active_record_store.rb @@ -1,7 +1,6 @@ require 'cgi' require 'cgi/session' require 'digest/md5' -require 'base64' class CGI class Session @@ -14,7 +13,7 @@ class CGI # A session store backed by an Active Record class. A default class is - # provided, but any object duck-typing to an Active Record +Session+ class + # provided, but any object duck-typing to an Active Record Session class # with text +session_id+ and +data+ attributes is sufficient. # # The default assumes a +sessions+ tables with columns: @@ -27,13 +26,13 @@ class CGI # ActionController::SessionOverflowError will be raised. # # You may configure the table name, primary key, and data column. - # For example, at the end of config/environment.rb: + # For example, at the end of config/environment.rb: # CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table' # CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id' # CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data' - # Note that setting the primary key to the session_id frees you from - # having a separate id column if you don't want it. However, you must - # set session.model.id = session.session_id by hand! A before_filter + # Note that setting the primary key to the +session_id+ frees you from + # having a separate +id+ column if you don't want it. However, you must + # set session.model.id = session.session_id by hand! A before filter # on ApplicationController is a good place. # # Since the default class is a simple Active Record, you get timestamps @@ -43,7 +42,7 @@ class CGI # You may provide your own session class implementation, whether a # feature-packed Active Record or a bare-metal high-performance SQL # store, by setting - # +CGI::Session::ActiveRecordStore.session_class = MySessionClass+ + # CGI::Session::ActiveRecordStore.session_class = MySessionClass # You must implement these methods: # self.find_by_session_id(session_id) # initialize(hash_of_session_id_and_data) @@ -80,8 +79,8 @@ class CGI find_by_session_id(session_id) end - def marshal(data) Base64.encode64(Marshal.dump(data)) if data end - def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end + def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end + def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end def create_table! connection.execute <<-end_sql @@ -155,8 +154,13 @@ class CGI # The database connection, table name, and session id and data columns # are configurable class attributes. Marshaling and unmarshaling # are implemented as class methods that you may override. By default, - # marshaling data is +Base64.encode64(Marshal.dump(data))+ and - # unmarshaling data is +Marshal.load(Base64.decode64(data))+. + # marshaling data is + # + # ActiveSupport::Base64.encode64(Marshal.dump(data)) + # + # and unmarshaling data is + # + # Marshal.load(ActiveSupport::Base64.decode64(data)) # # This marshaling behavior is intended to store the widest range of # binary session data in a +text+ column. For higher performance, @@ -190,8 +194,8 @@ class CGI end end - def marshal(data) Base64.encode64(Marshal.dump(data)) if data end - def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end + def marshal(data) ActiveSupport::Base64.encode64(Marshal.dump(data)) if data end + def unmarshal(data) Marshal.load(ActiveSupport::Base64.decode64(data)) if data end def create_table! @@connection.execute <<-end_sql @@ -333,4 +337,4 @@ class CGI end end end -end \ No newline at end of file +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 086f5a87..b477c1f7 100644 --- a/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb +++ b/vendor/rails/actionpack/lib/action_controller/session/cookie_store.rb @@ -1,6 +1,5 @@ require 'cgi' require 'cgi/session' -require 'base64' # to convert Marshal.dump to ASCII require 'openssl' # to generate the HMAC message digest # This cookie-based session store is the Rails default. Sessions typically @@ -15,27 +14,27 @@ require 'openssl' # to generate the HMAC message digest # TamperedWithCookie is raised if the data integrity check fails. # # A message digest is included with the cookie to ensure data integrity: -# a user cannot alter his user_id without knowing the secret key included in +# a user cannot alter his +user_id+ without knowing the secret key included in # the hash. New apps are generated with a pregenerated secret in # config/environment.rb. Set your own for old apps you're upgrading. # # Session options: -# :secret An application-wide key string or block returning a string -# called per generated digest. The block is called with the -# CGI::Session instance as an argument. It's important that the -# secret is not vulnerable to a dictionary attack. Therefore, -# you should choose a secret consisting of random numbers and -# letters and more than 30 characters. # -# Example: :secret => '449fe2e7daee471bffae2fd8dc02313d' -# :secret => Proc.new { User.current_user.secret_key } +# * :secret: An application-wide key string or block returning a string +# called per generated digest. The block is called with the CGI::Session +# instance as an argument. It's important that the secret is not vulnerable to +# a dictionary attack. Therefore, you should choose a secret consisting of +# random numbers and letters and more than 30 characters. Examples: # -# :digest The message digest algorithm used to verify session integrity -# defaults to 'SHA1' but may be any digest provided by OpenSSL, -# such as 'MD5', 'RIPEMD160', 'SHA256', etc. +# :secret => '449fe2e7daee471bffae2fd8dc02313d' +# :secret => Proc.new { User.current_user.secret_key } +# +# * :digest: The message digest algorithm used to verify session +# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL, +# such as 'MD5', 'RIPEMD160', 'SHA256', etc. # # To generate a secret key for an existing application, run -# `rake secret` and set the key in config/environment.rb +# "rake secret" and set the key in config/environment.rb. # # Note that changing digest or secret invalidates all existing sessions! class CGI::Session::CookieStore @@ -118,7 +117,7 @@ class CGI::Session::CookieStore def delete @data = nil clear_old_cookie_value - write_cookie('value' => '', 'expires' => 1.year.ago) + write_cookie('value' => nil, 'expires' => 1.year.ago) end # Generate the HMAC keyed message digest. Uses SHA1 by default. @@ -130,19 +129,22 @@ class CGI::Session::CookieStore private # Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) - data = Base64.encode64(Marshal.dump(session)).chop - CGI.escape "#{data}--#{generate_digest(data)}" + data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop + "#{data}--#{generate_digest(data)}" end # Unmarshal cookie data to a hash and verify its integrity. def unmarshal(cookie) if cookie - data, digest = CGI.unescape(cookie).split('--') - unless digest == generate_digest(data) + data, digest = cookie.split('--') + + # Do two checks to transparently support old double-escaped data. + unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data)) delete raise TamperedWithCookie end - Marshal.load(Base64.decode64(data)) + + Marshal.load(ActiveSupport::Base64.decode64(data)) end end diff --git a/vendor/rails/actionpack/lib/action_controller/session_management.rb b/vendor/rails/actionpack/lib/action_controller/session_management.rb index fabb6e7f..80a3ddd2 100644 --- a/vendor/rails/actionpack/lib/action_controller/session_management.rb +++ b/vendor/rails/actionpack/lib/action_controller/session_management.rb @@ -16,9 +16,11 @@ module ActionController #:nodoc: end module ClassMethods - # Set the session store to be used for keeping the session data between requests. By default, sessions are stored - # in browser cookies (:cookie_store), but you can also specify one of the other included stores - # (:active_record_store, :p_store, drb_store, :mem_cache_store, or :memory_store) or your own custom class. + # Set the session store to be used for keeping the session data between requests. + # By default, sessions are stored in browser cookies (:cookie_store), + # but you can also specify one of the other included stores (:active_record_store, + # :p_store, :drb_store, :mem_cache_store, or + # :memory_store) or your own custom class. def session_store=(store) ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] = store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store @@ -67,11 +69,16 @@ module ActionController #:nodoc: # session :off, # :if => Proc.new { |req| !(req.format.html? || req.format.js?) } # + # # turn the session back on, useful when it was turned off in the + # # application controller, and you need it on in another controller + # session :on + # # All session options described for ActionController::Base.process_cgi # are valid arguments. def session(*args) options = args.extract_options! + options[:disabled] = false if args.delete(:on) options[:disabled] = true if !args.empty? options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only] options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except] diff --git a/vendor/rails/actionpack/lib/action_controller/streaming.rb b/vendor/rails/actionpack/lib/action_controller/streaming.rb index 42fe4298..186e0e55 100644 --- a/vendor/rails/actionpack/lib/action_controller/streaming.rb +++ b/vendor/rails/actionpack/lib/action_controller/streaming.rb @@ -4,34 +4,37 @@ module ActionController #:nodoc: DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, :disposition => 'attachment'.freeze, - :stream => true, - :buffer_size => 4096 + :stream => true, + :buffer_size => 4096, + :x_sendfile => false }.freeze + X_SENDFILE_HEADER = 'X-Sendfile'.freeze + protected # Sends the file by streaming it 4096 bytes at a time. This way the # whole file doesn't need to be read into memory at once. This makes # it feasible to send even large files. # # Be careful to sanitize the path parameter if it coming from a web - # page. send_file(params[:path]) allows a malicious user to + # page. send_file(params[:path]) allows a malicious user to # download any file on your server. # # Options: # * :filename - suggests a filename for the browser to use. - # Defaults to File.basename(path). + # Defaults to File.basename(path). # * :type - specifies an HTTP content type. # Defaults to 'application/octet-stream'. - # * :disposition - specifies whether the file will be shown inline or downloaded. + # * :disposition - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). - # * :stream - whether to send the file to the user agent as it is read (true) - # or to read the entire file before sending (false). Defaults to true. + # * :stream - whether to send the file to the user agent as it is read (+true+) + # or to read the entire file before sending (+false+). Defaults to +true+. # * :buffer_size - specifies size (in bytes) of the buffer used to stream the file. # Defaults to 4096. # * :status - specifies the status code to send with the response. Defaults to '200 OK'. - # * :url_based_filename - set to true if you want the browser guess the filename from - # the URL, which is necessary for i18n filenames on certain browsers - # (setting :filename overrides this option). + # * :url_based_filename - set to +true+ if you want the browser guess the filename from + # the URL, which is necessary for i18n filenames on certain browsers + # (setting :filename overrides this option). # # The default Content-Type and Content-Disposition headers are # set to download arbitrary binary files in as many browsers as @@ -39,17 +42,20 @@ module ActionController #:nodoc: # a variety of quirks (especially when downloading over SSL). # # Simple download: + # # send_file '/path/to.zip' # # Show a JPEG in the browser: + # # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' # # Show a 404 page in the browser: + # # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404 # # Read about the other Content-* HTTP headers if you'd like to - # provide the user with more information (such as Content-Description). - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 + # provide the user with more information (such as Content-Description) in + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. # # Also be aware that the document may be cached by proxies and browsers. # The Pragma and Cache-Control headers declare how the file may be cached @@ -67,19 +73,24 @@ module ActionController #:nodoc: @performed_render = false - if options[:stream] - render :status => options[:status], :text => Proc.new { |response, output| - logger.info "Streaming file #{path}" unless logger.nil? - len = options[:buffer_size] || 4096 - File.open(path, 'rb') do |file| - while buf = file.read(len) - output.write(buf) - end - end - } + if options[:x_sendfile] + logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger + head options[:status], X_SENDFILE_HEADER => path else - logger.info "Sending file #{path}" unless logger.nil? - File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } + if options[:stream] + render :status => options[:status], :text => Proc.new { |response, output| + logger.info "Streaming file #{path}" unless logger.nil? + len = options[:buffer_size] || 4096 + File.open(path, 'rb') do |file| + while buf = file.read(len) + output.write(buf) + end + end + } + else + logger.info "Sending file #{path}" unless logger.nil? + File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } + end end end @@ -87,25 +98,28 @@ module ActionController #:nodoc: # and specify whether to show data inline or download as an attachment. # # Options: - # * :filename - Suggests a filename for the browser to use. + # * :filename - suggests a filename for the browser to use. # * :type - specifies an HTTP content type. # Defaults to 'application/octet-stream'. - # * :disposition - specifies whether the file will be shown inline or downloaded. + # * :disposition - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * :status - specifies the status code to send with the response. Defaults to '200 OK'. # # Generic data download: + # # send_data buffer # # Download a dynamically-generated tarball: + # # send_data generate_tgz('dir'), :filename => 'dir.tgz' # # Display an image Active Record in the browser: + # # send_data image.data, :type => image.content_type, :disposition => 'inline' # # See +send_file+ for more information on HTTP Content-* headers and caching. def send_data(data, options = {}) #:doc: - logger.info "Sending data #{options[:filename]}" unless logger.nil? + logger.info "Sending data #{options[:filename]}" if logger send_file_headers! options.merge(:length => data.size) @performed_render = false render :status => options[:status], :text => data @@ -130,10 +144,10 @@ module ActionController #:nodoc: ) # Fix a problem with IE 6.0 on opening downloaded files: - # If Cache-Control: no-cache is set (which Rails does by default), - # IE removes the file it just downloaded from its cache immediately - # after it displays the "open/save" dialog, which means that if you - # hit "open" the file isn't there anymore when the application that + # If Cache-Control: no-cache is set (which Rails does by default), + # IE removes the file it just downloaded from its cache immediately + # after it displays the "open/save" dialog, which means that if you + # hit "open" the file isn't there anymore when the application that # is called for handling the download is run, so let's workaround that headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache' end diff --git a/vendor/rails/actionpack/lib/action_controller/templates/rescues/_trace.erb b/vendor/rails/actionpack/lib/action_controller/templates/rescues/_trace.erb index b322b0aa..bb2d8375 100644 --- a/vendor/rails/actionpack/lib/action_controller/templates/rescues/_trace.erb +++ b/vendor/rails/actionpack/lib/action_controller/templates/rescues/_trace.erb @@ -10,17 +10,17 @@

    RAILS_ROOT: <%= defined?(RAILS_ROOT) ? RAILS_ROOT : "unset" %>

    - <% names.each do |name| -%> + <% names.each do |name| %> <% show = "document.getElementById('#{name.gsub /\s/, '-'}').style.display='block';" hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub /\s/, '-'}').style.display='none';"} %> <%= name %> <%= '|' unless names.last == name %> - <% end -%> + <% end %> - <% traces.each do |name, trace| -%> + <% traces.each do |name, trace| %>
    ;">
    <%= trace.join "\n" %>
    - <% end -%> -
    \ No newline at end of file + <% end %> + diff --git a/vendor/rails/actionpack/lib/action_controller/test_case.rb b/vendor/rails/actionpack/lib/action_controller/test_case.rb index d81cb5da..77c6f26e 100644 --- a/vendor/rails/actionpack/lib/action_controller/test_case.rb +++ b/vendor/rails/actionpack/lib/action_controller/test_case.rb @@ -3,14 +3,43 @@ require 'active_support/test_case' module ActionController class NonInferrableControllerError < ActionControllerError def initialize(name) + @name = name super "Unable to determine the controller to test from #{name}. " + "You'll need to specify it using 'tests YourController' in your " + - "test case definition" + "test case definition. This could mean that #{inferred_controller_name} does not exist " + + "or it contains syntax errors" + end + + def inferred_controller_name + @name.sub(/Test$/, '') end end class TestCase < ActiveSupport::TestCase + # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline + # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular + # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else + # than 0.0.0.0. + # + # The exception is stored in the exception accessor for further inspection. + module RaiseActionExceptions + attr_accessor :exception + + def rescue_action(e) + self.exception = e + + if request.remote_addr == "0.0.0.0" + raise(e) + else + super(e) + end + end + end + + setup :setup_controller_request_and_response + @@controller_class = nil + class << self def tests(controller_class) self.controller_class = controller_class @@ -25,7 +54,7 @@ module ActionController if current_controller_class = read_inheritable_attribute(:controller_class) current_controller_class else - self.controller_class= determine_default_controller_class(name) + self.controller_class = determine_default_controller_class(name) end end @@ -36,18 +65,19 @@ module ActionController end def prepare_controller_class(new_class) - new_class.class_eval do - def rescue_action(e) - raise e - end - end + new_class.send :include, RaiseActionExceptions end end - def setup + def setup_controller_request_and_response @controller = self.class.controller_class.new - @request = TestRequest.new - @response = TestResponse.new + @controller.request = @request = TestRequest.new + @response = TestResponse.new end - end + + # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local + def rescue_action_in_public! + @request.remote_addr = '208.77.188.166' # example.com + end + end end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_controller/test_process.rb b/vendor/rails/actionpack/lib/action_controller/test_process.rb index 88533115..0cf14321 100644 --- a/vendor/rails/actionpack/lib/action_controller/test_process.rb +++ b/vendor/rails/actionpack/lib/action_controller/test_process.rb @@ -1,8 +1,9 @@ require 'action_controller/assertions' +require 'action_controller/test_case' module ActionController #:nodoc: class Base - # Process a test request called with a +TestRequest+ object. + # Process a test request called with a TestRequest object. def self.process_test(request) new.process_test(request) end @@ -48,7 +49,7 @@ module ActionController #:nodoc: # Either the RAW_POST_DATA environment variable or the URL-encoded request # parameters. def raw_post - env['RAW_POST_DATA'] ||= url_encoded_request_parameters + env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) } end def port=(number) @@ -154,12 +155,12 @@ module ActionController #:nodoc: # A refactoring of TestResponse to allow the same behavior to be applied # to the "real" CgiResponse class in integration tests. module TestResponseBehavior #:nodoc: - # the response code of the request + # The response code of the request def response_code headers['Status'][0,3].to_i rescue 0 end - # returns a String to ensure compatibility with Net::HTTPResponse + # Returns a String to ensure compatibility with Net::HTTPResponse def code headers['Status'].to_s.split(' ')[0] end @@ -168,34 +169,34 @@ module ActionController #:nodoc: headers['Status'].to_s.split(' ',2)[1] end - # was the response successful? + # Was the response successful? def success? response_code == 200 end - # was the URL not found? + # Was the URL not found? def missing? response_code == 404 end - # were we redirected? + # Were we redirected? def redirect? (300..399).include?(response_code) end - # was there a server-side error? + # Was there a server-side error? def error? (500..599).include?(response_code) end alias_method :server_error?, :error? - # returns the redirection location or nil + # Returns the redirection location or nil def redirect_url headers['Location'] end - # does the redirect location match this regexp pattern? + # Does the redirect location match this regexp pattern? def redirect_url_match?( pattern ) return false if redirect_url.nil? p = Regexp.new(pattern) if pattern.class == String @@ -204,7 +205,7 @@ module ActionController #:nodoc: p.match(redirect_url) != nil end - # returns the template path of the file which was used to + # Returns the template path of the file which was used to # render this response (or nil) def rendered_file(with_controller=false) unless template.first_render.nil? @@ -216,50 +217,49 @@ module ActionController #:nodoc: end end - # was this template rendered by a file? + # Was this template rendered by a file? def rendered_with_file? !rendered_file.nil? end - # a shortcut to the flash (or an empty hash if no flash.. hey! that rhymes!) + # A shortcut to the flash. Returns an empyt hash if no session flash exists. def flash session['flash'] || {} end - # do we have a flash? + # Do we have a flash? def has_flash? !session['flash'].empty? end - # do we have a flash that has contents? + # Do we have a flash that has contents? def has_flash_with_contents? !flash.empty? end - # does the specified flash object exist? + # Does the specified flash object exist? def has_flash_object?(name=nil) !flash[name].nil? end - # does the specified object exist in the session? + # Does the specified object exist in the session? def has_session_object?(name=nil) !session[name].nil? end - # a shortcut to the template.assigns + # A shortcut to the template.assigns def template_objects template.assigns || {} end - # does the specified template object exist? + # Does the specified template object exist? def has_template_object?(name=nil) !template_objects[name].nil? end # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs - # Example: # - # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value + # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value def cookies headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash } end @@ -286,7 +286,7 @@ module ActionController #:nodoc: def initialize(attributes = nil) @session_id = '' - @attributes = attributes + @attributes = attributes.nil? ? nil : attributes.stringify_keys @saved_attributes = nil end @@ -295,11 +295,11 @@ module ActionController #:nodoc: end def [](key) - data[key] + data[key.to_s] end def []=(key, value) - data[key] = value + data[key.to_s] = value end def update @@ -340,6 +340,7 @@ module ActionController #:nodoc: @content_type = content_type @original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 } @tempfile = Tempfile.new(@original_filename) + @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding) @tempfile.binmode if binary FileUtils.copy_file(path, @tempfile.path) end @@ -357,7 +358,7 @@ module ActionController #:nodoc: module TestProcess def self.included(base) - # execute the request simulating a specific http method and set/volley the response + # execute the request simulating a specific HTTP method and set/volley the response %w( get post put delete head ).each do |method| base.class_eval <<-EOV, __FILE__, __LINE__ def #{method}(action, parameters = nil, session = nil, flash = nil) @@ -373,7 +374,7 @@ module ActionController #:nodoc: # Sanity check for required instance variables so we can give an # understandable error message. %w(@controller @request @response).each do |iv_name| - if !(instance_variables.include?(iv_name) || instance_variables.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? + if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? raise "#{iv_name} is nil: make sure you set it in your test's setup method." end end @@ -464,10 +465,13 @@ module ActionController #:nodoc: return super end - # Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example: + # Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type): + # # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') # - # To upload binary files on Windows, pass :binary as the last parameter. This will not affect other platforms. + # To upload binary files on Windows, pass :binary as the last parameter. + # This will not affect other platforms: + # # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary) def fixture_file_upload(path, mime_type = nil, binary = false) ActionController::TestUploadedFile.new( @@ -482,17 +486,17 @@ module ActionController #:nodoc: # with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block - # will create some routes using map.draw { map.connect ... }: + # will create some routes using map.draw { map.connect ... }: # - # with_routing do |set| - # set.draw do |map| - # map.connect ':controller/:action/:id' - # assert_equal( - # ['/content/10/show', {}], - # map.generate(:controller => 'content', :id => 10, :action => 'show') - # end - # end - # end + # with_routing do |set| + # set.draw do |map| + # map.connect ':controller/:action/:id' + # assert_equal( + # ['/content/10/show', {}], + # map.generate(:controller => 'content', :id => 10, :action => 'show') + # end + # end + # end # def with_routing real_routes = ActionController::Routing::Routes diff --git a/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb b/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb index c650763f..3a38f233 100644 --- a/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb +++ b/vendor/rails/actionpack/lib/action_controller/url_rewriter.rb @@ -1,82 +1,89 @@ -module ActionController +module ActionController # Write URLs from arbitrary places in your codebase, such as your mailers. - # + # # Example: - # + # # class MyMailer # include ActionController::UrlWriter # default_url_options[:host] = 'www.basecamphq.com' - # + # # def signup_url(token) # url_for(:controller => 'signup', action => 'index', :token => token) # end # end - # + # # In addition to providing +url_for+, named routes are also accessible after # including UrlWriter. module UrlWriter - # The default options for urls written by this writer. Typically a :host pair - # is provided. + # The default options for urls written by this writer. Typically a :host + # pair is provided. mattr_accessor :default_url_options self.default_url_options = {} - + def self.included(base) #:nodoc: - ActionController::Routing::Routes.install_helpers base + ActionController::Routing::Routes.install_helpers(base) base.mattr_accessor :default_url_options base.default_url_options ||= default_url_options end - - # Generate a url based on the options provided, default_url_options and the + + # Generate a url based on the options provided, default_url_options and the # routes defined in routes.rb. The following options are supported: - # - # * :only_path If true, the relative url is returned. Defaults to false. - # * :protocol The protocol to connect to. Defaults to 'http'. - # * :host Specifies the host the link should be targetted at. If :only_path is false, this option must be - # provided either explicitly, or via default_url_options. - # * :port Optionally specify the port to connect to. - # * :anchor An anchor name to be appended to the path. - # - # Any other key(:controller, :action, etc...) given to url_for is forwarded to the Routes module. - # + # + # * :only_path - If true, the relative url is returned. Defaults to +false+. + # * :protocol - The protocol to connect to. Defaults to 'http'. + # * :host - Specifies the host the link should be targetted at. + # If :only_path is false, this option must be + # provided either explicitly, or via +default_url_options+. + # * :port - Optionally specify the port to connect to. + # * :anchor - An anchor name to be appended to the path. + # * :skip_relative_url_root - If true, the url is not constructed using the + # +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root. + # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/" + # + # Any other key (:controller, :action, etc.) given to + # +url_for+ is forwarded to the Routes module. + # # Examples: - # + # # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing' # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' + # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' - # def url_for(options) options = self.class.default_url_options.merge(options) - + url = '' - unless options.delete :only_path + unless options.delete(:only_path) url << (options.delete(:protocol) || 'http') - url << '://' unless url.match("://") #dont add separator if its already been specified in :protocol - + url << '://' unless url.match("://") + raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] url << options.delete(:host) url << ":#{options.delete(:port)}" if options.key?(:port) else - # Delete the unused options to prevent their appearance in the query string - [:protocol, :host, :port].each { |k| options.delete k } + # Delete the unused options to prevent their appearance in the query string. + [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) } end - - anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options.key?(:anchor) - url << Routing::Routes.generate(options, {}) + trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash) + url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root] + anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor] + generated = Routing::Routes.generate(options, {}) + url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated) url << anchor if anchor - return url - end + url + end end - + # Rewrites URLs for Base.redirect_to and Base.url_for in the controller. class UrlRewriter #:nodoc: RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root] def initialize(request, parameters) @request, @parameters = request, parameters end - + def rewrite(options = {}) rewrite_url(options) end @@ -123,7 +130,7 @@ module ActionController # Generates the query string, too Routing::Routes.generate(options, @request.symbolized_path_parameters) end - + def rewrite_authentication(options) if options[:user] && options[:password] "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@" diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 1eb426ae..12c84051 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -107,7 +107,7 @@ module HTML # gauntlet if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ || - style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$))*$/ + style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$)\s*)*$/ return '' end @@ -170,4 +170,4 @@ module HTML (value =~ /(^[^\/:]*):|(�*58)|(p)|(%|%)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first)) end end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb index b950e846..602411ed 100644 --- a/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +++ b/vendor/rails/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb @@ -54,7 +54,7 @@ module HTML #:nodoc: tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/)) elsif @scanner.scan(/!\[CDATA\[/) tag << @scanner.matched - tag << @scanner.scan_until(/\]\]>/) + tag << (@scanner.scan_until(/\]\]>/) || @scanner.scan_until(/\Z/)) elsif @scanner.scan(/!/) # doctype tag << @scanner.matched tag << consume_quoted_regions diff --git a/vendor/rails/actionpack/lib/action_controller/verification.rb b/vendor/rails/actionpack/lib/action_controller/verification.rb index e5045fba..9f606e7b 100644 --- a/vendor/rails/actionpack/lib/action_controller/verification.rb +++ b/vendor/rails/actionpack/lib/action_controller/verification.rb @@ -43,72 +43,88 @@ module ActionController #:nodoc: # the user is redirected to a different action. The +options+ parameter # is a hash consisting of the following key/value pairs: # - # * :params - a single key or an array of keys that must - # be in the params hash in order for the action(s) to be safely - # called. - # * :session - a single key or an array of keys that must - # be in the session in order for the action(s) to be safely called. - # * :flash - a single key or an array of keys that must - # be in the flash in order for the action(s) to be safely called. - # * :method - a single key or an array of keys--any one of which - # must match the current request method in order for the action(s) to - # be safely called. (The key should be a symbol: :get or - # :post, for example.) - # * :xhr - true/false option to ensure that the request is coming - # from an Ajax call or not. - # * :add_flash - a hash of name/value pairs that should be merged - # into the session's flash if the prerequisites cannot be satisfied. - # * :add_headers - a hash of name/value pairs that should be - # merged into the response's headers hash if the prerequisites cannot - # be satisfied. - # * :redirect_to - the redirection parameters to be used when - # redirecting if the prerequisites cannot be satisfied. You can - # redirect either to named route or to the action in some controller. - # * :render - the render parameters to be used when - # the prerequisites cannot be satisfied. - # * :only - only apply this verification to the actions specified - # in the associated array (may also be a single value). - # * :except - do not apply this verification to the actions - # specified in the associated array (may also be a single value). + # :params:: + # a single key or an array of keys that must be in the params + # hash in order for the action(s) to be safely called. + # :session:: + # a single key or an array of keys that must be in the session + # in order for the action(s) to be safely called. + # :flash:: + # a single key or an array of keys that must be in the flash in order + # for the action(s) to be safely called. + # :method:: + # a single key or an array of keys--any one of which must match the + # current request method in order for the action(s) to be safely called. + # (The key should be a symbol: :get or :post, for + # example.) + # :xhr:: + # true/false option to ensure that the request is coming from an Ajax + # call or not. + # :add_flash:: + # a hash of name/value pairs that should be merged into the session's + # flash if the prerequisites cannot be satisfied. + # :add_headers:: + # a hash of name/value pairs that should be merged into the response's + # headers hash if the prerequisites cannot be satisfied. + # :redirect_to:: + # the redirection parameters to be used when redirecting if the + # prerequisites cannot be satisfied. You can redirect either to named + # route or to the action in some controller. + # :render:: + # the render parameters to be used when the prerequisites cannot be satisfied. + # :only:: + # only apply this verification to the actions specified in the associated + # array (may also be a single value). + # :except:: + # do not apply this verification to the actions specified in the associated + # array (may also be a single value). def verify(options={}) - filter_opts = { :only => options[:only], :except => options[:except] } - before_filter(filter_opts) do |c| + before_filter :only => options[:only], :except => options[:except] do |c| c.send! :verify_action, options end end end - def verify_action(options) #:nodoc: - prereqs_invalid = - [*options[:params] ].find { |v| params[v].nil? } || - [*options[:session]].find { |v| session[v].nil? } || - [*options[:flash] ].find { |v| flash[v].nil? } - - if !prereqs_invalid && options[:method] - prereqs_invalid ||= - [*options[:method]].all? { |v| request.method != v.to_sym } - end - - prereqs_invalid ||= (request.xhr? != options[:xhr]) unless options[:xhr].nil? - - if prereqs_invalid - flash.update(options[:add_flash]) if options[:add_flash] - response.headers.update(options[:add_headers]) if options[:add_headers] + private - unless performed? - case - when options[:render] - render(options[:render]) - when options[:redirect_to] - options[:redirect_to] = self.send!(options[:redirect_to]) if options[:redirect_to].is_a?(Symbol) - redirect_to(options[:redirect_to]) - else - head(:bad_request) - end - end + def verify_action(options) #:nodoc: + if prereqs_invalid?(options) + flash.update(options[:add_flash]) if options[:add_flash] + response.headers.update(options[:add_headers]) if options[:add_headers] + apply_remaining_actions(options) unless performed? + end + end + + def prereqs_invalid?(options) # :nodoc: + verify_presence_of_keys_in_hash_flash_or_params(options) || + verify_method(options) || + verify_request_xhr_status(options) + end + + def verify_presence_of_keys_in_hash_flash_or_params(options) # :nodoc: + [*options[:params] ].find { |v| params[v].nil? } || + [*options[:session]].find { |v| session[v].nil? } || + [*options[:flash] ].find { |v| flash[v].nil? } + end + + def verify_method(options) # :nodoc: + [*options[:method]].all? { |v| request.method != v.to_sym } if options[:method] + end + + def verify_request_xhr_status(options) # :nodoc: + request.xhr? != options[:xhr] unless options[:xhr].nil? + end + + def apply_redirect_to(redirect_to_option) # :nodoc: + redirect_to_option.is_a?(Symbol) ? self.send!(redirect_to_option) : redirect_to_option + end + + def apply_remaining_actions(options) # :nodoc: + case + when options[:render] ; render(options[:render]) + when options[:redirect_to] ; redirect_to(apply_redirect_to(options[:redirect_to])) + else head(:bad_request) end end - - private :verify_action end end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_pack.rb b/vendor/rails/actionpack/lib/action_pack.rb index 006c83db..c7fd3092 100644 --- a/vendor/rails/actionpack/lib/action_pack.rb +++ b/vendor/rails/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/vendor/rails/actionpack/lib/action_pack/version.rb b/vendor/rails/actionpack/lib/action_pack/version.rb index 7aa6a5db..c67654d9 100644 --- a/vendor/rails/actionpack/lib/action_pack/version.rb +++ b/vendor/rails/actionpack/lib/action_pack/version.rb @@ -1,8 +1,8 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 - MINOR = 0 - TINY = 2 + MINOR = 1 + TINY = 0 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/vendor/rails/actionpack/lib/action_view.rb b/vendor/rails/actionpack/lib/action_view.rb index bfcfcab0..5f4126e4 100644 --- a/vendor/rails/actionpack/lib/action_view.rb +++ b/vendor/rails/actionpack/lib/action_view.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -22,16 +22,24 @@ #++ require 'action_view/template_handler' +require 'action_view/template_handlers/compilable' require 'action_view/template_handlers/builder' require 'action_view/template_handlers/erb' require 'action_view/template_handlers/rjs' +require 'action_view/template_finder' +require 'action_view/template' +require 'action_view/partial_template' +require 'action_view/inline_template' + require 'action_view/base' require 'action_view/partials' require 'action_view/template_error' ActionView::Base.class_eval do include ActionView::Partials -end -ActionView::Base.load_helpers + ActionView::Base.helper_modules.each do |helper_module| + include helper_module + end +end diff --git a/vendor/rails/actionpack/lib/action_view/base.rb b/vendor/rails/actionpack/lib/action_view/base.rb index 0f966add..f3987565 100644 --- a/vendor/rails/actionpack/lib/action_view/base.rb +++ b/vendor/rails/actionpack/lib/action_view/base.rb @@ -1,10 +1,13 @@ module ActionView #:nodoc: class ActionViewError < StandardError #:nodoc: end + + class MissingTemplate < ActionViewError #:nodoc: + end - # Action View templates can be written in three ways. If the template file has a +.erb+ (or +.rhtml+) extension then it uses a mixture of ERb - # (included in Ruby) and HTML. If the template file has a +.builder+ (or +.rxml+) extension then Jim Weirich's Builder::XmlMarkup library is used. - # If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator. + # Action View templates can be written in three ways. If the template file has a .erb (or .rhtml) extension then it uses a mixture of ERb + # (included in Ruby) and HTML. If the template file has a .builder (or .rxml) extension then Jim Weirich's Builder::XmlMarkup library is used. + # If the template file has a .rjs extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator. # # = ERb # @@ -21,7 +24,7 @@ module ActionView #:nodoc: # # Hi, Mr. <% puts "Frodo" %> # - # If you absolutely must write from within a function, you can use the TextHelper#concat + # If you absolutely must write from within a function, you can use the TextHelper#concat. # # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>. # @@ -43,7 +46,7 @@ module ActionView #:nodoc: # <% @page_title = "A Wonderful Hello" %> # <%= render "shared/header" %> # - # Now the header can pick up on the @page_title variable and use it for outputting a title tag: + # Now the header can pick up on the @page_title variable and use it for outputting a title tag: # # <%= @page_title %> # @@ -53,7 +56,7 @@ module ActionView #:nodoc: # # <%= render "shared/header", { :headline => "Welcome", :person => person } %> # - # These can now be accessed in shared/header with: + # These can now be accessed in shared/header with: # # Headline: <%= headline %> # First name: <%= person.first_name %> @@ -74,13 +77,13 @@ module ActionView #:nodoc: # # == Builder # - # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object - # named +xml+ is automatically made available to templates with a +.builder+ extension. + # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object + # named +xml+ is automatically made available to templates with a .builder extension. # # Here are some basic examples: # # xml.em("emphasized") # => emphasized - # xml.em { xml.b("emph & bold") } # => emph & bold + # xml.em { xml.b("emph & bold") } # => emph & bold # xml.a("A Link", "href"=>"http://onestepback.org") # => A Link # xml.target("name"=>"compile", "option"=>"fast") # => # # NOTE: order of attributes is not specified. @@ -127,18 +130,18 @@ module ActionView #:nodoc: # # == JavaScriptGenerator # - # JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to + # JavaScriptGenerator templates end in .rjs. Unlike conventional templates which are used to # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax # and make updates to the page where the request originated from. # # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block. # - # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example: + # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example: # # link_to_remote :url => {:action => 'delete'} # - # The subsequently rendered +delete.rjs+ might look like: + # The subsequently rendered delete.rjs might look like: # # page.replace_html 'sidebar', :partial => 'sidebar' # page.remove "person-#{@person.id}" @@ -150,14 +153,12 @@ module ActionView #:nodoc: class Base include ERB::Util - attr_reader :first_render - attr_accessor :base_path, :assigns, :template_extension - attr_accessor :controller, :view_paths - - attr_reader :logger, :response, :headers - attr_internal :cookies, :flash, :headers, :params, :request, :response, :session + attr_reader :finder + attr_accessor :base_path, :assigns, :template_extension, :first_render + attr_accessor :controller attr_writer :template_format + attr_accessor :current_render_extension # Specify trim mode for the ERB compiler. Defaults to '-'. # See ERb documentation for suitable values. @@ -167,18 +168,12 @@ module ActionView #:nodoc: # Specify whether file modification times should be checked to see if a template needs recompilation @@cache_template_loading = false cattr_accessor :cache_template_loading - - # Specify whether file extension lookup should be cached, and whether template base path lookup should be cached. - # Should be +false+ for development environments. Defaults to +true+. - @@cache_template_extensions = true - cattr_accessor :cache_template_extensions - - # Specify whether local_assigns should be able to use string keys. - # Defaults to +true+. String keys are deprecated and will be removed - # shortly. - @@local_assigns_support_string_keys = true - cattr_accessor :local_assigns_support_string_keys + def self.cache_template_extensions=(*args) + ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no affect. " << + "Please remove it from your config files.", caller) + end + # Specify whether RJS responses should be wrapped in a try/catch block # that alert()s the caught exception (and then re-raises it). @@debug_rjs = false @@ -187,89 +182,47 @@ module ActionView #:nodoc: @@erb_variable = '_erbout' cattr_accessor :erb_variable - delegate :request_forgery_protection_token, :to => :controller + attr_internal :request - @@template_handlers = HashWithIndifferentAccess.new + delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, + :flash, :logger, :action_name, :to => :controller module CompiledTemplates #:nodoc: # holds compiled template code end include CompiledTemplates - # Maps inline templates to their method names + # Maps inline templates to their method names + cattr_accessor :method_names @@method_names = {} - # Map method names to their compile time - @@compile_time = {} # Map method names to the names passed in local assigns so far @@template_args = {} - # Count the number of inline templates - @@inline_template_count = 0 - # Maps template paths without extension to their file extension returned by pick_template_extension. - # If for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions - # used by pick_template_extension determines whether ext1 or ext2 will be stored. - @@cached_template_extension = {} - # Maps template paths / extensions to - @@cached_base_paths = {} # Cache public asset paths cattr_reader :computed_public_paths @@computed_public_paths = {} - @@template_handlers = {} - @@default_template_handlers = nil - class ObjectWrapper < Struct.new(:value) #:nodoc: end - def self.load_helpers #:nodoc: - Dir.entries("#{File.dirname(__FILE__)}/helpers").sort.each do |file| + def self.helper_modules #:nodoc: + helpers = [] + Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file| next unless file =~ /^([a-z][a-z_]*_helper).rb$/ require "action_view/helpers/#{$1}" helper_module_name = $1.camelize if Helpers.const_defined?(helper_module_name) - include Helpers.const_get(helper_module_name) + helpers << Helpers.const_get(helper_module_name) end end + return helpers end - # Register a class that knows how to handle template files with the given - # extension. This can be used to implement new template types. - # The constructor for the class must take the ActiveView::Base instance - # as a parameter, and the class must implement a #render method that - # takes the contents of the template to render as well as the Hash of - # local assigns available to the template. The #render method ought to - # return the rendered template as a string. - def self.register_template_handler(extension, klass) - @@template_handlers[extension.to_sym] = klass - end - - def self.template_handler_extensions - @@template_handler_extensions ||= @@template_handlers.keys.map(&:to_s).sort - end - - def self.register_default_template_handler(extension, klass) - register_template_handler(extension, klass) - @@default_template_handlers = klass - end - - def self.handler_for_extension(extension) - (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers - end - - register_default_template_handler :erb, TemplateHandlers::ERB - register_template_handler :rjs, TemplateHandlers::RJS - register_template_handler :builder, TemplateHandlers::Builder - - # TODO: Depreciate old template extensions - register_template_handler :rhtml, TemplateHandlers::ERB - register_template_handler :rxml, TemplateHandlers::Builder - def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: - @view_paths = view_paths.respond_to?(:find) ? view_paths.dup : [*view_paths].compact @assigns = assigns_for_first_render @assigns_added = nil @controller = controller - @logger = controller && controller.logger + @finder = TemplateFinder.new(self, view_paths) end # Renders the template present at template_path. If use_full_path is set to true, @@ -289,60 +242,28 @@ If you are rendering a subtemplate, you must now use controller-like partial syn END_ERROR end - @first_render ||= template_path - template_path_without_extension, template_extension = path_and_extension(template_path) - if use_full_path - if template_extension - template_file_name = full_template_path(template_path_without_extension, template_extension) - else - template_extension = pick_template_extension(template_path).to_s - unless template_extension - raise ActionViewError, "No template found for #{template_path} in #{view_paths.inspect}" - end - template_file_name = full_template_path(template_path, template_extension) - template_extension = template_extension.gsub(/^.+\./, '') # strip off any formats - end - else - template_file_name = template_path - end - - template_source = nil # Don't read the source until we know that it is required - - if template_file_name.blank? - raise ActionViewError, "Couldn't find template file for #{template_path} in #{view_paths.inspect}" - end - - begin - render_template(template_extension, template_source, template_file_name, local_assigns) - rescue Exception => e - if TemplateError === e - e.sub_template_of(template_file_name) - raise e - else - raise TemplateError.new(find_base_path_for("#{template_path_without_extension}.#{template_extension}") || view_paths.first, template_file_name, @assigns, template_source, e) - end - end + Template.new(self, template_path, use_full_path, local_assigns).render_template end # Renders the template present at template_path (relative to the view_paths array). # The hash in local_assigns is made available as local variables. - def render(options = {}, old_local_assigns = {}, &block) #:nodoc: + def render(options = {}, local_assigns = {}, &block) #:nodoc: if options.is_a?(String) - render_file(options, true, old_local_assigns) + render_file(options, true, local_assigns) elsif options == :update update_page(&block) elsif options.is_a?(Hash) options = options.reverse_merge(:locals => {}, :use_full_path => true) - if options[:layout] - path, partial_name = partial_pieces(options.delete(:layout)) - + if partial_layout = options.delete(:layout) if block_given? - @content_for_layout = capture(&block) - concat(render(options.merge(:partial => "#{path}/#{partial_name}")), block.binding) + wrap_content_for_layout capture(&block) do + concat(render(options.merge(:partial => partial_layout)), block.binding) + end else - @content_for_layout = render(options) - render(options.merge(:partial => "#{path}/#{partial_name}")) + wrap_content_for_layout render(options) do + render(options.merge(:partial => partial_layout)) + end end elsif options[:file] render_file(options[:file], options[:use_full_path], options[:locals]) @@ -351,61 +272,14 @@ If you are rendering a subtemplate, you must now use controller-like partial syn elsif options[:partial] render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]) elsif options[:inline] - render_template(options[:type], options[:inline], nil, options[:locals]) + template = InlineTemplate.new(self, options[:inline], options[:locals], options[:type]) + render_template(template) end end end - # Renders the +template+ which is given as a string as either erb or builder depending on template_extension. - # The hash in local_assigns is made available as local variables. - def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc: - handler = self.class.handler_for_extension(template_extension) - - if template_handler_is_compilable?(handler) - compile_and_render_template(handler, template, file_path, local_assigns) - else - template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded. - delegate_render(handler, template, local_assigns) - end - end - - # Gets the full template path with base path for the given template_path and extension. - # - # full_template_path('users/show', 'html.erb') - # # => '~/rails/app/views/users/show.html.erb - # - def full_template_path(template_path, extension) - if @@cache_template_extensions - (@@cached_base_paths[template_path] ||= {})[extension.to_s] ||= find_full_template_path(template_path, extension) - else - find_full_template_path(template_path, extension) - end - end - - # Gets the extension for an existing template with the given template_path. - # Returns the format with the extension if that template exists. - # - # pick_template_extension('users/show') - # # => 'html.erb' - # - # pick_template_extension('users/legacy') - # # => "rhtml" - # - def pick_template_extension(template_path)#:nodoc: - if @@cache_template_extensions - (@@cached_template_extension[template_path] ||= {})[template_format] ||= find_template_extension_for(template_path) - else - find_template_extension_for(template_path) - end - end - - def file_exists?(template_path)#:nodoc: - template_file_name, template_file_extension = path_and_extension(template_path) - if template_file_extension - template_exists?(template_file_name, template_file_extension) - else - template_exists?(template_file_name, pick_template_extension(template_path)) - end + def render_template(template) #:nodoc: + template.render_template end # Returns true is the file may be rendered implicitly. @@ -413,88 +287,38 @@ If you are rendering a subtemplate, you must now use controller-like partial syn template_path.split('/').last[0,1] != '_' end - # symbolized version of the :format parameter of the request, or :html by default. + # Returns a symbolized version of the :format parameter of the request, + # or :html by default. + # + # EXCEPTION: If the :format parameter is not set, the Accept header will be examined for + # whether it contains the JavaScript mime type as its first priority. If that's the case, + # it will be used. This ensures that Ajax applications can use the same URL to support both + # JavaScript and non-JavaScript users. def template_format return @template_format if @template_format - format = controller && controller.respond_to?(:request) && controller.request.parameters[:format] - @template_format = format.blank? ? :html : format.to_sym - end - # Adds a view_path to the front of the view_paths array. - # This change affects the current request only. - # - # @template.prepend_view_path("views/default") - # @template.prepend_view_path(["views/default", "views/custom"]) - # - def prepend_view_path(path) - @view_paths.unshift(*path) - end - - # Adds a view_path to the end of the view_paths array. - # This change affects the current request only. - # - # @template.append_view_path("views/default") - # @template.append_view_path(["views/default", "views/custom"]) - # - def append_view_path(path) - @view_paths.push(*path) + if controller && controller.respond_to?(:request) + parameter_format = controller.request.parameters[:format] + accept_format = controller.request.accepts.first + + case + when parameter_format.blank? && accept_format != :js + @template_format = :html + when parameter_format.blank? && accept_format == :js + @template_format = :js + else + @template_format = parameter_format.to_sym + end + else + @template_format = :html + end end private - def find_full_template_path(template_path, extension) - file_name = "#{template_path}.#{extension}" - base_path = find_base_path_for(file_name) - base_path.blank? ? "" : "#{base_path}/#{file_name}" - end - - # Asserts the existence of a template. - def template_exists?(template_path, extension) - file_path = full_template_path(template_path, extension) - !file_path.blank? && @@method_names.has_key?(file_path) || File.exist?(file_path) - end - - # Splits the path and extension from the given template_path and returns as an array. - def path_and_extension(template_path) - template_path_without_extension = template_path.sub(/\.(\w+)$/, '') - [ template_path_without_extension, $1 ] - end - - # Returns the view path that contains the given relative template path. - def find_base_path_for(template_file_name) - view_paths.find { |p| File.file?(File.join(p, template_file_name)) } - end - - # Returns the view path that the full path resides in. - def extract_base_path_from(full_path) - view_paths.find { |p| full_path[0..p.size - 1] == p } - end - - # Determines the template's file extension, such as rhtml, rxml, or rjs. - def find_template_extension_for(template_path) - find_template_extension_from_handler(template_path, true) || - find_template_extension_from_handler(template_path) || - find_template_extension_from_first_render() - end - - def find_template_extension_from_handler(template_path, formatted = nil) - checked_template_path = formatted ? "#{template_path}.#{template_format}" : template_path - - self.class.template_handler_extensions.each do |extension| - if template_exists?(checked_template_path, extension) - return formatted ? "#{template_format}.#{extension}" : extension.to_s - end - end - nil - end - - # Determine the template extension from the @first_render filename - def find_template_extension_from_first_render - File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1] - end - - # This method reads a template file. - def read_template_file(template_path, extension) - File.read(template_path) + def wrap_content_for_layout(content) + original_content_for_layout = @content_for_layout + @content_for_layout = content + returning(yield) { @content_for_layout = original_content_for_layout } end # Evaluate the local assigns and pushes them to the view. @@ -505,138 +329,15 @@ If you are rendering a subtemplate, you must now use controller-like partial syn end end - def delegate_render(handler, template, local_assigns) - handler.new(self).render(template, local_assigns) - end - - def delegate_compile(handler, template) - handler.new(self).compile(template) - end - - def template_handler_is_compilable?(handler) - handler.new(self).respond_to?(:compile) - end - # Assigns instance variables from the controller to the view. def assign_variables_from_controller @assigns.each { |key, value| instance_variable_set("@#{key}", value) } end - - - # Return true if the given template was compiled for a superset of the keys in local_assigns - def supports_local_assigns?(render_symbol, local_assigns) - local_assigns.empty? || - ((args = @@template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) }) - end - - # Method to check whether template compilation is necessary. - # The template will be compiled if the inline template or file has not been compiled yet, - # if local_assigns has a new key, which isn't supported by the compiled code yet, - # or if the file has changed on disk and checking file mods hasn't been disabled. - def compile_template?(template, file_name, local_assigns) - method_key = file_name || template - render_symbol = @@method_names[method_key] - - compile_time = @@compile_time[render_symbol] - if compile_time && supports_local_assigns?(render_symbol, local_assigns) - if file_name && !@@cache_template_loading - template_changed_since?(file_name, compile_time) - end - else - true - end - end - - # Method to handle checking a whether a template has changed since last compile; isolated so that templates - # not stored on the file system can hook and extend appropriately. - def template_changed_since?(file_name, compile_time) - lstat = File.lstat(file_name) - compile_time < lstat.mtime || - (lstat.symlink? && compile_time < File.stat(file_name).mtime) - end - - # Method to create the source code for a given template. - def create_template_source(handler, template, render_symbol, locals) - body = delegate_compile(handler, template) - - @@template_args[render_symbol] ||= {} - locals_keys = @@template_args[render_symbol].keys | locals - @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } - - locals_code = "" - locals_keys.each do |key| - locals_code << "#{key} = local_assigns[:#{key}]\n" - end - - "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend" - end - - def assign_method_name(handler, template, file_name) - method_key = file_name || template - @@method_names[method_key] ||= compiled_method_name(handler, template, file_name) - end - - def compiled_method_name(handler, template, file_name) - ['_run', handler.to_s.demodulize.underscore, compiled_method_name_file_path_segment(file_name)].compact.join('_').to_sym - end - - def compiled_method_name_file_path_segment(file_name) - if file_name - s = File.expand_path(file_name) - s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT) - s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord } - s - else - (@@inline_template_count += 1).to_s - end - end - - # Compile and evaluate the template's code - def compile_template(handler, template, file_name, local_assigns) - render_symbol = assign_method_name(handler, template, file_name) - render_source = create_template_source(handler, template, render_symbol, local_assigns.keys) - line_offset = @@template_args[render_symbol].size + handler.line_offset - - begin - file_name = 'compiled-template' if file_name.blank? - CompiledTemplates.module_eval(render_source, file_name, -line_offset) - rescue Exception => e # errors from template code - if logger - logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" - logger.debug "Function body: #{render_source}" - logger.debug "Backtrace: #{e.backtrace.join("\n")}" - end - - raise TemplateError.new(extract_base_path_from(file_name) || view_paths.first, file_name || template, @assigns, template, e) - end - - @@compile_time[render_symbol] = Time.now - # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger - end - - # Render the provided template with the given local assigns. If the template has not been rendered with the provided - # local assigns yet, or if the template has been updated on disk, then the template will be compiled to a method. - # - # Either, but not both, of template and file_path may be nil. If file_path is given, the template - # will only be read if it has to be compiled. - # - def compile_and_render_template(handler, template = nil, file_path = nil, local_assigns = {}) #:nodoc: - # convert string keys to symbols if requested - local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys - - # compile the given template, if necessary - if compile_template?(template, file_path, local_assigns) - template ||= read_template_file(file_path, nil) - compile_template(handler, template, file_path, local_assigns) - end - - # Get the method name for this template and run it - method_name = @@method_names[file_path || template] - evaluate_assigns - - send(method_name, local_assigns) do |*name| - instance_variable_get "@content_for_#{name.first || 'layout'}" - end + + def execute(template) + send(template.method, template.locals) do |*names| + instance_variable_get "@content_for_#{names.first || 'layout'}" + end end end end diff --git a/vendor/rails/actionpack/lib/action_view/compiled_templates.rb b/vendor/rails/actionpack/lib/action_view/compiled_templates.rb deleted file mode 100644 index 5a286432..00000000 --- a/vendor/rails/actionpack/lib/action_view/compiled_templates.rb +++ /dev/null @@ -1,69 +0,0 @@ -module ActionView - - # CompiledTemplates modules hold methods that have been compiled. - # Templates are compiled into these methods so that they do not need to be - # read and parsed for each request. - # - # Each template may be compiled into one or more methods. Each method accepts a given - # set of parameters which is used to implement local assigns passing. - # - # To use a compiled template module, create a new instance and include it into the class - # in which you want the template to be rendered. - class CompiledTemplates < Module - attr_reader :method_names - - def initialize - @method_names = Hash.new do |hash, key| - hash[key] = "__compiled_method_#{(hash.length + 1)}" - end - @mtimes = {} - end - - # Return the full key for the given identifier and argument names - def full_key(identifier, arg_names) - [identifier, arg_names] - end - - # Return the selector for this method or nil if it has not been compiled - def selector(identifier, arg_names) - key = full_key(identifier, arg_names) - method_names.key?(key) ? method_names[key] : nil - end - alias :compiled? :selector - - # Return the time at which the method for the given identifier and argument names was compiled. - def mtime(identifier, arg_names) - @mtimes[full_key(identifier, arg_names)] - end - - # Compile the provided source code for the given argument names and with the given initial line number. - # The identifier should be unique to this source. - # - # The file_name, if provided will appear in backtraces. If not provided, the file_name defaults - # to the identifier. - # - # This method will return the selector for the compiled version of this method. - def compile_source(identifier, arg_names, source, initial_line_number = 0, file_name = nil) - file_name ||= identifier - name = method_names[full_key(identifier, arg_names)] - arg_desc = arg_names.empty? ? '' : "(#{arg_names * ', '})" - fake_file_name = "#{file_name}#{arg_desc}" # Include the arguments for this version (for now) - - method_def = wrap_source(name, arg_names, source) - - begin - module_eval(method_def, fake_file_name, initial_line_number) - @mtimes[full_key(identifier, arg_names)] = Time.now - rescue Exception => e # errors from compiled source - e.blame_file! identifier - raise - end - name - end - - # Wrap the provided source in a def ... end block. - def wrap_source(name, arg_names, source) - "def #{name}(#{arg_names * ', '})\n#{source}\nend" - end - end -end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb index 9b3292a9..f3f204cc 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -8,47 +8,55 @@ module ActionView end module Helpers - # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the form + # The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the +form+ # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This # is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form. - # In that case, it's better to use the input method and the specialized form methods in link:classes/ActionView/Helpers/FormHelper.html + # In that case, it's better to use the +input+ method and the specialized +form+ methods in link:classes/ActionView/Helpers/FormHelper.html module ActiveRecordHelper - # Returns a default input tag for the type of object returned by the method. For example, let's say you have a model - # that has an attribute +title+ of type VARCHAR column, and this instance holds "Hello World": - # input("post", "title") => - # + # Returns a default input tag for the type of object returned by the method. For example, if @post + # has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World": + # + # input("post", "title") + # # => def input(record_name, method, options = {}) InstanceTag.new(record_name, method, self).to_tag(options) end - # Returns an entire form with all needed input tags for a specified Active Record object. For example, let's say you - # have a table model Post with attributes named title of type VARCHAR and body of type TEXT: + # Returns an entire form with all needed input tags for a specified Active Record object. For example, if @post + # has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then + # # form("post") - # That line would yield a form like the following: - #
    - #

    - #
    - # - #

    - #

    - #
    - # - #

    - # - #
    + # + # would yield a form like the following (modulus formatting): + # + #
    + #

    + #
    + # + #

    + #

    + #
    + # + #

    + # + #
    # # It's possible to specialize the form builder by using a different action name and by supplying another - # block renderer. For example, let's say you have a model Entry with an attribute message of type VARCHAR: + # block renderer. For example, if @entry has an attribute +message+ of type +VARCHAR+ then # - # form("entry", :action => "sign", :input_block => - # Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}
    " }) => + # form("entry", + # :action => "sign", + # :input_block => Proc.new { |record, column| + # "#{column.human_name}: #{input(record, column.name)}
    " + # }) # - #
    - # Message: - #
    - # - #
    + # would yield a form like the following (modulus formatting): + # + #
    + # Message: + #
    + # + #
    # # It's also possible to add additional content to the form by giving it a block, such as: # @@ -56,6 +64,14 @@ module ActionView # form << content_tag("b", "Department") # form << collection_select("department", "id", @departments, "id", "name") # end + # + # The following options are available: + # + # * :action - The action used when submitting the form (default: +create+ if a new record, otherwise +update+). + # * :input_block - Specialize the output using a different block, see above. + # * :method - The method used when submitting the form (default: +post+). + # * :multipart - Whether to change the enctype of the form to "multipart/form-data", used when uploading a file (default: +false+). + # * :submit_value - The text of the submit button (default: "Create" if a new record, otherwise "Update"). def form(record_name, options = {}) record = instance_variable_get("@#{record_name}") @@ -65,29 +81,27 @@ module ActionView submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize - contents = '' + contents = form_tag({:action => action}, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil) contents << hidden_field(record_name, :id) unless record.new_record? contents << all_input_tags(record, record_name, options) yield contents if block_given? contents << submit_tag(submit_value) - - content_tag('form', contents, :action => action, :method => 'post', :enctype => options[:multipart] ? 'multipart/form-data': nil) + contents << '' end # Returns a string containing the error message attached to the +method+ on the +object+ if one exists. # This error message is wrapped in a DIV tag, which can be extended to include a +prepend_text+ and/or +append_text+ # (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or - # the actual object. As an example, let's say you have a model - # +post+ that has an error message on the +title+ attribute: + # the actual object. As an example, let's say you have a model @post that has an error message on the +title+ attribute: # - # <%= error_message_on "post", "title" %> => - #
    can't be empty
    + # <%= error_message_on "post", "title" %> + # # =>
    can't be empty
    # - # <%= error_message_on @post, "title" %> => - #
    can't be empty
    + # <%= error_message_on @post, "title" %> + # # =>
    can't be empty
    # - # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> => - #
    Title simply can't be empty (or it won't work).
    + # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> + # # =>
    Title simply can't be empty (or it won't work).
    def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError") if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors.on(method)) @@ -103,30 +117,37 @@ module ActionView # # This DIV can be tailored by the following options: # - # * header_tag - Used for the header of the error div (default: h2) - # * id - The id of the error div (default: errorExplanation) - # * class - The class of the error div (default: errorExplanation) - # * object - The object (or array of objects) for which to display errors, if you need to escape the instance variable convention - # * object_name - The object name to use in the header, or any text that you prefer. If object_name is not set, the name of the first object will be used. - # * header_message - The message in the header of the error div. Pass +nil+ or an empty string to avoid the header message altogether. (default: X errors prohibited this object from being saved) - # * message - The explanation message after the header message and before the error list. Pass +nil+ or an empty string to avoid the explanation message altogether. (default: There were problems with the following fields:) + # * :header_tag - Used for the header of the error div (default: "h2"). + # * :id - The id of the error div (default: "errorExplanation"). + # * :class - The class of the error div (default: "errorExplanation"). + # * :object - The object (or array of objects) for which to display errors, + # if you need to escape the instance variable convention. + # * :object_name - The object name to use in the header, or any text that you prefer. + # If :object_name is not set, the name of the first object will be used. + # * :header_message - The message in the header of the error div. Pass +nil+ + # or an empty string to avoid the header message altogether. (Default: "X errors + # prohibited this object from being saved"). + # * :message - The explanation message after the header message and before + # the error list. Pass +nil+ or an empty string to avoid the explanation message + # altogether. (Default: "There were problems with the following fields:"). # - # To specify the display for one object, you simply provide its name as a parameter. For example, for the +User+ model: + # To specify the display for one object, you simply provide its name as a parameter. + # For example, for the @user model: # # error_messages_for 'user' # - # To specify more than one object, you simply list them; optionally, you can add an extra +object_name+ parameter, which - # will be the name used in the header message. + # To specify more than one object, you simply list them; optionally, you can add an extra :object_name parameter, which + # will be the name used in the header message: # # error_messages_for 'user_common', 'user', :object_name => 'user' # - # If the objects cannot be located as instance variables, you can add an extra +object+ paremeter which gives the actual - # object (or array of objects to use) + # If the objects cannot be located as instance variables, you can add an extra :object paremeter which gives the actual + # object (or array of objects to use): # # error_messages_for 'user', :object => @question.user # # NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what - # you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors + # you need is significantly different from the default presentation, it makes plenty of sense to access the object.errors # instance yourself and set it up. View the source of this method to see how easy it is. def error_messages_for(*params) options = params.extract_options!.symbolize_keys @@ -149,7 +170,7 @@ module ActionView options[:object_name] ||= params.first options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message) options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message) - error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } } + error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join contents = '' contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank? @@ -213,29 +234,29 @@ module ActionView end alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag - def to_date_select_tag(options = {}) + def to_date_select_tag(options = {}, html_options = {}) if object.respond_to?("errors") && object.errors.respond_to?("on") - error_wrapping(to_date_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) + error_wrapping(to_date_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name)) else - to_date_select_tag_without_error_wrapping(options) + to_date_select_tag_without_error_wrapping(options, html_options) end end alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag - def to_datetime_select_tag(options = {}) + def to_datetime_select_tag(options = {}, html_options = {}) if object.respond_to?("errors") && object.errors.respond_to?("on") - error_wrapping(to_datetime_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) + error_wrapping(to_datetime_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name)) else - to_datetime_select_tag_without_error_wrapping(options) + to_datetime_select_tag_without_error_wrapping(options, html_options) end end alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag - def to_time_select_tag(options = {}) + def to_time_select_tag(options = {}, html_options = {}) if object.respond_to?("errors") && object.errors.respond_to?("on") - error_wrapping(to_time_select_tag_without_error_wrapping(options), object.errors.on(@method_name)) + error_wrapping(to_time_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name)) else - to_time_select_tag_without_error_wrapping(options) + to_time_select_tag_without_error_wrapping(options, html_options) end end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb index ba474a8f..e5a95a96 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -11,24 +11,24 @@ module ActionView # === Using asset hosts # By default, Rails links to these assets on the current host in the public # folder, but you can direct Rails to link to assets from a dedicated assets server by - # setting ActionController::Base.asset_host in your environment.rb. For example, - # let's say your asset host is assets.example.com. + # setting ActionController::Base.asset_host in your config/environment.rb. For example, + # let's say your asset host is assets.example.com. # # ActionController::Base.asset_host = "assets.example.com" # image_tag("rails.png") # => Rails - # stylesheet_include_tag("application") + # stylesheet_link_tag("application") # => # # This is useful since browsers typically open at most two connections to a single host, # which means your assets often wait in single file for their turn to load. You can - # alleviate this by using a %d wildcard in asset_host (for example, "assets%d.example.com") - # to automatically distribute asset requests among four hosts (e.g., assets0.example.com through assets3.example.com) + # alleviate this by using a %d wildcard in asset_host (for example, "assets%d.example.com") + # to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com") # so browsers will open eight connections rather than two. # # image_tag("rails.png") # => Rails - # stylesheet_include_tag("application") + # stylesheet_link_tag("application") # => # # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME @@ -47,11 +47,13 @@ module ActionView # ActionController::Base.asset_host = Proc.new { |source| "http://assets#{rand(2) + 1}.example.com" } # image_tag("rails.png") # => Rails - # stylesheet_include_tag("application") + # stylesheet_link_tag("application") # => # - # The proc takes a single source parameter which is the path of the source asset. This can be used to - # generate a particular asset host depending on the asset path. + # The proc takes a source parameter (which is the path of the source asset) and an optional + # request parameter (which is an entire instance of an ActionController::AbstractRequest + # subclass). This can be used to generate a particular asset host depending on the asset path and the particular + # request. # # ActionController::Base.asset_host = Proc.new { |source| # if source.starts_with?('/images') @@ -62,9 +64,22 @@ module ActionView # } # image_tag("rails.png") # => Rails - # stylesheet_include_tag("application") + # stylesheet_link_tag("application") # => # + # The optional request parameter to the proc is useful in particular for serving assets from an + # SSL-protected page. The example proc below disables asset hosting for HTTPS connections, while still sending + # assets for plain HTTP requests from asset hosts. This is useful for avoiding mixed media warnings when serving + # non-HTTP assets from HTTPS web pages when you don't have an SSL certificate for each of the asset hosts. + # + # ActionController::Base.asset_host = Proc.new { |source, request| + # if request.ssl? + # "#{request.protocol}#{request.host_with_port}" + # else + # "#{request.protocol}assets.example.com" + # end + # } + # # === Using asset timestamps # # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the @@ -86,7 +101,7 @@ module ActionView # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being # requested over and over). module AssetTagHelper - ASSETS_DIR = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "public" + ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public" JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts" STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets" @@ -140,7 +155,8 @@ module ActionView alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) - @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup + @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup } + @@stylesheet_expansions = {} # Returns an html script tag for each of the +sources+ provided. You # can pass in the filename (.js extension is optional) of javascript files @@ -148,7 +164,7 @@ module ActionView # current page or you can pass the full path relative to your document # root. To include the Prototype and Scriptaculous javascript libraries in # your application, pass :defaults as the source. When using - # :defaults, if an application.js file exists in your public + # :defaults, if an application.js file exists in your public # javascripts directory, it will be included as well. You can modify the # html attributes of the script tag by passing a hash as the last argument. # @@ -217,7 +233,7 @@ module ActionView # # # - # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false => + # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true => # def javascript_include_tag(*sources) options = sources.extract_options!.stringify_keys @@ -234,22 +250,52 @@ module ActionView end end + # Register one or more javascript files to be included when symbol + # is passed to javascript_include_tag. This method is typically intended + # to be called from plugin initialization to register javascript files + # that the plugin installed in public/javascripts. + # + # ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] + # + # javascript_include_tag :monkey # => + # + # + # + def self.register_javascript_expansion(expansions) + @@javascript_expansions.merge!(expansions) + end + + # Register one or more stylesheet files to be included when symbol + # is passed to stylesheet_link_tag. This method is typically intended + # to be called from plugin initialization to register stylesheet files + # that the plugin installed in public/stylesheets. + # + # ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] + # + # stylesheet_link_tag :monkey # => + # + # + # + def self.register_stylesheet_expansion(expansions) + @@stylesheet_expansions.merge!(expansions) + end + # Register one or more additional JavaScript files to be included when # javascript_include_tag :defaults is called. This method is # typically intended to be called from plugin initialization to register additional # .js files that the plugin installed in public/javascripts. def self.register_javascript_include_default(*sources) - @@javascript_default_sources.concat(sources) + @@javascript_expansions[:defaults].concat(sources) end def self.reset_javascript_include_default #:nodoc: - @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup + @@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup end # Computes the path to a stylesheet asset in the public stylesheets directory. - # If the +source+ filename has no extension, .css will be appended. + # If the +source+ filename has no extension, .css will be appended. # Full paths from the document root will be passed through. - # Used internally by stylesheet_link_tag to build the stylesheet path. + # Used internally by +stylesheet_link_tag+ to build the stylesheet path. # # ==== Examples # stylesheet_path "style" # => /stylesheets/style.css @@ -263,7 +309,7 @@ module ActionView alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route # Returns a stylesheet link tag for the sources specified as arguments. If - # you don't specify an extension, .css will be appended automatically. + # you don't specify an extension, .css will be appended automatically. # You can modify the link attributes by passing a hash as the last argument. # # ==== Examples @@ -286,7 +332,7 @@ module ActionView # # # - # You can also include all styles in the stylesheet directory using :all as the source: + # You can also include all styles in the stylesheet directory using :all as the source: # # stylesheet_link_tag :all # => # @@ -333,7 +379,7 @@ module ActionView # Computes the path to an image asset in the public images directory. # Full paths from the document root will be passed through. - # Used internally by image_tag to build the image path. + # Used internally by +image_tag+ to build the image path. # # ==== Examples # image_path("edit") # => /images/edit @@ -383,7 +429,7 @@ module ActionView options.symbolize_keys! options[:src] = path_to_image(source) - options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize + options[:alt] ||= File.basename(options[:src], '.*').split('.').first.to_s.capitalize if size = options.delete(:size) options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} @@ -408,8 +454,8 @@ module ActionView end end - # Add the .ext if not present. Return full URLs otherwise untouched. - # Prefix with /dir/ if lacking a leading /. Account for relative URL + # Add the the extension +ext+ if not present. Return full URLs otherwise untouched. + # Prefix with /dir/ if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) @@ -428,16 +474,18 @@ module ActionView ActionView::Base.computed_public_paths[cache_key] ||= begin - source += ".#{ext}" if File.extname(source).blank? && ext + source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")) if source =~ %r{^[-a-z]+://} source else source = "/#{dir}/#{source}" unless source[0] == ?/ if has_request - source = "#{@controller.request.relative_url_root}#{source}" + unless source =~ %r{^#{@controller.request.relative_url_root}/} + source = "#{@controller.request.relative_url_root}#{source}" + end end - rewrite_asset_path!(source) + source = rewrite_asset_path(source) if include_host host = compute_asset_host(source) @@ -454,16 +502,21 @@ module ActionView end end - # Pick an asset host for this source. Returns nil if no host is set, + # Pick an asset host for this source. Returns +nil+ if no host is set, # the host if no wildcard is set, the host interpolated with the - # numbers 0-3 if it contains %d (the number is the source hash mod 4), + # numbers 0-3 if it contains %d (the number is the source hash mod 4), # or the value returned from invoking the proc if it's a proc. def compute_asset_host(source) if host = ActionController::Base.asset_host if host.is_a?(Proc) - host.call(source) + case host.arity + when 2 + host.call(source, @controller.request) + else + host.call(source) + end else - host % (source.hash % 4) + (host =~ /%d/) ? host % (source.hash % 4) : host end end end @@ -484,11 +537,15 @@ module ActionView end end - # Break out the asset path rewrite so you wish to put the asset id + # Break out the asset path rewrite in case plugins wish to put the asset id # someplace other than the query string. - def rewrite_asset_path!(source) + def rewrite_asset_path(source) asset_id = rails_asset_id(source) - source << "?#{asset_id}" if !asset_id.blank? + if asset_id.blank? + source + else + source + "?#{asset_id}" + end end def javascript_src_tag(source, options) @@ -508,28 +565,34 @@ module ActionView end def expand_javascript_sources(sources) - case - when sources.include?(:all) - all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).split(".", 0).first }.sort - sources = ((@@javascript_default_sources.dup & all_javascript_files) + all_javascript_files).uniq - - when sources.include?(:defaults) - sources = sources[0..(sources.index(:defaults))] + - @@javascript_default_sources.dup + - sources[(sources.index(:defaults) + 1)..sources.length] - - sources.delete(:defaults) - sources << "application" if file_exist?(File.join(JAVASCRIPTS_DIR, "application.js")) + if sources.include?(:all) + all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort + @@all_javascript_sources ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq + else + expanded_sources = sources.collect do |source| + determine_source(source, @@javascript_expansions) + end.flatten + expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js")) + expanded_sources end - - sources end def expand_stylesheet_sources(sources) if sources.first == :all - @@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).split(".", 1).first }.sort + @@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort else - sources + sources.collect do |source| + determine_source(source, @@stylesheet_expansions) + end.flatten + end + end + + def determine_source(source, collection) + case source + when Symbol + collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}") + else + source end end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb index c3ceecbf..ebb1cb34 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -42,33 +42,65 @@ module ActionView # end # end # - # The options are for atom_feed are: + # The options for atom_feed are: # # * :language: Defaults to "en-US". # * :root_url: The HTML alternative that this feed is doubling for. Defaults to / on the current host. # * :url: The URL for this feed. Defaults to the current URL. + # * :schema_date: The date at which the tag scheme for the feed was first used. A good default is the year you + # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, + # 2005 is used (as an "I don't care" value). # - # atom_feed yields a AtomFeedBuilder instance. + # Other namespaces can be added to the root element: + # + # app/views/posts/index.atom.builder: + # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', + # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| + # feed.title("My great blog!") + # feed.updated((@posts.first.created_at)) + # feed.tag!(openSearch:totalResults, 10) + # + # for post in @posts + # feed.entry(post) do |entry| + # entry.title(post.title) + # entry.content(post.body, :type => 'html') + # entry.tag!('app:edited', Time.now) + # + # entry.author do |author| + # author.name("DHH") + # end + # end + # end + # end + # + # + # atom_feed yields an AtomFeedBuilder instance. def atom_feed(options = {}, &block) + if options[:schema_date] + options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime) + else + options[:schema_date] = "2005" # The Atom spec copyright date + end + xml = options[:xml] || eval("xml", block.binding) xml.instruct! - xml.feed "xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom' do - xml.id("tag:#{request.host}:#{request.request_uri.split(".")[0].gsub("/", "")}") + feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'} + feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} + + xml.feed(feed_opts) do + xml.id("tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}") xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) - - if options[:url] - xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) - end - - yield AtomFeedBuilder.new(xml, self) + xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) + + yield AtomFeedBuilder.new(xml, self, options) end end class AtomFeedBuilder - def initialize(xml, view) - @xml, @view = xml, view + def initialize(xml, view, feed_options = {}) + @xml, @view, @feed_options = xml, view, feed_options end # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used. @@ -80,12 +112,12 @@ module ActionView # # Options: # - # * :updated: Time of update. Defaults to the created_at attribute on the record if one such exists. - # * :published: Time first published. Defaults to the updated_at attribute on the record if one such exists. + # * :published: Time first published. Defaults to the created_at attribute on the record if one such exists. + # * :updated: Time of update. Defaults to the updated_at attribute on the record if one such exists. # * :url: The URL for this entry. Defaults to the polymorphic_url for the record. def entry(record, options = {}) @xml.entry do - @xml.id("tag:#{@view.request.host_with_port}:#{record.class}#{record.id}") + @xml.id("tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") if options[:published] || (record.respond_to?(:created_at) && record.created_at) @xml.published((options[:published] || record.created_at).xmlschema) @@ -102,10 +134,10 @@ module ActionView end private - def method_missing(method, *arguments) - @xml.__send__(method, *arguments) + def method_missing(method, *arguments, &block) + @xml.__send__(method, *arguments, &block) end end end end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb index fefa28f4..743d1d40 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb @@ -21,11 +21,13 @@ module ActionView # You may give an optional logger level as the second argument # (:debug, :info, :warn, :error); the default value is :info. def benchmark(message = "Benchmarking", level = :info) - if @logger + if controller.logger real = Benchmark.realtime { yield } - @logger.send level, "#{message} (#{'%.5f' % real})" + controller.logger.send(level, "#{message} (#{'%.5f' % real})") + else + yield end end end end -end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb index cf5420a3..930c3977 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb @@ -31,8 +31,9 @@ module ActionView # <%= render :partial => "topics", :collection => @topic_list %> # Topics listed alphabetically # <% end %> - def cache(name = {}, &block) - @controller.cache_erb_fragment(block, name) + def cache(name = {}, options = nil, &block) + handler = Template.handler_class_for_extension(current_render_extension.to_sym) + handler.new(@controller).cache_fragment(block, name, options) end end end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb index fc8cd66d..9ea06568 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb @@ -118,8 +118,7 @@ module ActionView # for elements that will be fragment cached. # # The deprecated way of accessing a content_for block is to use an instance variable - # named @content_for_#{name_of_the_content_block}. So <%= content_for :footer %> - # would be available as <%= @content_for_footer %>. The preferred usage is now + # named @content_for_#{name_of_the_content_block}. The preferred usage is now # <%= yield :footer %>. def content_for(name, content = nil, &block) existing_content_for = instance_variable_get("@content_for_#{name}").to_s diff --git a/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb index c2091dfd..7ed62728 100755 --- a/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb @@ -1,4 +1,5 @@ require "date" +require 'action_view/helpers/tag_helper' module ActionView module Helpers @@ -11,31 +12,32 @@ module ActionView # * :discard_type - set to true if you want to discard the type part of the select name. If set to true, the select_month # method would use simply "date" (which can be overwritten using :prefix) instead of "date[month]". module DateHelper + include ActionView::Helpers::TagHelper DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX') # Reports the approximate distance in time between two Time or Date objects or integers as seconds. # Set include_seconds to true if you want more detailed approximations when distance < 1 min, 29 secs - # Distances are reported base on the following table: + # Distances are reported based on the following table: # - # 0 <-> 29 secs # => less than a minute - # 30 secs <-> 1 min, 29 secs # => 1 minute - # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes - # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour - # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours - # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day - # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days - # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month - # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months - # 1 yr <-> 2 yrs minus 1 secs # => about 1 year - # 2 yrs <-> max time or date # => over [2..X] years + # 0 <-> 29 secs # => less than a minute + # 30 secs <-> 1 min, 29 secs # => 1 minute + # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes + # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour + # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours + # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day + # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days + # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month + # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months + # 1 yr <-> 2 yrs minus 1 secs # => about 1 year + # 2 yrs <-> max time or date # => over [2..X] years # - # With include_seconds = true and the difference < 1 minute 29 seconds - # 0-4 secs # => less than 5 seconds - # 5-9 secs # => less than 10 seconds - # 10-19 secs # => less than 20 seconds - # 20-39 secs # => half a minute - # 40-59 secs # => less than a minute - # 60-89 secs # => 1 minute + # With include_seconds = true and the difference < 1 minute 29 seconds: + # 0-4 secs # => less than 5 seconds + # 5-9 secs # => less than 10 seconds + # 10-19 secs # => less than 20 seconds + # 20-39 secs # => half a minute + # 40-59 secs # => less than a minute + # 60-89 secs # => 1 minute # # ==== Examples # from_time = Time.now @@ -102,15 +104,17 @@ module ActionView # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash, - # which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of + # which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of # discard options. The discard options are :discard_year, :discard_month and :discard_day. Set to true, they'll # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly # set the order of the tags using the :order option with an array of symbols :year, :month and :day in # the desired order. Symbols may be omitted and the respective select is not included. # - # Pass the :default option to set the default date. Use a Time object or a Hash of :year, :month, :day, :hour, :minute, and :second. + # Pass the :default option to set the default date. Use a Time object or a Hash of :year, :month, :day, :hour, :minute, and :second. # - # Passing :disabled => true as part of the +options+ will make elements inaccessible for change. + # Passing :disabled => true as part of the +options+ will make elements inaccessible for change. + # + # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. # # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed. # @@ -148,14 +152,16 @@ module ActionView # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month # choices are valid. - def date_select(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options) + def date_select(object_name, method, options = {}, html_options = {}) + InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options, html_options) end # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified # time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+). # You can include the seconds with :include_seconds. # + # If anything is passed in the html_options hash it will be applied to every select tag in the set. + # # ==== Examples # # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute # time_select("post", "sunrise") @@ -181,13 +187,15 @@ module ActionView # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month # choices are valid. - def time_select(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options) + def time_select(object_name, method, options = {}, html_options = {}) + InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options, html_options) end # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples: # + # If anything is passed in the html_options hash it will be applied to every select tag in the set. + # # ==== Examples # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute # datetime_select("post", "written_on") @@ -205,8 +213,8 @@ module ActionView # datetime_select("post", "written_on", :discard_type => true) # # The selects are prepared for multi-parameter assignment to an Active Record object. - def datetime_select(object_name, method, options = {}) - InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options) + def datetime_select(object_name, method, options = {}, html_options = {}) + InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options, html_options) end # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+. @@ -215,6 +223,8 @@ module ActionView # will be appended onto the :order passed in. You can also add :date_separator and :time_separator # keys to the +options+ to control visual display of the elements. # + # If anything is passed in the html_options hash it will be applied to every select tag in the set. + # # ==== Examples # my_date_time = Time.now + 4.days # @@ -240,9 +250,9 @@ module ActionView # # prefixed with 'payday' rather than 'date' # select_datetime(my_date_time, :prefix => 'payday') # - def select_datetime(datetime = Time.now, options = {}) + def select_datetime(datetime = Time.current, options = {}, html_options = {}) separator = options[:datetime_separator] || '' - select_date(datetime, options) + separator + select_time(datetime, options) + select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options) end # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+. @@ -250,6 +260,8 @@ module ActionView # symbols :year, :month and :day in the desired order. If you do not supply a Symbol, it # will be appended onto the :order passed in. # + # If anything is passed in the html_options hash it will be applied to every select tag in the set. + # # ==== Examples # my_date = Time.today + 6.days # @@ -271,13 +283,13 @@ module ActionView # # prefixed with 'payday' rather than 'date' # select_datetime(my_date_time, :prefix => 'payday') # - def select_date(date = Date.today, options = {}) + def select_date(date = Date.current, options = {}, html_options = {}) options[:order] ||= [] [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) } select_date = '' options[:order].each do |o| - select_date << self.send("select_#{o}", date, options) + select_date << self.send("select_#{o}", date, options, html_options) end select_date end @@ -286,6 +298,8 @@ module ActionView # You can set :time_separator key to format the output, and # the :include_seconds option to include an input for seconds. # + # If anything is passed in the html_options hash it will be applied to every select tag in the set. + # # ==== Examples # my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds # @@ -307,9 +321,9 @@ module ActionView # # separated by ':' and includes an input for seconds # select_time(my_time, :time_separator => ':', :include_seconds => true) # - def select_time(datetime = Time.now, options = {}) + def select_time(datetime = Time.current, options = {}, html_options = {}) separator = options[:time_separator] || '' - select_hour(datetime, options) + separator + select_minute(datetime, options) + (options[:include_seconds] ? separator + select_second(datetime, options) : '') + select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) + (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '') end # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. @@ -329,7 +343,7 @@ module ActionView # # that is named 'interval' rather than 'second' # select_second(my_time, :field_name => 'interval') # - def select_second(datetime, options = {}) + def select_second(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : '' if options[:use_hidden] options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : '' @@ -337,11 +351,12 @@ module ActionView second_options = [] 0.upto(59) do |second| second_options << ((val == second) ? - %(\n) : - %(\n) + content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second), :selected => "selected") : + content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second)) ) + second_options << "\n" end - select_html(options[:field_name] || 'second', second_options, options) + select_html(options[:field_name] || 'second', second_options.join, options, html_options) end end @@ -363,7 +378,7 @@ module ActionView # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # - def select_minute(datetime, options = {}) + def select_minute(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : '' if options[:use_hidden] hidden_html(options[:field_name] || 'minute', val, options) @@ -371,11 +386,12 @@ module ActionView minute_options = [] 0.step(59, options[:minute_step] || 1) do |minute| minute_options << ((val == minute) ? - %(\n) : - %(\n) + content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute), :selected => "selected") : + content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute)) ) + minute_options << "\n" end - select_html(options[:field_name] || 'minute', minute_options, options) + select_html(options[:field_name] || 'minute', minute_options.join, options, html_options) end end @@ -396,7 +412,7 @@ module ActionView # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # - def select_hour(datetime, options = {}) + def select_hour(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : '' if options[:use_hidden] hidden_html(options[:field_name] || 'hour', val, options) @@ -404,11 +420,12 @@ module ActionView hour_options = [] 0.upto(23) do |hour| hour_options << ((val == hour) ? - %(\n) : - %(\n) + content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour), :selected => "selected") : + content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour)) ) + hour_options << "\n" end - select_html(options[:field_name] || 'hour', hour_options, options) + select_html(options[:field_name] || 'hour', hour_options.join, options, html_options) end end @@ -429,7 +446,7 @@ module ActionView # # that is named 'due' rather than 'day' # select_day(my_time, :field_name => 'due') # - def select_day(date, options = {}) + def select_day(date, options = {}, html_options = {}) val = date ? (date.kind_of?(Fixnum) ? date : date.day) : '' if options[:use_hidden] hidden_html(options[:field_name] || 'day', val, options) @@ -437,11 +454,12 @@ module ActionView day_options = [] 1.upto(31) do |day| day_options << ((val == day) ? - %(\n) : - %(\n) + content_tag(:option, day, :value => day, :selected => "selected") : + content_tag(:option, day, :value => day) ) + day_options << "\n" end - select_html(options[:field_name] || 'day', day_options, options) + select_html(options[:field_name] || 'day', day_options.join, options, html_options) end end @@ -479,7 +497,7 @@ module ActionView # # will use keys like "Januar", "Marts." # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # - def select_month(date, options = {}) + def select_month(date, options = {}, html_options = {}) val = date ? (date.kind_of?(Fixnum) ? date : date.month) : '' if options[:use_hidden] hidden_html(options[:field_name] || 'month', val, options) @@ -497,11 +515,12 @@ module ActionView end month_options << ((val == month_number) ? - %(\n) : - %(\n) + content_tag(:option, month_name, :value => month_number, :selected => "selected") : + content_tag(:option, month_name, :value => month_number) ) + month_options << "\n" end - select_html(options[:field_name] || 'month', month_options, options) + select_html(options[:field_name] || 'month', month_options.join, options, html_options) end end @@ -527,7 +546,7 @@ module ActionView # # has ascending year values # select_year(2006, :start_year => 2000, :end_year => 2010) # - def select_year(date, options = {}) + def select_year(date, options = {}, html_options = {}) val = date ? (date.kind_of?(Fixnum) ? date : date.year) : '' if options[:use_hidden] hidden_html(options[:field_name] || 'year', val, options) @@ -539,29 +558,31 @@ module ActionView step_val = start_year < end_year ? 1 : -1 start_year.step(end_year, step_val) do |year| year_options << ((val == year) ? - %(\n) : - %(\n) + content_tag(:option, year, :value => year, :selected => "selected") : + content_tag(:option, year, :value => year) ) + year_options << "\n" end - select_html(options[:field_name] || 'year', year_options, options) + select_html(options[:field_name] || 'year', year_options.join, options, html_options) end end private - def select_html(type, html_options, options) + def select_html(type, html_options, options, select_tag_options = {}) name_and_id_from_options(options, type) - select_html = %(\n" + content_tag(:select, select_html, select_options) + "\n" end def hidden_html(type, value, options) name_and_id_from_options(options, type) - hidden_html = %(\n) + hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n" end def name_and_id_from_options(options, type) @@ -577,20 +598,20 @@ module ActionView class InstanceTag #:nodoc: include DateHelper - def to_date_select_tag(options = {}) - date_or_time_select(options.merge(:discard_hour => true)) + def to_date_select_tag(options = {}, html_options = {}) + date_or_time_select(options.merge(:discard_hour => true), html_options) end - def to_time_select_tag(options = {}) - date_or_time_select options.merge(:discard_year => true, :discard_month => true) + def to_time_select_tag(options = {}, html_options = {}) + date_or_time_select(options.merge(:discard_year => true, :discard_month => true), html_options) end - def to_datetime_select_tag(options = {}) - date_or_time_select options + def to_datetime_select_tag(options = {}, html_options = {}) + date_or_time_select(options, html_options) end private - def date_or_time_select(options) + def date_or_time_select(options, html_options = {}) defaults = { :discard_type => true } options = defaults.merge(options) datetime = value(object) @@ -627,7 +648,7 @@ module ActionView # This ensures AR can reconstruct valid dates using ParseDate next if discard[param] && date_or_time_select.empty? - date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])))) + date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options)) date_or_time_select.insert(0, case param when :hour then (discard[:year] && discard[:day] ? "" : " — ") @@ -654,7 +675,7 @@ module ActionView def default_time_from_options(default) case default when nil - Time.now + Time.current when Date, Time default else @@ -662,26 +683,27 @@ module ActionView default[:min] ||= default[:minute] default[:sec] ||= default[:second] + time = Time.current + [:year, :month, :day, :hour, :min, :sec].each do |key| - default[key] ||= Time.now.send(key) + default[key] ||= time.send(key) end - Time.mktime(default[:year], default[:month], default[:day], - default[:hour], default[:min], default[:sec]) + Time.utc_time(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec]) end end end class FormBuilder - def date_select(method, options = {}) + def date_select(method, options = {}, html_options = {}) @template.date_select(@object_name, method, options.merge(:object => @object)) end - def time_select(method, options = {}) + def time_select(method, options = {}, html_options = {}) @template.time_select(@object_name, method, options.merge(:object => @object)) end - def datetime_select(method, options = {}) + def datetime_select(method, options = {}, html_options = {}) @template.datetime_select(@object_name, method, options.merge(:object => @object)) end end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb index 2bfb8966..0791feb9 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb @@ -1,6 +1,7 @@ require 'cgi' require 'action_view/helpers/date_helper' require 'action_view/helpers/tag_helper' +require 'action_view/helpers/form_tag_helper' module ActionView module Helpers @@ -32,6 +33,15 @@ module ActionView # # # + # If you are using a partial for your form fields, you can use this shortcut: + # + # <% form_for :person, @person, :url => { :action => "create" } do |f| %> + # <%= render :partial => f %> + # <%= submit_tag 'Create' %> + # <% end %> + # + # This example will render the people/_form partial, setting a local variable called form which references the yielded FormBuilder. + # # The params object created when this form is submitted would look like: # # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}} @@ -42,7 +52,7 @@ module ActionView # # If the object name contains square brackets the id for the object will be inserted. For example: # - # <%= text_field "person[]", "name" %> + # <%= text_field "person[]", "name" %> # # ...will generate the following ERb. # @@ -57,31 +67,87 @@ module ActionView # # # + # An index option may also be passed to form_for and fields_for. This automatically applies + # the index to all the nested fields. + # # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html, # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html module FormHelper - # Creates a form and a scope around a specific model object that is used as a base for questioning about - # values for the fields. + # Creates a form and a scope around a specific model object that is used as + # a base for questioning about values for the fields. # - # <% form_for :person, @person, :url => { :action => "update" } do |f| %> - # First name: <%= f.text_field :first_name %> - # Last name : <%= f.text_field :last_name %> - # Biography : <%= f.text_area :biography %> - # Admin? : <%= f.check_box :admin %> + # Rails provides succint resource-oriented form generation with +form_for+ + # like this: + # + # <% form_for @offer do |f| %> + # <%= f.label :version, 'Version' %>: + # <%= f.text_field :version %>
    + # <%= f.label :author, 'Author' %>: + # <%= f.text_field :author %>
    # <% end %> # - # Worth noting is that the form_for tag is called in a ERb evaluation block, not an ERb output block. So that's <% %>, - # not <%= %>. Also worth noting is that form_for yields a form_builder object, in this example as f, which emulates - # the API for the stand-alone FormHelper methods, but without the object name. So instead of text_field :person, :name, - # you get away with f.text_field :name. + # There, +form_for+ is able to generate the rest of RESTful form parameters + # based on introspection on the record, but to understand what it does we + # need to dig first into the alternative generic usage it is based upon. # - # Even further, the form_for method allows you to more easily escape the instance variable convention. So while the stand-alone - # approach would require text_field :person, :name, :object => person - # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with - # :person, person and all subsequent field calls save :person and :object => person. + # === Generic form_for # - # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods - # and methods from FormTagHelper. For example: + # The generic way to call +form_for+ yields a form builder around a model: + # + # <% form_for :person, :url => { :action => "update" } do |f| %> + # <%= f.error_messages %> + # First name: <%= f.text_field :first_name %>
    + # Last name : <%= f.text_field :last_name %>
    + # Biography : <%= f.text_area :biography %>
    + # Admin? : <%= f.check_box :admin %>
    + # <% end %> + # + # There, the first argument is a symbol or string with the name of the + # object the form is about, and also the name of the instance variable the + # object is stored in. + # + # The form builder acts as a regular form helper that somehow carries the + # model. Thus, the idea is that + # + # <%= f.text_field :first_name %> + # + # gets expanded to + # + # <%= text_field :person, :first_name %> + # + # If the instance variable is not @person you can pass the actual + # record as the second argument: + # + # <% form_for :person, person, :url => { :action => "update" } do |f| %> + # ... + # <% end %> + # + # In that case you can think + # + # <%= f.text_field :first_name %> + # + # gets expanded to + # + # <%= text_field :person, :first_name, :object => person %> + # + # You can even display error messages of the wrapped model this way: + # + # <%= f.error_messages %> + # + # In any of its variants, the rightmost argument to +form_for+ is an + # optional hash of options: + # + # * :url - The URL the form is submitted to. It takes the same fields + # you pass to +url_for+ or +link_to+. In particular you may pass here a + # named route directly as well. Defaults to the current action. + # * :html - Optional HTML attributes for the form tag. + # + # Worth noting is that the +form_for+ tag is called in a ERb evaluation block, + # not an ERb output block. So that's <% %>, not <%= %>. + # + # Also note that +form_for+ doesn't create an exclusive scope. It's still + # possible to use both the stand-alone FormHelper methods and methods from + # FormTagHelper. For example: # # <% form_for :person, @person, :url => { :action => "update" } do |f| %> # First name: <%= f.text_field :first_name %> @@ -90,42 +156,38 @@ module ActionView # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %> # <% end %> # - # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base, - # like FormOptionHelper#collection_select and DateHelper#datetime_select. + # This also works for the methods in FormOptionHelper and DateHelper that are + # designed to work with an object as base, like FormOptionHelper#collection_select + # and DateHelper#datetime_select. # - # HTML attributes for the form tag can be given as :html => {...}. For example: + # === Resource-oriented style # - # <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %> + # As we said above, in addition to manually configuring the +form_for+ call, + # you can rely on automated resource identification, which will use the conventions + # and named routes of that approach. This is the preferred way to use +form_for+ + # nowadays. + # + # For example, if @post is an existing record you want to edit + # + # <% form_for @post do |f| %> # ... # <% end %> # - # The above form will then have the id attribute with the value person_form, which you can then - # style with CSS or manipulate with JavaScript. - # - # === Relying on record identification - # - # In addition to manually configuring the form_for call, you can also rely on record identification, which will use - # the conventions and named routes of that approach. Examples: - # - # <% form_for(@post) do |f| %> - # ... - # <% end %> - # - # This will expand to be the same as: + # is equivalent to something like: # # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %> # ... # <% end %> # - # And for new records: + # And for new records # # <% form_for(Post.new) do |f| %> # ... # <% end %> # - # This will expand to be the same as: + # expands to # - # <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> + # <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> # ... # <% end %> # @@ -135,7 +197,7 @@ module ActionView # ... # <% end %> # - # And for namespaced routes, like admin_post_url: + # And for namespaced routes, like +admin_post_url+: # # <% form_for([:admin, @post]) do |f| %> # ... @@ -153,10 +215,17 @@ module ActionView # <%= check_box_tag "person[admin]", @person.company.admin? %> # <% end %> # + # In this case, if you use this: + # + # <%= render :partial => f %> + # + # The rendered template is people/_labelling_form and the local variable referencing the form builder is called labelling_form. + # # In many cases you will want to wrap the above in another helper, so you could do something like the following: # - # def labelled_form_for(name, object, options, &proc) - # form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc) + # def labelled_form_for(record_or_name_or_array, *args, &proc) + # options = args.extract_options! + # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc) # end # # If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag. @@ -255,13 +324,13 @@ module ActionView # # ==== Examples # label(:post, :title) - # #=> + # # => # # label(:post, :title, "A short title") - # #=> + # # => # # label(:post, :title, "A short title", :class => "title_label") - # #=> + # # => # def label(object_name, method, text = nil, options = {}) InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options) @@ -316,7 +385,7 @@ module ActionView # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example # shown. # - # ==== Examples + # ==== Examples # hidden_field(:signup, :pass_confirm) # # => # @@ -383,10 +452,10 @@ module ActionView # is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything, # we add a hidden value with the same name as the checkbox as a work around. # - # ==== Examples + # ==== Examples # # Let's say that @post.validated? is 1: # check_box("post", "validated") - # # => + # # => # # # # # Let's say that @puppy.gooddog is "no": @@ -394,8 +463,8 @@ module ActionView # # => # # # - # check_box("eula", "accepted", {}, "yes", "no", :class => 'eula_check') - # # => + # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no") + # # => # # # def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") @@ -411,27 +480,26 @@ module ActionView # # Let's say that @post.category returns "rails": # radio_button("post", "category", "rails") # radio_button("post", "category", "java") - # # => - # # + # # => + # # # # radio_button("user", "receive_newsletter", "yes") # radio_button("user", "receive_newsletter", "no") - # # => - # # + # # => + # # def radio_button(object_name, method, tag_value, options = {}) InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options) end end class InstanceTag #:nodoc: - include Helpers::TagHelper + include Helpers::TagHelper, Helpers::FormTagHelper attr_reader :method_name, :object_name DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS) DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS) DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS) - DEFAULT_DATE_OPTIONS = { :discard_type => true }.freeze unless const_defined?(:DEFAULT_DATE_OPTIONS) def initialize(object_name, method_name, template_object, local_binding = nil, object = nil) @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup @@ -447,11 +515,13 @@ module ActionView end def to_label_tag(text = nil, options = {}) + options = options.stringify_keys name_and_id = options.dup add_default_name_and_id(name_and_id) - options["for"] = name_and_id["id"] + options.delete("index") + options["for"] ||= name_and_id["id"] content = (text.blank? ? nil : text.to_s) || method_name.humanize - content_tag("label", content, options) + label_tag(name_and_id["id"], content, options) end def to_input_field_tag(field_type, options = {}) @@ -463,6 +533,7 @@ module ActionView end options["type"] = field_type options["value"] ||= value_before_type_cast(object) unless field_type == "file" + options["value"] &&= html_escape(options["value"]) add_default_name_and_id(options) tag("input", options) end @@ -480,8 +551,8 @@ module ActionView options["checked"] = "checked" if checked pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase options["id"] ||= defined?(@auto_index) ? - "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" : - "#{@object_name}_#{@method_name}_#{pretty_tag_value}" + "#{tag_id_with_index(@auto_index)}_#{pretty_tag_value}" : + "#{tag_id}_#{pretty_tag_value}" add_default_name_and_id(options) tag("input", options) end @@ -512,15 +583,6 @@ module ActionView tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value) end - def to_date_tag() - defaults = DEFAULT_DATE_OPTIONS.dup - date = value(object) || Date.today - options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") } - html_day_select(date, options.call(3)) + - html_month_select(date, options.call(2)) + - html_year_select(date, options.call(1)) - end - def to_boolean_select_tag(options = {}) options = options.stringify_keys add_default_name_and_id(options) @@ -573,6 +635,8 @@ module ActionView value != 0 when String value == checked_value + when Array + value.include?(checked_value) else value.to_i != 0 end @@ -599,23 +663,27 @@ module ActionView end def tag_name - "#{@object_name}[#{@method_name}]" + "#{@object_name}[#{sanitized_method_name}]" end def tag_name_with_index(index) - "#{@object_name}[#{index}][#{@method_name}]" + "#{@object_name}[#{index}][#{sanitized_method_name}]" end def tag_id - "#{sanitized_object_name}_#{@method_name}" + "#{sanitized_object_name}_#{sanitized_method_name}" end def tag_id_with_index(index) - "#{sanitized_object_name}_#{index}_#{@method_name}" + "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" end def sanitized_object_name - @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") + @sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") + end + + def sanitized_method_name + @sanitized_method_name ||= @method_name.sub(/\?$/,"") end end @@ -628,12 +696,13 @@ module ActionView def initialize(object_name, object, template, options, proc) @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc + @default_options = @options ? @options.slice(:index) : {} end (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| src = <<-end_src def #{selector}(method, options = {}) - @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object)) + @template.send(#{selector.inspect}, @object_name, method, objectify_options(options)) end end_src class_eval src, __FILE__, __LINE__ @@ -652,20 +721,20 @@ module ActionView name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" args.unshift(object) end - + @template.fields_for(name, *args, &block) end def label(method, text = nil, options = {}) - @template.label(@object_name, method, text, options.merge(:object => @object)) + @template.label(@object_name, method, text, objectify_options(options)) end def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") - @template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value) + @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value) end def radio_button(method, tag_value, options = {}) - @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object)) + @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError") @@ -673,12 +742,17 @@ module ActionView end def error_messages(options = {}) - @template.error_messages_for(@object_name, options.merge(:object => @object)) + @template.error_messages_for(@object_name, objectify_options(options)) end def submit(value = "Save changes", options = {}) @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit")) end + + private + def objectify_options(options) + @default_options.merge(options.merge(:object => @object)) + end end end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb index 0f1d2b02..e0a097e3 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -53,6 +53,21 @@ module ActionView # # # + # + # Like the other form helpers, +select+ can accept an :index option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this + # option to be in the +html_options+ parameter. + # + # Example: + # + # select("album[]", "genre", %w[rap rock country], {}, { :index => nil }) + # + # becomes: + # + # module FormOptionsHelper include ERB::Util @@ -78,8 +93,8 @@ module ActionView # This allows the user to submit a form page more than once with the expected results of creating multiple records. # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms. # - # By default, post.person_id is the selected option. Specify :selected => value to use a different selection - # or :selected => nil to leave all options unselected. + # By default, post.person_id is the selected option. Specify :selected => value to use a different selection + # or :selected => nil to leave all options unselected. def select(object, method, choices, options = {}, html_options = {}) InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options) end @@ -104,7 +119,7 @@ module ActionView # end # end # - # Sample usage (selecting the associated +Author+ for an instance of +Post+, @post): + # Sample usage (selecting the associated Author for an instance of Post, @post): # collection_select(:post, :author_id, Author.find(:all), :id, :name_with_initial, {:prompt => true}) # # If @post.author_id is already 1, this would return: @@ -129,8 +144,27 @@ module ActionView # In addition to the :include_blank option documented above, # this method also supports a :model option, which defaults # to TimeZone. This may be used by users to specify a different time - # zone model object. (See #time_zone_options_for_select for more + # zone model object. (See +time_zone_options_for_select+ for more # information.) + # + # You can also supply an array of TimeZone objects + # as +priority_zones+, so that they will be listed above the rest of the + # (long) list. (You can use TimeZone.us_zones as a convenience for + # obtaining a list of the US time zones.) + # + # Finally, this method supports a :default option, which selects + # a default TimeZone if the object's time zone is +nil+. + # + # Examples: + # time_zone_select( "user", "time_zone", nil, :include_blank => true) + # + # time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" ) + # + # time_zone_select( "user", 'time_zone', TimeZone.us_zones, :default => "Pacific Time (US & Canada)") + # + # time_zone_select( "user", 'time_zone', [ TimeZone['Alaska'], TimeZone['Hawaii'] ]) + # + # time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone) def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {}) InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options) end @@ -138,7 +172,7 @@ module ActionView # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values - # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+ + # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+ # may also be an array of values to be selected when using a multiple select. # # Examples (call, result): @@ -183,24 +217,22 @@ module ActionView options_for_select(options, selected) end - # Returns a string of tags, like #options_from_collection_for_select, but + # Returns a string of tags, like options_from_collection_for_select, but # groups them by tags based on the object relationships of the arguments. # # Parameters: - # +collection+:: An array of objects representing the tags - # +group_method+:: The name of a method which, when called on a member of +collection+, returns an - # array of child objects representing the tags - # +group_label_method+:: The name of a method which, when called on a member of +collection+, returns a - # string to be used as the +label+ attribute for its tag - # +option_key_method+:: The name of a method which, when called on a child object of a member of - # +collection+, returns a value to be used as the +value+ attribute for its - # tag - # +option_value_method+:: The name of a method which, when called on a child object of a member of - # +collection+, returns a value to be used as the contents of its - # tag - # +selected_key+:: A value equal to the +value+ attribute for one of the tags, - # which will have the +selected+ attribute set. Corresponds to the return value - # of one of the calls to +option_key_method+. If +nil+, no selection is made. + # * +collection+ - An array of objects representing the tags. + # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an + # array of child objects representing the tags. + # * group_label_method+ - The name of a method which, when called on a member of +collection+, returns a + # string to be used as the +label+ attribute for its tag. + # * +option_key_method+ - The name of a method which, when called on a child object of a member of + # +collection+, returns a value to be used as the +value+ attribute for its tag. + # * +option_value_method+ - The name of a method which, when called on a child object of a member of + # +collection+, returns a value to be used as the contents of its tag. + # * +selected_key+ - A value equal to the +value+ attribute for one of the tags, + # which will have the +selected+ attribute set. Corresponds to the return value of one of the calls + # to +option_key_method+. If +nil+, no selection is made. # # Example object structure for use with this method: # class Continent < ActiveRecord::Base @@ -266,8 +298,8 @@ module ActionView # a TimeZone. # # By default, +model+ is the TimeZone constant (which can be obtained - # in ActiveRecord as a value object). The only requirement is that the - # +model+ parameter be an object that responds to #all, and returns + # in Active Record as a value object). The only requirement is that the + # +model+ parameter be an object that responds to +all+, and returns # an array of objects that represent time zones. # # NOTE: Only the option tags are returned, you have to wrap this call in @@ -385,7 +417,7 @@ module ActionView value = value(object) content_tag("select", add_options( - time_zone_options_for_select(value, priority_zones, options[:model] || TimeZone), + time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || TimeZone), options, value ), html_options ) diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb index 07746508..ca58f4ba 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -3,7 +3,7 @@ require 'action_view/helpers/tag_helper' module ActionView module Helpers - # Provides a number of methods for creating form tags that doesn't rely on an ActiveRecord object assigned to the template like + # Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like # FormHelper does. Instead, you provide the names and values manually. # # NOTE: The HTML options disabled, readonly, and multiple can all be treated as booleans. So specifying @@ -14,9 +14,9 @@ module ActionView # # ==== Options # * :multipart - If set to true, the enctype is set to "multipart/form-data". - # * :method - The method to use when submitting the form, usually either "get" or "post". - # If "put", "delete", or another verb is used, a hidden input with name _method - # is added to simulate the verb over post. + # * :method - The method to use when submitting the form, usually either "get" or "post". + # If "put", "delete", or another verb is used, a hidden input with name _method + # is added to simulate the verb over post. # * A list of parameters to feed to the URL the form will be posted to. # # ==== Examples @@ -114,6 +114,24 @@ module ActionView tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys) end + # Creates a label field + # + # ==== Options + # * Creates standard HTML attributes for the tag. + # + # ==== Examples + # label_tag 'name' + # # => + # + # label_tag 'name', 'Your name' + # # => + # + # label_tag 'name', nil, :class => 'small_label' + # # => + def label_tag(name, text = nil, options = {}) + content_tag :label, text || name.humanize, { "for" => name }.update(options.stringify_keys) + end + # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or # data that should be hidden from the user. # @@ -298,9 +316,12 @@ module ActionView # Creates a submit button with the text value as the caption. # # ==== Options - # * :disabled - If set to true, the user will not be able to use this input. + # * :confirm => 'question?' - This will add a JavaScript confirm + # prompt with the question specified. If the user accepts, the form is + # processed normally, otherwise no action is taken. + # * :disabled - If true, the user will not be able to use this input. # * :disable_with - Value of this parameter will be used as the value for a disabled version - # of the submit button when the form is submitted. + # of the submit button when the form is submitted. # * Any other key creates standard HTML options for the tag. # # ==== Examples @@ -320,8 +341,9 @@ module ActionView # submit_tag nil, :class => "form_submit" # # => # - # submit_tag "Edit", :disable_with => "Editing...", :class => 'edit-button' - # # => + # submit_tag "Edit", :disable_with => "Editing...", :class => "edit-button" + # # => def submit_tag(value = "Save changes", options = {}) options.stringify_keys! @@ -333,10 +355,15 @@ module ActionView "#{options["onclick"]}", "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())", "if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }", - "return result", + "return result;", ].join(";") end - + + if confirm = options.delete("confirm") + options["onclick"] ||= '' + options["onclick"] += "return #{confirm_javascript_function(confirm)};" + end + tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys) end @@ -352,13 +379,13 @@ module ActionView # image_submit_tag("login.png") # # => # - # image_submit_tag("purchase.png"), :disabled => true + # image_submit_tag("purchase.png", :disabled => true) # # => # - # image_submit_tag("search.png"), :class => 'search-button' + # image_submit_tag("search.png", :class => 'search-button') # # => # - # image_submit_tag("agree.png"), :disabled => true, :class => "agree-disagree-button" + # image_submit_tag("agree.png", :disabled => true, :class => "agree-disagree-button") # # => def image_submit_tag(source, options = {}) tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options.stringify_keys) diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js index fbc4418b..5aaf0bb2 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) // Contributors: diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js index ccf4a1e4..bf5cfea6 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) // // script.aculo.us is freely distributable under the terms of an MIT-style license. diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js index 65aed239..f030b5db 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) diff --git a/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb index eae230ad..120bb4cc 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb @@ -54,6 +54,10 @@ module ActionView # * :unit - Sets the denomination of the currency (defaults to "$"). # * :separator - Sets the separator between the units (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults to ","). + # * :format - Sets the format of the output string (defaults to "%u%n"). The field types are: + # + # %u The currency unit + # %n The number # # ==== Examples # number_to_currency(1234567890.50) # => $1,234,567,890.50 @@ -62,16 +66,19 @@ module ActionView # # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "") # # => £1234567890,50 + # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") + # # => 1234567890,50 £ def number_to_currency(number, options = {}) options = options.stringify_keys precision = options["precision"] || 2 unit = options["unit"] || "$" separator = precision > 0 ? options["separator"] || "." : "" delimiter = options["delimiter"] || "," + format = options["format"] || "%u%n" begin parts = number_with_precision(number, precision).split('.') - unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s + format.gsub(/%n/, number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s).gsub(/%u/, unit) rescue number end @@ -137,11 +144,11 @@ module ActionView # # ==== Examples # number_with_precision(111.2345) # => 111.235 - # number_with_precision(111.2345, 2) # => 111.24 + # number_with_precision(111.2345, 2) # => 111.23 # number_with_precision(13, 5) # => 13.00000 # number_with_precision(389.32314, 0) # => 389 def number_with_precision(number, precision=3) - "%01.#{precision}f" % number + "%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision) rescue number end @@ -170,7 +177,7 @@ module ActionView when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte) when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte) else "%.#{precision}f TB" % (size / 1.0.terabyte) - end.sub(/([0-9])\.?0+ /, '\1 ' ) + end.sub(/([0-9]\.\d*?)0+ /, '\1 ' ).sub(/\. /,' ') rescue nil end diff --git a/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb index 0435f5c4..602832e4 100644 --- a/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -255,10 +255,10 @@ module ActionView link_to_function(name, remote_function(options), html_options || options.delete(:html)) end - # Periodically calls the specified url (options[:url]) every + # Periodically calls the specified url (options[:url]) every # options[:frequency] seconds (default is 10). Usually used to - # update a specified div (options[:update]) with the results - # of the remote call. The options for specifying the target with :url + # update a specified div (options[:update]) with the results + # of the remote call. The options for specifying the target with :url # and defining callbacks is the same as link_to_remote. # Examples: # # Call get_averages and put its results in 'avg' every 10 seconds @@ -291,11 +291,11 @@ module ActionView # though it's using JavaScript to serialize the form elements, the form # submission will work just like a regular submission as viewed by the # receiving side (all elements available in params). The options for - # specifying the target with :url and defining callbacks is the same as - # link_to_remote. + # specifying the target with :url and defining callbacks is the same as + # +link_to_remote+. # # A "fall-through" target for browsers that doesn't do JavaScript can be - # specified with the :action/:method options on :html. + # specified with the :action/:method options on :html. # # Example: # # Generates: @@ -304,11 +304,11 @@ module ActionView # form_remote_tag :html => { :action => # url_for(:controller => "some", :action => "place") } # - # The Hash passed to the :html key is equivalent to the options (2nd) + # The Hash passed to the :html key is equivalent to the options (2nd) # argument in the FormTagHelper.form_tag method. # # By default the fall-through action is the same as the one specified in - # the :url (and the default method is :post). + # the :url (and the default method is :post). # # form_remote_tag also takes a block, like form_tag: # # Generates: @@ -422,8 +422,8 @@ module ActionView end # Returns 'eval(request.responseText)' which is the JavaScript function - # that form_remote_tag can call in :complete to evaluate a multiple - # update return document using update_element_function calls. + # that +form_remote_tag+ can call in :complete to evaluate a multiple + # update return document using +update_element_function+ calls. def evaluate_remote_response "eval(request.responseText)" end @@ -458,7 +458,7 @@ module ActionView url_options = options[:url] url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash) - function << "'#{url_for(url_options)}'" + function << "'#{escape_javascript(url_for(url_options))}'" function << ", #{javascript_options})" function = "#{options[:before]}; #{function}" if options[:before] @@ -595,8 +595,8 @@ module ActionView # JavaScript sent with a Content-type of "text/javascript". # # Create new instances with PrototypeHelper#update_page or with - # ActionController::Base#render, then call #insert_html, #replace_html, - # #remove, #show, #hide, #visual_effect, or any other of the built-in + # ActionController::Base#render, then call +insert_html+, +replace_html+, + # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in # methods on the yielded generator in any order you like to modify the # content and appearance of the current page. # @@ -631,6 +631,27 @@ module ActionView # render(:update) { |page| page.update_time } # end # + # Calls to JavaScriptGenerator not matching a helper method below + # generate a proxy to the JavaScript Class named by the method called. + # + # Examples: + # + # # Generates: + # # Foo.init(); + # update_page do |page| + # page.foo.init + # end + # + # # Generates: + # # Event.observe('one', 'click', function () { + # # $('two').show(); + # # }); + # update_page do |page| + # page.event.observe('one', 'click') do |p| + # p[:two].show + # end + # end + # # You can also use PrototypeHelper#update_page_tag instead of # PrototypeHelper#update_page to wrap the generated JavaScript in a # " elsif encode == "hex" @@ -403,12 +457,9 @@ module ActionView protocol = 'mailto:' protocol.each_byte { |c| string << sprintf("&#%d;", c) } - for i in 0...email_address.length - if email_address[i,1] =~ /\w/ - string << sprintf("%%%x",email_address[i]) - else - string << email_address[i,1] - end + email_address.each_byte do |c| + char = c.chr + string << (char =~ /\w/ ? sprintf("%%%x", c) : char) end content_tag "a", name || email_address_encoded, html_options.merge({ "href" => "#{string}#{extras}" }) else diff --git a/vendor/rails/actionpack/lib/action_view/inline_template.rb b/vendor/rails/actionpack/lib/action_view/inline_template.rb new file mode 100644 index 00000000..87c012d1 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/inline_template.rb @@ -0,0 +1,20 @@ +module ActionView #:nodoc: + class InlineTemplate < Template #:nodoc: + + def initialize(view, source, locals = {}, type = nil) + @view = view + @finder = @view.finder + + @source = source + @extension = type + @locals = locals || {} + + @handler = self.class.handler_class_for_extension(@extension).new(@view) + end + + def method_key + @source + end + + end +end diff --git a/vendor/rails/actionpack/lib/action_view/partial_template.rb b/vendor/rails/actionpack/lib/action_view/partial_template.rb new file mode 100644 index 00000000..1fb3aaee --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/partial_template.rb @@ -0,0 +1,70 @@ +module ActionView #:nodoc: + class PartialTemplate < Template #:nodoc: + + attr_reader :variable_name, :object + + def initialize(view, partial_path, object = nil, locals = {}) + @path, @variable_name = extract_partial_name_and_path(view, partial_path) + super(view, @path, true, locals) + add_object_to_local_assigns!(object) + + # This is needed here in order to compile template with knowledge of 'counter' + initialize_counter + + # Prepare early. This is a performance optimization for partial collections + prepare! + end + + def render + ActionController::Base.benchmark("Rendered #{@path}", Logger::DEBUG, false) do + @handler.render(self) + end + end + + def render_member(object) + @locals[@counter_name] += 1 + @locals[:object] = @locals[@variable_name] = object + + template = render_template + @locals.delete(@variable_name) + @locals.delete(:object) + + template + end + + def counter=(num) + @locals[@counter_name] = num + end + + private + + def add_object_to_local_assigns!(object) + @locals[:object] ||= + @locals[@variable_name] ||= + if object.is_a?(ActionView::Base::ObjectWrapper) + object.value + else + object + end || @view.controller.instance_variable_get("@#{variable_name}") + end + + def extract_partial_name_and_path(view, partial_path) + path, partial_name = partial_pieces(view, partial_path) + [File.join(path, "_#{partial_name}"), partial_name.split('/').last.split('.').first.to_sym] + end + + def partial_pieces(view, partial_path) + if partial_path.include?('/') + return File.dirname(partial_path), File.basename(partial_path) + else + return view.controller.class.controller_path, partial_path + end + end + + def initialize_counter + @counter_name ||= "#{@variable_name}_counter".to_sym + @locals[@counter_name] = 0 + end + + end +end diff --git a/vendor/rails/actionpack/lib/action_view/partials.rb b/vendor/rails/actionpack/lib/action_view/partials.rb index 0795b8ec..6b294be6 100644 --- a/vendor/rails/actionpack/lib/action_view/partials.rb +++ b/vendor/rails/actionpack/lib/action_view/partials.rb @@ -100,101 +100,59 @@ module ActionView # Title: <%= chief.name %> # # - # As you can see, the :locals hash is shared between both the partial and its layout. + # As you can see, the :locals hash is shared between both the partial and its layout. module Partials private - def render_partial(partial_path, object_assigns = nil, local_assigns = nil) #:nodoc: + def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc: case partial_path when String, Symbol, NilClass - path, partial_name = partial_pieces(partial_path) - object = extracting_object(partial_name, object_assigns) - local_assigns = local_assigns ? local_assigns.clone : {} - add_counter_to_local_assigns!(partial_name, local_assigns) - add_object_to_local_assigns!(partial_name, local_assigns, object) - - if logger && logger.debug? - ActionController::Base.benchmark("Rendered #{path}/_#{partial_name}", Logger::DEBUG, false) do - render("#{path}/_#{partial_name}", local_assigns) - end - else - render("#{path}/_#{partial_name}", local_assigns) - end - when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Associations::HasManyThroughAssociation + # Render the template + ActionView::PartialTemplate.new(self, partial_path, object_assigns, local_assigns).render_template + when ActionView::Helpers::FormBuilder + builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '') + render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path)) + when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope if partial_path.any? - path = ActionController::RecordIdentifier.partial_path(partial_path.first) collection = partial_path - render_partial_collection(path, collection, nil, object_assigns.value) + render_partial_collection(nil, collection, nil, local_assigns) else "" end else - render_partial( - ActionController::RecordIdentifier.partial_path(partial_path), - object_assigns, local_assigns) + render_partial(ActionController::RecordIdentifier.partial_path(partial_path, controller.class.controller_path), partial_path, local_assigns) end end - def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil) #:nodoc: - collection_of_partials = Array.new - counter_name = partial_counter_name(partial_name) + def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}) #:nodoc: + return " " if collection.empty? + local_assigns = local_assigns ? local_assigns.clone : {} - collection.each_with_index do |element, counter| - local_assigns[counter_name] = counter - collection_of_partials.push(render_partial(partial_name, element, local_assigns)) - end + spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : '' - return " " if collection_of_partials.empty? - - if partial_spacer_template - spacer_path, spacer_name = partial_pieces(partial_spacer_template) - collection_of_partials.join(render("#{spacer_path}/_#{spacer_name}")) + if partial_path.nil? + render_partial_collection_with_unknown_partial_path(collection, local_assigns) else - collection_of_partials.join + render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns) + end.join(spacer) + end + + def render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns) + template = ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns) + collection.map do |element| + template.render_member(element) end end - alias_method :render_collection_of_partials, :render_partial_collection - - def partial_pieces(partial_path) - if partial_path.include?('/') - return File.dirname(partial_path), File.basename(partial_path) - else - return controller.class.controller_path, partial_path + def render_partial_collection_with_unknown_partial_path(collection, local_assigns) + templates = Hash.new + i = 0 + collection.map do |element| + partial_path = ActionController::RecordIdentifier.partial_path(element, controller.class.controller_path) + template = templates[partial_path] ||= ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns) + template.counter = i + i += 1 + template.render_member(element) end end - - def partial_counter_name(partial_name) - "#{partial_variable_name(partial_name)}_counter".intern - end - - def partial_variable_name(partial_name) - partial_name.split('/').last.split('.').first.intern - end - - def extracting_object(partial_name, object_assigns) - variable_name = partial_variable_name(partial_name) - if object_assigns.nil? - controller.instance_variable_get("@#{variable_name}") - else - object_assigns - end - end - - def add_counter_to_local_assigns!(partial_name, local_assigns) - counter_name = partial_counter_name(partial_name) - local_assigns[counter_name] = 1 unless local_assigns.has_key?(counter_name) - end - - def add_object_to_local_assigns!(partial_name, local_assigns, object) - variable_name = partial_variable_name(partial_name) - - local_assigns[:object] ||= - local_assigns[variable_name] ||= - if object.is_a?(ActionView::Base::ObjectWrapper) - object.value - else - object - end || controller.instance_variable_get("@#{variable_name}") - end end end diff --git a/vendor/rails/actionpack/lib/action_view/template.rb b/vendor/rails/actionpack/lib/action_view/template.rb new file mode 100644 index 00000000..36952618 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/template.rb @@ -0,0 +1,127 @@ +module ActionView #:nodoc: + class Template #:nodoc: + + attr_accessor :locals + attr_reader :handler, :path, :extension, :filename, :path_without_extension, :method + + def initialize(view, path, use_full_path, locals = {}) + @view = view + @finder = @view.finder + + # Clear the forward slash at the beginning if exists + @path = use_full_path ? path.sub(/^\//, '') : path + @view.first_render ||= @path + @source = nil # Don't read the source until we know that it is required + set_extension_and_file_name(use_full_path) + + @locals = locals || {} + @handler = self.class.handler_class_for_extension(@extension).new(@view) + end + + def render_template + render + rescue Exception => e + raise e unless filename + if TemplateError === e + e.sub_template_of(filename) + raise e + else + raise TemplateError.new(self, @view.assigns, e) + end + end + + def render + prepare! + @handler.render(self) + end + + def source + @source ||= File.read(self.filename) + end + + def method_key + @filename + end + + def base_path_for_exception + @finder.find_base_path_for("#{@path_without_extension}.#{@extension}") || @finder.view_paths.first + end + + def prepare! + @view.send :evaluate_assigns + @view.current_render_extension = @extension + + if @handler.compilable? + @handler.compile_template(self) # compile the given template, if necessary + @method = @view.method_names[method_key] # Set the method name for this template and run it + end + end + + private + + def set_extension_and_file_name(use_full_path) + @path_without_extension, @extension = @finder.path_and_extension(@path) + if use_full_path + if @extension + @filename = @finder.pick_template(@path_without_extension, @extension) + else + @extension = @finder.pick_template_extension(@path).to_s + raise_missing_template_exception unless @extension + + @filename = @finder.pick_template(@path, @extension) + @extension = @extension.gsub(/^.+\./, '') # strip off any formats + end + else + @filename = @path + end + + raise_missing_template_exception if @filename.blank? + end + + def raise_missing_template_exception + full_template_path = @path.include?('.') ? @path : "#{@path}.#{@view.template_format}.erb" + display_paths = @finder.view_paths.join(':') + template_type = (@path =~ /layouts/i) ? 'layout' : 'template' + raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}") + end + + # Template Handlers + + @@template_handlers = HashWithIndifferentAccess.new + @@default_template_handlers = nil + + # Register a class that knows how to handle template files with the given + # extension. This can be used to implement new template types. + # The constructor for the class must take the ActiveView::Base instance + # as a parameter, and the class must implement a +render+ method that + # takes the contents of the template to render as well as the Hash of + # local assigns available to the template. The +render+ method ought to + # return the rendered template as a string. + def self.register_template_handler(extension, klass) + @@template_handlers[extension.to_sym] = klass + TemplateFinder.update_extension_cache_for(extension.to_s) + end + + def self.template_handler_extensions + @@template_handlers.keys.map(&:to_s).sort + end + + def self.register_default_template_handler(extension, klass) + register_template_handler(extension, klass) + @@default_template_handlers = klass + end + + def self.handler_class_for_extension(extension) + (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers + end + + register_default_template_handler :erb, TemplateHandlers::ERB + register_template_handler :rjs, TemplateHandlers::RJS + register_template_handler :builder, TemplateHandlers::Builder + + # TODO: Depreciate old template extensions + register_template_handler :rhtml, TemplateHandlers::ERB + register_template_handler :rxml, TemplateHandlers::Builder + + end +end diff --git a/vendor/rails/actionpack/lib/action_view/template_error.rb b/vendor/rails/actionpack/lib/action_view/template_error.rb index 01b4b155..65d80362 100644 --- a/vendor/rails/actionpack/lib/action_view/template_error.rb +++ b/vendor/rails/actionpack/lib/action_view/template_error.rb @@ -6,10 +6,11 @@ module ActionView attr_reader :original_exception - def initialize(base_path, file_path, assigns, source, original_exception) - @base_path, @assigns, @source, @original_exception = - base_path, assigns.dup, source, original_exception - @file_path = file_path + def initialize(template, assigns, original_exception) + @base_path = template.base_path_for_exception + @assigns, @source, @original_exception = assigns.dup, template.source, original_exception + @file_path = template.filename + @backtrace = compute_backtrace end def message @@ -40,8 +41,9 @@ module ActionView indent = ' ' * indentation line_counter = start_on_line - - source_code[start_on_line..end_on_line].sum do |line| + return unless source_code = source_code[start_on_line..end_on_line] + + source_code.sum do |line| line_counter += 1 "#{indent}#{line_counter}: #{line}" end @@ -72,14 +74,20 @@ module ActionView "#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n" end + # don't do anything nontrivial here. Any raised exception from here becomes fatal + # (and can't be rescued). def backtrace - [ - "#{source_location.capitalize}\n\n#{source_extract(4)}\n " + - clean_backtrace.join("\n ") - ] + @backtrace end private + def compute_backtrace + [ + "#{source_location.capitalize}\n\n#{source_extract(4)}\n " + + clean_backtrace.join("\n ") + ] + end + def strip_base_path(path) stripped_path = File.expand_path(path).gsub(@base_path, "") stripped_path.gsub!(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '') if defined?(RAILS_ROOT) diff --git a/vendor/rails/actionpack/lib/action_view/template_finder.rb b/vendor/rails/actionpack/lib/action_view/template_finder.rb new file mode 100644 index 00000000..83b7e27c --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/template_finder.rb @@ -0,0 +1,177 @@ +module ActionView #:nodoc: + class TemplateFinder #:nodoc: + + class InvalidViewPath < StandardError #:nodoc: + attr_reader :unprocessed_path + def initialize(path) + @unprocessed_path = path + super("Unprocessed view path found: #{@unprocessed_path.inspect}. Set your view paths with #append_view_path, #prepend_view_path, or #view_paths=.") + end + end + + cattr_reader :processed_view_paths + @@processed_view_paths = Hash.new {|hash, key| hash[key] = []} + + cattr_reader :file_extension_cache + @@file_extension_cache = Hash.new {|hash, key| + hash[key] = Hash.new {|hash, key| hash[key] = []} + } + + class << self #:nodoc: + + # This method is not thread safe. Mutex should be used whenever this is accessed from an instance method + def process_view_paths(*view_paths) + view_paths.flatten.compact.each do |dir| + next if @@processed_view_paths.has_key?(dir) + @@processed_view_paths[dir] = [] + + # + # Dir.glob("#{dir}/**/*/**") reads all the directories in view path and templates inside those directories + # Dir.glob("#{dir}/**") reads templates residing at top level of view path + # + (Dir.glob("#{dir}/**/*/**") | Dir.glob("#{dir}/**")).each do |file| + unless File.directory?(file) + @@processed_view_paths[dir] << file.split(dir).last.sub(/^\//, '') + + # Build extension cache + extension = file.split(".").last + if template_handler_extensions.include?(extension) + key = file.split(dir).last.sub(/^\//, '').sub(/\.(\w+)$/, '') + @@file_extension_cache[dir][key] << extension + end + end + end + end + end + + def update_extension_cache_for(extension) + @@processed_view_paths.keys.each do |dir| + Dir.glob("#{dir}/**/*.#{extension}").each do |file| + key = file.split(dir).last.sub(/^\//, '').sub(/\.(\w+)$/, '') + @@file_extension_cache[dir][key] << extension + end + end + end + + def template_handler_extensions + ActionView::Template.template_handler_extensions + end + + def reload! + view_paths = @@processed_view_paths.keys + + @@processed_view_paths = Hash.new {|hash, key| hash[key] = []} + @@file_extension_cache = Hash.new {|hash, key| + hash[key] = Hash.new {|hash, key| hash[key] = []} + } + + process_view_paths(view_paths) + end + end + + attr_accessor :view_paths + + def initialize(*args) + @template = args.shift + + @view_paths = args.flatten + @view_paths = @view_paths.respond_to?(:find) ? @view_paths.dup : [*@view_paths].compact + check_view_paths(@view_paths) + end + + def prepend_view_path(path) + @view_paths.unshift(*path) + + self.class.process_view_paths(path) + end + + def append_view_path(path) + @view_paths.push(*path) + + self.class.process_view_paths(path) + end + + def view_paths=(path) + @view_paths = path + self.class.process_view_paths(path) + end + + def pick_template(template_path, extension) + file_name = "#{template_path}.#{extension}" + base_path = find_base_path_for(file_name) + base_path.blank? ? false : "#{base_path}/#{file_name}" + end + alias_method :template_exists?, :pick_template + + def file_exists?(template_path) + # Clear the forward slash in the beginning if exists + template_path = template_path.sub(/^\//, '') + + template_file_name, template_file_extension = path_and_extension(template_path) + + if template_file_extension + template_exists?(template_file_name, template_file_extension) + else + template_exists?(template_file_name, pick_template_extension(template_path)) + end + end + + def find_base_path_for(template_file_name) + @view_paths.find { |path| @@processed_view_paths[path].include?(template_file_name) } + end + + # Returns the view path that the full path resides in. + def extract_base_path_from(full_path) + @view_paths.find { |p| full_path[0..p.size - 1] == p } + end + + # Gets the extension for an existing template with the given template_path. + # Returns the format with the extension if that template exists. + # + # pick_template_extension('users/show') + # # => 'html.erb' + # + # pick_template_extension('users/legacy') + # # => "rhtml" + # + def pick_template_extension(template_path) + if extension = find_template_extension_from_handler(template_path, @template.template_format) || find_template_extension_from_first_render + extension + elsif @template.template_format == :js && extension = find_template_extension_from_handler(template_path, :html) + @template.template_format = :html + extension + end + end + + def find_template_extension_from_handler(template_path, template_format = @template.template_format) + formatted_template_path = "#{template_path}.#{template_format}" + + view_paths.each do |path| + if (extensions = @@file_extension_cache[path][formatted_template_path]).any? + return "#{template_format}.#{extensions.first}" + elsif (extensions = @@file_extension_cache[path][template_path]).any? + return extensions.first.to_s + end + end + nil + end + + # Splits the path and extension from the given template_path and returns as an array. + def path_and_extension(template_path) + template_path_without_extension = template_path.sub(/\.(\w+)$/, '') + [ template_path_without_extension, $1 ] + end + + # Determine the template extension from the @first_render filename + def find_template_extension_from_first_render + File.basename(@template.first_render.to_s)[/^[^.]+\.(.+)$/, 1] + end + + private + def check_view_paths(view_paths) + view_paths.each do |path| + raise InvalidViewPath.new(path) unless @@processed_view_paths.has_key?(path) + end + end + end +end diff --git a/vendor/rails/actionpack/lib/action_view/template_handler.rb b/vendor/rails/actionpack/lib/action_view/template_handler.rb index b9f4330a..ec407e3f 100644 --- a/vendor/rails/actionpack/lib/action_view/template_handler.rb +++ b/vendor/rails/actionpack/lib/action_view/template_handler.rb @@ -1,17 +1,34 @@ module ActionView class TemplateHandler + def self.line_offset 0 end + def self.compilable? + false + end + def initialize(view) @view = view end - def render(template, local_assigns) + def render(template) end def compile(template) end + + def compilable? + self.class.compilable? + end + + def line_offset + self.class.line_offset + end + + # Called by CacheHelper#cache + def cache_fragment(block, name = {}, options = nil) + end end end diff --git a/vendor/rails/actionpack/lib/action_view/template_handlers/builder.rb b/vendor/rails/actionpack/lib/action_view/template_handlers/builder.rb index 0f49d6ab..f76d8977 100644 --- a/vendor/rails/actionpack/lib/action_view/template_handlers/builder.rb +++ b/vendor/rails/actionpack/lib/action_view/template_handlers/builder.rb @@ -3,6 +3,8 @@ require 'builder' module ActionView module TemplateHandlers class Builder < TemplateHandler + include Compilable + def self.line_offset 2 end @@ -10,10 +12,16 @@ module ActionView def compile(template) content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller") "#{content_type_handler}.content_type ||= Mime::XML\n" + - "xml = Builder::XmlMarkup.new(:indent => 2)\n" + - template + + "xml = ::Builder::XmlMarkup.new(:indent => 2)\n" + + template.source + "\nxml.target!\n" end + + def cache_fragment(block, name = {}, options = nil) + @view.fragment_for(block, name, options) do + eval('xml.target!', block.binding) + end + end end end end diff --git a/vendor/rails/actionpack/lib/action_view/template_handlers/compilable.rb b/vendor/rails/actionpack/lib/action_view/template_handlers/compilable.rb new file mode 100644 index 00000000..25bd0fea --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/template_handlers/compilable.rb @@ -0,0 +1,128 @@ +module ActionView + module TemplateHandlers + module Compilable + + def self.included(base) + base.extend ClassMethod + + # Map method names to their compile time + base.cattr_accessor :compile_time + base.compile_time = {} + + # Map method names to the names passed in local assigns so far + base.cattr_accessor :template_args + base.template_args = {} + + # Count the number of inline templates + base.cattr_accessor :inline_template_count + base.inline_template_count = 0 + end + + module ClassMethod + # If a handler is mixin this module, set compilable to true + def compilable? + true + end + end + + def render(template) + @view.send :execute, template + end + + # Compile and evaluate the template's code + def compile_template(template) + return unless compile_template?(template) + + render_symbol = assign_method_name(template) + render_source = create_template_source(template, render_symbol) + line_offset = self.template_args[render_symbol].size + self.line_offset + + begin + file_name = template.filename || 'compiled-template' + ActionView::Base::CompiledTemplates.module_eval(render_source, file_name, -line_offset) + rescue Exception => e # errors from template code + if @view.logger + @view.logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" + @view.logger.debug "Function body: #{render_source}" + @view.logger.debug "Backtrace: #{e.backtrace.join("\n")}" + end + + raise ActionView::TemplateError.new(template, @view.assigns, e) + end + + self.compile_time[render_symbol] = Time.now + # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger + end + + private + + # Method to check whether template compilation is necessary. + # The template will be compiled if the inline template or file has not been compiled yet, + # if local_assigns has a new key, which isn't supported by the compiled code yet, + # or if the file has changed on disk and checking file mods hasn't been disabled. + def compile_template?(template) + method_key = template.method_key + render_symbol = @view.method_names[method_key] + + compile_time = self.compile_time[render_symbol] + if compile_time && supports_local_assigns?(render_symbol, template.locals) + if template.filename && !@view.cache_template_loading + template_changed_since?(template.filename, compile_time) + end + else + true + end + end + + def assign_method_name(template) + @view.method_names[template.method_key] ||= compiled_method_name(template) + end + + def compiled_method_name(template) + ['_run', self.class.to_s.demodulize.underscore, compiled_method_name_file_path_segment(template.filename)].compact.join('_').to_sym + end + + def compiled_method_name_file_path_segment(file_name) + if file_name + s = File.expand_path(file_name) + s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT) + s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord } + s + else + (self.inline_template_count += 1).to_s + end + end + + # Method to create the source code for a given template. + def create_template_source(template, render_symbol) + body = compile(template) + + self.template_args[render_symbol] ||= {} + locals_keys = self.template_args[render_symbol].keys | template.locals.keys + self.template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } + + locals_code = "" + locals_keys.each do |key| + locals_code << "#{key} = local_assigns[:#{key}]\n" + end + + "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend" + end + + # Return true if the given template was compiled for a superset of the keys in local_assigns + def supports_local_assigns?(render_symbol, local_assigns) + local_assigns.empty? || + ((args = self.template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) }) + end + + # Method to handle checking a whether a template has changed since last compile; isolated so that templates + # not stored on the file system can hook and extend appropriately. + def template_changed_since?(file_name, compile_time) + lstat = File.lstat(file_name) + compile_time < lstat.mtime || + (lstat.symlink? && compile_time < File.stat(file_name).mtime) + end + + end + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_view/template_handlers/erb.rb b/vendor/rails/actionpack/lib/action_view/template_handlers/erb.rb index 022fc362..15a90644 100644 --- a/vendor/rails/actionpack/lib/action_view/template_handlers/erb.rb +++ b/vendor/rails/actionpack/lib/action_view/template_handlers/erb.rb @@ -2,19 +2,54 @@ require 'erb' class ERB module Util - HTML_ESCAPE = { '&' => '&', '"' => '"', '>' => '>', '<' => '<' } + HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } + JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } + # A utility method for escaping HTML tag characters. + # This method is also aliased as h. + # + # In your ERb templates, use this method to escape any unsafe content. For example: + # <%=h @person.name %> + # + # ==== Example: + # puts html_escape("is a > 0 & a < 10?") + # # => is a > 0 & a < 10? def html_escape(s) s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] } end + + # A utility method for escaping HTML entities in JSON strings. + # This method is also aliased as j. + # + # In your ERb templates, use this method to escape any HTML entities: + # <%=j @person.to_json %> + # + # ==== Example: + # puts json_escape("is a > 0 & a < 10?") + # # => is a \u003E 0 \u0026 a \u003C 10? + def json_escape(s) + s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] } + end + + alias j json_escape + module_function :j + module_function :json_escape end end module ActionView module TemplateHandlers class ERB < TemplateHandler + include Compilable + def compile(template) - ::ERB.new(template, nil, @view.erb_trim_mode).src + ::ERB.new(template.source, nil, @view.erb_trim_mode).src + end + + def cache_fragment(block, name = {}, options = nil) #:nodoc: + @view.fragment_for(block, name, options) do + eval(ActionView::Base.erb_variable, block.binding) + end end end end diff --git a/vendor/rails/actionpack/lib/action_view/template_handlers/rjs.rb b/vendor/rails/actionpack/lib/action_view/template_handlers/rjs.rb index 4ca9fc32..5854e33f 100644 --- a/vendor/rails/actionpack/lib/action_view/template_handlers/rjs.rb +++ b/vendor/rails/actionpack/lib/action_view/template_handlers/rjs.rb @@ -1,13 +1,26 @@ module ActionView module TemplateHandlers class RJS < TemplateHandler + include Compilable + def self.line_offset 2 end def compile(template) "controller.response.content_type ||= Mime::JS\n" + - "update_page do |page|\n#{template}\nend" + "update_page do |page|\n#{template.source}\nend" + end + + def cache_fragment(block, name = {}, options = nil) #:nodoc: + @view.fragment_for(block, name, options) do + begin + debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false + eval('page.to_s', block.binding) + ensure + ActionView::Base.debug_rjs = debug_mode + end + end end end end diff --git a/vendor/rails/actionpack/lib/action_view/test_case.rb b/vendor/rails/actionpack/lib/action_view/test_case.rb new file mode 100644 index 00000000..16fedd97 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/test_case.rb @@ -0,0 +1,58 @@ +require 'active_support/test_case' + +module ActionView + class TestCase < ActiveSupport::TestCase + class_inheritable_accessor :helper_class + @@helper_class = nil + + class << self + def tests(helper_class) + self.helper_class = helper_class + end + + def helper_class + if current_helper_class = read_inheritable_attribute(:helper_class) + current_helper_class + else + self.helper_class = determine_default_helper_class(name) + end + end + + def determine_default_helper_class(name) + name.sub(/Test$/, '').constantize + rescue NameError + nil + end + end + + ActionView::Base.helper_modules.each do |helper_module| + include helper_module + end + include ActionController::PolymorphicRoutes + include ActionController::RecordIdentifier + + setup :setup_with_helper_class + + def setup_with_helper_class + if helper_class && !self.class.ancestors.include?(helper_class) + self.class.send(:include, helper_class) + end + end + + class TestController < ActionController::Base + attr_accessor :request, :response + + def initialize + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + end + + private + def method_missing(selector, *args) + controller = TestController.new + return controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector) + super + end + end +end diff --git a/vendor/rails/actionpack/test/abstract_unit.rb b/vendor/rails/actionpack/test/abstract_unit.rb index 700bc1f5..fa1c3293 100644 --- a/vendor/rails/actionpack/test/abstract_unit.rb +++ b/vendor/rails/actionpack/test/abstract_unit.rb @@ -8,6 +8,7 @@ require 'test/unit' require 'action_controller' require 'action_controller/cgi_ext' require 'action_controller/test_process' +require 'action_view/test_case' begin require 'ruby-debug' @@ -19,7 +20,6 @@ end ActiveSupport::Deprecation.debug = true ActionController::Base.logger = nil -ActionController::Base.ignore_missing_templates = false ActionController::Routing::Routes.reload rescue nil diff --git a/vendor/rails/actionpack/test/action_view_test.rb b/vendor/rails/actionpack/test/action_view_test.rb deleted file mode 100644 index a69ff36f..00000000 --- a/vendor/rails/actionpack/test/action_view_test.rb +++ /dev/null @@ -1,44 +0,0 @@ -require File.dirname(__FILE__) + '/abstract_unit' -require 'test/unit' - -class ActionViewTests < Test::Unit::TestCase - def test_find_template_extension_from_first_render - base = ActionView::Base.new - - assert_nil base.send(:find_template_extension_from_first_render) - - { - nil => nil, - '' => nil, - 'foo' => nil, - '/foo' => nil, - 'foo.rb' => 'rb', - 'foo.bar.rb' => 'bar.rb', - 'baz/foo.rb' => 'rb', - 'baz/foo.bar.rb' => 'bar.rb', - 'baz/foo.o/foo.rb' => 'rb', - 'baz/foo.o/foo.bar.rb' => 'bar.rb', - }.each do |input,expectation| - base.instance_variable_set('@first_render', input) - assert_equal expectation, base.send(:find_template_extension_from_first_render) - end - end - - def test_should_report_file_exists_correctly - base = ActionView::Base.new - - assert_nil base.send(:find_template_extension_from_first_render) - - assert_equal false, base.send(:file_exists?, 'test.rhtml') - assert_equal false, base.send(:file_exists?, 'test.rb') - - base.instance_variable_set('@first_render', 'foo.rb') - - assert_equal 'rb', base.send(:find_template_extension_from_first_render) - - assert_equal false, base.send(:file_exists?, 'baz') - assert_equal false, base.send(:file_exists?, 'baz.rb') - - end - -end diff --git a/vendor/rails/actionpack/test/active_record_unit.rb b/vendor/rails/actionpack/test/active_record_unit.rb index 5f2745b5..a7d52685 100644 --- a/vendor/rails/actionpack/test/active_record_unit.rb +++ b/vendor/rails/actionpack/test/active_record_unit.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/abstract_unit' +require 'abstract_unit' # Define the essentials class ActiveRecordTestConnector @@ -84,8 +84,7 @@ class ActiveRecordTestConnector end end -# Test case for inheritance -class ActiveRecordTestCase < Test::Unit::TestCase +class ActiveRecordTestCase < ActiveSupport::TestCase # Set our fixture path if ActiveRecordTestConnector.able_to_connect self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/" @@ -100,9 +99,7 @@ class ActiveRecordTestCase < Test::Unit::TestCase super if ActiveRecordTestConnector.connected end - # Default so Test::Unit::TestCase doesn't complain - def test_truth - end + def default_test; end end ActiveRecordTestConnector.setup diff --git a/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb b/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb index 707a0a75..fd7da89a 100644 --- a/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb +++ b/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb @@ -1,9 +1,8 @@ # These tests exercise CGI::Session::ActiveRecordStore, so you're going to # need AR in a sibling directory to AP and have SQLite installed. -require File.dirname(__FILE__) + '/../active_record_unit' +require 'active_record_unit' require 'action_controller/session/active_record_store' - module CommonActiveRecordStoreTests def test_basics s = session_class.new(:session_id => '1234', :data => { 'foo' => 'bar' }) @@ -66,7 +65,7 @@ class ActiveRecordStoreTest < ActiveRecordTestCase def test_save_unloaded_session c = session_class.connection - bogus_class = c.quote(Base64.encode64("\004\010o:\vBlammo\000")) + bogus_class = c.quote(ActiveSupport::Base64.encode64("\004\010o:\vBlammo\000")) c.insert("INSERT INTO #{session_class.table_name} ('#{session_id_column}', 'data') VALUES ('abcdefghijklmnop', #{bogus_class})") sess = session_class.find_by_session_id('abcdefghijklmnop') diff --git a/vendor/rails/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/vendor/rails/actionpack/test/activerecord/render_partial_with_record_identification_test.rb index ccebbefe..ed10e729 100644 --- a/vendor/rails/actionpack/test/activerecord/render_partial_with_record_identification_test.rb +++ b/vendor/rails/actionpack/test/activerecord/render_partial_with_record_identification_test.rb @@ -1,39 +1,49 @@ -require File.dirname(__FILE__) + '/../active_record_unit' +require 'active_record_unit' + +class RenderPartialWithRecordIdentificationController < ActionController::Base + def render_with_has_many_and_belongs_to_association + @developer = Developer.find(1) + render :partial => @developer.projects + end + + def render_with_has_many_association + @topic = Topic.find(1) + render :partial => @topic.replies + end + + def render_with_named_scope + render :partial => Reply.base + end + + def render_with_has_many_through_association + @developer = Developer.find(:first) + render :partial => @developer.topics + end + + def render_with_has_one_association + @company = Company.find(1) + render :partial => @company.mascot + end + + def render_with_belongs_to_association + @reply = Reply.find(1) + render :partial => @reply.topic + end + + def render_with_record + @developer = Developer.find(:first) + render :partial => @developer + end + + def render_with_record_collection + @developers = Developer.find(:all) + render :partial => @developers + end +end +RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase - fixtures :developers, :projects, :developers_projects, :topics, :replies - - class RenderPartialWithRecordIdentificationController < ActionController::Base - def render_with_has_many_and_belongs_to_association - @developer = Developer.find(1) - render :partial => @developer.projects - end - - def render_with_has_many_association - @topic = Topic.find(1) - render :partial => @topic.replies - end - - def render_with_has_many_through_association - @developer = Developer.find(:first) - render :partial => @developer.topics - end - - def render_with_belongs_to_association - @reply = Reply.find(1) - render :partial => @reply.topic - end - - def render_with_record - @developer = Developer.find(:first) - render :partial => @developer - end - - def render_with_record_collection - @developers = Developer.find(:all) - render :partial => @developers - end - end + fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots def setup @controller = RenderPartialWithRecordIdentificationController.new @@ -52,14 +62,9 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase assert_template 'replies/_reply' end - def test_rendering_partial_with_has_many_association - get :render_with_has_many_through_association - assert_template 'topics/_topic' - end - - def test_rendering_partial_with_belongs_to_association - get :render_with_belongs_to_association - assert_template 'topics/_topic' + def test_rendering_partial_with_named_scope + get :render_with_named_scope + assert_template 'replies/_reply' end def test_render_with_record @@ -71,4 +76,116 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase get :render_with_record_collection assert_template 'developers/_developer' end + + def test_rendering_partial_with_has_one_association + mascot = Company.find(1).mascot + get :render_with_has_one_association + assert_template 'mascots/_mascot' + assert_equal mascot.name, @response.body + end end + +class RenderPartialWithRecordIdentificationController < ActionController::Base + def render_with_has_many_and_belongs_to_association + @developer = Developer.find(1) + render :partial => @developer.projects + end + + def render_with_has_many_association + @topic = Topic.find(1) + render :partial => @topic.replies + end + + def render_with_has_many_through_association + @developer = Developer.find(:first) + render :partial => @developer.topics + end + + def render_with_belongs_to_association + @reply = Reply.find(1) + render :partial => @reply.topic + end + + def render_with_record + @developer = Developer.find(:first) + render :partial => @developer + end + + def render_with_record_collection + @developers = Developer.find(:all) + render :partial => @developers + end +end +RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + +class Game < Struct.new(:name, :id) + def to_param + id.to_s + end +end + +module Fun + class NestedController < ActionController::Base + def render_with_record_in_nested_controller + render :partial => Game.new("Pong") + end + + def render_with_record_collection_in_nested_controller + render :partial => [ Game.new("Pong"), Game.new("Tank") ] + end + end + NestedController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + + module Serious + class NestedDeeperController < ActionController::Base + def render_with_record_in_deeper_nested_controller + render :partial => Game.new("Chess") + end + + def render_with_record_collection_in_deeper_nested_controller + render :partial => [ Game.new("Chess"), Game.new("Sudoku"), Game.new("Solitaire") ] + end + end + NestedDeeperController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + end +end + +class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveRecordTestCase + def setup + @controller = Fun::NestedController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + super + end + + def test_render_with_record_in_nested_controller + get :render_with_record_in_nested_controller + assert_template 'fun/games/_game' + end + + def test_render_with_record_collection_in_nested_controller + get :render_with_record_collection_in_nested_controller + assert_template 'fun/games/_game' + end + +end + +class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < ActiveRecordTestCase + def setup + @controller = Fun::Serious::NestedDeeperController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + super + end + + def test_render_with_record_in_deeper_nested_controller + get :render_with_record_in_deeper_nested_controller + assert_template 'fun/serious/games/_game' + end + + def test_render_with_record_collection_in_deeper_nested_controller + get :render_with_record_collection_in_deeper_nested_controller + assert_template 'fun/serious/games/_game' + end + +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/adv_attr_test.rb b/vendor/rails/actionpack/test/adv_attr_test.rb new file mode 100644 index 00000000..fdda4ad9 --- /dev/null +++ b/vendor/rails/actionpack/test/adv_attr_test.rb @@ -0,0 +1,20 @@ +require File.dirname(__FILE__) + '/abstract_unit' +require 'action_mailer/adv_attr_accessor' + +class AdvAttrTest < Test::Unit::TestCase + class Person + include ActionMailer::AdvAttrAccessor + adv_attr_accessor :name + end + + def test_adv_attr + bob = Person.new + assert_nil bob.name + bob.name 'Bob' + assert_equal 'Bob', bob.name + + assert_raise(ArgumentError) {bob.name 'x', 'y'} + end + + +end \ No newline at end of file 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 1eb9610d..f152b1d1 100644 --- a/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb +++ b/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' # a controller class to facilitate the tests class ActionPackAssertionsController < ActionController::Base @@ -124,6 +124,19 @@ class ActionPackAssertionsController < ActionController::Base def rescue_action(e) raise; end end +# Used to test that assert_response includes the exception message +# in the failure message when an action raises and assert_response +# is expecting something other than an error. +class AssertResponseWithUnexpectedErrorController < ActionController::Base + def index + raise 'FAIL' + end + + def show + render :text => "Boom", :status => 500 + end +end + module Admin class InnerModuleController < ActionController::Base def index @@ -465,6 +478,25 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase rescue Test::Unit::AssertionFailedError => e end end + + def test_assert_response_uses_exception_message + @controller = AssertResponseWithUnexpectedErrorController.new + get :index + assert_response :success + flunk 'Expected non-success response' + rescue Test::Unit::AssertionFailedError => e + assert e.message.include?('FAIL') + end + + def test_assert_response_failure_response_with_no_exception + @controller = AssertResponseWithUnexpectedErrorController.new + get :show + assert_response :success + flunk 'Expected non-success response' + rescue Test::Unit::AssertionFailedError + rescue + flunk "assert_response failed to handle failure response with missing, but optional, exception." + end end class ActionPackHeaderTest < Test::Unit::TestCase diff --git a/vendor/rails/actionpack/test/controller/addresses_render_test.rb b/vendor/rails/actionpack/test/controller/addresses_render_test.rb index d1e9122d..a3173420 100644 --- a/vendor/rails/actionpack/test/controller/addresses_render_test.rb +++ b/vendor/rails/actionpack/test/controller/addresses_render_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class Address diff --git a/vendor/rails/actionpack/test/controller/assert_select_test.rb b/vendor/rails/actionpack/test/controller/assert_select_test.rb index b0f3d6ce..5af579f3 100644 --- a/vendor/rails/actionpack/test/controller/assert_select_test.rb +++ b/vendor/rails/actionpack/test/controller/assert_select_test.rb @@ -3,8 +3,8 @@ # Under MIT and/or CC By license. #++ -require "#{File.dirname(__FILE__)}/../abstract_unit" -require "#{File.dirname(__FILE__)}/fake_controllers" +require 'abstract_unit' +require 'controller/fake_controllers' unless defined?(ActionMailer) @@ -345,10 +345,17 @@ class AssertSelectTest < Test::Unit::TestCase page.replace "test", "
    \343\203\201\343\202\261\343\203\203\343\203\210
    " end assert_select_rjs do - assert_select "#1", :text => "\343\203\201\343\202\261\343\203\203\343\203\210" - assert_select "#1", "\343\203\201\343\202\261\343\203\203\343\203\210" - assert_select "#1", Regexp.new("\343\203\201..\343\203\210",0,'U') - assert_raises(AssertionFailedError) { assert_select "#1", Regexp.new("\343\203\201.\343\203\210",0,'U') } + str = "#1" + assert_select str, :text => "\343\203\201\343\202\261\343\203\203\343\203\210" + assert_select str, "\343\203\201\343\202\261\343\203\203\343\203\210" + if str.respond_to?(:force_encoding) + str.force_encoding(Encoding::UTF_8) + assert_select str, /\343\203\201..\343\203\210/u + assert_raises(AssertionFailedError) { assert_select str, /\343\203\201.\343\203\210/u } + else + assert_select str, Regexp.new("\343\203\201..\343\203\210",0,'U') + assert_raises(AssertionFailedError) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') } + end end end diff --git a/vendor/rails/actionpack/test/controller/base_test.rb b/vendor/rails/actionpack/test/controller/base_test.rb index 60e61b62..b2871759 100644 --- a/vendor/rails/actionpack/test/controller/base_test.rb +++ b/vendor/rails/actionpack/test/controller/base_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' -require 'test/unit' +require 'abstract_unit' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late # Provide some controller to run the tests on. @@ -49,6 +48,15 @@ protected end +class DefaultUrlOptionsController < ActionController::Base + def default_url_options_action + end + + def default_url_options(options = nil) + { :host => 'www.override.com', :action => 'new', :bacon => 'chunky' } + end +end + class ControllerClassTests < Test::Unit::TestCase def test_controller_path assert_equal 'empty', EmptyController.controller_path @@ -87,7 +95,10 @@ class ControllerInstanceTests < Test::Unit::TestCase # Mocha adds some public instance methods to Object that would be # considered actions, so explicitly hide_action them. def hide_mocha_methods_from_controller(controller) - mocha_methods = [:expects, :metaclass, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__] + mocha_methods = [ + :expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, + :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher, + ] controller.class.send!(:hide_action, *mocha_methods) end end @@ -132,3 +143,41 @@ class PerformActionTest < Test::Unit::TestCase assert_response 404 end end + +class DefaultUrlOptionsTest < Test::Unit::TestCase + def setup + @controller = DefaultUrlOptionsController.new + + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @request.host = 'www.example.com' + end + + def test_default_url_options_are_used_if_set + ActionController::Routing::Routes.draw do |map| + map.default_url_options 'default_url_options', :controller => 'default_url_options' + map.connect ':controller/:action/:id' + end + + get :default_url_options_action # Make a dummy request so that the controller is initialized properly. + + assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options') + assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url) + ensure + ActionController::Routing::Routes.load! + end +end + +class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase + def test_named_routes_still_work + ActionController::Routing::Routes.draw do |map| + map.resources :things + end + EmptyController.send :include, ActionController::UrlWriter + + assert_equal '/things', EmptyController.new.send(:things_path) + ensure + ActionController::Routing::Routes.load! + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/benchmark_test.rb b/vendor/rails/actionpack/test/controller/benchmark_test.rb index f346e575..608ea5f5 100644 --- a/vendor/rails/actionpack/test/controller/benchmark_test.rb +++ b/vendor/rails/actionpack/test/controller/benchmark_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' -require 'test/unit' +require 'abstract_unit' # Provide some static controllers. class BenchmarkedController < ActionController::Base diff --git a/vendor/rails/actionpack/test/controller/caching_test.rb b/vendor/rails/actionpack/test/controller/caching_test.rb index d6982fbc..f9b6b87b 100644 --- a/vendor/rails/actionpack/test/controller/caching_test.rb +++ b/vendor/rails/actionpack/test/controller/caching_test.rb @@ -1,14 +1,16 @@ require 'fileutils' -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' CACHE_DIR = 'test_cache' # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) ActionController::Base.page_cache_directory = FILE_STORE_PATH -ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH +ActionController::Base.cache_store = :file_store, FILE_STORE_PATH +ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/' ] class PageCachingTestController < ActionController::Base - caches_page :ok, :no_content, :found, :not_found + caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } + caches_page :found, :not_found def ok head :ok @@ -25,17 +27,17 @@ class PageCachingTestController < ActionController::Base def not_found head :not_found end - + def custom_path render :text => "Super soaker" cache_page("Super soaker", "/index.html") end - + def expire_custom_path expire_page("/index.html") head :ok end - + def trailing_slash render :text => "Sneak attack" end @@ -95,7 +97,7 @@ class PageCachingTest < Test::Unit::TestCase get :expire_custom_path assert !File.exist?("#{FILE_STORE_PATH}/index.html") end - + def test_should_cache_without_trailing_slash_on_url @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash' assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") @@ -128,6 +130,12 @@ class PageCachingTest < Test::Unit::TestCase end end + def test_page_caching_conditional_options + @request.env['HTTP_ACCEPT'] = 'application/json' + get :ok + assert_page_not_cached :ok + end + private def assert_page_cached(action, message = "#{action} should have been cached") assert page_cached?(action), message @@ -144,12 +152,15 @@ end class ActionCachingTestController < ActionController::Base - caches_action :index, :redirected, :forbidden + caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? } caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } + caches_action :with_layout + + layout 'talk_from_action.erb' def index - @cache_this = Time.now.to_f.to_s + @cache_this = MockTime.now.to_f.to_s render :text => @cache_this end @@ -162,14 +173,26 @@ class ActionCachingTestController < ActionController::Base headers["Status"] = "403 Forbidden" end + def with_layout + @cache_this = MockTime.now.to_f.to_s + render :text => @cache_this, :layout => true + end + alias_method :show, :index alias_method :edit, :index + alias_method :destroy, :index def expire expire_action :controller => 'action_caching_test', :action => 'index' render :nothing => true end +end +class MockTime < Time + # Let Time spicy to assure that Time.now != Time.now + def to_f + super+rand + end end class ActionCachingMockController @@ -209,18 +232,48 @@ class ActionCacheTest < Test::Unit::TestCase get :index cached_time = content_to_cache assert_equal cached_time, @response.body - assert_cache_exists 'hostname.com/action_caching_test' + assert fragment_exist?('hostname.com/action_caching_test') reset! get :index assert_equal cached_time, @response.body end - + + def test_simple_action_not_cached + get :destroy + cached_time = content_to_cache + assert_equal cached_time, @response.body + assert !fragment_exist?('hostname.com/action_caching_test/destroy') + reset! + + get :destroy + assert_not_equal cached_time, @response.body + end + + def test_action_cache_with_layout + get :with_layout + cached_time = content_to_cache + assert_not_equal cached_time, @response.body + assert fragment_exist?('hostname.com/action_caching_test/with_layout') + reset! + + get :with_layout + assert_not_equal cached_time, @response.body + + assert_equal @response.body, read_fragment('hostname.com/action_caching_test/with_layout') + end + + def test_action_cache_conditional_options + @request.env['HTTP_ACCEPT'] = 'application/json' + get :index + assert !fragment_exist?('hostname.com/action_caching_test') + end + def test_action_cache_with_custom_cache_path get :show cached_time = content_to_cache assert_equal cached_time, @response.body - assert_cache_exists 'test.host/custom/show' + assert fragment_exist?('test.host/custom/show') reset! get :show @@ -229,11 +282,11 @@ class ActionCacheTest < Test::Unit::TestCase def test_action_cache_with_custom_cache_path_in_block get :edit - assert_cache_exists 'test.host/edit' + assert fragment_exist?('test.host/edit') reset! get :edit, :id => 1 - assert_cache_exists 'test.host/1;edit' + assert fragment_exist?('test.host/1;edit') end def test_cache_expiration @@ -262,9 +315,9 @@ class ActionCacheTest < Test::Unit::TestCase @request.host = 'jamis.hostname.com' get :index jamis_cache = content_to_cache - + reset! - + @request.host = 'david.hostname.com' get :index david_cache = content_to_cache @@ -308,7 +361,7 @@ class ActionCacheTest < Test::Unit::TestCase assert_equal 'xml', path_object.extension assert_equal 'example.org/posts/index.xml', path_object.path end - + def test_correct_content_type_is_returned_for_cache_hit # run it twice to cache it the first time get :index, :id => 'content-type.xml' @@ -341,9 +394,217 @@ class ActionCacheTest < Test::Unit::TestCase @controller = ActionCachingTestController.new @request.host = 'hostname.com' end - - def assert_cache_exists(path) - full_path = File.join(FILE_STORE_PATH, path + '.cache') - assert File.exist?(full_path), "#{full_path.inspect} does not exist." + + def fragment_exist?(path) + @controller.fragment_exist?(path) + end + + def read_fragment(path) + @controller.read_fragment(path) end end + +class FragmentCachingTestController < ActionController::Base + def some_action; end; +end + +class FragmentCachingTest < Test::Unit::TestCase + def setup + ActionController::Base.perform_caching = true + @store = ActiveSupport::Cache::MemoryStore.new + ActionController::Base.cache_store = @store + @controller = FragmentCachingTestController.new + @params = {:controller => 'posts', :action => 'index'} + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller.params = @params + @controller.request = @request + @controller.response = @response + @controller.send(:initialize_current_url) + end + + def test_fragment_cache_key + assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') + assert_equal( "views/test.host/fragment_caching_test/some_action", + @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action')) + end + + def test_read_fragment__with_caching_enabled + @store.write('views/name', 'value') + assert_equal 'value', @controller.read_fragment('name') + end + + def test_read_fragment__with_caching_disabled + ActionController::Base.perform_caching = false + @store.write('views/name', 'value') + assert_nil @controller.read_fragment('name') + end + + def test_fragment_exist__with_caching_enabled + @store.write('views/name', 'value') + assert @controller.fragment_exist?('name') + assert !@controller.fragment_exist?('other_name') + end + + def test_fragment_exist__with_caching_disabled + ActionController::Base.perform_caching = false + @store.write('views/name', 'value') + assert !@controller.fragment_exist?('name') + assert !@controller.fragment_exist?('other_name') + end + + def test_write_fragment__with_caching_enabled + assert_nil @store.read('views/name') + assert_equal 'value', @controller.write_fragment('name', 'value') + assert_equal 'value', @store.read('views/name') + end + + def test_write_fragment__with_caching_disabled + assert_nil @store.read('views/name') + ActionController::Base.perform_caching = false + assert_equal nil, @controller.write_fragment('name', 'value') + assert_nil @store.read('views/name') + end + + def test_expire_fragment__with_simple_key + @store.write('views/name', 'value') + @controller.expire_fragment 'name' + assert_nil @store.read('views/name') + end + + def test_expire_fragment__with__regexp + @store.write('views/name', 'value') + @store.write('views/another_name', 'another_value') + @store.write('views/primalgrasp', 'will not expire ;-)') + + @controller.expire_fragment /name/ + + assert_nil @store.read('views/name') + assert_nil @store.read('views/another_name') + assert_equal 'will not expire ;-)', @store.read('views/primalgrasp') + end + + def test_fragment_for__with_disabled_caching + ActionController::Base.perform_caching = false + + @store.write('views/expensive', 'fragment content') + fragment_computed = false + + buffer = 'generated till now -> ' + @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer } + + assert fragment_computed + assert_equal 'generated till now -> ', buffer + end + + def test_fragment_for + @store.write('views/expensive', 'fragment content') + fragment_computed = false + + buffer = 'generated till now -> ' + @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer} + + assert !fragment_computed + assert_equal 'generated till now -> fragment content', buffer + end + + def test_cache_erb_fragment + @store.write('views/expensive', 'fragment content') + _erbout = 'generated till now -> ' + + assert_equal( 'generated till now -> fragment content', + ActionView::TemplateHandlers::ERB.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) + end + + def test_cache_rxml_fragment + @store.write('views/expensive', 'fragment content') + xml = 'generated till now -> ' + class << xml; def target!; to_s; end; end + + assert_equal( 'generated till now -> fragment content', + ActionView::TemplateHandlers::Builder.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) + end + + def test_cache_rjs_fragment + @store.write('views/expensive', 'fragment content') + page = 'generated till now -> ' + + assert_equal( 'generated till now -> fragment content', + ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) + end + + def test_cache_rjs_fragment_debug_mode_does_not_interfere + @store.write('views/expensive', 'fragment content') + page = 'generated till now -> ' + + begin + debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, true + assert_equal( 'generated till now -> fragment content', + ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive')) + assert ActionView::Base.debug_rjs + ensure + ActionView::Base.debug_rjs = debug_mode + end + end +end + + +class FunctionalCachingController < ActionController::Base + def fragment_cached + end + + def html_fragment_cached_with_partial + respond_to do |format| + format.html + end + end + + def js_fragment_cached_with_partial + respond_to do |format| + format.js + end + end + + + def rescue_action(e) + raise e + end +end + +FunctionalCachingController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ] + +class FunctionalFragmentCachingTest < Test::Unit::TestCase + def setup + ActionController::Base.perform_caching = true + @store = ActiveSupport::Cache::MemoryStore.new + ActionController::Base.cache_store = @store + @controller = FunctionalCachingController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + def test_fragment_caching + get :fragment_cached + assert_response :success + expected_body = <<-CACHED +Hello +This bit's fragment cached +CACHED + assert_equal expected_body, @response.body + + assert_equal "This bit's fragment cached", @store.read('views/test.host/functional_caching/fragment_cached') + end + + def test_fragment_caching_in_partials + get :html_fragment_cached_with_partial + assert_response :success + assert_match /Fragment caching in a partial/, @response.body + assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial') + end + + def test_fragment_caching_in_rjs_partials + xhr :get, :js_fragment_cached_with_partial + assert_response :success + assert_match /Fragment caching in a partial/, @response.body + assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial') + end +end diff --git a/vendor/rails/actionpack/test/controller/capture_test.rb b/vendor/rails/actionpack/test/controller/capture_test.rb index 7ec5f32a..aaafea39 100644 --- a/vendor/rails/actionpack/test/controller/capture_test.rb +++ b/vendor/rails/actionpack/test/controller/capture_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class CaptureController < ActionController::Base def self.controller_name; "test"; end diff --git a/vendor/rails/actionpack/test/controller/cgi_test.rb b/vendor/rails/actionpack/test/controller/cgi_test.rb index 021781df..87f72fda 100755 --- a/vendor/rails/actionpack/test/controller/cgi_test.rb +++ b/vendor/rails/actionpack/test/controller/cgi_test.rb @@ -1,11 +1,12 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' require 'action_controller/cgi_process' class BaseCgiTest < Test::Unit::TestCase def setup @request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"} - # cookie as returned by some Nokia phone browsers (no space after semicolon separator) - @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2;is_admin=yes"} + # some Nokia phone browsers omit the space after the semicolon separator. + # some developers have grown accustomed to using comma in cookie values. + @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"} @fake_cgi = Struct.new(:env_table).new(@request_hash) @request = ActionController::CgiRequest.new(@fake_cgi) end @@ -76,7 +77,7 @@ class CgiRequestTest < BaseCgiTest assert_equal ["yes"], cookies["is_admin"], cookies.inspect alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]); - assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect + assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect end end diff --git a/vendor/rails/actionpack/test/controller/components_test.rb b/vendor/rails/actionpack/test/controller/components_test.rb index debd8a27..82c55483 100644 --- a/vendor/rails/actionpack/test/controller/components_test.rb +++ b/vendor/rails/actionpack/test/controller/components_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class CallerController < ActionController::Base def calling_from_controller diff --git a/vendor/rails/actionpack/test/controller/content_type_test.rb b/vendor/rails/actionpack/test/controller/content_type_test.rb index 1841d37c..d262ce81 100644 --- a/vendor/rails/actionpack/test/controller/content_type_test.rb +++ b/vendor/rails/actionpack/test/controller/content_type_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class ContentTypeController < ActionController::Base def render_content_type_from_body @@ -136,4 +136,4 @@ class ContentTypeTest < Test::Unit::TestCase get :render_default_content_types_for_respond_to assert_equal Mime::XML, @response.content_type end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/test/controller/cookie_test.rb b/vendor/rails/actionpack/test/controller/cookie_test.rb index 6a833fee..b45fbb17 100644 --- a/vendor/rails/actionpack/test/controller/cookie_test.rb +++ b/vendor/rails/actionpack/test/controller/cookie_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class CookieTest < Test::Unit::TestCase class TestController < ActionController::Base @@ -37,7 +37,7 @@ class CookieTest < Test::Unit::TestCase end def rescue_action(e) - raise unless ActionController::MissingTemplate # No templates here, and we don't care about the output + raise unless ActionView::MissingTemplate # No templates here, and we don't care about the output end end @@ -82,6 +82,7 @@ class CookieTest < Test::Unit::TestCase 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 def test_cookiejar_accessor @@ -132,4 +133,14 @@ class CookieTest < Test::Unit::TestCase assert cookie_str !~ /secure/ assert cookie_str !~ /HttpOnly/ end + + def test_cookies_should_not_be_split_on_ampersand_values + cookies = CGI::Cookie.parse('return_to=http://rubyonrails.org/search?term=api&scope=all&global=true') + assert_equal({"return_to" => ["http://rubyonrails.org/search?term=api&scope=all&global=true"]}, cookies) + end + + def test_cookies_should_not_be_split_on_values_with_newlines + cookies = CGI::Cookie.new("name" => "val", "value" => "this\nis\na\ntest") + assert cookies.size == 1 + end end diff --git a/vendor/rails/actionpack/test/controller/custom_handler_test.rb b/vendor/rails/actionpack/test/controller/custom_handler_test.rb index 2747a0f3..ac484ae1 100644 --- a/vendor/rails/actionpack/test/controller/custom_handler_test.rb +++ b/vendor/rails/actionpack/test/controller/custom_handler_test.rb @@ -1,33 +1,36 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' -class CustomHandler +class CustomHandler < ActionView::TemplateHandler def initialize( view ) @view = view end - def render( template, local_assigns ) - [ template, - local_assigns, + def render( template ) + [ template.source, + template.locals, @view ] end end class CustomHandlerTest < Test::Unit::TestCase def setup - ActionView::Base.register_template_handler "foo", CustomHandler - ActionView::Base.register_template_handler :foo2, CustomHandler + ActionView::Template.register_template_handler "foo", CustomHandler + ActionView::Template.register_template_handler :foo2, CustomHandler @view = ActionView::Base.new end def test_custom_render - result = @view.render_template( "foo", "hello <%= one %>", nil, :one => "two" ) + template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "foo") + + result = @view.render_template(template) assert_equal( [ "hello <%= one %>", { :one => "two" }, @view ], result ) end def test_custom_render2 - result = @view.render_template( "foo2", "hello <%= one %>", nil, :one => "two" ) + template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "foo2") + result = @view.render_template(template) assert_equal( [ "hello <%= one %>", { :one => "two" }, @view ], result ) @@ -35,7 +38,8 @@ class CustomHandlerTest < Test::Unit::TestCase def test_unhandled_extension # uses the ERb handler by default if the extension isn't recognized - result = @view.render_template( "bar", "hello <%= one %>", nil, :one => "two" ) + template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "bar") + result = @view.render_template(template) assert_equal "hello two", result end end diff --git a/vendor/rails/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb b/vendor/rails/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb index 6d7157e1..8c1a8954 100644 --- a/vendor/rails/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb +++ b/vendor/rails/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../../abstract_unit' +require 'abstract_unit' class DeprecatedBaseMethodsTest < Test::Unit::TestCase class Target < ActionController::Base diff --git a/vendor/rails/actionpack/test/controller/dispatcher_test.rb b/vendor/rails/actionpack/test/controller/dispatcher_test.rb index ec937ebf..eea0813e 100644 --- a/vendor/rails/actionpack/test/controller/dispatcher_test.rb +++ b/vendor/rails/actionpack/test/controller/dispatcher_test.rb @@ -1,4 +1,4 @@ -require "#{File.dirname(__FILE__)}/../abstract_unit" +require 'abstract_unit' uses_mocha 'dispatcher tests' do @@ -11,107 +11,89 @@ class DispatcherTest < Test::Unit::TestCase @output = StringIO.new ENV['REQUEST_METHOD'] = 'GET' - Dispatcher.callbacks[:prepare].clear + # Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks + Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + + Dispatcher.stubs(:require_dependency) + @dispatcher = Dispatcher.new(@output) end def teardown - ENV['REQUEST_METHOD'] = nil + ENV.delete 'REQUEST_METHOD' end def test_clears_dependencies_after_dispatch_if_in_loading_mode - Dependencies.stubs(:load?).returns(true) - ActionController::Routing::Routes.expects(:reload).once Dependencies.expects(:clear).once - dispatch + dispatch(@output, false) end def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode - Dependencies.stubs(:load?).returns(false) - ActionController::Routing::Routes.expects(:reload).never Dependencies.expects(:clear).never dispatch end + # Stub out dispatch error logger + class << Dispatcher + def log_failsafe_exception(status, exception); end + end + def test_failsafe_response CGI.expects(:new).raises('some multipart parsing failure') - - ActionController::Routing::Routes.stubs(:reload) - Dispatcher.stubs(:log_failsafe_exception) + Dispatcher.expects(:log_failsafe_exception) assert_nothing_raised { dispatch } assert_equal "Status: 400 Bad Request\r\nContent-Type: text/html\r\n\r\n

    400 Bad Request

    ", @output.string end - def test_reload_application_sets_unprepared_if_loading_dependencies - Dependencies.stubs(:load?).returns(false) - ActionController::Routing::Routes.expects(:reload).never - @dispatcher.unprepared = false - @dispatcher.send!(:reload_application) - assert !@dispatcher.unprepared - - Dependencies.stubs(:load?).returns(true) - ActionController::Routing::Routes.expects(:reload).once - @dispatcher.send!(:reload_application) - assert @dispatcher.unprepared - end - - def test_prepare_application_runs_callbacks_if_unprepared + def test_prepare_callbacks a = b = c = nil - Dispatcher.to_prepare { a = b = c = 1 } - Dispatcher.to_prepare { b = c = 2 } - Dispatcher.to_prepare { c = 3 } + Dispatcher.to_prepare { |*args| a = b = c = 1 } + Dispatcher.to_prepare { |*args| b = c = 2 } + Dispatcher.to_prepare { |*args| c = 3 } - # Skip the callbacks when already prepared. - @dispatcher.unprepared = false - @dispatcher.send! :prepare_application + # Ensure to_prepare callbacks are not run when defined assert_nil a || b || c - # Perform the callbacks when unprepared. - @dispatcher.unprepared = true - @dispatcher.send! :prepare_application + # Run callbacks + @dispatcher.send :run_callbacks, :prepare_dispatch + assert_equal 1, a assert_equal 2, b assert_equal 3, c - # But when not :load, make sure they are only run once + # Make sure they are only run once a = b = c = nil - @dispatcher.send! :prepare_application + @dispatcher.send :dispatch assert_nil a || b || c end def test_to_prepare_with_identifier_replaces a = b = nil - Dispatcher.to_prepare(:unique_id) { a = b = 1 } - Dispatcher.to_prepare(:unique_id) { a = 2 } + Dispatcher.to_prepare(:unique_id) { |*args| a = b = 1 } + Dispatcher.to_prepare(:unique_id) { |*args| a = 2 } - @dispatcher.unprepared = true - @dispatcher.send! :prepare_application + @dispatcher.send :run_callbacks, :prepare_dispatch assert_equal 2, a assert_equal nil, b end - def test_to_prepare_only_runs_once_if_not_loading_dependencies - Dependencies.stubs(:load?).returns(false) - called = 0 - Dispatcher.to_prepare(:unprepared_test) { called += 1 } - 2.times { dispatch } - assert_equal 1, called - end - private - def dispatch(output = @output) + def dispatch(output = @output, cache_classes = true) controller = mock controller.stubs(:process).returns(controller) controller.stubs(:out).with(output).returns('response') ActionController::Routing::Routes.stubs(:recognize).returns(controller) + Dispatcher.define_dispatcher_callbacks(cache_classes) Dispatcher.dispatch(nil, {}, output) end diff --git a/vendor/rails/actionpack/test/controller/fake_controllers.rb b/vendor/rails/actionpack/test/controller/fake_controllers.rb index 5f958b28..75c114c1 100644 --- a/vendor/rails/actionpack/test/controller/fake_controllers.rb +++ b/vendor/rails/actionpack/test/controller/fake_controllers.rb @@ -10,6 +10,23 @@ module Admin class NewsFeedController < Class.new(ActionController::Base); end end +# For speed test +class SpeedController < ActionController::Base; end +class SearchController < SpeedController; end +class VideosController < SpeedController; end +class VideoFileController < SpeedController; end +class VideoSharesController < SpeedController; end +class VideoAbusesController < SpeedController; end +class VideoUploadsController < SpeedController; end +class VideoVisitsController < SpeedController; end +class UsersController < SpeedController; end +class SettingsController < SpeedController; end +class ChannelsController < SpeedController; end +class ChannelVideosController < SpeedController; end +class SessionsController < SpeedController; end +class LostPasswordsController < SpeedController; end +class PagesController < SpeedController; end + ActionController::Routing::Routes.draw do |map| map.route_one 'route_one', :controller => 'elsewhere', :action => 'flash_me' map.connect ':controller/:action/:id' diff --git a/vendor/rails/actionpack/test/controller/fake_models.rb b/vendor/rails/actionpack/test/controller/fake_models.rb index 2761b09f..7420579e 100644 --- a/vendor/rails/actionpack/test/controller/fake_models.rb +++ b/vendor/rails/actionpack/test/controller/fake_models.rb @@ -3,3 +3,9 @@ class Customer < Struct.new(:name, :id) id.to_s end end + +class BadCustomer < Customer +end + +class GoodCustomer < Customer +end diff --git a/vendor/rails/actionpack/test/controller/filter_params_test.rb b/vendor/rails/actionpack/test/controller/filter_params_test.rb index 7b810b16..c4de1018 100644 --- a/vendor/rails/actionpack/test/controller/filter_params_test.rb +++ b/vendor/rails/actionpack/test/controller/filter_params_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class FilterParamController < ActionController::Base end @@ -7,14 +7,14 @@ class FilterParamTest < Test::Unit::TestCase def setup @controller = FilterParamController.new end - + def test_filter_parameters assert FilterParamController.respond_to?(:filter_parameter_logging) assert !@controller.respond_to?(:filter_parameters) - + FilterParamController.filter_parameter_logging assert @controller.respond_to?(:filter_parameters) - + test_hashes = [[{},{},[]], [{'foo'=>nil},{'foo'=>nil},[]], [{'foo'=>'bar'},{'foo'=>'bar'},[]], @@ -24,11 +24,11 @@ class FilterParamTest < Test::Unit::TestCase [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']] - + test_hashes.each do |before_filter, after_filter, filter_words| FilterParamController.filter_parameter_logging(*filter_words) - assert_equal after_filter, @controller.filter_parameters(before_filter) - + assert_equal after_filter, @controller.send!(:filter_parameters, before_filter) + filter_words.push('blah') FilterParamController.filter_parameter_logging(*filter_words) do |key, value| value.reverse! if key =~ /bargain/ @@ -37,7 +37,13 @@ class FilterParamTest < Test::Unit::TestCase before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}} after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}} - assert_equal after_filter, @controller.filter_parameters(before_filter) + assert_equal after_filter, @controller.send!(:filter_parameters, before_filter) end end + + def test_filter_parameters_is_protected + FilterParamController.filter_parameter_logging(:foo) + assert !FilterParamController.action_methods.include?('filter_parameters') + assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) } + end end diff --git a/vendor/rails/actionpack/test/controller/filters_test.rb b/vendor/rails/actionpack/test/controller/filters_test.rb index 188e75af..3652c482 100644 --- a/vendor/rails/actionpack/test/controller/filters_test.rb +++ b/vendor/rails/actionpack/test/controller/filters_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' # FIXME: crashes Ruby 1.9 class FilterTest < Test::Unit::TestCase @@ -134,6 +134,11 @@ class FilterTest < Test::Unit::TestCase before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true} end + class ConditionalOptionsFilter < ConditionalFilterController + before_filter :ensure_login, :if => Proc.new { |c| true } + before_filter :clean_up_tmp, :if => Proc.new { |c| false } + end + class EmptyFilterChainController < TestController self.filter_chain.clear def show @@ -153,6 +158,30 @@ class FilterTest < Test::Unit::TestCase end end + class SkippingAndLimitedController < TestController + skip_before_filter :ensure_login + before_filter :ensure_login, :only => :index + + def index + render :text => 'ok' + end + + def public + end + end + + class SkippingAndReorderingController < TestController + skip_before_filter :ensure_login + before_filter :find_record + before_filter :ensure_login + + private + def find_record + @ran_filter ||= [] + @ran_filter << "find_record" + end + end + class ConditionalSkippingController < TestController skip_before_filter :ensure_login, :only => [ :login ] skip_after_filter :clean_up, :only => [ :login ] @@ -466,6 +495,11 @@ class FilterTest < Test::Unit::TestCase assert !response.template.assigns["ran_proc_filter2"] end + def test_running_conditional_options + response = test_process(ConditionalOptionsFilter) + assert_equal %w( ensure_login ), response.template.assigns["ran_filter"] + end + def test_running_collection_condition_filters assert_equal %w( ensure_login ), test_process(ConditionalCollectionFilterController).template.assigns["ran_filter"] assert_equal nil, test_process(ConditionalCollectionFilterController, "show_without_filter").template.assigns["ran_filter"] @@ -499,13 +533,6 @@ class FilterTest < Test::Unit::TestCase assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"] end - def test_bad_filter - bad_filter_controller = Class.new(ActionController::Base) - assert_raises(ActionController::ActionControllerError) do - bad_filter_controller.before_filter 2 - end - end - def test_around_filter controller = test_process(AroundFilterController) assert controller.template.assigns["before_ran"] @@ -562,6 +589,15 @@ class FilterTest < Test::Unit::TestCase response = test_process(PrependingBeforeAndAfterController) assert_equal %w( before_all between_before_all_and_after_all after_all ), response.template.assigns["ran_filter"] end + + def test_skipping_and_limiting_controller + assert_equal %w( ensure_login ), test_process(SkippingAndLimitedController, "index").template.assigns["ran_filter"] + assert_nil test_process(SkippingAndLimitedController, "public").template.assigns["ran_filter"] + end + + def test_skipping_and_reordering_controller + assert_equal %w( find_record ensure_login ), test_process(SkippingAndReorderingController, "index").template.assigns["ran_filter"] + end def test_conditional_skipping_of_filters assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"] @@ -696,10 +732,6 @@ class ControllerWithProcFilter < PostsController end end -class ControllerWithWrongFilterType < PostsController - around_filter lambda { yield }, :only => :no_raise -end - class ControllerWithNestedFilters < ControllerWithSymbolAsFilter around_filter :raise_before, :raise_after, :without_exception, :only => :raises_both end @@ -746,17 +778,10 @@ class YieldingAroundFiltersTest < Test::Unit::TestCase assert_equal 1, ControllerWithFilterClass.filter_chain.size assert_equal 1, ControllerWithFilterInstance.filter_chain.size assert_equal 3, ControllerWithSymbolAsFilter.filter_chain.size - assert_equal 1, ControllerWithWrongFilterType.filter_chain.size assert_equal 6, ControllerWithNestedFilters.filter_chain.size assert_equal 4, ControllerWithAllTypesOfFilters.filter_chain.size end - def test_wrong_filter_type - assert_raise(ActionController::ActionControllerError) do - test_process(ControllerWithWrongFilterType,'no_raise') - end - end - def test_base controller = PostsController assert_nothing_raised { test_process(controller,'no_raise') } diff --git a/vendor/rails/actionpack/test/controller/flash_test.rb b/vendor/rails/actionpack/test/controller/flash_test.rb index 4a6f3c9e..e562531b 100644 --- a/vendor/rails/actionpack/test/controller/flash_test.rb +++ b/vendor/rails/actionpack/test/controller/flash_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class FlashTest < Test::Unit::TestCase class TestController < ActionController::Base @@ -52,7 +52,7 @@ class FlashTest < Test::Unit::TestCase end def rescue_action(e) - raise unless ActionController::MissingTemplate === e + raise unless ActionView::MissingTemplate === e end # methods for test_sweep_after_halted_filter_chain diff --git a/vendor/rails/actionpack/test/controller/fragment_store_setting_test.rb b/vendor/rails/actionpack/test/controller/fragment_store_setting_test.rb deleted file mode 100644 index 3df6fd0b..00000000 --- a/vendor/rails/actionpack/test/controller/fragment_store_setting_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -require File.dirname(__FILE__) + '/../abstract_unit' - -MemCache = Struct.new(:MemCache, :address) unless Object.const_defined?(:MemCache) - -class FragmentCacheStoreSettingTest < Test::Unit::TestCase - def teardown - ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::MemoryStore.new - end - - def test_file_fragment_cache_store - ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" - assert_kind_of( - ActionController::Caching::Fragments::FileStore, - ActionController::Base.fragment_cache_store - ) - assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path - end - - def test_drb_fragment_cache_store - ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" - assert_kind_of( - ActionController::Caching::Fragments::DRbStore, - ActionController::Base.fragment_cache_store - ) - assert_equal "druby://localhost:9192", ActionController::Base.fragment_cache_store.address - end - - if defined? CGI::Session::MemCacheStore - def test_mem_cache_fragment_cache_store - ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost" - assert_kind_of( - ActionController::Caching::Fragments::MemCacheStore, - ActionController::Base.fragment_cache_store - ) - assert_equal %w(localhost), ActionController::Base.fragment_cache_store.addresses - end - end - - def test_object_assigned_fragment_cache_store - ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory") - assert_kind_of( - ActionController::Caching::Fragments::FileStore, - ActionController::Base.fragment_cache_store - ) - assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path - end -end diff --git a/vendor/rails/actionpack/test/controller/header_test.rb b/vendor/rails/actionpack/test/controller/header_test.rb new file mode 100644 index 00000000..33c14a18 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/header_test.rb @@ -0,0 +1,14 @@ +require 'abstract_unit' + +class HeaderTest < Test::Unit::TestCase + def setup + @headers = ActionController::Http::Headers.new("HTTP_CONTENT_TYPE"=>"text/plain") + end + + def test_content_type_works + assert_equal "text/plain", @headers["Content-Type"] + assert_equal "text/plain", @headers["content-type"] + assert_equal "text/plain", @headers["CONTENT_TYPE"] + assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"] + end +end diff --git a/vendor/rails/actionpack/test/controller/helper_test.rb b/vendor/rails/actionpack/test/controller/helper_test.rb index 117f73b7..83e3b085 100644 --- a/vendor/rails/actionpack/test/controller/helper_test.rb +++ b/vendor/rails/actionpack/test/controller/helper_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' ActionController::Helpers::HELPERS_DIR.replace File.dirname(__FILE__) + '/../fixtures/helpers' @@ -46,22 +46,10 @@ class HelperTest < Test::Unit::TestCase eval("class #{controller_class_name} < TestController; end") @controller_class = self.class.const_get(controller_class_name) - # Generate new template class and assign to controller. - template_class_name = "Test#{@symbol}View" - eval("class #{template_class_name} < ActionView::Base; end") - @template_class = self.class.const_get(template_class_name) - @controller_class.template_class = @template_class - # Set default test helper. self.test_helper = LocalAbcHelper end - - def teardown - # Reset template class. - #ActionController::Base.template_class = ActionView::Base - end - - + def test_deprecated_helper assert_equal expected_helper_methods, missing_methods assert_nothing_raised { @controller_class.helper TestHelper } @@ -97,7 +85,7 @@ class HelperTest < Test::Unit::TestCase def test_helper_block_include assert_equal expected_helper_methods, missing_methods assert_nothing_raised { - @controller_class.helper { include TestHelper } + @controller_class.helper { include HelperTest::TestHelper } } assert [], missing_methods end @@ -142,6 +130,22 @@ class HelperTest < Test::Unit::TestCase assert methods.include?('foobar') end + def test_helper_proxy + methods = ApplicationController.helpers.methods.map(&:to_s) + + # ActionView + assert methods.include?('pluralize') + + # abc_helper.rb + assert methods.include?('bare_a') + + # fun/games_helper.rb + assert methods.include?('stratego') + + # fun/pdf_helper.rb + assert methods.include?('foobar') + end + private def expected_helper_methods TestHelper.instance_methods.map(&:to_s) diff --git a/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb index 0719883f..0519533d 100644 --- a/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb +++ b/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../../abstract_unit' -require 'test/unit' +require 'abstract_unit' class DocumentTest < Test::Unit::TestCase def test_handle_doctype diff --git a/vendor/rails/actionpack/test/controller/html-scanner/node_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/node_test.rb index 1cf0a4bb..240f01ac 100644 --- a/vendor/rails/actionpack/test/controller/html-scanner/node_test.rb +++ b/vendor/rails/actionpack/test/controller/html-scanner/node_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../../abstract_unit' -require 'test/unit' +require 'abstract_unit' class NodeTest < Test::Unit::TestCase diff --git a/vendor/rails/actionpack/test/controller/html-scanner/sanitizer_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/sanitizer_test.rb index 8fe9bbc5..db142f0b 100644 --- a/vendor/rails/actionpack/test/controller/html-scanner/sanitizer_test.rb +++ b/vendor/rails/actionpack/test/controller/html-scanner/sanitizer_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../../abstract_unit' -require 'test/unit' +require 'abstract_unit' class SanitizerTest < Test::Unit::TestCase def setup @@ -203,6 +202,12 @@ class SanitizerTest < Test::Unit::TestCase assert_equal expected, sanitize_css(raw) end + def test_should_sanitize_with_trailing_space + raw = "display:block; " + expected = "display: block;" + assert_equal expected, sanitize_css(raw) + end + def test_should_sanitize_xul_style_attributes raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')) assert_equal '', sanitize_css(raw) @@ -235,16 +240,20 @@ class SanitizerTest < Test::Unit::TestCase end def test_should_sanitize_img_vbscript - assert_sanitized %(), '' + assert_sanitized %(), '' end protected def assert_sanitized(input, expected = nil) @sanitizer ||= HTML::WhiteListSanitizer.new - assert_equal expected || input, @sanitizer.sanitize(input) + if input + assert_dom_equal expected || input, @sanitizer.sanitize(input) + else + assert_nil @sanitizer.sanitize(input) + end end - + def sanitize_css(input) (@sanitizer ||= HTML::WhiteListSanitizer.new).sanitize_css(input) end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/test/controller/html-scanner/tag_node_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/tag_node_test.rb index daeada9b..d1d46673 100644 --- a/vendor/rails/actionpack/test/controller/html-scanner/tag_node_test.rb +++ b/vendor/rails/actionpack/test/controller/html-scanner/tag_node_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../../abstract_unit' -require 'test/unit' +require 'abstract_unit' class TagNodeTest < Test::Unit::TestCase def test_open_without_attributes diff --git a/vendor/rails/actionpack/test/controller/html-scanner/text_node_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/text_node_test.rb index 9853701f..1ab3f445 100644 --- a/vendor/rails/actionpack/test/controller/html-scanner/text_node_test.rb +++ b/vendor/rails/actionpack/test/controller/html-scanner/text_node_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../../abstract_unit' -require 'test/unit' +require 'abstract_unit' class TextNodeTest < Test::Unit::TestCase def setup @@ -48,4 +47,4 @@ class TextNodeTest < Test::Unit::TestCase def test_match_other assert_nil @node.match(:hello) end -end \ No newline at end of file +end diff --git a/vendor/rails/actionpack/test/controller/html-scanner/tokenizer_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/tokenizer_test.rb index 437136b9..a001bcbb 100644 --- a/vendor/rails/actionpack/test/controller/html-scanner/tokenizer_test.rb +++ b/vendor/rails/actionpack/test/controller/html-scanner/tokenizer_test.rb @@ -1,5 +1,4 @@ -require File.dirname(__FILE__) + '/../../abstract_unit' -require 'test/unit' +require 'abstract_unit' class TokenizerTest < Test::Unit::TestCase @@ -79,6 +78,13 @@ class TokenizerTest < Test::Unit::TestCase assert_end end + def test_unterminated_cdata_tag + tokenize %{} + assert_next %{ world} assert_next %{original } diff --git a/vendor/rails/actionpack/test/controller/http_authentication_test.rb b/vendor/rails/actionpack/test/controller/http_authentication_test.rb index 6f7b31a4..c0069e80 100644 --- a/vendor/rails/actionpack/test/controller/http_authentication_test.rb +++ b/vendor/rails/actionpack/test/controller/http_authentication_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class HttpBasicAuthenticationTest < Test::Unit::TestCase include ActionController::HttpAuthentication::Basic diff --git a/vendor/rails/actionpack/test/controller/integration_test.rb b/vendor/rails/actionpack/test/controller/integration_test.rb index 4213bb4a..62c00c5b 100644 --- a/vendor/rails/actionpack/test/controller/integration_test.rb +++ b/vendor/rails/actionpack/test/controller/integration_test.rb @@ -1,27 +1,23 @@ -require File.dirname(__FILE__) + '/../abstract_unit' - -$:.unshift File.dirname(__FILE__) + '/../../../railties/lib' +require 'abstract_unit' require 'action_controller/integration' uses_mocha 'integration' do -# Stub process for testing. -module ActionController - module Integration - class Session - def process(*args) - end - - def generic_url_rewriter - end - end +module IntegrationSessionStubbing + def stub_integration_session(session) + session.stubs(:process) + session.stubs(:generic_url_rewriter) end end class SessionTest < Test::Unit::TestCase + include IntegrationSessionStubbing + def setup @session = ActionController::Integration::Session.new + stub_integration_session(@session) end + def test_https_bang_works_and_sets_truth_by_default assert !@session.https? @session.https! @@ -212,11 +208,13 @@ class SessionTest < Test::Unit::TestCase end class IntegrationTestTest < Test::Unit::TestCase + include IntegrationSessionStubbing def setup @test = ::ActionController::IntegrationTest.new(:default_test) @test.class.stubs(:fixture_table_names).returns([]) @session = @test.open_session + stub_integration_session(@session) end def test_opens_new_session @@ -235,12 +233,15 @@ end # Tests that integration tests don't call Controller test methods for processing. # Integration tests have their own setup and teardown. class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest + include IntegrationSessionStubbing def self.fixture_table_names [] end def test_integration_methods_called + reset! + stub_integration_session(@integration_session) %w( get post head put delete ).each do |verb| assert_nothing_raised("'#{verb}' should use integration test methods") { send!(verb, '/') } end @@ -248,8 +249,4 @@ class IntegrationTestUsesCorrectClass < ActionController::IntegrationTest end -# TODO -# class MockCGITest < Test::Unit::TestCase -# end - end # uses_mocha diff --git a/vendor/rails/actionpack/test/controller/integration_upload_test.rb b/vendor/rails/actionpack/test/controller/integration_upload_test.rb new file mode 100644 index 00000000..33df1131 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/integration_upload_test.rb @@ -0,0 +1,43 @@ +require 'abstract_unit' +require 'action_controller/integration' +require 'action_controller/routing' + +unless defined? ApplicationController + class ApplicationController < ActionController::Base + end +end + +class UploadTestController < ActionController::Base + session :off + + def update + SessionUploadTest.last_request_type = ActionController::Base.param_parsers[request.content_type] + render :text => "got here" + end +end + +class SessionUploadTest < ActionController::IntegrationTest + FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart' + + class << self + attr_accessor :last_request_type + end + + # def setup + # @session = ActionController::Integration::Session.new + # end + def test_post_with_upload + uses_mocha "test_post_with_upload" do + Dependencies.stubs(:load?).returns(false) + with_routing do |set| + set.draw do |map| + map.update 'update', :controller => "upload_test", :action => "update", :method => :post + end + + params = { :uploaded_data => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg") } + post '/update', params, :location => 'blah' + assert_equal(:multipart_form, SessionUploadTest.last_request_type) + end + end + end +end diff --git a/vendor/rails/actionpack/test/controller/layout_test.rb b/vendor/rails/actionpack/test/controller/layout_test.rb index 85cc3a08..3dc311b7 100644 --- a/vendor/rails/actionpack/test/controller/layout_test.rb +++ b/vendor/rails/actionpack/test/controller/layout_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' # The view_paths array must be set on Base and not LayoutTest so that LayoutTest's inherited # method has access to the view_paths array when looking for a layout to automatically assign. @@ -31,16 +31,16 @@ end class MultipleExtensions < LayoutTest end -class MabView +class MabView < ActionView::TemplateHandler def initialize(view) end - def render(text, locals = {}) - text + def render(template) + template.source end end -ActionView::Base::register_template_handler :mab, MabView +ActionView::Template::register_template_handler :mab, MabView class LayoutAutoDiscoveryTest < Test::Unit::TestCase def setup @@ -67,6 +67,7 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase get :hello assert_equal 'layouts/third_party_template_library', @controller.active_layout assert_equal 'layouts/third_party_template_library', @response.layout + assert_response :success assert_equal 'Mab', @response.body end @@ -215,7 +216,7 @@ class LayoutExceptionRaised < Test::Unit::TestCase @controller = SetsNonExistentLayoutFile.new get :hello @response.template.class.module_eval { attr_accessor :exception } - assert_equal ActionController::MissingTemplate, @response.template.exception.class + assert_equal ActionView::MissingTemplate, @response.template.exception.class end end @@ -237,3 +238,22 @@ class LayoutStatusIsRenderedTest < Test::Unit::TestCase assert_response 401 end end + +class LayoutSymlinkedTest < LayoutTest + layout "symlinked/symlinked_layout" +end + +class LayoutSymlinkedIsRenderedTest < Test::Unit::TestCase + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_symlinked_layout_is_rendered + @controller = LayoutSymlinkedTest.new + get :hello + assert_response 200 + assert_equal "layouts/symlinked/symlinked_layout", @response.layout + end +end + \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/mime_responds_test.rb b/vendor/rails/actionpack/test/controller/mime_responds_test.rb index f121dd9f..c617cb2e 100644 --- a/vendor/rails/actionpack/test/controller/mime_responds_test.rb +++ b/vendor/rails/actionpack/test/controller/mime_responds_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class RespondToController < ActionController::Base layout :set_layout @@ -107,6 +107,13 @@ class RespondToController < ActionController::Base type.any(:js, :xml) { render :text => "Either JS or XML" } end end + + def handle_any_any + respond_to do |type| + type.html { render :text => 'HTML' } + type.any { render :text => 'Whatever you ask for, I got it' } + end + end def all_types_with_layout respond_to do |type| @@ -335,6 +342,35 @@ class MimeControllerTest < Test::Unit::TestCase assert_equal 'Either JS or XML', @response.body end + def test_handle_any_any + @request.env["HTTP_ACCEPT"] = "*/*" + get :handle_any_any + assert_equal 'HTML', @response.body + end + + def test_handle_any_any_parameter_format + get :handle_any_any, {:format=>'html'} + assert_equal 'HTML', @response.body + end + + def test_handle_any_any_explicit_html + @request.env["HTTP_ACCEPT"] = "text/html" + get :handle_any_any + assert_equal 'HTML', @response.body + end + + def test_handle_any_any_javascript + @request.env["HTTP_ACCEPT"] = "text/javascript" + get :handle_any_any + assert_equal 'Whatever you ask for, I got it', @response.body + end + + def test_handle_any_any_xml + @request.env["HTTP_ACCEPT"] = "text/xml" + get :handle_any_any + assert_equal 'Whatever you ask for, I got it', @response.body + end + def test_rjs_type_skips_layout @request.env["HTTP_ACCEPT"] = "text/javascript" get :all_types_with_layout @@ -432,16 +468,12 @@ class MimeControllerTest < Test::Unit::TestCase assert_equal '
    Hello future from Firefox!
    ', @response.body @request.env["HTTP_ACCEPT"] = "text/iphone" - assert_raises(ActionController::MissingTemplate) { get :iphone_with_html_response_type_without_layout } + assert_raises(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout } end end class AbstractPostController < ActionController::Base - class << self - def view_paths - [ File.dirname(__FILE__) + "/../fixtures/post_test/" ] - end - end + self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/" end # For testing layouts which are set automatically diff --git a/vendor/rails/actionpack/test/controller/mime_type_test.rb b/vendor/rails/actionpack/test/controller/mime_type_test.rb index d4aea3c0..f16a3c68 100644 --- a/vendor/rails/actionpack/test/controller/mime_type_test.rb +++ b/vendor/rails/actionpack/test/controller/mime_type_test.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/../abstract_unit' +require 'abstract_unit' class MimeTypeTest < Test::Unit::TestCase Mime::Type.register "image/png", :png @@ -28,6 +28,13 @@ class MimeTypeTest < Test::Unit::TestCase expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL] assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s } end + + # Accept header send with user HTTP_USER_AGENT: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1) + def test_parse_crappy_broken_acceptlines2 + accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*" + expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::ALL ] + assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s } + end def test_custom_type Mime::Type.register("image/gif", :gif) @@ -39,17 +46,39 @@ class MimeTypeTest < Test::Unit::TestCase Mime.module_eval { remove_const :GIF if const_defined?(:GIF) } end + def test_type_should_be_equal_to_symbol + assert_equal Mime::HTML, 'application/xhtml+xml' + assert_equal Mime::HTML, :html + end + def test_type_convenience_methods - types = [:html, :xml, :png, :pdf, :yaml, :url_encoded_form] + # Don't test Mime::ALL, since it Mime::ALL#html? == true + types = Mime::SET.to_a.map(&:to_sym).uniq - [:all] + + # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE + types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } + types.each do |type| mime = Mime.const_get(type.to_s.upcase) - assert mime.send("#{type}?"), "Mime::#{type.to_s.upcase} is not #{type}?" - (types - [type]).each { |t| assert !mime.send("#{t}?"), "Mime::#{t.to_s.upcase} is #{t}?" } + assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?" + (types - [type]).each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" } end end - + def test_mime_all_is_html assert Mime::ALL.all?, "Mime::ALL is not all?" assert Mime::ALL.html?, "Mime::ALL is not html?" end + + def test_verifiable_mime_types + unverified_types = Mime::Type.unverifiable_types + all_types = Mime::SET.to_a.map(&:to_sym) + all_types.uniq! + # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE + all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } + + unverified, verified = all_types.partition { |type| Mime::Type.unverifiable_types.include? type } + assert verified.all? { |type| Mime.const_get(type.to_s.upcase).verify_request? }, "Not all Mime Types are verified: #{verified.inspect}" + assert unverified.all? { |type| !Mime.const_get(type.to_s.upcase).verify_request? }, "Some Mime Types are verified: #{unverified.inspect}" + end end diff --git a/vendor/rails/actionpack/test/controller/new_render_test.rb b/vendor/rails/actionpack/test/controller/new_render_test.rb index 3168daea..6e2c6d90 100644 --- a/vendor/rails/actionpack/test/controller/new_render_test.rb +++ b/vendor/rails/actionpack/test/controller/new_render_test.rb @@ -1,5 +1,5 @@ -require File.dirname(__FILE__) + '/../abstract_unit' -require File.dirname(__FILE__) + '/fake_models' +require 'abstract_unit' +require 'controller/fake_models' class CustomersController < ActionController::Base end @@ -17,6 +17,9 @@ module NewRenderTestHelper end end +class LabellingFormBuilder < ActionView::Helpers::FormBuilder +end + class NewRenderTestController < ActionController::Base layout :determine_layout @@ -75,11 +78,6 @@ class NewRenderTestController < ActionController::Base @secret = 'in the sauce' render :file => 'test/render_file_with_ivar', :use_full_path => true end - - def render_file_not_using_full_path_with_relative_path - @secret = 'in the sauce' - render :file => 'test/../test/render_file_with_ivar', :use_full_path => true - end def render_file_not_using_full_path_with_dot_in_path @secret = 'in the sauce' @@ -136,15 +134,50 @@ class NewRenderTestController < ActionController::Base def partial_with_locals render :partial => "customer", :locals => { :customer => Customer.new("david") } end - + + def partial_with_form_builder + render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, @template, {}, Proc.new {}) + end + + def partial_with_form_builder_subclass + render :partial => LabellingFormBuilder.new(:post, nil, @template, {}, Proc.new {}) + end + def partial_collection render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ] end + + def partial_collection_with_spacer + render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ] + end + + def partial_collection_with_counter + render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ] + end def partial_collection_with_locals render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } end + def partial_collection_shorthand_with_locals + render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } + end + + def partial_collection_shorthand_with_different_types_of_records + render :partial => [ + BadCustomer.new("mark"), + GoodCustomer.new("craig"), + BadCustomer.new("john"), + GoodCustomer.new("zach"), + GoodCustomer.new("brandon"), + BadCustomer.new("dan") ], + :locals => { :greeting => "Bonjour" } + end + + def partial_collection_shorthand_with_different_types_of_records_with_counter + partial_collection_shorthand_with_different_types_of_records + end + def empty_partial_collection render :partial => "customer", :collection => [] end @@ -206,6 +239,18 @@ class NewRenderTestController < ActionController::Base render :inline => "Hello: <%= params[:name] %>" end + def accessing_request_in_template + render :inline => "Hello: <%= request.host %>" + end + + def accessing_logger_in_template + render :inline => "<%= logger.class %>" + end + + def accessing_action_name_in_template + render :inline => "<%= action_name %>" + end + def accessing_params_in_template_with_layout render :layout => nil, :inline => "Hello: <%= params[:name] %>" end @@ -361,10 +406,18 @@ class NewRenderTestController < ActionController::Base render :action => "calling_partial_with_layout" end + def render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout + render :action => "calling_partial_with_layout" + end + def render_using_layout_around_block render :action => "using_layout_around_block" end + def render_using_layout_around_block_in_main_layout_and_within_content_for_layout + render :action => "using_layout_around_block" + end + def rescue_action(e) raise end private @@ -387,6 +440,10 @@ class NewRenderTestController < ActionController::Base "layouts/builder" when "action_talk_to_layout", "layout_overriding_layout" "layouts/talk_from_action" + when "render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout" + "layouts/partial_with_layout" + when "render_using_layout_around_block_in_main_layout_and_within_content_for_layout" + "layouts/block_with_layout" end end end @@ -465,11 +522,6 @@ class NewRenderTest < Test::Unit::TestCase assert_equal "The secret is in the sauce\n", @response.body end - def test_render_file_not_using_full_path_with_relative_path - get :render_file_not_using_full_path_with_relative_path - assert_equal "The secret is in the sauce\n", @response.body - end - def test_render_file_not_using_full_path_with_dot_in_path get :render_file_not_using_full_path_with_dot_in_path assert_equal "The secret is in the sauce\n", @response.body @@ -489,25 +541,18 @@ class NewRenderTest < Test::Unit::TestCase end def test_access_to_request_in_view - view_internals_old_value = ActionController::Base.view_controller_internals + get :accessing_request_in_template + assert_equal "Hello: www.nextangle.com", @response.body + end - ActionController::Base.view_controller_internals = false - ActionController::Base.protected_variables_cache = nil - - get :hello_world - assert !assigns.include?('request'), 'request should not be in assigns' - - ActionController::Base.view_controller_internals = true - ActionController::Base.protected_variables_cache = nil - - get :hello_world - assert !assigns.include?('request'), 'request should not be in assigns' - assert_kind_of ActionController::AbstractRequest, assigns['_request'] - assert_kind_of ActionController::AbstractRequest, @response.template.request - - ensure - ActionController::Base.view_controller_internals = view_internals_old_value - ActionController::Base.protected_variables_cache = nil + def test_access_to_logger_in_view + get :accessing_logger_in_template + assert_equal "Logger", @response.body + end + + def test_access_to_action_name_in_view + get :accessing_action_name_in_template + assert_equal "accessing_action_name_in_template", @response.body end def test_render_xml @@ -532,6 +577,12 @@ EOS assert_equal "

    This is grand!

    \n", @response.body end + def test_render_with_default_from_accept_header + @request.env["HTTP_ACCEPT"] = "text/javascript" + get :greeting + assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body + end + def test_render_rjs_with_default get :delete_with_js assert_equal %!Element.remove("person");\nnew Effect.Highlight(\"project-4\",{});!, @response.body @@ -605,7 +656,7 @@ EOS end def test_bad_render_to_string_still_throws_exception - assert_raises(ActionController::MissingTemplate) { get :render_to_string_with_exception } + assert_raises(ActionView::MissingTemplate) { get :render_to_string_with_exception } end def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns @@ -672,15 +723,47 @@ EOS assert_equal "Hello: david", @response.body end + def test_partial_with_form_builder + get :partial_with_form_builder + assert_match(/