require 'rbconfig'
require 'find'
require 'ftools'

include Config

# this was adapted from rdoc's install.rb by way of Log4r

$sitedir = CONFIG["sitelibdir"]
unless $sitedir
  version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  $libdir = File.join(CONFIG["libdir"], "ruby", version)
  $sitedir = $:.find {|x| x =~ /site_ruby/ }
  if !$sitedir
    $sitedir = File.join($libdir, "site_ruby")
  elsif $sitedir !~ Regexp.quote(version)
    $sitedir = File.join($sitedir, version)
  end
end

# the acual gruntwork
Dir.chdir("lib")

Find.find("action_mailer", "action_mailer.rb") { |f|
  if f[-3..-1] == ".rb"
    File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  else
    File::makedirs(File.join($sitedir, *f.split(/\//)))
  end
}
module ActionMailer
  module AdvAttrAccessor #:nodoc:
    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    module ClassMethods #:nodoc:
      def adv_attr_accessor(*names)
        names.each do |name|
          ivar = "@#{name}"

          define_method("#{name}=") do |value|
            instance_variable_set(ivar, value)
          end

          define_method(name) do |*parameters|
            raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
            if parameters.empty?
              if instance_variables.include?(ivar)
                instance_variable_get(ivar)
              end
            else
              instance_variable_set(ivar, parameters.first)
            end
          end
        end
      end
    end
  end
end
require 'action_mailer/adv_attr_accessor'
require 'action_mailer/part'
require 'action_mailer/part_container'
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.
  #
  # = Mailer Models
  # To use ActionMailer, 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 
  # to add attachments.
  #
  # Examples:
  #
  #  class Notifier < ActionMailer::Base
  #    def signup_notification(recipient)
  #      recipients recipient.email_address_with_name
  #      from       "system@example.com"
  #      subject    "New account information"
  #      body       "account" => recipient
  #    end
  #  end
  #
  # Mailer methods have the following configuration methods available.
  #
  # * <tt>recipients</tt> - Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the <tt>To:</tt> header.
  # * <tt>subject</tt> - The subject of your email. Sets the <tt>Subject:</tt> header.
  # * <tt>from</tt> - Who the email you are sending is from. Sets the <tt>From:</tt> header.
  # * <tt>cc</tt> - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the <tt>Cc:</tt> header.
  # * <tt>bcc</tt> - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc</tt> header.
  # * <tt>sent_on</tt> - The date on which the message was sent. If not set, the header wil be set by the delivery agent.
  # * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>.
  # * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>.
  #
  # The <tt>body</tt> 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, <tt>body "account" => recipient</tt> would result
  # in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the 
  # view.
  #
  # = Mailer Views
  # Like ActionController, 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 <tt>.rhtml</tt> file with the same name as the method
  # in your mailer model. For example, in the mailer defined above, the template at 
  # <tt>app/views/notifier/signup_notification.rhtml</tt> would be used to generate the email.
  #
  # Variables defined in the model are accessible as instance variables in the view.
  #
  # Emails by default are sent in plain text, so a sample view for our model example might look like this:
  #
  #   Hi <%= @account.name %>,
  #   Thanks for joining our service! Please check back often.
  #
  # = Sending Mail
  # 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
  # <tt>deliver_</tt> followed by the name of the mailer method that you would
  # like to deliver. The <tt>signup_notification</tt> method defined above is
  # delivered by invoking <tt>Notifier.deliver_signup_notification</tt>.
  #
  # = HTML Email
  # To send mail as HTML, make sure your view (the <tt>.rhtml</tt> file) generates HTML and
  # set the content type to html.
  #
  #   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
  #     end
  #   end  
  #
  # = Multipart Email
  # You can explicitly specify multipart messages:
  #
  #   class ApplicationMailer < ActionMailer::Base
  #     def signup_notification(recipient)
  #       recipients      recipient.email_address_with_name
  #       subject         "New account information"
  #       from            "system@example.com"
  #
  #       part :content_type => "text/html",
  #         :body => render_message("signup-as-html", :account => recipient)
  #
  #       part "text/plain" do |p|
  #         p.body = render_message("signup-as-plain", :account => recipient)
  #         p.transfer_encoding = "base64"
  #       end
  #     end
  #   end
  #  
  # Multipart messages can also be used implicitly because ActionMailer 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.rhtml
  # * signup_notification.text.html.rhtml
  # * signup_notification.text.xml.rxml
  # * signup_notification.text.x-yaml.rhtml
  #  
  # 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.
  #
  # = Attachments
  # Attachments can be added by using the +attachment+ method.
  #
  # Example:
  #
  #   class ApplicationMailer < ActionMailer::Base
  #     # attachments
  #     def signup_notification(recipient)
  #       recipients      recipient.email_address_with_name
  #       subject         "New account information"
  #       from            "system@example.com"
  #
  #       attachment :content_type => "image/jpeg",
  #         :body => File.read("an-image.jpg")
  #
  #       attachment "application/pdf" do |a|
  #         a.body = generate_your_pdf_here()
  #       end
  #     end
  #   end 
  #
  # = Configuration options
  #
  # These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
  #
  # * <tt>template_root</tt> - template root determines the base from which template references will be made.
  #
  # * <tt>logger</tt> - 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.
  #
  # * <tt>server_settings</tt> -  Allows detailed configuration of the server:
  #   * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
  #   * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
  #   * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
  #   * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
  #   * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
  #   * <tt>:authentication</tt> 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
  #
  # * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
  #
  # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
  #   Sendmail is assumed to be present at "/usr/sbin/sendmail".
  #
  # * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
  #   but this can be turned off to help functional testing.
  #
  # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful
  #   for unit and functional testing.
  #
  # * <tt>default_charset</tt> - 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 <tt>@charset</tt>.
  # * <tt>default_content_type</tt> - 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 <tt>@content_type</tt>. 
  # * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to nil. You
  #   can also pick a different value from inside a method with <tt>@mime_version</tt>. When multipart messages are in
  #   use, <tt>@mime_version</tt> will be set to "1.0" if it is not set inside a method.
  # * <tt>default_implicit_parts_order</tt> - 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
  #   and appear last in the mime encoded message. You can also pick a different order from inside a method with
  #   <tt>@implicit_parts_order</tt>.
  class Base
    include AdvAttrAccessor, PartContainer

    # Action Mailer subclasses should be reloaded by the dispatcher in Rails
    # when Dependencies.mechanism = :load.
    include Reloadable::Subclasses
    
    private_class_method :new #:nodoc:

    class_inheritable_accessor :template_root
    cattr_accessor :logger

    @@server_settings = { 
      :address        => "localhost", 
      :port           => 25, 
      :domain         => 'localhost.localdomain', 
      :user_name      => nil, 
      :password       => nil, 
      :authentication => nil
    }
    cattr_accessor :server_settings

    @@raise_delivery_errors = true
    cattr_accessor :raise_delivery_errors

    @@delivery_method = :smtp
    cattr_accessor :delivery_method
    
    @@perform_deliveries = true
    cattr_accessor :perform_deliveries
    
    @@deliveries = []
    cattr_accessor :deliveries

    @@default_charset = "utf-8"
    cattr_accessor :default_charset

    @@default_content_type = "text/plain"
    cattr_accessor :default_content_type
    
    @@default_mime_version = nil
    cattr_accessor :default_mime_version

    @@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
    cattr_accessor :default_implicit_parts_order

    # 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 <tt>text/plain</tt>
    # 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 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
    
    # Override the mailer name, which defaults to an inflected version of the
    # mailer's class name. If you want to use a template in a non-standard
    # location, you can use this to specify that location.
    adv_attr_accessor :mailer_name
    
    # 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.
    adv_attr_accessor :template

    # The mail object instance referenced by this mailer.
    attr_reader :mail

    class << self
      def method_missing(method_symbol, *parameters)#:nodoc:
        case method_symbol.id2name
          when /^create_([_a-z]\w*)/  then new($1, *parameters).mail
          when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
          when "new" then nil
          else super
        end
      end

      # 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
      # method that accepts the email object as a parameter:
      #
      #   class MyMailer < ActionMailer::Base
      #     def receive(mail)
      #       ...
      #     end
      #   end
      def receive(raw_email)
        logger.info "Received mail:\n #{raw_email}" unless logger.nil?
        mail = TMail::Mail.parse(raw_email)
        mail.base64_decode
        new.receive(mail)
      end

      # Deliver the given mail object directly. This can be used to deliver
      # a preconstructed mail object, like:
      #
      #   email = MyMailer.create_some_mail(parameters)
      #   email.set_some_obscure_header "frobnicate"
      #   MyMailer.deliver(email)
      def deliver(mail)
        new.deliver!(mail)
      end
    end

    # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
    # will be initialized according to the named method. If not, the mailer will
    # 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 
    end

    # Initialize the mailer via the given +method_name+. The body will be
    # rendered and a new TMail::Mail object created.
    def create!(method_name, *parameters) #:nodoc:
      initialize_defaults(method_name)
      send(method_name, *parameters)

      # If an explicit, textual body has not been set, we check assumptions.
      unless String === @body
        # First, we look to see if there are any likely templates that match,
        # which include the content-type in their file name (i.e.,
        # "the_template_file.text.html.rhtml", etc.). Only do this if parts
        # have not already been specified manually.
        if @parts.empty?
          templates = Dir.glob("#{template_path}/#{@template}.*")
          templates.each do |path|
            # TODO: don't hardcode rhtml|rxml
            basename = File.basename(path)
            next unless md = /^([^\.]+)\.([^\.]+\.[^\+]+)\.(rhtml|rxml)$/.match(basename)
            template_name = basename
            content_type = md.captures[1].gsub('.', '/')
            @parts << Part.new(:content_type => content_type,
              :disposition => "inline", :charset => charset,
              :body => render_message(template_name, @body))
          end
          unless @parts.empty?
            @content_type = "multipart/alternative"
            @parts = sort_parts(@parts, @implicit_parts_order)
          end
        end

        # Then, if there were such templates, we check to see if we ought to
        # also render a "normal" template (without the content type). If a
        # normal template exists (or if there were no implicit parts) we render
        # it.
        template_exists = @parts.empty?
        template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 }
        @body = render_message(@template, @body) if template_exists

        # Finally, if there are other message parts and a textual body exists,
        # we shift it onto the front of the parts and set the body to nil (so
        # that create_mail doesn't try to render it in addition to the parts).
        if !@parts.empty? && String === @body
          @parts.unshift Part.new(:charset => charset, :body => @body)
          @body = nil
        end
      end

      # If this is a multipart e-mail add the mime_version if it is not
      # already set.
      @mime_version ||= "1.0" if !@parts.empty?

      # build the mail object itself
      @mail = create_mail
    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
    # 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?

      begin
        send("perform_delivery_#{delivery_method}", mail) if perform_deliveries
      rescue Object => e
        raise e if raise_delivery_errors
      end

      return mail
    end

    private
      # Set up the default values for the various instance variables of this
      # mailer. Subclasses may override this method to provide different
      # defaults.
      def initialize_defaults(method_name)
        @charset ||= @@default_charset.dup
        @content_type ||= @@default_content_type.dup
        @implicit_parts_order ||= @@default_implicit_parts_order.dup
        @template ||= method_name
        @mailer_name ||= Inflector.underscore(self.class.name)
        @parts ||= []
        @headers ||= {}
        @body ||= {}
        @mime_version = @@default_mime_version.dup if @@default_mime_version
      end

      def render_message(method_name, body)
        render :file => method_name, :body => body
      end

      def render(opts)
        body = opts.delete(:body)
        initialize_template_class(body).render(opts)
      end

      def template_path
        "#{template_root}/#{mailer_name}"
      end

      def initialize_template_class(assigns)
        ActionView::Base.new(template_path, assigns, self)
      end

      def sort_parts(parts, order = [])
        order = order.collect { |s| s.downcase }

        parts = parts.sort do |a, b|
          a_ct = a.content_type.downcase
          b_ct = b.content_type.downcase

          a_in = order.include? a_ct
          b_in = order.include? b_ct

          s = case
          when a_in && b_in
            order.index(a_ct) <=> order.index(b_ct)
          when a_in
            -1
          when b_in
            1
          else
            a_ct <=> b_ct
          end

          # reverse the ordering because parts that come last are displayed
          # first in mail clients
          (s * -1)
        end

        parts
      end

      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.mime_version = mime_version unless mime_version.nil?
        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

        if @parts.empty?
          m.set_content_type(real_content_type, nil, ctype_attrs)
          m.body = Utils.normalize_new_lines(body)
        else
          if String === body
            part = TMail::Mail.new
            part.body = Utils.normalize_new_lines(body)
            part.set_content_type(real_content_type, nil, ctype_attrs)
            part.set_content_disposition "inline"
            m.parts << part
          end

          @parts.each do |p|
            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)
          end
        end

        @mail = m
      end

      def perform_delivery_smtp(mail)
        destinations = mail.destinations
        mail.ready_to_send

        Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain], 
            server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
          smtp.sendmail(mail.encoded, mail.from, destinations)
        end
      end

      def perform_delivery_sendmail(mail)
        IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm|
          sm.print(mail.encoded.gsub(/\r/, ''))
          sm.flush
        end
      end

      def perform_delivery_test(mail)
        deliveries << mail
      end
  end
end
module ActionMailer
  module Helpers #:nodoc:
    def self.append_features(base) #:nodoc:
      super

      # Initialize the base module to aggregate its helpers.
      base.class_inheritable_accessor :master_helper_module
      base.master_helper_module = Module.new

      # Extend base with class methods to declare helpers.
      base.extend(ClassMethods)

      base.class_eval do
        # Wrap inherited to create a new master helper module for subclasses.
        class << self
          alias_method :inherited_without_helper, :inherited
          alias_method :inherited, :inherited_with_helper
        end

        # Wrap initialize_template_class to extend new template class
        # instances with the master helper module.
        alias_method :initialize_template_class_without_helper, :initialize_template_class
        alias_method :initialize_template_class, :initialize_template_class_with_helper
      end
    end

    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 
      # available to the templates.
      def add_template_helper(helper_module) #:nodoc:
        master_helper_module.module_eval "include #{helper_module}"
      end

      # Declare a helper:
      #   helper :foo
      # requires 'foo_helper' and includes FooHelper in the template class.
      #   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.
      #   helper(:three, BlindHelper) { def mice() 'mice' end }
      # does all three.
      def helper(*args, &block)
        args.flatten.each do |arg|
          case arg
            when Module
              add_template_helper(arg)
            when String, Symbol
              file_name  = arg.to_s.underscore + '_helper'
              class_name = file_name.camelize
                
              begin
                require_dependency(file_name)
              rescue LoadError => load_error
                requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
                msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
                raise LoadError.new(msg).copy_blame!(load_error)
              end

              add_template_helper(class_name.constantize)
            else
              raise ArgumentError, 'helper expects String, Symbol, or Module argument'
          end
        end

        # Evaluate block in template class if given.
        master_helper_module.module_eval(&block) if block_given?
      end

      # Declare a controller method as a helper.  For example,
      #   helper_method :link_to
      #   def link_to(name, options) ... end
      # makes the link_to controller method available in the view.
      def helper_method(*methods)
        methods.flatten.each do |method|
          master_helper_module.module_eval <<-end_eval
            def #{method}(*args, &block)
              controller.send(%(#{method}), *args, &block)
            end
          end_eval
        end
      end

      # Declare a controller attribute as a helper.  For example,
      #   helper_attr :name
      #   attr_accessor :name
      # makes the name and name= controller methods available in the view.
      # The is a convenience wrapper for helper_method.
      def helper_attr(*attrs)
        attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
      end

      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
          rescue MissingSourceFile => e
            raise unless e.is_missing?("helpers/#{child.name.underscore}_helper")
          end
        end        
    end

    private
      # Extend the template class instance with our controller's helper module.
      def initialize_template_class_with_helper(assigns)
        returning(template = initialize_template_class_without_helper(assigns)) do
          template.extend self.class.master_helper_module
        end
      end
  end
endrequire 'text/format'

module MailHelper
  # Uses Text::Format to take the text and format it, indented two spaces for
  # each line, and wrapped at 72 columns.
  def block_format(text)
    formatted = text.split(/\n\r\n/).collect { |paragraph| 
      Text::Format.new(
        :columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
      ).format
    }.join("\n")
    
    # Make list points stand on their own line
    formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| "  #{$1} #{$2.strip}\n" }
    formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| "  #{$1} #{$2.strip}\n" }

    formatted
  end
end
require 'action_mailer/adv_attr_accessor'
require 'action_mailer/part_container'
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
  # to use the helper methods in ActionMailer::PartContainer.
  class Part
    include ActionMailer::AdvAttrAccessor
    include ActionMailer::PartContainer

    # 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
    # and assign the result here.
    adv_attr_accessor :body
    
    # Specify the charset for this subpart. By default, it will be the charset
    # of the containing part or mailer.
    adv_attr_accessor :charset
    
    # The content disposition of this part, typically either "inline" or
    # "attachment".
    adv_attr_accessor :content_disposition
    
    # The content type of the part.
    adv_attr_accessor :content_type
    
    # The filename to use for this subpart (usually for attachments).
    adv_attr_accessor :filename
    
    # Accessor for specifying additional headers to include with this part.
    adv_attr_accessor :headers
    
    # The transfer encoding to use for this subpart, like "base64" or
    # "quoted-printable".
    adv_attr_accessor :transfer_encoding

    # Create a new part from the given +params+ hash. The valid params keys
    # correspond to the accessors.
    def initialize(params)
      @content_type = params[:content_type]
      @content_disposition = params[:disposition] || "inline"
      @charset = params[:charset]
      @body = params[:body]
      @filename = params[:filename]
      @transfer_encoding = params[:transfer_encoding] || "quoted-printable"
      @headers = params[:headers] || {}
      @parts = []
    end

    # Convert the part to a mail object which can be included in the parts
    # list of another mail object.
    def to_mail(defaults)
      part = TMail::Mail.new

      real_content_type, ctype_attrs = parse_content_type(defaults)

      if @parts.empty?
        part.content_transfer_encoding = transfer_encoding || "quoted-printable"
        case (transfer_encoding || "").downcase
          when "base64" then
            part.body = TMail::Base64.folding_encode(body)
          when "quoted-printable"
            part.body = [Utils.normalize_new_lines(body)].pack("M*")
          else
            part.body = body
        end

        # Always set the content_type after setting the body and or parts!
        # Also don't set filename and name when there is none (like in
        # non-attachment parts)
        if content_disposition == "attachment"
          ctype_attrs.delete "charset"
          part.set_content_type(real_content_type, nil,
            squish("name" => filename).merge(ctype_attrs))
          part.set_content_disposition(content_disposition,
            squish("filename" => filename).merge(ctype_attrs))
        else
          part.set_content_type(real_content_type, nil, ctype_attrs)
          part.set_content_disposition(content_disposition) 
        end        
      else
        if String === body
          part = TMail::Mail.new
          part.body = body
          part.set_content_type(real_content_type, nil, ctype_attrs)
          part.set_content_disposition "inline"
          m.parts << part
        end
          
        @parts.each do |p|
          prt = (TMail::Mail === p ? p : p.to_mail(defaults))
          part.parts << prt
        end
        
        part.set_content_type(real_content_type, nil, ctype_attrs) if real_content_type =~ /multipart/
      end

      headers.each { |k,v| part[k] = v }

      part
    end

    private

      def squish(values={})
        values.delete_if { |k,v| v.nil? }
      end
  end
end
module ActionMailer
  # Accessors and helpers that ActionMailer::Base and ActionMailer::Part have
  # in common. Using these helpers you can easily add subparts or attachments
  # to your message:
  #
  #   def my_mail_message(...)
  #     ...
  #     part "text/plain" do |p|
  #       p.body "hello, world"
  #       p.transfer_encoding "base64"
  #     end
  #
  #     attachment "image/jpg" do |a|
  #       a.body = File.read("hello.jpg")
  #       a.filename = "hello.jpg"
  #     end
  #   end
  module PartContainer
    # The list of subparts of this container
    attr_reader :parts

    # Add a part to a multipart message, with the given content-type. The
    # part itself is yielded to the block so that other properties (charset,
    # body, headers, etc.) can be set on it.
    def part(params)
      params = {:content_type => params} if String === params
      part = Part.new(params)
      yield part if block_given?
      @parts << part
    end

    # Add an attachment to a multipart message. This is simply a part with the
    # content-disposition set to "attachment".
    def attachment(params, &block)
      params = { :content_type => params } if String === params
      params = { :disposition => "attachment",
                 :transfer_encoding => "base64" }.merge(params)
      part(params, &block)
    end

    private
    
      def parse_content_type(defaults=nil)
        return [defaults && defaults.content_type, {}] if content_type.blank?
        ctype, *attrs = content_type.split(/;\s*/)
        attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h }
        [ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)]
      end

  end
end
module ActionMailer
  module Quoting #:nodoc:
    # Convert the given text into quoted printable format, with an instruction
    # that the text be eventually interpreted in the given charset.
    def quoted_printable(text, charset)
      text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
                  gsub( / /, "_" )
      "=?#{charset}?Q?#{text}?="
    end

    # Convert the given character to quoted printable format, taking into
    # account multi-byte characters (if executing with $KCODE="u", for instance)
    def quoted_printable_encode(character)
      result = ""
      character.each_byte { |b| result << "=%02x" % b }
      result
    end

    # A quick-and-dirty regexp for determining whether a string contains any
    # characters that need escaping.
    if !defined?(CHARS_NEEDING_QUOTING)
      CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
    end

    # Quote the given text if it contains any "illegal" characters
    def quote_if_necessary(text, charset)
      (text =~ CHARS_NEEDING_QUOTING) ?
        quoted_printable(text, charset) :
        text
    end

    # Quote any of the given strings if they contain any "illegal" characters
    def quote_any_if_necessary(charset, *args)
      args.map { |v| quote_if_necessary(v, charset) }
    end

    # Quote the given address if it needs to be. The address may be a
    # 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.
    def quote_address_if_necessary(address, charset)
      if Array === address
        address.map { |a| quote_address_if_necessary(a, charset) }
      elsif address =~ /^(\S.*)\s+(<.*>)$/
        address = $2
        phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
        "\"#{phrase}\" #{address}"
      else
        address
      end
    end

    # Quote any of the given addresses, if they need to be.
    def quote_any_address_if_necessary(charset, *args)
      args.map { |v| quote_address_if_necessary(v, charset) }
    end
  end
end
module ActionMailer
  module Utils #:nodoc:
    def normalize_new_lines(text)
      text.to_s.gsub(/\r\n?/, "\n")
    end
    module_function :normalize_new_lines
  end
end
#--
# Text::Format for Ruby
# Version 0.63
#
# Copyright (c) 2002 - 2003 Austin Ziegler
#
# $Id: format.rb,v 1.1.1.1 2004/10/14 11:59:57 webster132 Exp $
#
# ==========================================================================
# Revision History ::
# YYYY.MM.DD  Change ID   Developer
#             Description
# --------------------------------------------------------------------------
# 2002.10.18              Austin Ziegler
#             Fixed a minor problem with tabs not being counted. Changed
#             abbreviations from Hash to Array to better suit Ruby's
#             capabilities. Fixed problems with the way that Array arguments
#             are handled in calls to the major object types, excepting in
#             Text::Format#expand and Text::Format#unexpand (these will
#             probably need to be fixed).
# 2002.10.30              Austin Ziegler
#             Fixed the ordering of the <=> for binary tests. Fixed
#             Text::Format#expand and Text::Format#unexpand to handle array
#             arguments better.
# 2003.01.24              Austin Ziegler
#             Fixed a problem with Text::Format::RIGHT_FILL handling where a
#             single word is larger than #columns. Removed Comparable
#             capabilities (<=> doesn't make sense; == does). Added Symbol
#             equivalents for the Hash initialization. Hash initialization has
#             been modified so that values are set as follows (Symbols are
#             highest priority; strings are middle; defaults are lowest):
#                 @columns = arg[:columns] || arg['columns'] || @columns
#             Added #hard_margins, #split_rules, #hyphenator, and #split_words.
# 2003.02.07              Austin Ziegler
#             Fixed the installer for proper case-sensitive handling.
# 2003.03.28              Austin Ziegler
#             Added the ability for a hyphenator to receive the formatter
#             object. Fixed a bug for strings matching /\A\s*\Z/ failing
#             entirely. Fixed a test case failing under 1.6.8. 
# 2003.04.04              Austin Ziegler
#             Handle the case of hyphenators returning nil for first/rest.
# 2003.09.17          Austin Ziegler
#             Fixed a problem where #paragraphs(" ") was raising
#             NoMethodError.
#
# ==========================================================================
#++

module Text #:nodoc:
   # Text::Format for Ruby is copyright 2002 - 2005 by Austin Ziegler. It
   # is available under Ruby's licence, the Perl Artistic licence, or the
   # GNU GPL version 2 (or at your option, any later version). As a
   # special exception, for use with official Rails (provided by the
   # rubyonrails.org development team) and any project created with
   # official Rails, the following alternative MIT-style licence may be
   # used:
   #
   # == Text::Format Licence for Rails and Rails Applications
   # 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 names of its contributors may not be used to endorse or
   #   promote products derived from this software without specific prior
   #   written permission.
   #
   # 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.  
   class Format
    VERSION = '0.63'

      # Local abbreviations. More can be added with Text::Format.abbreviations
    ABBREV = [ 'Mr', 'Mrs', 'Ms', 'Jr', 'Sr' ]

      # Formatting values
    LEFT_ALIGN  = 0
    RIGHT_ALIGN = 1
    RIGHT_FILL  = 2
    JUSTIFY     = 3

      # Word split modes (only applies when #hard_margins is true).
    SPLIT_FIXED                     = 1
    SPLIT_CONTINUATION              = 2
    SPLIT_HYPHENATION               = 4
    SPLIT_CONTINUATION_FIXED        = SPLIT_CONTINUATION | SPLIT_FIXED
    SPLIT_HYPHENATION_FIXED         = SPLIT_HYPHENATION | SPLIT_FIXED
    SPLIT_HYPHENATION_CONTINUATION  = SPLIT_HYPHENATION | SPLIT_CONTINUATION
    SPLIT_ALL                       = SPLIT_HYPHENATION | SPLIT_CONTINUATION | SPLIT_FIXED

      # Words forcibly split by Text::Format will be stored as split words.
      # This class represents a word forcibly split.
    class SplitWord
        # The word that was split.
      attr_reader :word
        # The first part of the word that was split.
      attr_reader :first
        # The remainder of the word that was split.
      attr_reader :rest

      def initialize(word, first, rest) #:nodoc:
        @word = word
        @first = first
        @rest = rest
      end
    end

  private
    LEQ_RE = /[.?!]['"]?$/

    def brk_re(i) #:nodoc:
      %r/((?:\S+\s+){#{i}})(.+)/
    end

    def posint(p) #:nodoc:
      p.to_i.abs
    end

  public
      # Compares two Text::Format objects. All settings of the objects are
      # compared *except* #hyphenator. Generated results (e.g., #split_words)
      # are not compared, either.
    def ==(o)
      (@text          ==  o.text)           &&
      (@columns       ==  o.columns)        &&
      (@left_margin   ==  o.left_margin)    &&
      (@right_margin  ==  o.right_margin)   &&
      (@hard_margins  ==  o.hard_margins)   &&
      (@split_rules   ==  o.split_rules)    &&
      (@first_indent  ==  o.first_indent)   &&
      (@body_indent   ==  o.body_indent)    &&
      (@tag_text      ==  o.tag_text)       &&
      (@tabstop       ==  o.tabstop)        &&
      (@format_style  ==  o.format_style)   &&
      (@extra_space   ==  o.extra_space)    &&
      (@tag_paragraph ==  o.tag_paragraph)  &&
      (@nobreak       ==  o.nobreak)        &&
      (@abbreviations ==  o.abbreviations)  &&
      (@nobreak_regex ==  o.nobreak_regex)
    end

      # The text to be manipulated. Note that value is optional, but if the
      # formatting functions are called without values, this text is what will
      # be formatted.
      #
      # *Default*::       <tt>[]</tt>
      # <b>Used in</b>::  All methods
    attr_accessor :text

      # The total width of the format area. The margins, indentation, and text
      # are formatted into this space.
      #
      #                             COLUMNS
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  indent  text is formatted into here  right margin
      #
      # *Default*::       <tt>72</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>,
      #                   <tt>#center</tt>
    attr_reader :columns

      # The total width of the format area. The margins, indentation, and text
      # are formatted into this space. The value provided is silently
      # converted to a positive integer.
      #
      #                             COLUMNS
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  indent  text is formatted into here  right margin
      #
      # *Default*::       <tt>72</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>,
      #                   <tt>#center</tt>
    def columns=(c)
      @columns = posint(c)
    end

      # The number of spaces used for the left margin.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   LEFT MARGIN  indent  text is formatted into here  right margin
      #
      # *Default*::       <tt>0</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>,
      #                   <tt>#center</tt>
    attr_reader :left_margin

      # The number of spaces used for the left margin. The value provided is
      # silently converted to a positive integer value.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   LEFT MARGIN  indent  text is formatted into here  right margin
      #
      # *Default*::       <tt>0</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>,
      #                   <tt>#center</tt>
    def left_margin=(left)
      @left_margin = posint(left)
    end

      # The number of spaces used for the right margin.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  indent  text is formatted into here  RIGHT MARGIN
      #
      # *Default*::       <tt>0</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>,
      #                   <tt>#center</tt>
    attr_reader :right_margin

      # The number of spaces used for the right margin. The value provided is
      # silently converted to a positive integer value.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  indent  text is formatted into here  RIGHT MARGIN
      #
      # *Default*::       <tt>0</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>,
      #                   <tt>#center</tt>
    def right_margin=(r)
      @right_margin = posint(r)
    end

      # The number of spaces to indent the first line of a paragraph.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  INDENT  text is formatted into here  right margin
      #
      # *Default*::       <tt>4</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_reader :first_indent

      # The number of spaces to indent the first line of a paragraph. The
      # value provided is silently converted to a positive integer value.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  INDENT  text is formatted into here  right margin
      #
      # *Default*::       <tt>4</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def first_indent=(f)
      @first_indent = posint(f)
    end

      # The number of spaces to indent all lines after the first line of a
      # paragraph.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  INDENT  text is formatted into here  right margin
      #
      # *Default*::       <tt>0</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
  attr_reader :body_indent

      # The number of spaces to indent all lines after the first line of
      # a paragraph. The value provided is silently converted to a
      # positive integer value.
      #
      #                             columns
      #  <-------------------------------------------------------------->
      #  <-----------><------><---------------------------><------------>
      #   left margin  INDENT  text is formatted into here  right margin
      #
      # *Default*::       <tt>0</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def body_indent=(b)
      @body_indent = posint(b)
    end

      # Normally, words larger than the format area will be placed on a line
      # by themselves. Setting this to +true+ will force words larger than the
      # format area to be split into one or more "words" each at most the size
      # of the format area. The first line and the original word will be
      # placed into <tt>#split_words</tt>. Note that this will cause the
      # output to look *similar* to a #format_style of JUSTIFY. (Lines will be
      # filled as much as possible.)
      #
      # *Default*::       +false+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_accessor :hard_margins

      # An array of words split during formatting if #hard_margins is set to
      # +true+.
      #   #split_words << Text::Format::SplitWord.new(word, first, rest)
    attr_reader :split_words

      # The object responsible for hyphenating. It must respond to
      # #hyphenate_to(word, size) or #hyphenate_to(word, size, formatter) and
      # return an array of the word split into two parts; if there is a
      # hyphenation mark to be applied, responsibility belongs to the
      # hyphenator object. The size is the MAXIMUM size permitted, including
      # any hyphenation marks. If the #hyphenate_to method has an arity of 3,
      # the formatter will be provided to the method. This allows the
      # hyphenator to make decisions about the hyphenation based on the
      # formatting rules.
      #
      # *Default*::       +nil+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_reader :hyphenator

      # The object responsible for hyphenating. It must respond to
      # #hyphenate_to(word, size) and return an array of the word hyphenated
      # into two parts. The size is the MAXIMUM size permitted, including any
      # hyphenation marks.
      #
      # *Default*::       +nil+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def hyphenator=(h)
      raise ArgumentError, "#{h.inspect} is not a valid hyphenator." unless h.respond_to?(:hyphenate_to)
      arity = h.method(:hyphenate_to).arity
      raise ArgumentError, "#{h.inspect} must have exactly two or three arguments." unless [2, 3].include?(arity)
      @hyphenator = h
      @hyphenator_arity = arity
    end

      # Specifies the split mode; used only when #hard_margins is set to
      # +true+. Allowable values are:
      # [+SPLIT_FIXED+]         The word will be split at the number of
      #                         characters needed, with no marking at all.
      #      repre
      #      senta
      #      ion
      # [+SPLIT_CONTINUATION+]  The word will be split at the number of
      #                         characters needed, with a C-style continuation
      #                         character. If a word is the only item on a
      #                         line and it cannot be split into an
      #                         appropriate size, SPLIT_FIXED will be used.
      #       repr\
      #       esen\
      #       tati\
      #       on
      # [+SPLIT_HYPHENATION+]   The word will be split according to the
      #                         hyphenator specified in #hyphenator. If there
      #                         is no #hyphenator specified, works like
      #                         SPLIT_CONTINUATION. The example is using
      #                         TeX::Hyphen. If a word is the only item on a
      #                         line and it cannot be split into an
      #                         appropriate size, SPLIT_CONTINUATION mode will
      #                         be used.
      #       rep-
      #       re-
      #       sen-
      #       ta-
      #       tion
      #
      # *Default*::       <tt>Text::Format::SPLIT_FIXED</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_reader :split_rules

      # Specifies the split mode; used only when #hard_margins is set to
      # +true+. Allowable values are:
      # [+SPLIT_FIXED+]         The word will be split at the number of
      #                         characters needed, with no marking at all.
      #      repre
      #      senta
      #      ion
      # [+SPLIT_CONTINUATION+]  The word will be split at the number of
      #                         characters needed, with a C-style continuation
      #                         character.
      #       repr\
      #       esen\
      #       tati\
      #       on
      # [+SPLIT_HYPHENATION+]   The word will be split according to the
      #                         hyphenator specified in #hyphenator. If there
      #                         is no #hyphenator specified, works like
      #                         SPLIT_CONTINUATION. The example is using
      #                         TeX::Hyphen as the #hyphenator.
      #       rep-
      #       re-
      #       sen-
      #       ta-
      #       tion
      #
      # These values can be bitwise ORed together (e.g., <tt>SPLIT_FIXED |
      # SPLIT_CONTINUATION</tt>) to provide fallback split methods. In the
      # example given, an attempt will be made to split the word using the
      # rules of SPLIT_CONTINUATION; if there is not enough room, the word
      # will be split with the rules of SPLIT_FIXED. These combinations are
      # also available as the following values:
      # * +SPLIT_CONTINUATION_FIXED+
      # * +SPLIT_HYPHENATION_FIXED+
      # * +SPLIT_HYPHENATION_CONTINUATION+
      # * +SPLIT_ALL+
      #
      # *Default*::       <tt>Text::Format::SPLIT_FIXED</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def split_rules=(s)
      raise ArgumentError, "Invalid value provided for split_rules." if ((s < SPLIT_FIXED) || (s > SPLIT_ALL))
      @split_rules = s
    end

      # Indicates whether sentence terminators should be followed by a single
      # space (+false+), or two spaces (+true+).
      #
      # *Default*::       +false+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_accessor :extra_space

      # Defines the current abbreviations as an array. This is only used if
      # extra_space is turned on.
      #
      # If one is abbreviating "President" as "Pres." (abbreviations =
      # ["Pres"]), then the results of formatting will be as illustrated in
      # the table below:
      #
      #       extra_space  |  include?        |  !include?
      #         true       |  Pres. Lincoln   |  Pres.  Lincoln
      #         false      |  Pres. Lincoln   |  Pres. Lincoln
      #
      # *Default*::       <tt>{}</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_accessor :abbreviations

      # Indicates whether the formatting of paragraphs should be done with
      # tagged paragraphs. Useful only with <tt>#tag_text</tt>.
      #
      # *Default*::       +false+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_accessor :tag_paragraph

      # The array of text to be placed before each paragraph when
      # <tt>#tag_paragraph</tt> is +true+. When <tt>#format()</tt> is called,
      # only the first element of the array is used. When <tt>#paragraphs</tt>
      # is called, then each entry in the array will be used once, with
      # corresponding paragraphs. If the tag elements are exhausted before the
      # text is exhausted, then the remaining paragraphs will not be tagged.
      # Regardless of indentation settings, a blank line will be inserted
      # between all paragraphs when <tt>#tag_paragraph</tt> is +true+.
      #
      # *Default*::       <tt>[]</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_accessor :tag_text

      # Indicates whether or not the non-breaking space feature should be
      # used.
      #
      # *Default*::       +false+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_accessor :nobreak

      # A hash which holds the regular expressions on which spaces should not
      # be broken. The hash is set up such that the key is the first word and
      # the value is the second word.
      #
      # For example, if +nobreak_regex+ contains the following hash:
      #
      #   { '^Mrs?\.$' => '\S+$', '^\S+$' => '^(?:S|J)r\.$'}
      #
      # Then "Mr. Jones", "Mrs. Jones", and "Jones Jr." would not be broken.
      # If this simple matching algorithm indicates that there should not be a
      # break at the current end of line, then a backtrack is done until there
      # are two words on which line breaking is permitted. If two such words
      # are not found, then the end of the line will be broken *regardless*.
      # If there is a single word on the current line, then no backtrack is
      # done and the word is stuck on the end.
      #
      # *Default*::       <tt>{}</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_accessor :nobreak_regex

      # Indicates the number of spaces that a single tab represents.
      #
      # *Default*::       <tt>8</tt>
      # <b>Used in</b>::  <tt>#expand</tt>, <tt>#unexpand</tt>,
      #                   <tt>#paragraphs</tt>
    attr_reader :tabstop

      # Indicates the number of spaces that a single tab represents.
      #
      # *Default*::       <tt>8</tt>
      # <b>Used in</b>::  <tt>#expand</tt>, <tt>#unexpand</tt>,
      #                   <tt>#paragraphs</tt>
    def tabstop=(t)
      @tabstop = posint(t)
    end

      # Specifies the format style. Allowable values are:
      # [+LEFT_ALIGN+]    Left justified, ragged right.
      #      |A paragraph that is|
      #      |left aligned.|
      # [+RIGHT_ALIGN+]   Right justified, ragged left.
      #      |A paragraph that is|
      #      |     right aligned.|
      # [+RIGHT_FILL+]    Left justified, right ragged, filled to width by
      #                   spaces. (Essentially the same as +LEFT_ALIGN+ except
      #                   that lines are padded on the right.)
      #      |A paragraph that is|
      #      |left aligned.      |
      # [+JUSTIFY+]       Fully justified, words filled to width by spaces,
      #                   except the last line.
      #      |A paragraph  that|
      #      |is     justified.|
      #
      # *Default*::       <tt>Text::Format::LEFT_ALIGN</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    attr_reader :format_style

      # Specifies the format style. Allowable values are:
      # [+LEFT_ALIGN+]    Left justified, ragged right.
      #      |A paragraph that is|
      #      |left aligned.|
      # [+RIGHT_ALIGN+]   Right justified, ragged left.
      #      |A paragraph that is|
      #      |     right aligned.|
      # [+RIGHT_FILL+]    Left justified, right ragged, filled to width by
      #                   spaces. (Essentially the same as +LEFT_ALIGN+ except
      #                   that lines are padded on the right.)
      #      |A paragraph that is|
      #      |left aligned.      |
      # [+JUSTIFY+]       Fully justified, words filled to width by spaces.
      #      |A paragraph  that|
      #      |is     justified.|
      #
      # *Default*::       <tt>Text::Format::LEFT_ALIGN</tt>
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def format_style=(fs)
      raise ArgumentError, "Invalid value provided for format_style." if ((fs < LEFT_ALIGN) || (fs > JUSTIFY))
      @format_style = fs
    end

      # Indicates that the format style is left alignment.
      #
      # *Default*::       +true+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def left_align?
      return @format_style == LEFT_ALIGN
    end

      # Indicates that the format style is right alignment.
      #
      # *Default*::       +false+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def right_align?
      return @format_style == RIGHT_ALIGN
    end

      # Indicates that the format style is right fill.
      #
      # *Default*::       +false+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def right_fill?
      return @format_style == RIGHT_FILL
    end

      # Indicates that the format style is full justification.
      #
      # *Default*::       +false+
      # <b>Used in</b>::  <tt>#format</tt>, <tt>#paragraphs</tt>
    def justify?
      return @format_style == JUSTIFY
    end

      # The default implementation of #hyphenate_to implements
      # SPLIT_CONTINUATION.
    def hyphenate_to(word, size)
      [word[0 .. (size - 2)] + "\\", word[(size - 1) .. -1]]
    end

  private
    def __do_split_word(word, size) #:nodoc:
      [word[0 .. (size - 1)], word[size .. -1]]
    end

    def __format(to_wrap) #:nodoc:
      words = to_wrap.split(/\s+/).compact
      words.shift if words[0].nil? or words[0].empty?
      to_wrap = []

      abbrev = false
      width = @columns - @first_indent - @left_margin - @right_margin
      indent_str = ' ' * @first_indent
      first_line = true
      line = words.shift
      abbrev = __is_abbrev(line) unless line.nil? || line.empty?

      while w = words.shift
        if (w.size + line.size < (width - 1)) ||
           ((line !~ LEQ_RE || abbrev) && (w.size + line.size < width))
          line << " " if (line =~ LEQ_RE) && (not abbrev)
          line << " #{w}"
        else
          line, w = __do_break(line, w) if @nobreak
          line, w = __do_hyphenate(line, w, width) if @hard_margins
          if w.index(/\s+/)
            w, *w2 = w.split(/\s+/)
            words.unshift(w2)
            words.flatten!
          end
          to_wrap << __make_line(line, indent_str, width, w.nil?) unless line.nil?
          if first_line
            first_line = false
            width = @columns - @body_indent - @left_margin - @right_margin
            indent_str = ' ' * @body_indent
          end
          line = w
        end

        abbrev = __is_abbrev(w) unless w.nil?
      end

      loop do
        break if line.nil? or line.empty?
        line, w = __do_hyphenate(line, w, width) if @hard_margins
        to_wrap << __make_line(line, indent_str, width, w.nil?)
        line = w
      end

      if (@tag_paragraph && (to_wrap.size > 0)) then
        clr = %r{`(\w+)'}.match([caller(1)].flatten[0])[1]
        clr = "" if clr.nil?

        if ((not @tag_text[0].nil?) && (@tag_cur.size < 1) &&
            (clr != "__paragraphs")) then
          @tag_cur = @tag_text[0]
        end

        fchar = /(\S)/.match(to_wrap[0])[1]
        white = to_wrap[0].index(fchar)
        if ((white - @left_margin - 1) > @tag_cur.size) then
          white = @tag_cur.size + @left_margin
          to_wrap[0].gsub!(/^ {#{white}}/, "#{' ' * @left_margin}#{@tag_cur}")
        else
          to_wrap.unshift("#{' ' * @left_margin}#{@tag_cur}\n")
        end
      end
      to_wrap.join('')
    end

      # format lines in text into paragraphs with each element of @wrap a
      # paragraph; uses Text::Format.format for the formatting
    def __paragraphs(to_wrap) #:nodoc:
      if ((@first_indent == @body_indent) || @tag_paragraph) then
        p_end = "\n"
      else
        p_end = ''
      end

      cnt = 0
      ret = []
      to_wrap.each do |tw|
        @tag_cur = @tag_text[cnt] if @tag_paragraph
        @tag_cur = '' if @tag_cur.nil?
        line = __format(tw)
        ret << "#{line}#{p_end}" if (not line.nil?) && (line.size > 0)
        cnt += 1
      end

      ret[-1].chomp! unless ret.empty?
      ret.join('')
    end

      # center text using spaces on left side to pad it out empty lines
      # are preserved
    def __center(to_center) #:nodoc:
      tabs = 0
      width = @columns - @left_margin - @right_margin
      centered = []
      to_center.each do |tc|
        s = tc.strip
        tabs = s.count("\t")
        tabs = 0 if tabs.nil?
        ct = ((width - s.size - (tabs * @tabstop) + tabs) / 2)
        ct = (width - @left_margin - @right_margin) - ct
        centered << "#{s.rjust(ct)}\n"
      end
      centered.join('')
    end

      # expand tabs to spaces should be similar to Text::Tabs::expand
    def __expand(to_expand) #:nodoc:
      expanded = []
      to_expand.split("\n").each { |te| expanded << te.gsub(/\t/, ' ' * @tabstop) }
      expanded.join('')
    end

    def __unexpand(to_unexpand) #:nodoc:
      unexpanded = []
      to_unexpand.split("\n").each { |tu| unexpanded << tu.gsub(/ {#{@tabstop}}/, "\t") }
      unexpanded.join('')
    end

    def __is_abbrev(word) #:nodoc:
        # remove period if there is one.
      w = word.gsub(/\.$/, '') unless word.nil?
      return true if (!@extra_space || ABBREV.include?(w) || @abbreviations.include?(w))
      false
    end

    def __make_line(line, indent, width, last = false) #:nodoc:
      lmargin = " " * @left_margin
      fill = " " * (width - line.size) if right_fill? && (line.size <= width)

      if (justify? && ((not line.nil?) && (not line.empty?)) && line =~ /\S+\s+\S+/ && !last)
        spaces = width - line.size
        words = line.split(/(\s+)/)
        ws = spaces / (words.size / 2)
        spaces = spaces % (words.size / 2) if ws > 0
        words.reverse.each do |rw|
          next if (rw =~ /^\S/)
          rw.sub!(/^/, " " * ws)
          next unless (spaces > 0)
          rw.sub!(/^/, " ")
          spaces -= 1
        end
        line = words.join('')
      end
      line = "#{lmargin}#{indent}#{line}#{fill}\n" unless line.nil?
      if right_align? && (not line.nil?)
        line.sub(/^/, " " * (@columns - @right_margin - (line.size - 1)))
      else
        line
      end
    end

    def __do_hyphenate(line, next_line, width) #:nodoc:
      rline = line.dup rescue line
      rnext = next_line.dup rescue next_line
      loop do
        if rline.size == width
          break
        elsif rline.size > width
          words = rline.strip.split(/\s+/)
          word = words[-1].dup
          size = width - rline.size + word.size
          if (size <= 0)
            words[-1] = nil
            rline = words.join(' ').strip
            rnext = "#{word} #{rnext}".strip
            next
          end

          first = rest = nil

          if ((@split_rules & SPLIT_HYPHENATION) != 0)
            if @hyphenator_arity == 2
              first, rest = @hyphenator.hyphenate_to(word, size)
            else
              first, rest = @hyphenator.hyphenate_to(word, size, self)
            end
          end

          if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?
            first, rest = self.hyphenate_to(word, size)
          end

          if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?
            first.nil? or @split_rules == SPLIT_FIXED
            first, rest = __do_split_word(word, size)
          end

          if first.nil?
            words[-1] = nil
            rest = word
          else
            words[-1] = first
            @split_words << SplitWord.new(word, first, rest)
          end
          rline = words.join(' ').strip
          rnext = "#{rest} #{rnext}".strip
          break
        else
          break if rnext.nil? or rnext.empty? or rline.nil? or rline.empty?
          words = rnext.split(/\s+/)
          word = words.shift
          size = width - rline.size - 1

          if (size <= 0)
            rnext = "#{word} #{words.join(' ')}".strip
            break
          end

          first = rest = nil

          if ((@split_rules & SPLIT_HYPHENATION) != 0)
            if @hyphenator_arity == 2
              first, rest = @hyphenator.hyphenate_to(word, size)
            else
              first, rest = @hyphenator.hyphenate_to(word, size, self)
            end
          end

          first, rest = self.hyphenate_to(word, size) if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?

          first, rest = __do_split_word(word, size) if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?

          if (rline.size + (first ? first.size : 0)) < width
            @split_words << SplitWord.new(word, first, rest)
            rline = "#{rline} #{first}".strip
            rnext = "#{rest} #{words.join(' ')}".strip
          end
          break
        end
      end
      [rline, rnext]
    end

    def __do_break(line, next_line) #:nodoc:
      no_brk = false
      words = []
      words = line.split(/\s+/) unless line.nil?
      last_word = words[-1]

      @nobreak_regex.each { |k, v| no_brk = ((last_word =~ /#{k}/) and (next_line =~ /#{v}/)) }

      if no_brk && words.size > 1
        i = words.size
        while i > 0
          no_brk = false
          @nobreak_regex.each { |k, v| no_brk = ((words[i + 1] =~ /#{k}/) && (words[i] =~ /#{v}/)) }
          i -= 1
          break if not no_brk
        end
        if i > 0
          l = brk_re(i).match(line)
          line.sub!(brk_re(i), l[1])
          next_line = "#{l[2]} #{next_line}"
          line.sub!(/\s+$/, '')
        end
      end
      [line, next_line]
    end

    def __create(arg = nil, &block) #:nodoc:
        # Format::Text.new(text-to-wrap)
      @text = arg unless arg.nil?
        # Defaults
      @columns          = 72
      @tabstop          = 8
      @first_indent     = 4
      @body_indent      = 0
      @format_style     = LEFT_ALIGN
      @left_margin      = 0
      @right_margin     = 0
      @extra_space      = false
      @text             = Array.new if @text.nil?
      @tag_paragraph    = false
      @tag_text         = Array.new
      @tag_cur          = ""
      @abbreviations    = Array.new
      @nobreak          = false
      @nobreak_regex    = Hash.new
      @split_words      = Array.new
      @hard_margins     = false
      @split_rules      = SPLIT_FIXED
      @hyphenator       = self
      @hyphenator_arity = self.method(:hyphenate_to).arity

      instance_eval(&block) unless block.nil?
    end

  public
      # Formats text into a nice paragraph format. The text is separated
      # into words and then reassembled a word at a time using the settings
      # of this Format object. If a word is larger than the number of
      # columns available for formatting, then that word will appear on the
      # line by itself.
      #
      # If +to_wrap+ is +nil+, then the value of <tt>#text</tt> will be
      # worked on.
    def format(to_wrap = nil)
      to_wrap = @text if to_wrap.nil?
      if to_wrap.class == Array
        __format(to_wrap[0])
      else
        __format(to_wrap)
      end
    end

      # Considers each element of text (provided or internal) as a paragraph.
      # If <tt>#first_indent</tt> is the same as <tt>#body_indent</tt>, then
      # paragraphs will be separated by a single empty line in the result;
      # otherwise, the paragraphs will follow immediately after each other.
      # Uses <tt>#format</tt> to do the heavy lifting.
    def paragraphs(to_wrap = nil)
      to_wrap = @text if to_wrap.nil?
      __paragraphs([to_wrap].flatten)
    end

      # Centers the text, preserving empty lines and tabs.
    def center(to_center = nil)
      to_center = @text if to_center.nil?
      __center([to_center].flatten)
    end

      # Replaces all tab characters in the text with <tt>#tabstop</tt> spaces.
    def expand(to_expand = nil)
      to_expand = @text if to_expand.nil?
      if to_expand.class == Array
        to_expand.collect { |te| __expand(te) }
      else
        __expand(to_expand)
      end
    end

      # Replaces all occurrences of <tt>#tabstop</tt> consecutive spaces
      # with a tab character.
    def unexpand(to_unexpand = nil)
      to_unexpand = @text if to_unexpand.nil?
      if to_unexpand.class == Array
        to_unexpand.collect { |te| v << __unexpand(te) }
      else
        __unexpand(to_unexpand)
      end
    end

      # This constructor takes advantage of a technique for Ruby object
      # construction introduced by Andy Hunt and Dave Thomas (see reference),
      # where optional values are set using commands in a block.
      #
      #   Text::Format.new {
      #       columns         = 72
      #       left_margin     = 0
      #       right_margin    = 0
      #       first_indent    = 4
      #       body_indent     = 0
      #       format_style    = Text::Format::LEFT_ALIGN
      #       extra_space     = false
      #       abbreviations   = {}
      #       tag_paragraph   = false
      #       tag_text        = []
      #       nobreak         = false
      #       nobreak_regex   = {}
      #       tabstop         = 8
      #       text            = nil
      #   }
      #
      # As shown above, +arg+ is optional. If +arg+ is specified and is a
      # +String+, then arg is used as the default value of <tt>#text</tt>.
      # Alternately, an existing Text::Format object can be used or a Hash can
      # be used. With all forms, a block can be specified.
      #
      # *Reference*:: "Object Construction and Blocks"
      #               <http://www.pragmaticprogrammer.com/ruby/articles/insteval.html>
      #
    def initialize(arg = nil, &block)
      case arg
      when Text::Format
        __create(arg.text) do
          @columns        = arg.columns
          @tabstop        = arg.tabstop
          @first_indent   = arg.first_indent
          @body_indent    = arg.body_indent
          @format_style   = arg.format_style
          @left_margin    = arg.left_margin
          @right_margin   = arg.right_margin
          @extra_space    = arg.extra_space
          @tag_paragraph  = arg.tag_paragraph
          @tag_text       = arg.tag_text
          @abbreviations  = arg.abbreviations
          @nobreak        = arg.nobreak
          @nobreak_regex  = arg.nobreak_regex
          @text           = arg.text
          @hard_margins   = arg.hard_margins
          @split_words    = arg.split_words
          @split_rules    = arg.split_rules
          @hyphenator     = arg.hyphenator
        end
        instance_eval(&block) unless block.nil?
      when Hash
        __create do
          @columns       = arg[:columns]       || arg['columns']       || @columns
          @tabstop       = arg[:tabstop]       || arg['tabstop']       || @tabstop
          @first_indent  = arg[:first_indent]  || arg['first_indent']  || @first_indent
          @body_indent   = arg[:body_indent]   || arg['body_indent']   || @body_indent
          @format_style  = arg[:format_style]  || arg['format_style']  || @format_style
          @left_margin   = arg[:left_margin]   || arg['left_margin']   || @left_margin
          @right_margin  = arg[:right_margin]  || arg['right_margin']  || @right_margin
          @extra_space   = arg[:extra_space]   || arg['extra_space']   || @extra_space
          @text          = arg[:text]          || arg['text']          || @text
          @tag_paragraph = arg[:tag_paragraph] || arg['tag_paragraph'] || @tag_paragraph
          @tag_text      = arg[:tag_text]      || arg['tag_text']      || @tag_text
          @abbreviations = arg[:abbreviations] || arg['abbreviations'] || @abbreviations
          @nobreak       = arg[:nobreak]       || arg['nobreak']       || @nobreak
          @nobreak_regex = arg[:nobreak_regex] || arg['nobreak_regex'] || @nobreak_regex
          @hard_margins  = arg[:hard_margins]  || arg['hard_margins']  || @hard_margins
          @split_rules   = arg[:split_rules] || arg['split_rules'] || @split_rules
          @hyphenator    = arg[:hyphenator] || arg['hyphenator'] || @hyphenator
        end
        instance_eval(&block) unless block.nil?
      when String
        __create(arg, &block)
      when NilClass
        __create(&block)
      else
        raise TypeError
      end
    end
  end
end

if __FILE__ == $0
  require 'test/unit'

  class TestText__Format < Test::Unit::TestCase #:nodoc:
    attr_accessor :format_o

    GETTYSBURG = <<-'EOS'
    Four score and seven years ago our fathers brought forth on this
    continent a new nation, conceived in liberty and dedicated to the
    proposition that all men are created equal. Now we are engaged in
    a great civil war, testing whether that nation or any nation so
    conceived and so dedicated can long endure. We are met on a great
    battlefield of that war. We have come to dedicate a portion of
    that field as a final resting-place for those who here gave their
    lives that that nation might live. It is altogether fitting and
    proper that we should do this. But in a larger sense, we cannot
    dedicate, we cannot consecrate, we cannot hallow this ground.
    The brave men, living and dead who struggled here have consecrated
    it far above our poor power to add or detract. The world will
    little note nor long remember what we say here, but it can never
    forget what they did here. It is for us the living rather to be
    dedicated here to the unfinished work which they who fought here
    have thus far so nobly advanced. It is rather for us to be here
    dedicated to the great task remaining before us--that from these
    honored dead we take increased devotion to that cause for which
    they gave the last full measure of devotion--that we here highly
    resolve that these dead shall not have died in vain, that this
    nation under God shall have a new birth of freedom, and that
    government of the people, by the people, for the people shall
    not perish from the earth.

            -- Pres. Abraham Lincoln, 19 November 1863
    EOS

    FIVE_COL = "Four \nscore\nand s\neven \nyears\nago o\nur fa\nthers\nbroug\nht fo\nrth o\nn thi\ns con\ntinen\nt a n\new na\ntion,\nconce\nived \nin li\nberty\nand d\nedica\nted t\no the\npropo\nsitio\nn tha\nt all\nmen a\nre cr\neated\nequal\n. Now\nwe ar\ne eng\naged \nin a \ngreat\ncivil\nwar, \ntesti\nng wh\nether\nthat \nnatio\nn or \nany n\nation\nso co\nnceiv\ned an\nd so \ndedic\nated \ncan l\nong e\nndure\n. We \nare m\net on\na gre\nat ba\nttlef\nield \nof th\nat wa\nr. We\nhave \ncome \nto de\ndicat\ne a p\nortio\nn of \nthat \nfield\nas a \nfinal\nresti\nng-pl\nace f\nor th\nose w\nho he\nre ga\nve th\neir l\nives \nthat \nthat \nnatio\nn mig\nht li\nve. I\nt is \naltog\nether\nfitti\nng an\nd pro\nper t\nhat w\ne sho\nuld d\no thi\ns. Bu\nt in \na lar\nger s\nense,\nwe ca\nnnot \ndedic\nate, \nwe ca\nnnot \nconse\ncrate\n, we \ncanno\nt hal\nlow t\nhis g\nround\n. The\nbrave\nmen, \nlivin\ng and\ndead \nwho s\ntrugg\nled h\nere h\nave c\nonsec\nrated\nit fa\nr abo\nve ou\nr poo\nr pow\ner to\nadd o\nr det\nract.\nThe w\norld \nwill \nlittl\ne not\ne nor\nlong \nremem\nber w\nhat w\ne say\nhere,\nbut i\nt can\nnever\nforge\nt wha\nt the\ny did\nhere.\nIt is\nfor u\ns the\nlivin\ng rat\nher t\no be \ndedic\nated \nhere \nto th\ne unf\ninish\ned wo\nrk wh\nich t\nhey w\nho fo\nught \nhere \nhave \nthus \nfar s\no nob\nly ad\nvance\nd. It\nis ra\nther \nfor u\ns to \nbe he\nre de\ndicat\ned to\nthe g\nreat \ntask \nremai\nning \nbefor\ne us-\n-that\nfrom \nthese\nhonor\ned de\nad we\ntake \nincre\nased \ndevot\nion t\no tha\nt cau\nse fo\nr whi\nch th\ney ga\nve th\ne las\nt ful\nl mea\nsure \nof de\nvotio\nn--th\nat we\nhere \nhighl\ny res\nolve \nthat \nthese\ndead \nshall\nnot h\nave d\nied i\nn vai\nn, th\nat th\nis na\ntion \nunder\nGod s\nhall \nhave \na new\nbirth\nof fr\needom\n, and\nthat \ngover\nnment\nof th\ne peo\nple, \nby th\ne peo\nple, \nfor t\nhe pe\nople \nshall\nnot p\nerish\nfrom \nthe e\narth.\n-- Pr\nes. A\nbraha\nm Lin\ncoln,\n19 No\nvembe\nr 186\n3    \n"

    FIVE_CNT = "Four \nscore\nand  \nseven\nyears\nago  \nour  \nfath\\\ners  \nbrou\\\nght  \nforth\non t\\\nhis  \ncont\\\ninent\na new\nnati\\\non,  \nconc\\\neived\nin l\\\niber\\\nty a\\\nnd d\\\nedic\\\nated \nto t\\\nhe p\\\nropo\\\nsiti\\\non t\\\nhat  \nall  \nmen  \nare  \ncrea\\\nted  \nequa\\\nl. N\\\now we\nare  \nenga\\\nged  \nin a \ngreat\ncivil\nwar, \ntest\\\ning  \nwhet\\\nher  \nthat \nnati\\\non or\nany  \nnati\\\non so\nconc\\\neived\nand  \nso d\\\nedic\\\nated \ncan  \nlong \nendu\\\nre.  \nWe a\\\nre m\\\net on\na gr\\\neat  \nbatt\\\nlefi\\\neld  \nof t\\\nhat  \nwar. \nWe h\\\nave  \ncome \nto d\\\nedic\\\nate a\nport\\\nion  \nof t\\\nhat  \nfield\nas a \nfinal\nrest\\\ning-\\\nplace\nfor  \nthose\nwho  \nhere \ngave \ntheir\nlives\nthat \nthat \nnati\\\non m\\\night \nlive.\nIt is\nalto\\\ngeth\\\ner f\\\nitti\\\nng a\\\nnd p\\\nroper\nthat \nwe s\\\nhould\ndo t\\\nhis. \nBut  \nin a \nlarg\\\ner s\\\nense,\nwe c\\\nannot\ndedi\\\ncate,\nwe c\\\nannot\ncons\\\necra\\\nte,  \nwe c\\\nannot\nhall\\\now t\\\nhis  \ngrou\\\nnd.  \nThe  \nbrave\nmen, \nlivi\\\nng a\\\nnd d\\\nead  \nwho  \nstru\\\nggled\nhere \nhave \ncons\\\necra\\\nted  \nit f\\\nar a\\\nbove \nour  \npoor \npower\nto a\\\ndd or\ndetr\\\nact. \nThe  \nworld\nwill \nlitt\\\nle n\\\note  \nnor  \nlong \nreme\\\nmber \nwhat \nwe s\\\nay h\\\nere, \nbut  \nit c\\\nan n\\\never \nforg\\\net w\\\nhat  \nthey \ndid  \nhere.\nIt is\nfor  \nus t\\\nhe l\\\niving\nrath\\\ner to\nbe d\\\nedic\\\nated \nhere \nto t\\\nhe u\\\nnfin\\\nished\nwork \nwhich\nthey \nwho  \nfoug\\\nht h\\\nere  \nhave \nthus \nfar  \nso n\\\nobly \nadva\\\nnced.\nIt is\nrath\\\ner f\\\nor us\nto be\nhere \ndedi\\\ncated\nto t\\\nhe g\\\nreat \ntask \nrema\\\nining\nbefo\\\nre u\\\ns--t\\\nhat  \nfrom \nthese\nhono\\\nred  \ndead \nwe t\\\nake  \nincr\\\neased\ndevo\\\ntion \nto t\\\nhat  \ncause\nfor  \nwhich\nthey \ngave \nthe  \nlast \nfull \nmeas\\\nure  \nof d\\\nevot\\\nion-\\\n-that\nwe h\\\nere  \nhigh\\\nly r\\\nesol\\\nve t\\\nhat  \nthese\ndead \nshall\nnot  \nhave \ndied \nin v\\\nain, \nthat \nthis \nnati\\\non u\\\nnder \nGod  \nshall\nhave \na new\nbirth\nof f\\\nreed\\\nom,  \nand  \nthat \ngove\\\nrnme\\\nnt of\nthe  \npeop\\\nle,  \nby t\\\nhe p\\\neopl\\\ne, f\\\nor t\\\nhe p\\\neople\nshall\nnot  \nperi\\\nsh f\\\nrom  \nthe  \neart\\\nh. --\nPres.\nAbra\\\nham  \nLinc\\\noln, \n19 N\\\novem\\\nber  \n1863 \n"

      # Tests both abbreviations and abbreviations=
    def test_abbreviations
      abbr = ["    Pres. Abraham Lincoln\n", "    Pres.  Abraham Lincoln\n"]
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal([], @format_o.abbreviations)
      assert_nothing_raised { @format_o.abbreviations = [ 'foo', 'bar' ] }
      assert_equal([ 'foo', 'bar' ], @format_o.abbreviations)
      assert_equal(abbr[0], @format_o.format(abbr[0]))
      assert_nothing_raised { @format_o.extra_space = true }
      assert_equal(abbr[1], @format_o.format(abbr[0]))
      assert_nothing_raised { @format_o.abbreviations = [ "Pres" ] }
      assert_equal([ "Pres" ], @format_o.abbreviations)
      assert_equal(abbr[0], @format_o.format(abbr[0]))
      assert_nothing_raised { @format_o.extra_space = false }
      assert_equal(abbr[0], @format_o.format(abbr[0]))
    end

      # Tests both body_indent and body_indent=
    def test_body_indent
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal(0, @format_o.body_indent)
      assert_nothing_raised { @format_o.body_indent = 7 }
      assert_equal(7, @format_o.body_indent)
      assert_nothing_raised { @format_o.body_indent = -3 }
      assert_equal(3, @format_o.body_indent)
      assert_nothing_raised { @format_o.body_indent = "9" }
      assert_equal(9, @format_o.body_indent)
      assert_nothing_raised { @format_o.body_indent = "-2" }
      assert_equal(2, @format_o.body_indent)
      assert_match(/^  [^ ]/, @format_o.format(GETTYSBURG).split("\n")[1])
    end

      # Tests both columns and columns=
    def test_columns
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal(72, @format_o.columns)
      assert_nothing_raised { @format_o.columns = 7 }
      assert_equal(7, @format_o.columns)
      assert_nothing_raised { @format_o.columns = -3 }
      assert_equal(3, @format_o.columns)
      assert_nothing_raised { @format_o.columns = "9" }
      assert_equal(9, @format_o.columns)
      assert_nothing_raised { @format_o.columns = "-2" }
      assert_equal(2, @format_o.columns)
      assert_nothing_raised { @format_o.columns = 40 }
      assert_equal(40, @format_o.columns)
      assert_match(/this continent$/,
                   @format_o.format(GETTYSBURG).split("\n")[1])
    end

      # Tests both extra_space and extra_space=
    def test_extra_space
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(!@format_o.extra_space)
      assert_nothing_raised { @format_o.extra_space = true }
      assert(@format_o.extra_space)
        # The behaviour of extra_space is tested in test_abbreviations. There
        # is no need to reproduce it here.
    end

      # Tests both first_indent and first_indent=
    def test_first_indent
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal(4, @format_o.first_indent)
      assert_nothing_raised { @format_o.first_indent = 7 }
      assert_equal(7, @format_o.first_indent)
      assert_nothing_raised { @format_o.first_indent = -3 }
      assert_equal(3, @format_o.first_indent)
      assert_nothing_raised { @format_o.first_indent = "9" }
      assert_equal(9, @format_o.first_indent)
      assert_nothing_raised { @format_o.first_indent = "-2" }
      assert_equal(2, @format_o.first_indent)
      assert_match(/^  [^ ]/, @format_o.format(GETTYSBURG).split("\n")[0])
    end

    def test_format_style
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal(Text::Format::LEFT_ALIGN, @format_o.format_style)
      assert_match(/^November 1863$/,
                   @format_o.format(GETTYSBURG).split("\n")[-1])
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_ALIGN
      }
      assert_equal(Text::Format::RIGHT_ALIGN, @format_o.format_style)
      assert_match(/^ +November 1863$/,
                   @format_o.format(GETTYSBURG).split("\n")[-1])
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_FILL
      }
      assert_equal(Text::Format::RIGHT_FILL, @format_o.format_style)
      assert_match(/^November 1863 +$/,
                   @format_o.format(GETTYSBURG).split("\n")[-1])
      assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
      assert_equal(Text::Format::JUSTIFY, @format_o.format_style)
      assert_match(/^of freedom, and that government of the people, by the  people,  for  the$/,
                   @format_o.format(GETTYSBURG).split("\n")[-3])
      assert_raises(ArgumentError) { @format_o.format_style = 33 }
    end

    def test_tag_paragraph
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(!@format_o.tag_paragraph)
      assert_nothing_raised { @format_o.tag_paragraph = true }
      assert(@format_o.tag_paragraph)
      assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
                       Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
    end

    def test_tag_text
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal([], @format_o.tag_text)
      assert_equal(@format_o.format(GETTYSBURG),
                   Text::Format.new.format(GETTYSBURG))
      assert_nothing_raised {
        @format_o.tag_paragraph = true
        @format_o.tag_text = ["Gettysburg Address", "---"]
      }
      assert_not_equal(@format_o.format(GETTYSBURG),
                       Text::Format.new.format(GETTYSBURG))
      assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
                       Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
      assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG,
                                             GETTYSBURG]),
                       Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG,
                                                    GETTYSBURG]))
    end

    def test_justify?
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(!@format_o.justify?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_ALIGN
      }
      assert(!@format_o.justify?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_FILL
      }
      assert(!@format_o.justify?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::JUSTIFY
      }
      assert(@format_o.justify?)
        # The format testing is done in test_format_style
    end

    def test_left_align?
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(@format_o.left_align?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_ALIGN
      }
      assert(!@format_o.left_align?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_FILL
      }
      assert(!@format_o.left_align?)
      assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
      assert(!@format_o.left_align?)
        # The format testing is done in test_format_style
    end

    def test_left_margin
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal(0, @format_o.left_margin)
      assert_nothing_raised { @format_o.left_margin = -3 }
      assert_equal(3, @format_o.left_margin)
      assert_nothing_raised { @format_o.left_margin = "9" }
      assert_equal(9, @format_o.left_margin)
      assert_nothing_raised { @format_o.left_margin = "-2" }
      assert_equal(2, @format_o.left_margin)
      assert_nothing_raised { @format_o.left_margin = 7 }
      assert_equal(7, @format_o.left_margin)
      assert_nothing_raised {
        ft = @format_o.format(GETTYSBURG).split("\n")
        assert_match(/^ {11}Four score/, ft[0])
        assert_match(/^ {7}November/, ft[-1])
      }
    end

    def test_hard_margins
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(!@format_o.hard_margins)
      assert_nothing_raised {
        @format_o.hard_margins = true
        @format_o.columns = 5
        @format_o.first_indent = 0
        @format_o.format_style = Text::Format::RIGHT_FILL
      }
      assert(@format_o.hard_margins)
      assert_equal(FIVE_COL, @format_o.format(GETTYSBURG))
      assert_nothing_raised {
        @format_o.split_rules |= Text::Format::SPLIT_CONTINUATION
        assert_equal(Text::Format::SPLIT_CONTINUATION_FIXED,
                     @format_o.split_rules)
      }
      assert_equal(FIVE_CNT, @format_o.format(GETTYSBURG))
    end

      # Tests both nobreak and nobreak_regex, since one is only useful
      # with the other.
    def test_nobreak
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(!@format_o.nobreak)
      assert(@format_o.nobreak_regex.empty?)
      assert_nothing_raised {
        @format_o.nobreak = true
        @format_o.nobreak_regex = { '^this$' => '^continent$' }
        @format_o.columns = 77
      }
      assert(@format_o.nobreak)
      assert_equal({ '^this$' => '^continent$' }, @format_o.nobreak_regex)
      assert_match(/^this continent/,
                   @format_o.format(GETTYSBURG).split("\n")[1])
    end

    def test_right_align?
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(!@format_o.right_align?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_ALIGN
      }
      assert(@format_o.right_align?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_FILL
      }
      assert(!@format_o.right_align?)
      assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
      assert(!@format_o.right_align?)
        # The format testing is done in test_format_style
    end

    def test_right_fill?
      assert_nothing_raised { @format_o = Text::Format.new }
      assert(!@format_o.right_fill?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_ALIGN
      }
      assert(!@format_o.right_fill?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::RIGHT_FILL
      }
      assert(@format_o.right_fill?)
      assert_nothing_raised {
        @format_o.format_style = Text::Format::JUSTIFY
      }
      assert(!@format_o.right_fill?)
        # The format testing is done in test_format_style
    end

    def test_right_margin
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal(0, @format_o.right_margin)
      assert_nothing_raised { @format_o.right_margin = -3 }
      assert_equal(3, @format_o.right_margin)
      assert_nothing_raised { @format_o.right_margin = "9" }
      assert_equal(9, @format_o.right_margin)
      assert_nothing_raised { @format_o.right_margin = "-2" }
      assert_equal(2, @format_o.right_margin)
      assert_nothing_raised { @format_o.right_margin = 7 }
      assert_equal(7, @format_o.right_margin)
      assert_nothing_raised {
        ft = @format_o.format(GETTYSBURG).split("\n")
        assert_match(/^ {4}Four score.*forth on$/, ft[0])
        assert_match(/^November/, ft[-1])
      }
    end

    def test_tabstop
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal(8, @format_o.tabstop)
      assert_nothing_raised { @format_o.tabstop = 7 }
      assert_equal(7, @format_o.tabstop)
      assert_nothing_raised { @format_o.tabstop = -3 }
      assert_equal(3, @format_o.tabstop)
      assert_nothing_raised { @format_o.tabstop = "9" }
      assert_equal(9, @format_o.tabstop)
      assert_nothing_raised { @format_o.tabstop = "-2" }
      assert_equal(2, @format_o.tabstop)
    end

    def test_text
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal([], @format_o.text)
      assert_nothing_raised { @format_o.text = "Test Text" }
      assert_equal("Test Text", @format_o.text)
      assert_nothing_raised { @format_o.text = ["Line 1", "Line 2"] }
      assert_equal(["Line 1", "Line 2"], @format_o.text)
    end

    def test_s_new
          # new(NilClass) { block }
      assert_nothing_raised do
        @format_o = Text::Format.new {
          self.text = "Test 1, 2, 3"
        }
      end
      assert_equal("Test 1, 2, 3", @format_o.text)

        # new(Hash Symbols)
      assert_nothing_raised { @format_o = Text::Format.new(:columns => 72) }
      assert_equal(72, @format_o.columns)

        # new(Hash String)
      assert_nothing_raised { @format_o = Text::Format.new('columns' => 72) }
      assert_equal(72, @format_o.columns)

        # new(Hash) { block }
      assert_nothing_raised do
        @format_o = Text::Format.new('columns' => 80) {
          self.text = "Test 4, 5, 6"
        }
      end
      assert_equal("Test 4, 5, 6", @format_o.text)
      assert_equal(80, @format_o.columns)

        # new(Text::Format)
      assert_nothing_raised do
        fo = Text::Format.new(@format_o)
        assert(fo == @format_o)
      end

        # new(Text::Format) { block }
      assert_nothing_raised do
        fo = Text::Format.new(@format_o) { self.columns = 79 }
        assert(fo != @format_o)
      end

          # new(String)
      assert_nothing_raised { @format_o = Text::Format.new("Test A, B, C") }
      assert_equal("Test A, B, C", @format_o.text)

          # new(String) { block }
      assert_nothing_raised do
        @format_o = Text::Format.new("Test X, Y, Z") { self.columns = -5 }
      end
      assert_equal("Test X, Y, Z", @format_o.text)
      assert_equal(5, @format_o.columns)
    end

    def test_center
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_nothing_raised do
        ct = @format_o.center(GETTYSBURG.split("\n")).split("\n")
        assert_match(/^    Four score and seven years ago our fathers brought forth on this/, ct[0])
        assert_match(/^                       not perish from the earth./, ct[-3])
      end
    end

    def test_expand
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal("          ", @format_o.expand("\t  "))
      assert_nothing_raised { @format_o.tabstop = 4 }
      assert_equal("      ", @format_o.expand("\t  "))
    end

    def test_unexpand
      assert_nothing_raised { @format_o = Text::Format.new }
      assert_equal("\t  ", @format_o.unexpand("          "))
      assert_nothing_raised { @format_o.tabstop = 4 }
      assert_equal("\t  ", @format_o.unexpand("      "))
    end

    def test_space_only
      assert_equal("", Text::Format.new.format(" "))
      assert_equal("", Text::Format.new.format("\n"))
      assert_equal("", Text::Format.new.format("        "))
      assert_equal("", Text::Format.new.format("    \n"))
      assert_equal("", Text::Format.new.paragraphs("\n"))
      assert_equal("", Text::Format.new.paragraphs(" "))
      assert_equal("", Text::Format.new.paragraphs("        "))
      assert_equal("", Text::Format.new.paragraphs("    \n"))
      assert_equal("", Text::Format.new.paragraphs(["\n"]))
      assert_equal("", Text::Format.new.paragraphs([" "]))
      assert_equal("", Text::Format.new.paragraphs(["        "]))
      assert_equal("", Text::Format.new.paragraphs(["    \n"]))
    end

    def test_splendiferous
      h = nil
      test = "This is a splendiferous test"
      assert_nothing_raised { @format_o = Text::Format.new(:columns => 6, :left_margin => 0, :indent => 0, :first_indent => 0) }
      assert_match(/^splendiferous$/, @format_o.format(test))
      assert_nothing_raised { @format_o.hard_margins = true }
      assert_match(/^lendif$/, @format_o.format(test))
      assert_nothing_raised { h = Object.new }
      assert_nothing_raised do
        @format_o.split_rules = Text::Format::SPLIT_HYPHENATION
        class << h #:nodoc:
          def hyphenate_to(word, size)
            return ["", word] if size < 2
            [word[0 ... size], word[size .. -1]]
          end
        end
        @format_o.hyphenator = h
      end
      assert_match(/^iferou$/, @format_o.format(test))
      assert_nothing_raised { h = Object.new }
      assert_nothing_raised do
        class << h #:nodoc:
          def hyphenate_to(word, size, formatter)
            return ["", word] if word.size < formatter.columns
            [word[0 ... size], word[size .. -1]]
          end
        end
        @format_o.hyphenator = h
      end
      assert_match(/^ferous$/, @format_o.format(test))
    end
  end
end
#
# address.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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
require 'stringio'

module TMail
  class Attachment < StringIO
    attr_accessor :original_filename, :content_type
  end

  class Mail
    def has_attachments?
      multipart? && parts.any? { |part| attachment?(part) }
    end

    def attachment?(part)
      (part['content-disposition'] && part['content-disposition'].disposition == "attachment") ||
      part.header['content-type'].main_type != "text"
    end

    def attachments
      if multipart?
        parts.collect { |part| 
          if attachment?(part)
            content   = part.body # unquoted automatically by TMail#body
            file_name = (part['content-location'] &&
                          part['content-location'].body) ||
                        part.sub_header("content-type", "name") ||
                        part.sub_header("content-disposition", "filename")
            
            next if file_name.blank? || content.blank?
            
            attachment = Attachment.new(content)
            attachment.original_filename = file_name.strip
            attachment.content_type = part.content_type
            attachment
          end
        }.compact
      end      
    end
  end
end
#
# base64.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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

  module Base64

    module_function

    def rb_folding_encode( str, eol = "\n", limit = 60 )
      [str].pack('m')
    end

    def rb_encode( str )
      [str].pack('m').tr( "\r\n", '' )
    end

    def rb_decode( str, strict = false )
      str.unpack('m')
    end

    begin
      require 'tmail/base64.so'
      alias folding_encode c_folding_encode
      alias encode         c_encode
      alias decode         c_decode
      class << self
        alias folding_encode c_folding_encode
        alias encode         c_encode
        alias decode         c_decode
      end
    rescue LoadError
      alias folding_encode rb_folding_encode
      alias encode         rb_encode
      alias decode         rb_decode
      class << self
        alias folding_encode rb_folding_encode
        alias encode         rb_encode
        alias decode         rb_decode
      end
    end

  end

end
#
# config.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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

  class Config

    def initialize( strict )
      @strict_parse = strict
      @strict_base64decode = strict
    end

    def strict_parse?
      @strict_parse
    end

    attr_writer :strict_parse

    def strict_base64decode?
      @strict_base64decode
    end

    attr_writer :strict_base64decode

    def new_body_port( mail )
      StringPort.new
    end

    alias new_preamble_port  new_body_port
    alias new_part_port      new_body_port
  
  end

  DEFAULT_CONFIG        = Config.new(false)
  DEFAULT_STRICT_CONFIG = Config.new(true)

  def Config.to_config( arg )
    return DEFAULT_STRICT_CONFIG if arg == true
    return DEFAULT_CONFIG        if arg == false
    arg or DEFAULT_CONFIG
  end

end
#
# encode.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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 'nkf'
require 'tmail/base64.rb'
require 'tmail/stringio'
require 'tmail/utils'


module TMail

  module StrategyInterface

    def create_dest( obj )
      case obj
      when nil
        StringOutput.new
      when String
        StringOutput.new(obj)
      when IO, StringOutput
        obj
      else
        raise TypeError, 'cannot handle this type of object for dest'
      end
    end
    module_function :create_dest

    def encoded( eol = "\r\n", charset = 'j', dest = nil )
      accept_strategy Encoder, eol, charset, dest
    end

    def decoded( eol = "\n", charset = 'e', dest = nil )
      accept_strategy Decoder, eol, charset, dest
    end

    alias to_s decoded
  
    def accept_strategy( klass, eol, charset, dest = nil )
      dest ||= ''
      accept klass.new(create_dest(dest), charset, eol)
      dest
    end

  end


  ###
  ### MIME B encoding decoder
  ###

  class Decoder

    include TextUtils

    encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
    ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i

    OUTPUT_ENCODING = {
      'EUC'  => 'e',
      'SJIS' => 's',
    }

    def self.decode( str, encoding = nil )
      encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
      opt = '-m' + encoding
      str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
    end

    def initialize( dest, encoding = nil, eol = "\n" )
      @f = StrategyInterface.create_dest(dest)
      @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
      @eol = eol
    end

    def decode( str )
      self.class.decode(str, @encoding)
    end
    private :decode

    def terminate
    end

    def header_line( str )
      @f << decode(str)
    end

    def header_name( nm )
      @f << nm << ': '
    end

    def header_body( str )
      @f << decode(str)
    end
      
    def space
      @f << ' '
    end

    alias spc space

    def lwsp( str )
      @f << str
    end
      
    def meta( str )
      @f << str
    end

    def text( str )
      @f << decode(str)
    end

    def phrase( str )
      @f << quote_phrase(decode(str))
    end

    def kv_pair( k, v )
      @f << k << '=' << v
    end

    def puts( str = nil )
      @f << str if str
      @f << @eol
    end

    def write( str )
      @f << str
    end

  end


  ###
  ### MIME B-encoding encoder
  ###

  #
  # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
  #
  class Encoder

    include TextUtils

    BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)

    def Encoder.encode( str )
      e = new()
      e.header_body str
      e.terminate
      e.dest.string
    end

    SPACER       = "\t"
    MAX_LINE_LEN = 70

    OPTIONS = {
      'EUC'  => '-Ej -m0',
      'SJIS' => '-Sj -m0',
      'UTF8' => nil,      # FIXME
      'NONE' => nil
    }

    def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
      @f = StrategyInterface.create_dest(dest)
      @opt = OPTIONS[$KCODE]
      @eol = eol
      reset
    end

    def normalize_encoding( str )
      if @opt
      then NKF.nkf(@opt, str)
      else str
      end
    end

    def reset
      @text = ''
      @lwsp = ''
      @curlen = 0
    end

    def terminate
      add_lwsp ''
      reset
    end

    def dest
      @f
    end

    def puts( str = nil )
      @f << str if str
      @f << @eol
    end

    def write( str )
      @f << str
    end

    #
    # add
    #

    def header_line( line )
      scanadd line
    end

    def header_name( name )
      add_text name.split(/-/).map {|i| i.capitalize }.join('-')
      add_text ':'
      add_lwsp ' '
    end

    def header_body( str )
      scanadd normalize_encoding(str)
    end

    def space
      add_lwsp ' '
    end

    alias spc space

    def lwsp( str )
      add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
    end

    def meta( str )
      add_text str
    end

    def text( str )
      scanadd normalize_encoding(str)
    end

    def phrase( str )
      str = normalize_encoding(str)
      if CONTROL_CHAR === str
        scanadd str
      else
        add_text quote_phrase(str)
      end
    end

    # FIXME: implement line folding
    #
    def kv_pair( k, v )
      return if v.nil?
      v = normalize_encoding(v)
      if token_safe?(v)
        add_text k + '=' + v
      elsif not CONTROL_CHAR === v
        add_text k + '=' + quote_token(v)
      else
        # apply RFC2231 encoding
        kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
        add_text kv
      end
    end

    def encode_value( str )
      str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
    end

    private

    def scanadd( str, force = false )
      types = ''
      strs = []

      until str.empty?
        if m = /\A[^\e\t\r\n ]+/.match(str)
          types << (force ? 'j' : 'a')
          strs.push m[0]

        elsif m = /\A[\t\r\n ]+/.match(str)
          types << 's'
          strs.push m[0]

        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]
          end

        else
          raise 'TMail FATAL: encoder scan fail'
        end
        (str = m.post_match) unless m.nil?
      end

      do_encode types, strs
    end

    def do_encode( types, strs )
      #
      # result  : (A|E)(S(A|E))*
      # E       : W(SW)*
      # W       : (J|A)+ but must contain J  # (J|A)*J(J|A)*
      # A       : <<A character string not to be encoded>>
      # J       : <<A character string to be encoded>>
      # S       : <<LWSP>>
      #
      # An encoding unit is `E'.
      # Input (parameter `types') is  (J|A)(J|A|S)*(J|A)
      #
      if BENCODE_DEBUG
        puts
        puts '-- do_encode ------------'
        puts types.split(//).join(' ')
        p strs
      end

      e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/

      while m = e.match(types)
        pre = m.pre_match
        concat_A_S pre, strs[0, pre.size] unless pre.empty?
        concat_E m[0], strs[m.begin(0) ... m.end(0)]
        types = m.post_match
        strs.slice! 0, m.end(0)
      end
      concat_A_S types, strs
    end

    def concat_A_S( types, strs )
      i = 0
      types.each_byte do |t|
        case t
        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,
      ?a => :extract_A,
      ?s => :extract_S
    }

    def concat_E( types, strs )
      if BENCODE_DEBUG
        puts '---- concat_E'
        puts "types=#{types.split(//).join(' ')}"
        puts "strs =#{strs.inspect}"
      end

      flush() unless @text.empty?

      chunk = ''
      strs.each_with_index do |s,i|
        mid = METHOD_ID[types[i]]
        until s.empty?
          unless c = __send__(mid, chunk.size, s)
            add_with_encode chunk unless chunk.empty?
            flush
            chunk = ''
            fold
            c = __send__(mid, 0, s)
            raise 'TMail FATAL: extract fail' unless c
          end
          chunk << c
        end
      end
      add_with_encode chunk unless chunk.empty?
    end

    def extract_J( chunksize, str )
      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"
    end

    def extract_A( chunksize, str )
      size = max_bytes(chunksize, str.size)
      return nil if size <= 0
      str.slice!(0, size)
    end

    alias extract_S extract_A

    def max_bytes( chunksize, ssize )
      (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
    end

    #
    # free length buffer
    #

    def add_text( str )
      @text << str
      # puts '---- text -------------------------------------'
      # puts "+ #{str.inspect}"
      # puts "txt >>>#{@text.inspect}<<<"
    end

    def add_with_encode( str )
      @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
    end

    def add_lwsp( lwsp )
      # puts '---- lwsp -------------------------------------'
      # puts "+ #{lwsp.inspect}"
      fold if restsize() <= 0
      flush
      @lwsp = lwsp
    end

    def flush
      # puts '---- flush ----'
      # puts "spc >>>#{@lwsp.inspect}<<<"
      # puts "txt >>>#{@text.inspect}<<<"
      @f << @lwsp << @text
      @curlen += (@lwsp.size + @text.size)
      @text = ''
      @lwsp = ''
    end

    def fold
      # puts '---- fold ----'
      @f << @eol
      @curlen = 0
      @lwsp = SPACER
    end

    def restsize
      MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
    end

  end

end    # module TMail
#
# facade.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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
#
# header.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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/address'
require 'tmail/parser'
require 'tmail/config'
require 'tmail/utils'


module TMail

  class HeaderField

    include TextUtils

    class << self

      alias newobj new

      def new( name, body, conf = DEFAULT_CONFIG )
        klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
        klass.newobj body, conf
      end

      def new_from_port( port, name, conf = DEFAULT_CONFIG )
        re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
        str = nil
        port.ropen {|f|
            f.each do |line|
              if m = re.match(line)            then str = m.post_match.strip
              elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
              elsif /\A-*\s*\z/ === line       then break
              elsif str                        then break
              end
            end
        }
        new(name, str, Config.to_config(conf))
      end

      def internal_new( name, conf )
        FNAME_TO_CLASS[name].newobj('', conf, true)
      end

    end   # class << self

    def initialize( body, conf, intern = false )
      @body = body
      @config = conf

      @illegal = false
      @parsed = false
      if intern
        @parsed = true
        parse_init
      end
    end

    def inspect
      "#<#{self.class} #{@body.inspect}>"
    end

    def illegal?
      @illegal
    end

    def empty?
      ensure_parsed
      return true if @illegal
      isempty?
    end

    private

    def ensure_parsed
      return if @parsed
      @parsed = true
      parse
    end

    # defabstract parse
    # end

    def clear_parse_status
      @parsed = false
      @illegal = false
    end

    public

    def body
      ensure_parsed
      v = Decoder.new(s = '')
      do_accept v
      v.terminate
      s
    end

    def body=( str )
      @body = str
      clear_parse_status
    end

    include StrategyInterface

    def accept( strategy, dummy1 = nil, dummy2 = nil )
      ensure_parsed
      do_accept strategy
      strategy.terminate
    end

    # abstract do_accept

  end


  class UnstructuredHeader < HeaderField

    def body
      ensure_parsed
      @body
    end

    def body=( arg )
      ensure_parsed
      @body = arg
    end

    private

    def parse_init
    end

    def parse
      @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
    end

    def isempty?
      not @body
    end

    def do_accept( strategy )
      strategy.text @body
    end

  end


  class StructuredHeader < HeaderField

    def comments
      ensure_parsed
      @comments
    end

    private

    def parse
      save = nil

      begin
        parse_init
        do_parse
      rescue SyntaxError
        if not save and mime_encoded? @body
          save = @body
          @body = Decoder.decode(save)
          retry
        elsif save
          @body = save
        end

        @illegal = true
        raise if @config.strict_parse?
      end
    end

    def parse_init
      @comments = []
      init
    end

    def do_parse
      obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
      set obj if obj
    end

  end


  class DateTimeHeader < StructuredHeader

    PARSE_TYPE = :DATETIME

    def date
      ensure_parsed
      @date
    end

    def date=( arg )
      ensure_parsed
      @date = arg
    end

    private

    def init
      @date = nil
    end

    def set( t )
      @date = t
    end

    def isempty?
      not @date
    end

    def do_accept( strategy )
      strategy.meta time2str(@date)
    end

  end


  class AddressHeader < StructuredHeader

    PARSE_TYPE = :MADDRESS

    def addrs
      ensure_parsed
      @addrs
    end

    private

    def init
      @addrs = []
    end

    def set( a )
      @addrs = a
    end

    def isempty?
      @addrs.empty?
    end

    def do_accept( strategy )
      first = true
      @addrs.each do |a|
        if first
          first = false
        else
          strategy.meta ','
          strategy.space
        end
        a.accept strategy
      end

      @comments.each do |c|
        strategy.space
        strategy.meta '('
        strategy.text c
        strategy.meta ')'
      end
    end

  end


  class ReturnPathHeader < AddressHeader

    PARSE_TYPE = :RETPATH

    def addr
      addrs()[0]
    end

    def spec
      a = addr() or return nil
      a.spec
    end

    def routes
      a = addr() or return nil
      a.routes
    end

    private

    def do_accept( strategy )
      a = addr()

      strategy.meta '<'
      unless a.routes.empty?
        strategy.meta a.routes.map {|i| '@' + i }.join(',')
        strategy.meta ':'
      end
      spec = a.spec
      strategy.meta spec if spec
      strategy.meta '>'
    end

  end


  class SingleAddressHeader < AddressHeader

    def addr
      addrs()[0]
    end

    private

    def do_accept( strategy )
      a = addr()
      a.accept strategy
      @comments.each do |c|
        strategy.space
        strategy.meta '('
        strategy.text c
        strategy.meta ')'
      end
    end

  end


  class MessageIdHeader < StructuredHeader

    def id
      ensure_parsed
      @id
    end

    def id=( arg )
      ensure_parsed
      @id = arg
    end

    private

    def init
      @id = nil
    end

    def isempty?
      not @id
    end

    def do_parse
      @id = @body.slice(MESSAGE_ID) or
              raise SyntaxError, "wrong Message-ID format: #{@body}"
    end

    def do_accept( strategy )
      strategy.meta @id
    end

  end


  class ReferencesHeader < StructuredHeader

    def refs
      ensure_parsed
      @refs
    end

    def each_id
      self.refs.each do |i|
        yield i if MESSAGE_ID === i
      end
    end

    def ids
      ensure_parsed
      @ids
    end

    def each_phrase
      self.refs.each do |i|
        yield i unless MESSAGE_ID === i
      end
    end

    def phrases
      ret = []
      each_phrase {|i| ret.push i }
      ret
    end

    private

    def init
      @refs = []
      @ids = []
    end

    def isempty?
      @ids.empty?
    end

    def do_parse
      str = @body
      while m = MESSAGE_ID.match(str)
        pre = m.pre_match.strip
        @refs.push pre unless pre.empty?
        @refs.push s = m[0]
        @ids.push s
        str = m.post_match
      end
      str = str.strip
      @refs.push str unless str.empty?
    end

    def do_accept( strategy )
      first = true
      @ids.each do |i|
        if first
          first = false
        else
          strategy.space
        end
        strategy.meta i
      end
    end

  end


  class ReceivedHeader < StructuredHeader

    PARSE_TYPE = :RECEIVED

    def from
      ensure_parsed
      @from
    end

    def from=( arg )
      ensure_parsed
      @from = arg
    end

    def by
      ensure_parsed
      @by
    end

    def by=( arg )
      ensure_parsed
      @by = arg
    end

    def via
      ensure_parsed
      @via
    end

    def via=( arg )
      ensure_parsed
      @via = arg
    end

    def with
      ensure_parsed
      @with
    end

    def id
      ensure_parsed
      @id
    end

    def id=( arg )
      ensure_parsed
      @id = arg
    end

    def _for
      ensure_parsed
      @_for
    end

    def _for=( arg )
      ensure_parsed
      @_for = arg
    end

    def date
      ensure_parsed
      @date
    end

    def date=( arg )
      ensure_parsed
      @date = arg
    end

    private

    def init
      @from = @by = @via = @with = @id = @_for = nil
      @with = []
      @date = nil
    end

    def set( args )
      @from, @by, @via, @with, @id, @_for, @date = *args
    end

    def isempty?
      @with.empty? and not (@from or @by or @via or @id or @_for or @date)
    end

    def do_accept( strategy )
      list = []
      list.push 'from '  + @from       if @from
      list.push 'by '    + @by         if @by
      list.push 'via '   + @via        if @via
      @with.each do |i|
        list.push 'with ' + i
      end
      list.push 'id '    + @id         if @id
      list.push 'for <'  + @_for + '>' if @_for

      first = true
      list.each do |i|
        strategy.space unless first
        strategy.meta i
        first = false
      end
      if @date
        strategy.meta ';'
        strategy.space
        strategy.meta time2str(@date)
      end
    end

  end


  class KeywordsHeader < StructuredHeader

    PARSE_TYPE = :KEYWORDS

    def keys
      ensure_parsed
      @keys
    end

    private

    def init
      @keys = []
    end

    def set( a )
      @keys = a
    end

    def isempty?
      @keys.empty?
    end

    def do_accept( strategy )
      first = true
      @keys.each do |i|
        if first
          first = false
        else
          strategy.meta ','
        end
        strategy.meta i
      end
    end

  end


  class EncryptedHeader < StructuredHeader

    PARSE_TYPE = :ENCRYPTED

    def encrypter
      ensure_parsed
      @encrypter
    end

    def encrypter=( arg )
      ensure_parsed
      @encrypter = arg
    end

    def keyword
      ensure_parsed
      @keyword
    end

    def keyword=( arg )
      ensure_parsed
      @keyword = arg
    end

    private

    def init
      @encrypter = nil
      @keyword = nil
    end

    def set( args )
      @encrypter, @keyword = args
    end

    def isempty?
      not (@encrypter or @keyword)
    end

    def do_accept( strategy )
      if @key
        strategy.meta @encrypter + ','
        strategy.space
        strategy.meta @keyword
      else
        strategy.meta @encrypter
      end
    end

  end


  class MimeVersionHeader < StructuredHeader

    PARSE_TYPE = :MIMEVERSION

    def major
      ensure_parsed
      @major
    end

    def major=( arg )
      ensure_parsed
      @major = arg
    end

    def minor
      ensure_parsed
      @minor
    end

    def minor=( arg )
      ensure_parsed
      @minor = arg
    end

    def version
      sprintf('%d.%d', major, minor)
    end

    private

    def init
      @major = nil
      @minor = nil
    end

    def set( args )
      @major, @minor = *args
    end

    def isempty?
      not (@major or @minor)
    end

    def do_accept( strategy )
      strategy.meta sprintf('%d.%d', @major, @minor)
    end

  end


  class ContentTypeHeader < StructuredHeader

    PARSE_TYPE = :CTYPE

    def main_type
      ensure_parsed
      @main
    end

    def main_type=( arg )
      ensure_parsed
      @main = arg.downcase
    end

    def sub_type
      ensure_parsed
      @sub
    end

    def sub_type=( arg )
      ensure_parsed
      @sub = arg.downcase
    end

    def content_type
      ensure_parsed
      @sub ? sprintf('%s/%s', @main, @sub) : @main
    end

    def params
      ensure_parsed
      @params
    end

    def []( key )
      ensure_parsed
      @params and @params[key]
    end

    def []=( key, val )
      ensure_parsed
      (@params ||= {})[key] = val
    end

    private

    def init
      @main = @sub = @params = nil
    end

    def set( args )
      @main, @sub, @params = *args
    end

    def isempty?
      not (@main or @sub)
    end

    def do_accept( strategy )
      if @sub
        strategy.meta sprintf('%s/%s', @main, @sub)
      else
        strategy.meta @main
      end
      @params.each do |k,v|
        if v
          strategy.meta ';'
          strategy.space
          strategy.kv_pair k, v
        end
      end
    end

  end


  class ContentTransferEncodingHeader < StructuredHeader

    PARSE_TYPE = :CENCODING

    def encoding
      ensure_parsed
      @encoding
    end

    def encoding=( arg )
      ensure_parsed
      @encoding = arg
    end

    private

    def init
      @encoding = nil
    end

    def set( s )
      @encoding = s
    end

    def isempty?
      not @encoding
    end

    def do_accept( strategy )
      strategy.meta @encoding.capitalize
    end

  end


  class ContentDispositionHeader < StructuredHeader

    PARSE_TYPE = :CDISPOSITION

    def disposition
      ensure_parsed
      @disposition
    end

    def disposition=( str )
      ensure_parsed
      @disposition = str.downcase
    end

    def params
      ensure_parsed
      @params
    end

    def []( key )
      ensure_parsed
      @params and @params[key]
    end

    def []=( key, val )
      ensure_parsed
      (@params ||= {})[key] = val
    end

    private

    def init
      @disposition = @params = nil
    end

    def set( args )
      @disposition, @params = *args
    end

    def isempty?
      not @disposition and (not @params or @params.empty?)
    end

    def do_accept( strategy )
      strategy.meta @disposition
      @params.each do |k,v|
        strategy.meta ';'
        strategy.space
        strategy.kv_pair k, v
      end
    end
      
  end


  class HeaderField   # redefine

    FNAME_TO_CLASS = {
      'date'                      => DateTimeHeader,
      'resent-date'               => DateTimeHeader,
      'to'                        => AddressHeader,
      'cc'                        => AddressHeader,
      'bcc'                       => AddressHeader,
      'from'                      => AddressHeader,
      'reply-to'                  => AddressHeader,
      'resent-to'                 => AddressHeader,
      'resent-cc'                 => AddressHeader,
      'resent-bcc'                => AddressHeader,
      'resent-from'               => AddressHeader,
      'resent-reply-to'           => AddressHeader,
      'sender'                    => SingleAddressHeader,
      'resent-sender'             => SingleAddressHeader,
      'return-path'               => ReturnPathHeader,
      'message-id'                => MessageIdHeader,
      'resent-message-id'         => MessageIdHeader,
      'in-reply-to'               => ReferencesHeader,
      'received'                  => ReceivedHeader,
      'references'                => ReferencesHeader,
      'keywords'                  => KeywordsHeader,
      'encrypted'                 => EncryptedHeader,
      'mime-version'              => MimeVersionHeader,
      'content-type'              => ContentTypeHeader,
      'content-transfer-encoding' => ContentTransferEncodingHeader,
      'content-disposition'       => ContentDispositionHeader,
      'content-id'                => MessageIdHeader,
      'subject'                   => UnstructuredHeader,
      'comments'                  => UnstructuredHeader,
      'content-description'       => UnstructuredHeader
    }

  end

end   # module TMail
#
# info.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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
require 'tmail/mailbox'
#
# mail.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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/facade'
require 'tmail/encode'
require 'tmail/header'
require 'tmail/port'
require 'tmail/config'
require 'tmail/utils'
require 'tmail/attachments'
require 'tmail/quoting'
require 'socket'


module TMail

  class Mail

    class << self
      def load( fname )
        new(FilePort.new(fname))
      end

      alias load_from load
      alias loadfrom load

      def parse( str )
        new(StringPort.new(str))
      end
    end

    def initialize( port = nil, conf = DEFAULT_CONFIG )
      @port = port || StringPort.new
      @config = Config.to_config(conf)

      @header      = {}
      @body_port   = nil
      @body_parsed = false
      @epilogue    = ''
      @parts       = []

      @port.ropen {|f|
          parse_header f
          parse_body f unless @port.reproducible?
      }
    end

    attr_reader :port

    def inspect
      "\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
    end

    #
    # to_s interfaces
    #

    public

    include StrategyInterface

    def write_back( eol = "\n", charset = 'e' )
      parse_body
      @port.wopen {|stream| encoded eol, charset, stream }
    end

    def accept( strategy )
      with_multipart_encoding(strategy) {
          ordered_each do |name, field|
            next if field.empty?
            strategy.header_name canonical(name)
            field.accept strategy
            strategy.puts
          end
          strategy.puts
          body_port().ropen {|r|
              strategy.write r.read
          }
      }
    end

    private

    def canonical( name )
      name.split(/-/).map {|s| s.capitalize }.join('-')
    end

    def with_multipart_encoding( strategy )
      if parts().empty?    # DO NOT USE @parts
        yield

      else
        bound = ::TMail.new_boundary
        if @header.key? 'content-type'
          @header['content-type'].params['boundary'] = bound
        else
          store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
        end

        yield

        parts().each do |tm|
          strategy.puts
          strategy.puts '--' + bound
          tm.accept strategy
        end
        strategy.puts
        strategy.puts '--' + bound + '--'
        strategy.write epilogue()
      end
    end

    ###
    ### header
    ###

    public

    ALLOW_MULTIPLE = {
      'received'          => true,
      'resent-date'       => true,
      'resent-from'       => true,
      'resent-sender'     => true,
      'resent-to'         => true,
      'resent-cc'         => true,
      'resent-bcc'        => true,
      'resent-message-id' => true,
      'comments'          => true,
      'keywords'          => true
    }
    USE_ARRAY = ALLOW_MULTIPLE

    def header
      @header.dup
    end

    def []( key )
      @header[key.downcase]
    end

    def sub_header(key, param)
      (hdr = self[key]) ? hdr[param] : nil
    end

    alias fetch []

    def []=( key, val )
      dkey = key.downcase

      if val.nil?
        @header.delete dkey
        return nil
      end

      case val
      when String
        header = new_hf(key, val)
      when HeaderField
        ;
      when Array
        ALLOW_MULTIPLE.include? dkey or
                raise ArgumentError, "#{key}: Header must not be multiple"
        @header[dkey] = val
        return val
      else
        header = new_hf(key, val.to_s)
      end
      if ALLOW_MULTIPLE.include? dkey
        (@header[dkey] ||= []).push header
      else
        @header[dkey] = header
      end

      val
    end

    alias store []=

    def each_header
      @header.each do |key, val|
        [val].flatten.each {|v| yield key, v }
      end
    end

    alias each_pair each_header

    def each_header_name( &block )
      @header.each_key(&block)
    end

    alias each_key each_header_name

    def each_field( &block )
      @header.values.flatten.each(&block)
    end

    alias each_value each_field

    FIELD_ORDER = %w(
      return-path received
      resent-date resent-from resent-sender resent-to
      resent-cc resent-bcc resent-message-id
      date from sender reply-to to cc bcc
      message-id in-reply-to references
      subject comments keywords
      mime-version content-type content-transfer-encoding
      content-disposition content-description
    )

    def ordered_each
      list = @header.keys
      FIELD_ORDER.each do |name|
        if list.delete(name)
          [@header[name]].flatten.each {|v| yield name, v }
        end
      end
      list.each do |name|
        [@header[name]].flatten.each {|v| yield name, v }
      end
    end

    def clear
      @header.clear
    end

    def delete( key )
      @header.delete key.downcase
    end

    def delete_if
      @header.delete_if do |key,val|
        if Array === val
          val.delete_if {|v| yield key, v }
          val.empty?
        else
          yield key, val
        end
      end
    end

    def keys
      @header.keys
    end

    def key?( key )
      @header.key? key.downcase
    end

    def values_at( *args )
      args.map {|k| @header[k.downcase] }.flatten
    end

    alias indexes values_at
    alias indices values_at

    private

    def parse_header( f )
      name = field = nil
      unixfrom = nil

      while line = f.gets
        case line
        when /\A[ \t]/             # continue from prev line
          raise SyntaxError, 'mail is began by space' unless field
          field << ' ' << line.strip

        when /\A([^\: \t]+):\s*/   # new header line
          add_hf name, field if field
          name = $1
          field = $' #.strip

        when /\A\-*\s*\z/          # end of header
          add_hf name, field if field
          name = field = nil
          break

        when /\AFrom (\S+)/
          unixfrom = $1

  		  when /^charset=.*/
				
        else
          raise SyntaxError, "wrong mail header: '#{line.inspect}'"
        end
      end
      add_hf name, field if name

      if unixfrom
        add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
      end
    end

    def add_hf( name, field )
      key = name.downcase
      field = new_hf(name, field)

      if ALLOW_MULTIPLE.include? key
        (@header[key] ||= []).push field
      else
        @header[key] = field
      end
    end

    def new_hf( name, field )
      HeaderField.new(name, field, @config)
    end

    ###
    ### body
    ###

    public

    def body_port
      parse_body
      @body_port
    end

    def each( &block )
      body_port().ropen {|f| f.each(&block) }
    end

    def quoted_body
      parse_body
      @body_port.ropen {|f|
          return f.read
      }
    end

    def body=( str )
      parse_body
      @body_port.wopen {|f| f.write str }
      str
    end

    alias preamble  body
    alias preamble= body=

    def epilogue
      parse_body
      @epilogue.dup
    end

    def epilogue=( str )
      parse_body
      @epilogue = str
      str
    end

    def parts
      parse_body
      @parts
    end
    
    def each_part( &block )
      parts().each(&block)
    end

    private

    def parse_body( f = nil )
      return if @body_parsed
      if f
        parse_body_0 f
      else
        @port.ropen {|f|
            skip_header f
            parse_body_0 f
        }
      end
      @body_parsed = true
    end

    def skip_header( f )
      while line = f.gets
        return if /\A[\r\n]*\z/ === line
      end
    end

    def parse_body_0( f )
      if multipart?
        read_multipart f
      else
        @body_port = @config.new_body_port(self)
        @body_port.wopen {|w|
            w.write f.read
        }
      end
    end
    
    def read_multipart( src )
      bound = @header['content-type'].params['boundary']
      is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
      lastbound = "--#{bound}--"

      ports = [ @config.new_preamble_port(self) ]
      begin
        f = ports.last.wopen
        while line = src.gets
          if is_sep === line
            f.close
            break if line.strip == lastbound
            ports.push @config.new_part_port(self)
            f = ports.last.wopen
          else
            f << line
          end
        end
        @epilogue = (src.read || '')
      ensure
        f.close if f and not f.closed?
      end

      @body_port = ports.shift
      @parts = ports.map {|p| self.class.new(p, @config) }
    end

  end   # class Mail

end   # module TMail
#
# mailbox.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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/port'
require 'socket'
require 'mutex_m'


unless [].respond_to?(:sort_by)
module Enumerable#:nodoc:
  def sort_by
    map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
  end
end
end


module TMail

  class MhMailbox

    PORT_CLASS = MhPort

    def initialize( dir )
      edir = File.expand_path(dir)
      raise ArgumentError, "not directory: #{dir}"\
                              unless FileTest.directory? edir
      @dirname = edir
      @last_file = nil
      @last_atime = nil
    end

    def directory
      @dirname
    end

    alias dirname directory

    attr_accessor :last_atime

    def inspect
      "#<#{self.class} #{@dirname}>"
    end

    def close
    end

    def new_port
      PORT_CLASS.new(next_file_name())
    end

    def each_port
      mail_files().each do |path|
        yield PORT_CLASS.new(path)
      end
      @last_atime = Time.now
    end

    alias each each_port

    def reverse_each_port
      mail_files().reverse_each do |path|
        yield PORT_CLASS.new(path)
      end
      @last_atime = Time.now
    end

    alias reverse_each reverse_each_port

    # old #each_mail returns Port
    #def each_mail
    #  each_port do |port|
    #    yield Mail.new(port)
    #  end
    #end

    def each_new_port( mtime = nil, &block )
      mtime ||= @last_atime
      return each_port(&block) unless mtime
      return unless File.mtime(@dirname) >= mtime

      mail_files().each do |path|
        yield PORT_CLASS.new(path) if File.mtime(path) > mtime
      end
      @last_atime = Time.now
    end

    private

    def mail_files
      Dir.entries(@dirname)\
              .select {|s| /\A\d+\z/ === s }\
              .map {|s| s.to_i }\
              .sort\
              .map {|i| "#{@dirname}/#{i}" }\
              .select {|path| FileTest.file? path }
    end

    def next_file_name
      unless n = @last_file
        n = 0
        Dir.entries(@dirname)\
                .select {|s| /\A\d+\z/ === s }\
                .map {|s| s.to_i }.sort\
        .each do |i|
          next unless FileTest.file? "#{@dirname}/#{i}"
          n = i
        end
      end
      begin
        n += 1
      end while FileTest.exist? "#{@dirname}/#{n}"
      @last_file = n

      "#{@dirname}/#{n}"
    end

  end   # MhMailbox

  MhLoader = MhMailbox


  class UNIXMbox
  
    def UNIXMbox.lock( fname )
      begin
        f = File.open(fname)
        f.flock File::LOCK_EX
        yield f
      ensure
        f.flock File::LOCK_UN
        f.close if f and not f.closed?
      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

    def initialize( fname, mhdir, readonly, static )
      @filename = fname
      @readonly = readonly
      @closed = false

      Dir.mkdir mhdir
      @real = MhMailbox.new(mhdir)
      @finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
      ObjectSpace.define_finalizer self, @finalizer
    end

    def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
      lambda {
          if writeback_p
            lock(mboxfile) {|f|
                mh.each_port do |port|
                  f.puts create_from_line(port)
                  port.ropen {|r|
                      f.puts r.read
                  }
                end
            }
          end
          if cleanup_p
            Dir.foreach(mh.dirname) do |fname|
              next if /\A\.\.?\z/ === fname
              File.unlink "#{mh.dirname}/#{fname}"
            end
            Dir.rmdir mh.dirname
          end
      }
    end

    # make _From line
    def UNIXMbox.create_from_line( port )
      sprintf 'From %s %s',
              fromaddr(), TextUtils.time2str(File.mtime(port.filename))
    end

    def UNIXMbox.fromaddr
      h = HeaderField.new_from_port(port, 'Return-Path') ||
          HeaderField.new_from_port(port, 'From') or return 'nobody'
      a = h.addrs[0] or return 'nobody'
      a.spec
    end
    private_class_method :fromaddr

    def close
      return if @closed

      ObjectSpace.undefine_finalizer self
      @finalizer.call
      @finalizer = nil
      @real = nil
      @closed = true
      @updated = nil
    end

    def each_port( &block )
      close_check
      update
      @real.each_port(&block)
    end

    alias each each_port

    def reverse_each_port( &block )
      close_check
      update
      @real.reverse_each_port(&block)
    end

    alias reverse_each reverse_each_port

    # old #each_mail returns Port
    #def each_mail( &block )
    #  each_port do |port|
    #    yield Mail.new(port)
    #  end
    #end

    def each_new_port( mtime = nil )
      close_check
      update
      @real.each_new_port(mtime) {|p| yield p }
    end

    def new_port
      close_check
      @real.new_port
    end

    private

    def close_check
      @closed and raise ArgumentError, 'accessing already closed mbox'
    end

    def update
      return if FileTest.zero?(@filename)
      return if @updated and File.mtime(@filename) < @updated
      w = nil
      port = nil
      time = nil
      UNIXMbox.lock(@filename) {|f|
          begin
            f.each do |line|
              if /\AFrom / === line
                w.close if w
                File.utime time, time, port.filename if time

                port = @real.new_port
                w = port.wopen
                time = fromline2time(line)
              else
                w.print line if w
              end
            end
          ensure
            if w and not w.closed?
              w.close
              File.utime time, time, port.filename if time
            end
          end
          f.truncate(0) unless @readonly
          @updated = Time.now
      }
    end

    def fromline2time( line )
      m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
              or return nil
      Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
    end

  end   # UNIXMbox

  MboxLoader = UNIXMbox


  class Maildir

    extend Mutex_m

    PORT_CLASS = MaildirPort

    @seq = 0
    def Maildir.unique_number
      synchronize {
          @seq += 1
          return @seq
      }
    end

    def initialize( dir = nil )
      @dirname = dir || ENV['MAILDIR']
      raise ArgumentError, "not directory: #{@dirname}"\
                              unless FileTest.directory? @dirname
      @new = "#{@dirname}/new"
      @tmp = "#{@dirname}/tmp"
      @cur = "#{@dirname}/cur"
    end

    def directory
      @dirname
    end

    def inspect
      "#<#{self.class} #{@dirname}>"
    end

    def close
    end

    def each_port
      mail_files(@cur).each do |path|
        yield PORT_CLASS.new(path)
      end
    end

    alias each each_port

    def reverse_each_port
      mail_files(@cur).reverse_each do |path|
        yield PORT_CLASS.new(path)
      end
    end

    alias reverse_each reverse_each_port

    def new_port
      fname = nil
      tmpfname = nil
      newfname = nil

      begin
        fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
        
        tmpfname = "#{@tmp}/#{fname}"
        newfname = "#{@new}/#{fname}"
      end while FileTest.exist? tmpfname

      if block_given?
        File.open(tmpfname, 'w') {|f| yield f }
        File.rename tmpfname, newfname
        PORT_CLASS.new(newfname)
      else
        File.open(tmpfname, 'w') {|f| f.write "\n\n" }
        PORT_CLASS.new(tmpfname)
      end
    end

    def each_new_port
      mail_files(@new).each do |path|
        dest = @cur + '/' + File.basename(path)
        File.rename path, dest
        yield PORT_CLASS.new(dest)
      end

      check_tmp
    end

    TOO_OLD = 60 * 60 * 36   # 36 hour

    def check_tmp
      old = Time.now.to_i - TOO_OLD
      
      each_filename(@tmp) do |full, fname|
        if FileTest.file? full and
           File.stat(full).mtime.to_i < old
          File.unlink full
        end
      end
    end

    private

    def mail_files( dir )
      Dir.entries(dir)\
              .select {|s| s[0] != ?. }\
              .sort_by {|s| s.slice(/\A\d+/).to_i }\
              .map {|s| "#{dir}/#{s}" }\
              .select {|path| FileTest.file? path }
    end

    def each_filename( dir )
      Dir.foreach(dir) do |fname|
        path = "#{dir}/#{fname}"
        if fname[0] != ?. and FileTest.file? path
          yield path, fname
        end
      end
    end
    
  end   # Maildir

  MaildirLoader = Maildir

end   # module TMail
require 'tmail/mailbox'
#
# net.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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 'nkf'


module TMail

  class Mail

    def send_to( smtp )
      do_send_to(smtp) do
        ready_to_send
      end
    end

    def send_text_to( smtp )
      do_send_to(smtp) do
        ready_to_send
        mime_encode
      end
    end

    def do_send_to( smtp )
      from = from_address or raise ArgumentError, 'no from address'
      (dests = destinations).empty? and raise ArgumentError, 'no receipient'
      yield
      send_to_0 smtp, from, dests
    end
    private :do_send_to

    def send_to_0( smtp, from, to )
      smtp.ready(from, to) do |f|
        encoded "\r\n", 'j', f, ''
      end
    end

    def ready_to_send
      delete_no_send_fields
      add_message_id
      add_date
    end

    NOSEND_FIELDS = %w(
      received
      bcc
    )

    def delete_no_send_fields
      NOSEND_FIELDS.each do |nm|
        delete nm
      end
      delete_if {|n,v| v.empty? }
    end

    def add_message_id( fqdn = nil )
      self.message_id = ::TMail::new_message_id(fqdn)
    end

    def add_date
      self.date = Time.now
    end

    def mime_encode
      if parts.empty?
        mime_encode_singlepart
      else
        mime_encode_multipart true
      end
    end

    def mime_encode_singlepart
      self.mime_version = '1.0'
      b = body
      if NKF.guess(b) != NKF::BINARY
        mime_encode_text b
      else
        mime_encode_binary b
      end
    end

    def mime_encode_text( body )
      self.body = NKF.nkf('-j -m0', body)
      self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
      self.encoding = '7bit'
    end

    def mime_encode_binary( body )
      self.body = [body].pack('m')
      self.set_content_type 'application', 'octet-stream'
      self.encoding = 'Base64'
    end

    def mime_encode_multipart( top = true )
      self.mime_version = '1.0' if top
      self.set_content_type 'multipart', 'mixed'
      e = encoding(nil)
      if e and not /\A(?:7bit|8bit|binary)\z/i === e
        raise ArgumentError,
              '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


  class DeleteFields

    NOSEND_FIELDS = %w(
      received
      bcc
    )

    def initialize( nosend = nil, delempty = true )
      @no_send_fields = nosend || NOSEND_FIELDS.dup
      @delete_empty_fields = delempty
    end

    attr :no_send_fields
    attr :delete_empty_fields, true

    def exec( mail )
      @no_send_fields.each do |nm|
        delete nm
      end
      delete_if {|n,v| v.empty? } if @delete_empty_fields
    end
  
  end


  class AddMessageId

    def initialize( fqdn = nil )
      @fqdn = fqdn
    end

    attr :fqdn, true

    def exec( mail )
      mail.message_id = ::TMail::new_msgid(@fqdn)
    end
  
  end


  class AddDate

    def exec( mail )
      mail.date = Time.now
    end
  
  end


  class MimeEncodeAuto

    def initialize( s = nil, m = nil )
      @singlepart_composer = s || MimeEncodeSingle.new
      @multipart_composer  = m || MimeEncodeMulti.new
    end

    attr :singlepart_composer
    attr :multipart_composer

    def exec( mail )
      if mail._builtin_multipart?
      then @multipart_composer
      else @singlepart_composer end.exec mail
    end
  
  end

  
  class MimeEncodeSingle

    def exec( mail )
      mail.mime_version = '1.0'
      b = mail.body
      if NKF.guess(b) != NKF::BINARY
        on_text b
      else
        on_binary b
      end
    end

    def on_text( body )
      mail.body = NKF.nkf('-j -m0', body)
      mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
      mail.encoding = '7bit'
    end

    def on_binary( body )
      mail.body = [body].pack('m')
      mail.set_content_type 'application', 'octet-stream'
      mail.encoding = 'Base64'
    end
  
  end


  class MimeEncodeMulti

    def exec( mail, top = true )
      mail.mime_version = '1.0' if top
      mail.set_content_type 'multipart', 'mixed'
      e = encoding(nil)
      if e and not /\A(?:7bit|8bit|binary)\z/i === e
        raise ArgumentError,
              'using C.T.Encoding with multipart mail is not permitted'
      end
      mail.parts.each do |m|
        exec m, false if m._builtin_multipart?
      end
    end

  end

end   # module TMail
#
# obsolete.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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

  # mail.rb
  class Mail
    alias include? key?
    alias has_key? key?

    def values
      ret = []
      each_field {|v| ret.push v }
      ret
    end

    def value?( val )
      HeaderField === val or return false

      [ @header[val.name.downcase] ].flatten.include? val
    end

    alias has_value? value?
  end


  # facade.rb
  class Mail
    def from_addr( default = nil )
      addr, = from_addrs(nil)
      addr || default
    end

    def from_address( default = nil )
      if a = from_addr(nil)
        a.spec
      else
        default
      end
    end

    alias from_address= from_addrs=

    def from_phrase( default = nil )
      if a = from_addr(nil)
        a.phrase
      else
        default
      end
    end

    alias msgid  message_id
    alias msgid= message_id=

    alias each_dest each_destination
  end


  # address.rb
  class Address
    alias route routes
    alias addr spec

    def spec=( str )
      @local, @domain = str.split(/@/,2).map {|s| s.split(/\./) }
    end

    alias addr= spec=
    alias address= spec=
  end


  # mbox.rb
  class MhMailbox
    alias new_mail new_port
    alias each_mail each_port
    alias each_newmail each_new_port
  end
  class UNIXMbox
    alias new_mail new_port
    alias each_mail each_port
    alias each_newmail each_new_port
  end
  class Maildir
    alias new_mail new_port
    alias each_mail each_port
    alias each_newmail each_new_port
  end


  # utils.rb
  extend TextUtils

  class << self
    alias msgid?    message_id?
    alias boundary  new_boundary
    alias msgid     new_message_id
    alias new_msgid new_message_id
  end

  def Mail.boundary
    ::TMail.new_boundary
  end

  def Mail.msgid
    ::TMail.new_message_id
  end

end   # module TMail
#
# DO NOT MODIFY!!!!
# This file is automatically generated by racc 1.4.3
# from racc grammer file "parser.y".
#
#
# parser.rb: generated by racc (runtime embedded)
#

###### racc/parser.rb

unless $".index 'racc/parser.rb'
$".push 'racc/parser.rb'

self.class.module_eval <<'..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d', '/home/aamine/lib/ruby/racc/parser.rb', 1
#
# parser.rb
#
#   Copyright (c) 1999-2003 Minero Aoki <aamine@loveruby.net>
#
#   This program is free software.
#   You can distribute/modify this program under the same terms of ruby.
#
#   As a special exception, when this code is copied by Racc
#   into a Racc output file, you may use that output file
#   without restriction.
#
#   $Id: parser.rb,v 1.1.1.1 2004/10/14 11:59:58 webster132 Exp $
#

unless defined? NotImplementedError
  NotImplementedError = NotImplementError
end


module Racc
  class ParseError < StandardError; end
end
unless defined?(::ParseError)
  ParseError = Racc::ParseError
end


module Racc

  unless defined? Racc_No_Extentions
    Racc_No_Extentions = false
  end

  class Parser

    Racc_Runtime_Version = '1.4.3'
    Racc_Runtime_Revision = '$Revision: 1.1.1.1 $'.split(/\s+/)[1]

    Racc_Runtime_Core_Version_R = '1.4.3'
    Racc_Runtime_Core_Revision_R = '$Revision: 1.1.1.1 $'.split(/\s+/)[1]
    begin
      require 'racc/cparse'
    # Racc_Runtime_Core_Version_C  = (defined in extention)
      Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split(/\s+/)[2]
      unless new.respond_to?(:_racc_do_parse_c, true)
        raise LoadError, 'old cparse.so'
      end
      if Racc_No_Extentions
        raise LoadError, 'selecting ruby version of racc runtime core'
      end

      Racc_Main_Parsing_Routine    = :_racc_do_parse_c
      Racc_YY_Parse_Method         = :_racc_yyparse_c
      Racc_Runtime_Core_Version    = Racc_Runtime_Core_Version_C
      Racc_Runtime_Core_Revision   = Racc_Runtime_Core_Revision_C
      Racc_Runtime_Type            = 'c'
    rescue LoadError
      Racc_Main_Parsing_Routine    = :_racc_do_parse_rb
      Racc_YY_Parse_Method         = :_racc_yyparse_rb
      Racc_Runtime_Core_Version    = Racc_Runtime_Core_Version_R
      Racc_Runtime_Core_Revision   = Racc_Runtime_Core_Revision_R
      Racc_Runtime_Type            = 'ruby'
    end

    def self.racc_runtime_type
      Racc_Runtime_Type
    end

    private

    def _racc_setup
      @yydebug = false unless self.class::Racc_debug_parser
      @yydebug = false unless defined? @yydebug
      if @yydebug
        @racc_debug_out = $stderr unless defined? @racc_debug_out
        @racc_debug_out ||= $stderr
      end
      arg = self.class::Racc_arg
      arg[13] = true if arg.size < 14
      arg
    end

    def _racc_init_sysvars
      @racc_state  = [0]
      @racc_tstack = []
      @racc_vstack = []

      @racc_t = nil
      @racc_val = nil

      @racc_read_next = true

      @racc_user_yyerror = false
      @racc_error_status = 0
    end


    ###
    ### do_parse
    ###

    def do_parse
      __send__ Racc_Main_Parsing_Routine, _racc_setup(), false
    end

    def next_token
      raise NotImplementedError, "#{self.class}\#next_token is not defined"
    end

    def _racc_do_parse_rb( arg, in_debug )
      action_table, action_check, action_default, action_pointer,
      goto_table,   goto_check,   goto_default,   goto_pointer,
      nt_base,      reduce_table, token_table,    shift_n,
      reduce_n,     use_result,   * = arg

      _racc_init_sysvars
      tok = act = i = nil
      nerr = 0

      catch(:racc_end_parse) {
          while true
            if i = action_pointer[@racc_state[-1]]
              if @racc_read_next
                if @racc_t != 0   # not EOF
                  tok, @racc_val = next_token()
                  unless tok      # EOF
                    @racc_t = 0
                  else
                    @racc_t = (token_table[tok] or 1)   # error token
                  end
                  racc_read_token(@racc_t, tok, @racc_val) if @yydebug
                  @racc_read_next = false
                end
              end
              i += @racc_t
              if  i >= 0 and
                  act = action_table[i] and
                  action_check[i] == @racc_state[-1]
                ;
              else
                act = action_default[@racc_state[-1]]
              end
            else
              act = action_default[@racc_state[-1]]
            end
            while act = _racc_evalact(act, arg)
            end
          end
      }
    end


    ###
    ### yyparse
    ###

    def yyparse( recv, mid )
      __send__ Racc_YY_Parse_Method, recv, mid, _racc_setup(), true
    end

    def _racc_yyparse_rb( recv, mid, arg, c_debug )
      action_table, action_check, action_default, action_pointer,
      goto_table,   goto_check,   goto_default,   goto_pointer,
      nt_base,      reduce_table, token_table,    shift_n,
      reduce_n,     use_result,   * = arg

      _racc_init_sysvars
      tok = nil
      act = nil
      i = nil
      nerr = 0


      catch(:racc_end_parse) {
        until i = action_pointer[@racc_state[-1]]
          while act = _racc_evalact(action_default[@racc_state[-1]], arg)
          end
        end

        recv.__send__(mid) do |tok, val|
# $stderr.puts "rd: tok=#{tok}, val=#{val}"
          unless tok
            @racc_t = 0
          else
            @racc_t = (token_table[tok] or 1)   # error token
          end
          @racc_val = val
          @racc_read_next = false

          i += @racc_t
          if  i >= 0 and
              act = action_table[i] and
              action_check[i] == @racc_state[-1]
            ;
# $stderr.puts "01: act=#{act}"
          else
            act = action_default[@racc_state[-1]]
# $stderr.puts "02: act=#{act}"
# $stderr.puts "curstate=#{@racc_state[-1]}"
          end

          while act = _racc_evalact(act, arg)
          end

          while not (i = action_pointer[@racc_state[-1]]) or
                not @racc_read_next or
                @racc_t == 0   # $
            if i and i += @racc_t and
               i >= 0 and
               act = action_table[i] and
               action_check[i] == @racc_state[-1]
              ;
# $stderr.puts "03: act=#{act}"
            else
# $stderr.puts "04: act=#{act}"
              act = action_default[@racc_state[-1]]
            end

            while act = _racc_evalact(act, arg)
            end
          end
        end
      }
    end


    ###
    ### common
    ###

    def _racc_evalact( act, arg )
# $stderr.puts "ea: act=#{act}"
      action_table, action_check, action_default, action_pointer,
      goto_table,   goto_check,   goto_default,   goto_pointer,
      nt_base,      reduce_table, token_table,    shift_n,
      reduce_n,     use_result,   * = arg
nerr = 0   # tmp

      if act > 0 and act < shift_n
        #
        # shift
        #

        if @racc_error_status > 0
          @racc_error_status -= 1 unless @racc_t == 1   # error token
        end

        @racc_vstack.push @racc_val
        @racc_state.push act
        @racc_read_next = true

        if @yydebug
          @racc_tstack.push @racc_t
          racc_shift @racc_t, @racc_tstack, @racc_vstack
        end

      elsif act < 0 and act > -reduce_n
        #
        # reduce
        #

        code = catch(:racc_jump) {
            @racc_state.push _racc_do_reduce(arg, act)
            false
        }
        if code
          case code
          when 1 # yyerror
            @racc_user_yyerror = true   # user_yyerror
            return -reduce_n
          when 2 # yyaccept
            return shift_n
          else
            raise RuntimeError, '[Racc Bug] unknown jump code'
          end
        end

      elsif act == shift_n
        #
        # accept
        #

        racc_accept if @yydebug
        throw :racc_end_parse, @racc_vstack[0]

      elsif act == -reduce_n
        #
        # error
        #

        case @racc_error_status
        when 0
          unless arg[21]    # user_yyerror
            nerr += 1
            on_error @racc_t, @racc_val, @racc_vstack
          end
        when 3
          if @racc_t == 0   # is $
            throw :racc_end_parse, nil
          end
          @racc_read_next = true
        end
        @racc_user_yyerror = false
        @racc_error_status = 3

        while true
          if i = action_pointer[@racc_state[-1]]
            i += 1   # error token
            if  i >= 0 and
                (act = action_table[i]) and
                action_check[i] == @racc_state[-1]
              break
            end
          end

          throw :racc_end_parse, nil if @racc_state.size < 2
          @racc_state.pop
          @racc_vstack.pop
          if @yydebug
            @racc_tstack.pop
            racc_e_pop @racc_state, @racc_tstack, @racc_vstack
          end
        end

        return act

      else
        raise RuntimeError, "[Racc Bug] unknown action #{act.inspect}"
      end

      racc_next_state(@racc_state[-1], @racc_state) if @yydebug

      nil
    end

    def _racc_do_reduce( arg, act )
      action_table, action_check, action_default, action_pointer,
      goto_table,   goto_check,   goto_default,   goto_pointer,
      nt_base,      reduce_table, token_table,    shift_n,
      reduce_n,     use_result,   * = arg
      state = @racc_state
      vstack = @racc_vstack
      tstack = @racc_tstack

      i = act * -3
      len       = reduce_table[i]
      reduce_to = reduce_table[i+1]
      method_id = reduce_table[i+2]
      void_array = []

      tmp_t = tstack[-len, len] if @yydebug
      tmp_v = vstack[-len, len]
      tstack[-len, len] = void_array if @yydebug
      vstack[-len, len] = void_array
      state[-len, len]  = void_array

      # tstack must be updated AFTER method call
      if use_result
        vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
      else
        vstack.push __send__(method_id, tmp_v, vstack)
      end
      tstack.push reduce_to

      racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug

      k1 = reduce_to - nt_base
      if i = goto_pointer[k1]
        i += state[-1]
        if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
          return curstate
        end
      end
      goto_default[k1]
    end

    def on_error( t, val, vstack )
      raise ParseError, sprintf("\nparse error on value %s (%s)",
                                val.inspect, token_to_str(t) || '?')
    end

    def yyerror
      throw :racc_jump, 1
    end

    def yyaccept
      throw :racc_jump, 2
    end

    def yyerrok
      @racc_error_status = 0
    end


    # for debugging output

    def racc_read_token( t, tok, val )
      @racc_debug_out.print 'read    '
      @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') '
      @racc_debug_out.puts val.inspect
      @racc_debug_out.puts
    end

    def racc_shift( tok, tstack, vstack )
      @racc_debug_out.puts "shift   #{racc_token2str tok}"
      racc_print_stacks tstack, vstack
      @racc_debug_out.puts
    end

    def racc_reduce( toks, sim, tstack, vstack )
      out = @racc_debug_out
      out.print 'reduce '
      if toks.empty?
        out.print ' <none>'
      else
        toks.each {|t| out.print ' ', racc_token2str(t) }
      end
      out.puts " --> #{racc_token2str(sim)}"
          
      racc_print_stacks tstack, vstack
      @racc_debug_out.puts
    end

    def racc_accept
      @racc_debug_out.puts 'accept'
      @racc_debug_out.puts
    end

    def racc_e_pop( state, tstack, vstack )
      @racc_debug_out.puts 'error recovering mode: pop token'
      racc_print_states state
      racc_print_stacks tstack, vstack
      @racc_debug_out.puts
    end

    def racc_next_state( curstate, state )
      @racc_debug_out.puts  "goto    #{curstate}"
      racc_print_states state
      @racc_debug_out.puts
    end

    def racc_print_stacks( t, v )
      out = @racc_debug_out
      out.print '        ['
      t.each_index do |i|
        out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')'
      end
      out.puts ' ]'
    end

    def racc_print_states( s )
      out = @racc_debug_out
      out.print '        ['
      s.each {|st| out.print ' ', st }
      out.puts ' ]'
    end

    def racc_token2str( tok )
      self.class::Racc_token_to_s_table[tok] or
          raise RuntimeError, "[Racc Bug] can't convert token #{tok} to string"
    end

    def token_to_str( t )
      self.class::Racc_token_to_s_table[t]
    end

  end

end
..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d
end   # end of racc/parser.rb


#
# parser.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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/scanner'
require 'tmail/utils'


module TMail

  class Parser < Racc::Parser

module_eval <<'..end parser.y modeval..id43721faf1c', 'parser.y', 331

  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

..end parser.y modeval..id43721faf1c

##### racc 1.4.3 generates ###

racc_reduce_table = [
 0, 0, :racc_error,
 2, 35, :_reduce_1,
 2, 35, :_reduce_2,
 2, 35, :_reduce_3,
 2, 35, :_reduce_4,
 2, 35, :_reduce_5,
 2, 35, :_reduce_6,
 2, 35, :_reduce_7,
 2, 35, :_reduce_8,
 2, 35, :_reduce_9,
 2, 35, :_reduce_10,
 2, 35, :_reduce_11,
 2, 35, :_reduce_12,
 6, 36, :_reduce_13,
 0, 48, :_reduce_none,
 2, 48, :_reduce_none,
 3, 49, :_reduce_16,
 5, 49, :_reduce_17,
 1, 50, :_reduce_18,
 7, 37, :_reduce_19,
 0, 51, :_reduce_none,
 2, 51, :_reduce_21,
 0, 52, :_reduce_none,
 2, 52, :_reduce_23,
 1, 58, :_reduce_24,
 3, 58, :_reduce_25,
 2, 58, :_reduce_26,
 0, 53, :_reduce_none,
 2, 53, :_reduce_28,
 0, 54, :_reduce_29,
 3, 54, :_reduce_30,
 0, 55, :_reduce_none,
 2, 55, :_reduce_32,
 2, 55, :_reduce_33,
 0, 56, :_reduce_none,
 2, 56, :_reduce_35,
 1, 61, :_reduce_36,
 1, 61, :_reduce_37,
 0, 57, :_reduce_none,
 2, 57, :_reduce_39,
 1, 38, :_reduce_none,
 1, 38, :_reduce_none,
 3, 38, :_reduce_none,
 1, 46, :_reduce_none,
 1, 46, :_reduce_none,
 1, 46, :_reduce_none,
 1, 39, :_reduce_none,
 2, 39, :_reduce_47,
 1, 64, :_reduce_48,
 3, 64, :_reduce_49,
 1, 68, :_reduce_none,
 1, 68, :_reduce_none,
 1, 69, :_reduce_52,
 3, 69, :_reduce_53,
 1, 47, :_reduce_none,
 1, 47, :_reduce_none,
 2, 47, :_reduce_56,
 2, 67, :_reduce_none,
 3, 65, :_reduce_58,
 2, 65, :_reduce_59,
 1, 70, :_reduce_60,
 2, 70, :_reduce_61,
 4, 62, :_reduce_62,
 3, 62, :_reduce_63,
 2, 72, :_reduce_none,
 2, 73, :_reduce_65,
 4, 73, :_reduce_66,
 3, 63, :_reduce_67,
 1, 63, :_reduce_68,
 1, 74, :_reduce_none,
 2, 74, :_reduce_70,
 1, 71, :_reduce_71,
 3, 71, :_reduce_72,
 1, 59, :_reduce_73,
 3, 59, :_reduce_74,
 1, 76, :_reduce_75,
 2, 76, :_reduce_76,
 1, 75, :_reduce_none,
 1, 75, :_reduce_none,
 1, 75, :_reduce_none,
 1, 77, :_reduce_none,
 1, 77, :_reduce_none,
 1, 77, :_reduce_none,
 1, 66, :_reduce_none,
 2, 66, :_reduce_none,
 3, 60, :_reduce_85,
 1, 40, :_reduce_86,
 3, 40, :_reduce_87,
 1, 79, :_reduce_none,
 2, 79, :_reduce_89,
 1, 41, :_reduce_90,
 2, 41, :_reduce_91,
 3, 42, :_reduce_92,
 5, 43, :_reduce_93,
 3, 43, :_reduce_94,
 0, 80, :_reduce_95,
 5, 80, :_reduce_96,
 1, 82, :_reduce_none,
 1, 82, :_reduce_none,
 1, 44, :_reduce_99,
 3, 45, :_reduce_100,
 0, 81, :_reduce_none,
 1, 81, :_reduce_none,
 1, 78, :_reduce_none,
 1, 78, :_reduce_none,
 1, 78, :_reduce_none,
 1, 78, :_reduce_none,
 1, 78, :_reduce_none,
 1, 78, :_reduce_none,
 1, 78, :_reduce_none ]

racc_reduce_n = 110

racc_shift_n = 168

racc_action_table = [
   -70,   -69,    23,    25,   146,   147,    29,    31,   105,   106,
    16,    17,    20,    22,   136,    27,   -70,   -69,    32,   101,
   -70,   -69,   154,   100,   113,   115,   -70,   -69,   -70,   109,
    75,    23,    25,   101,   155,    29,    31,   142,   143,    16,
    17,    20,    22,   107,    27,    23,    25,    32,    98,    29,
    31,    96,    94,    16,    17,    20,    22,    78,    27,    23,
    25,    32,   112,    29,    31,    74,    91,    16,    17,    20,
    22,    88,   117,    92,    81,    32,    23,    25,    80,   123,
    29,    31,   100,   125,    16,    17,    20,    22,   126,    23,
    25,   109,    32,    29,    31,    91,   128,    16,    17,    20,
    22,   129,    27,    23,    25,    32,   101,    29,    31,   101,
   130,    16,    17,    20,    22,    79,    52,    23,    25,    32,
    78,    29,    31,   133,    78,    16,    17,    20,    22,    77,
    23,    25,    75,    32,    29,    31,    65,    62,    16,    17,
    20,    22,   139,    23,    25,   101,    32,    29,    31,    60,
   100,    16,    17,    20,    22,    44,    27,   101,   148,    32,
    23,    25,   120,   149,    29,    31,   152,   153,    16,    17,
    20,    22,    42,    27,   157,   159,    32,    23,    25,   120,
    40,    29,    31,    15,   164,    16,    17,    20,    22,    40,
    27,    23,    25,    32,    68,    29,    31,   166,   167,    16,
    17,    20,    22,   nil,    27,    23,    25,    32,   nil,    29,
    31,    74,   nil,    16,    17,    20,    22,   nil,    23,    25,
   nil,    32,    29,    31,   nil,   nil,    16,    17,    20,    22,
   nil,    23,    25,   nil,    32,    29,    31,   nil,   nil,    16,
    17,    20,    22,   nil,    23,    25,   nil,    32,    29,    31,
   nil,   nil,    16,    17,    20,    22,   nil,    23,    25,   nil,
    32,    29,    31,   nil,   nil,    16,    17,    20,    22,   nil,
    27,    23,    25,    32,   nil,    29,    31,   nil,   nil,    16,
    17,    20,    22,   nil,    23,    25,   nil,    32,    29,    31,
   nil,   nil,    16,    17,    20,    22,   nil,    23,    25,   nil,
    32,    29,    31,   nil,   nil,    16,    17,    20,    22,   nil,
    84,    25,   nil,    32,    29,    31,   nil,    87,    16,    17,
    20,    22,     4,     6,     7,     8,     9,    10,    11,    12,
    13,     1,     2,     3,    84,    25,   nil,   nil,    29,    31,
   nil,    87,    16,    17,    20,    22,    84,    25,   nil,   nil,
    29,    31,   nil,    87,    16,    17,    20,    22,    84,    25,
   nil,   nil,    29,    31,   nil,    87,    16,    17,    20,    22,
    84,    25,   nil,   nil,    29,    31,   nil,    87,    16,    17,
    20,    22,    84,    25,   nil,   nil,    29,    31,   nil,    87,
    16,    17,    20,    22,    84,    25,   nil,   nil,    29,    31,
   nil,    87,    16,    17,    20,    22 ]

racc_action_check = [
    75,    28,    68,    68,   136,   136,    68,    68,    72,    72,
    68,    68,    68,    68,   126,    68,    75,    28,    68,    67,
    75,    28,   143,    66,    86,    86,    75,    28,    75,    75,
    28,     3,     3,    86,   143,     3,     3,   134,   134,     3,
     3,     3,     3,    73,     3,   152,   152,     3,    62,   152,
   152,    60,    56,   152,   152,   152,   152,    51,   152,    52,
    52,   152,    80,    52,    52,    52,    50,    52,    52,    52,
    52,    45,    89,    52,    42,    52,    71,    71,    41,    96,
    71,    71,    97,    98,    71,    71,    71,    71,   100,     7,
     7,   101,    71,     7,     7,   102,   104,     7,     7,     7,
     7,   105,     7,     8,     8,     7,   108,     8,     8,   111,
   112,     8,     8,     8,     8,    40,     8,     9,     9,     8,
    36,     9,     9,   117,   121,     9,     9,     9,     9,    33,
    10,    10,    70,     9,    10,    10,    13,    12,    10,    10,
    10,    10,   130,     2,     2,   131,    10,     2,     2,    11,
   135,     2,     2,     2,     2,     6,     2,   138,   139,     2,
    90,    90,    90,   140,    90,    90,   141,   142,    90,    90,
    90,    90,     5,    90,   148,   151,    90,   127,   127,   127,
     4,   127,   127,     1,   157,   127,   127,   127,   127,   159,
   127,    26,    26,   127,    26,    26,    26,   163,   164,    26,
    26,    26,    26,   nil,    26,    27,    27,    26,   nil,    27,
    27,    27,   nil,    27,    27,    27,    27,   nil,   155,   155,
   nil,    27,   155,   155,   nil,   nil,   155,   155,   155,   155,
   nil,   122,   122,   nil,   155,   122,   122,   nil,   nil,   122,
   122,   122,   122,   nil,    76,    76,   nil,   122,    76,    76,
   nil,   nil,    76,    76,    76,    76,   nil,    38,    38,   nil,
    76,    38,    38,   nil,   nil,    38,    38,    38,    38,   nil,
    38,    55,    55,    38,   nil,    55,    55,   nil,   nil,    55,
    55,    55,    55,   nil,    94,    94,   nil,    55,    94,    94,
   nil,   nil,    94,    94,    94,    94,   nil,    59,    59,   nil,
    94,    59,    59,   nil,   nil,    59,    59,    59,    59,   nil,
   114,   114,   nil,    59,   114,   114,   nil,   114,   114,   114,
   114,   114,     0,     0,     0,     0,     0,     0,     0,     0,
     0,     0,     0,     0,    77,    77,   nil,   nil,    77,    77,
   nil,    77,    77,    77,    77,    77,    44,    44,   nil,   nil,
    44,    44,   nil,    44,    44,    44,    44,    44,   113,   113,
   nil,   nil,   113,   113,   nil,   113,   113,   113,   113,   113,
    88,    88,   nil,   nil,    88,    88,   nil,    88,    88,    88,
    88,    88,    74,    74,   nil,   nil,    74,    74,   nil,    74,
    74,    74,    74,    74,   129,   129,   nil,   nil,   129,   129,
   nil,   129,   129,   129,   129,   129 ]

racc_action_pointer = [
   320,   152,   129,    17,   165,   172,   137,    75,    89,   103,
   116,   135,   106,   105,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,   nil,   177,   191,     1,   nil,
   nil,   nil,   nil,   109,   nil,   nil,    94,   nil,   243,   nil,
    99,    64,    74,   nil,   332,    52,   nil,   nil,   nil,   nil,
    50,    31,    45,   nil,   nil,   257,    36,   nil,   nil,   283,
    22,   nil,    16,   nil,   nil,   nil,    -3,   -10,   -12,   nil,
   103,    62,    -8,    15,   368,     0,   230,   320,   nil,   nil,
    47,   nil,   nil,   nil,   nil,   nil,     4,   nil,   356,    50,
   146,   nil,   nil,   nil,   270,   nil,    65,    56,    52,   nil,
    57,    62,    79,   nil,    68,    81,   nil,   nil,    77,   nil,
   nil,    80,    96,   344,   296,   nil,   nil,   108,   nil,   nil,
   nil,    98,   217,   nil,   nil,   nil,   -19,   163,   nil,   380,
   128,   116,   nil,   nil,    14,   124,   -26,   nil,   128,   141,
   148,   141,   152,     7,   nil,   nil,   nil,   nil,   160,   nil,
   nil,   149,    31,   nil,   nil,   204,   nil,   167,   nil,   174,
   nil,   nil,   nil,   169,   184,   nil,   nil,   nil ]

racc_action_default = [
  -110,  -110,  -110,  -110,   -14,  -110,   -20,  -110,  -110,  -110,
  -110,  -110,  -110,  -110,   -10,   -95,  -106,  -107,   -77,   -44,
  -108,   -11,  -109,   -79,   -43,  -103,  -110,  -110,   -60,  -104,
   -55,  -105,   -78,   -68,   -54,   -71,   -45,   -12,  -110,    -1,
  -110,  -110,  -110,    -2,  -110,   -22,   -51,   -48,   -50,    -3,
   -40,   -41,  -110,   -46,    -4,   -86,    -5,   -88,    -6,   -90,
  -110,    -7,   -95,    -8,    -9,   -99,  -101,   -61,   -59,   -56,
   -69,  -110,  -110,  -110,  -110,   -75,  -110,  -110,   -57,   -15,
  -110,   168,   -73,   -80,   -82,   -21,   -24,   -81,  -110,   -27,
  -110,   -83,   -47,   -89,  -110,   -91,  -110,  -101,  -110,  -100,
  -102,   -75,   -58,   -52,  -110,  -110,   -64,   -63,   -65,   -76,
   -72,   -67,  -110,  -110,  -110,   -26,   -23,  -110,   -29,   -49,
   -84,   -42,   -87,   -92,   -94,   -95,  -110,  -110,   -62,  -110,
  -110,   -25,   -74,   -28,   -31,  -101,  -110,   -53,   -66,  -110,
  -110,   -34,  -110,  -110,   -93,   -96,   -98,   -97,  -110,   -18,
   -13,   -38,  -110,   -30,   -33,  -110,   -32,   -16,   -19,   -14,
   -35,   -36,   -37,  -110,  -110,   -39,   -85,   -17 ]

racc_goto_table = [
    39,    67,    70,    73,    24,    37,    69,    66,    36,    38,
    57,    59,    55,    67,   108,    83,    90,   111,    69,    99,
    85,    49,    53,    76,   158,   134,   141,    70,    73,   151,
   118,    89,    45,   156,   160,   150,   140,    21,    14,    19,
   119,   102,    64,    63,    61,    83,    70,   104,    83,    58,
   124,   132,    56,   131,    97,    54,    93,    43,     5,    83,
    95,   145,    76,   nil,   116,    76,   nil,   nil,   127,   138,
   103,   nil,   nil,   nil,    38,   nil,   nil,   110,   nil,   nil,
   nil,   nil,   nil,   nil,    83,    83,   nil,   nil,   144,   nil,
   nil,   nil,   nil,   nil,   nil,    57,   121,   122,   nil,   nil,
    83,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,   nil,   nil,   135,   nil,   nil,
   nil,   nil,   nil,    93,   nil,   nil,   nil,    70,   162,   137,
    70,   163,   161,    38,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,   165 ]

racc_goto_check = [
     2,    37,    37,    29,    13,    13,    28,    46,    31,    36,
    41,    41,    45,    37,    25,    44,    32,    25,    28,    47,
    24,     4,     4,    42,    23,    20,    21,    37,    29,    22,
    19,    18,    17,    26,    27,    16,    15,    12,    11,    33,
    34,    35,    10,     9,     8,    44,    37,    29,    44,     7,
    47,    43,     6,    25,    46,     5,    41,     3,     1,    44,
    41,    48,    42,   nil,    24,    42,   nil,   nil,    32,    25,
    13,   nil,   nil,   nil,    36,   nil,   nil,    41,   nil,   nil,
   nil,   nil,   nil,   nil,    44,    44,   nil,   nil,    47,   nil,
   nil,   nil,   nil,   nil,   nil,    41,    31,    45,   nil,   nil,
    44,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,   nil,   nil,    46,   nil,   nil,
   nil,   nil,   nil,    41,   nil,   nil,   nil,    37,    29,    13,
    37,    29,    28,    36,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,     2 ]

racc_goto_pointer = [
   nil,    58,    -4,    51,    14,    47,    43,    39,    33,    31,
    29,    37,    35,     2,   nil,   -94,  -105,    26,   -14,   -59,
   -93,  -108,  -112,  -127,   -24,   -60,  -110,  -118,   -20,   -24,
   nil,     6,   -34,    37,   -50,   -27,     6,   -25,   nil,   nil,
   nil,     1,    -5,   -63,   -29,     3,    -8,   -47,   -75 ]

racc_goto_default = [
   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,    48,    41,   nil,   nil,   nil,   nil,   nil,
   nil,   nil,   nil,   nil,   nil,    86,   nil,   nil,    30,    34,
    50,    51,   nil,    46,    47,   nil,    26,    28,    71,    72,
    33,    35,   114,    82,    18,   nil,   nil,   nil,   nil ]

racc_token_table = {
 false => 0,
 Object.new => 1,
 :DATETIME => 2,
 :RECEIVED => 3,
 :MADDRESS => 4,
 :RETPATH => 5,
 :KEYWORDS => 6,
 :ENCRYPTED => 7,
 :MIMEVERSION => 8,
 :CTYPE => 9,
 :CENCODING => 10,
 :CDISPOSITION => 11,
 :ADDRESS => 12,
 :MAILBOX => 13,
 :DIGIT => 14,
 :ATOM => 15,
 "," => 16,
 ":" => 17,
 :FROM => 18,
 :BY => 19,
 "@" => 20,
 :DOMLIT => 21,
 :VIA => 22,
 :WITH => 23,
 :ID => 24,
 :FOR => 25,
 ";" => 26,
 "<" => 27,
 ">" => 28,
 "." => 29,
 :QUOTED => 30,
 :TOKEN => 31,
 "/" => 32,
 "=" => 33 }

racc_use_result_var = false

racc_nt_base = 34

Racc_arg = [
 racc_action_table,
 racc_action_check,
 racc_action_default,
 racc_action_pointer,
 racc_goto_table,
 racc_goto_check,
 racc_goto_default,
 racc_goto_pointer,
 racc_nt_base,
 racc_reduce_table,
 racc_token_table,
 racc_shift_n,
 racc_reduce_n,
 racc_use_result_var ]

Racc_token_to_s_table = [
'$end',
'error',
'DATETIME',
'RECEIVED',
'MADDRESS',
'RETPATH',
'KEYWORDS',
'ENCRYPTED',
'MIMEVERSION',
'CTYPE',
'CENCODING',
'CDISPOSITION',
'ADDRESS',
'MAILBOX',
'DIGIT',
'ATOM',
'","',
'":"',
'FROM',
'BY',
'"@"',
'DOMLIT',
'VIA',
'WITH',
'ID',
'FOR',
'";"',
'"<"',
'">"',
'"."',
'QUOTED',
'TOKEN',
'"/"',
'"="',
'$start',
'content',
'datetime',
'received',
'addrs_TOP',
'retpath',
'keys',
'enc',
'version',
'ctype',
'cencode',
'cdisp',
'addr_TOP',
'mbox',
'day',
'hour',
'zone',
'from',
'by',
'via',
'with',
'id',
'for',
'received_datetime',
'received_domain',
'domain',
'msgid',
'received_addrspec',
'routeaddr',
'spec',
'addrs',
'group_bare',
'commas',
'group',
'addr',
'mboxes',
'addr_phrase',
'local_head',
'routes',
'at_domains',
'local',
'word',
'dots',
'domword',
'atom',
'phrase',
'params',
'opt_semicolon',
'value']

Racc_debug_parser = false

##### racc system variables end #####

 # reduce 0 omitted

module_eval <<'.,.,', 'parser.y', 16
  def _reduce_1( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 17
  def _reduce_2( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 18
  def _reduce_3( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 19
  def _reduce_4( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 20
  def _reduce_5( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 21
  def _reduce_6( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 22
  def _reduce_7( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 23
  def _reduce_8( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 24
  def _reduce_9( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 25
  def _reduce_10( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 26
  def _reduce_11( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 27
  def _reduce_12( val, _values)
 val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 33
  def _reduce_13( val, _values)
                  t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0)
                  (t + val[4] - val[5]).localtime
  end
.,.,

 # reduce 14 omitted

 # reduce 15 omitted

module_eval <<'.,.,', 'parser.y', 42
  def _reduce_16( val, _values)
                  (val[0].to_i * 60 * 60) +
                  (val[2].to_i * 60)
  end
.,.,

module_eval <<'.,.,', 'parser.y', 47
  def _reduce_17( val, _values)
                  (val[0].to_i * 60 * 60) +
                  (val[2].to_i * 60) +
                  (val[4].to_i)
  end
.,.,

module_eval <<'.,.,', 'parser.y', 54
  def _reduce_18( val, _values)
                  timezone_string_to_unixtime(val[0])
  end
.,.,

module_eval <<'.,.,', 'parser.y', 59
  def _reduce_19( val, _values)
                  val
  end
.,.,

 # reduce 20 omitted

module_eval <<'.,.,', 'parser.y', 65
  def _reduce_21( val, _values)
                  val[1]
  end
.,.,

 # reduce 22 omitted

module_eval <<'.,.,', 'parser.y', 71
  def _reduce_23( val, _values)
                  val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 77
  def _reduce_24( val, _values)
                  join_domain(val[0])
  end
.,.,

module_eval <<'.,.,', 'parser.y', 81
  def _reduce_25( val, _values)
                  join_domain(val[2])
  end
.,.,

module_eval <<'.,.,', 'parser.y', 85
  def _reduce_26( val, _values)
                  join_domain(val[0])
  end
.,.,

 # reduce 27 omitted

module_eval <<'.,.,', 'parser.y', 91
  def _reduce_28( val, _values)
                  val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 96
  def _reduce_29( val, _values)
                  []
  end
.,.,

module_eval <<'.,.,', 'parser.y', 100
  def _reduce_30( val, _values)
                  val[0].push val[2]
                  val[0]
  end
.,.,

 # reduce 31 omitted

module_eval <<'.,.,', 'parser.y', 107
  def _reduce_32( val, _values)
                  val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 111
  def _reduce_33( val, _values)
                  val[1]
  end
.,.,

 # reduce 34 omitted

module_eval <<'.,.,', 'parser.y', 117
  def _reduce_35( val, _values)
                  val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 123
  def _reduce_36( val, _values)
                  val[0].spec
  end
.,.,

module_eval <<'.,.,', 'parser.y', 127
  def _reduce_37( val, _values)
                  val[0].spec
  end
.,.,

 # reduce 38 omitted

module_eval <<'.,.,', 'parser.y', 134
  def _reduce_39( val, _values)
                  val[1]
  end
.,.,

 # reduce 40 omitted

 # reduce 41 omitted

 # reduce 42 omitted

 # reduce 43 omitted

 # reduce 44 omitted

 # reduce 45 omitted

 # reduce 46 omitted

module_eval <<'.,.,', 'parser.y', 146
  def _reduce_47( val, _values)
 [ Address.new(nil, nil) ]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 148
  def _reduce_48( val, _values)
 val
  end
.,.,

module_eval <<'.,.,', 'parser.y', 149
  def _reduce_49( val, _values)
 val[0].push val[2]; val[0]
  end
.,.,

 # reduce 50 omitted

 # reduce 51 omitted

module_eval <<'.,.,', 'parser.y', 156
  def _reduce_52( val, _values)
                  val
  end
.,.,

module_eval <<'.,.,', 'parser.y', 160
  def _reduce_53( val, _values)
                  val[0].push val[2]
                  val[0]
  end
.,.,

 # reduce 54 omitted

 # reduce 55 omitted

module_eval <<'.,.,', 'parser.y', 168
  def _reduce_56( val, _values)
                  val[1].phrase = Decoder.decode(val[0])
                  val[1]
  end
.,.,

 # reduce 57 omitted

module_eval <<'.,.,', 'parser.y', 176
  def _reduce_58( val, _values)
                  AddressGroup.new(val[0], val[2])
  end
.,.,

module_eval <<'.,.,', 'parser.y', 178
  def _reduce_59( val, _values)
 AddressGroup.new(val[0], [])
  end
.,.,

module_eval <<'.,.,', 'parser.y', 181
  def _reduce_60( val, _values)
 val[0].join('.')
  end
.,.,

module_eval <<'.,.,', 'parser.y', 182
  def _reduce_61( val, _values)
 val[0] << ' ' << val[1].join('.')
  end
.,.,

module_eval <<'.,.,', 'parser.y', 186
  def _reduce_62( val, _values)
                  val[2].routes.replace val[1]
                  val[2]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 191
  def _reduce_63( val, _values)
                  val[1]
  end
.,.,

 # reduce 64 omitted

module_eval <<'.,.,', 'parser.y', 196
  def _reduce_65( val, _values)
 [ val[1].join('.') ]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 197
  def _reduce_66( val, _values)
 val[0].push val[3].join('.'); val[0]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 199
  def _reduce_67( val, _values)
 Address.new( val[0], val[2] )
  end
.,.,

module_eval <<'.,.,', 'parser.y', 200
  def _reduce_68( val, _values)
 Address.new( val[0], nil )
  end
.,.,

 # reduce 69 omitted

module_eval <<'.,.,', 'parser.y', 203
  def _reduce_70( val, _values)
 val[0].push ''; val[0]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 206
  def _reduce_71( val, _values)
 val
  end
.,.,

module_eval <<'.,.,', 'parser.y', 209
  def _reduce_72( val, _values)
                  val[1].times do
                    val[0].push ''
                  end
                  val[0].push val[2]
                  val[0]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 217
  def _reduce_73( val, _values)
 val
  end
.,.,

module_eval <<'.,.,', 'parser.y', 220
  def _reduce_74( val, _values)
                  val[1].times do
                    val[0].push ''
                  end
                  val[0].push val[2]
                  val[0]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 227
  def _reduce_75( val, _values)
 0
  end
.,.,

module_eval <<'.,.,', 'parser.y', 228
  def _reduce_76( val, _values)
 1
  end
.,.,

 # reduce 77 omitted

 # reduce 78 omitted

 # reduce 79 omitted

 # reduce 80 omitted

 # reduce 81 omitted

 # reduce 82 omitted

 # reduce 83 omitted

 # reduce 84 omitted

module_eval <<'.,.,', 'parser.y', 243
  def _reduce_85( val, _values)
                  val[1] = val[1].spec
                  val.join('')
  end
.,.,

module_eval <<'.,.,', 'parser.y', 247
  def _reduce_86( val, _values)
 val
  end
.,.,

module_eval <<'.,.,', 'parser.y', 248
  def _reduce_87( val, _values)
 val[0].push val[2]; val[0]
  end
.,.,

 # reduce 88 omitted

module_eval <<'.,.,', 'parser.y', 251
  def _reduce_89( val, _values)
 val[0] << ' ' << val[1]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 255
  def _reduce_90( val, _values)
                  val.push nil
                  val
  end
.,.,

module_eval <<'.,.,', 'parser.y', 260
  def _reduce_91( val, _values)
                  val
  end
.,.,

module_eval <<'.,.,', 'parser.y', 265
  def _reduce_92( val, _values)
                  [ val[0].to_i, val[2].to_i ]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 270
  def _reduce_93( val, _values)
                  [ val[0].downcase, val[2].downcase, decode_params(val[3]) ]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 274
  def _reduce_94( val, _values)
                  [ val[0].downcase, nil, decode_params(val[1]) ]
  end
.,.,

module_eval <<'.,.,', 'parser.y', 279
  def _reduce_95( val, _values)
                  {}
  end
.,.,

module_eval <<'.,.,', 'parser.y', 283
  def _reduce_96( val, _values)
                  val[0][ val[2].downcase ] = val[4]
                  val[0]
  end
.,.,

 # reduce 97 omitted

 # reduce 98 omitted

module_eval <<'.,.,', 'parser.y', 292
  def _reduce_99( val, _values)
                  val[0].downcase
  end
.,.,

module_eval <<'.,.,', 'parser.y', 297
  def _reduce_100( val, _values)
                  [ val[0].downcase, decode_params(val[1]) ]
  end
.,.,

 # reduce 101 omitted

 # reduce 102 omitted

 # reduce 103 omitted

 # reduce 104 omitted

 # reduce 105 omitted

 # reduce 106 omitted

 # reduce 107 omitted

 # reduce 108 omitted

 # reduce 109 omitted

 def _reduce_none( val, _values)
  val[0]
 end

  end   # class Parser

end   # module TMail
#
# port.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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/stringio'


module TMail

  class Port
    def reproducible?
      false
    end
  end


  ###
  ### FilePort
  ###

  class FilePort < Port

    def initialize( fname )
      @filename = File.expand_path(fname)
      super()
    end

    attr_reader :filename

    alias ident filename

    def ==( other )
      other.respond_to?(:filename) and @filename == other.filename
    end

    alias eql? ==

    def hash
      @filename.hash
    end

    def inspect
      "#<#{self.class}:#{@filename}>"
    end

    def reproducible?
      true
    end

    def size
      File.size @filename
    end


    def ropen( &block )
      File.open(@filename, &block)
    end

    def wopen( &block )
      File.open(@filename, 'w', &block)
    end

    def aopen( &block )
      File.open(@filename, 'a', &block)
    end


    def read_all
      ropen {|f|
          return f.read
      }
    end


    def remove
      File.unlink @filename
    end

    def move_to( port )
      begin
        File.link @filename, port.filename
      rescue Errno::EXDEV
        copy_to port
      end
      File.unlink @filename
    end

    alias mv move_to

    def copy_to( port )
      if FilePort === port
        copy_file @filename, port.filename
      else
        File.open(@filename) {|r|
        port.wopen {|w|
            while s = r.sysread(4096)
              w.write << s
            end
        } }
      end
    end

    alias cp copy_to

    private

    # from fileutils.rb
    def copy_file( src, dest )
      st = r = w = nil

      File.open(src,  'rb') {|r|
      File.open(dest, 'wb') {|w|
          st = r.stat
          begin
            while true
              w.write r.sysread(st.blksize)
            end
          rescue EOFError
          end
      } }
    end

  end


  module MailFlags

    def seen=( b )
      set_status 'S', b
    end

    def seen?
      get_status 'S'
    end

    def replied=( b )
      set_status 'R', b
    end

    def replied?
      get_status 'R'
    end

    def flagged=( b )
      set_status 'F', b
    end

    def flagged?
      get_status 'F'
    end

    private

    def procinfostr( str, tag, true_p )
      a = str.upcase.split(//)
      a.push true_p ? tag : nil
      a.delete tag unless true_p
      a.compact.sort.join('').squeeze
    end
  
  end


  class MhPort < FilePort

    include MailFlags

    private
    
    def set_status( tag, flag )
      begin
        tmpfile = @filename + '.tmailtmp.' + $$.to_s
        File.open(tmpfile, 'w') {|f|
          write_status f, tag, flag
        }
        File.unlink @filename
        File.link tmpfile, @filename
      ensure
        File.unlink tmpfile
      end
    end

    def write_status( f, tag, flag )
      stat = ''
      File.open(@filename) {|r|
        while line = r.gets
          if line.strip.empty?
            break
          elsif m = /\AX-TMail-Status:/i.match(line)
            stat = m.post_match.strip
          else
            f.print line
          end
        end

        s = procinfostr(stat, tag, flag)
        f.puts 'X-TMail-Status: ' + s unless s.empty?
        f.puts

        while s = r.read(2048)
          f.write s
        end
      }
    end

    def get_status( tag )
      File.foreach(@filename) {|line|
        return false if line.strip.empty?
        if m = /\AX-TMail-Status:/i.match(line)
          return m.post_match.strip.include?(tag[0])
        end
      }
      false
    end
  
  end


  class MaildirPort < FilePort

    def move_to_new
      new = replace_dir(@filename, 'new')
      File.rename @filename, new
      @filename = new
    end

    def move_to_cur
      new = replace_dir(@filename, 'cur')
      File.rename @filename, new
      @filename = new
    end

    def replace_dir( path, dir )
      "#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
    end
    private :replace_dir


    include MailFlags

    private

    MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/

    def set_status( tag, flag )
      if m = MAIL_FILE.match(File.basename(@filename))
        s, uniq, type, info, = m.to_a
        return if type and type != '2'  # do not change anything
        newname = File.dirname(@filename) + '/' +
                  uniq + ':2,' + procinfostr(info.to_s, tag, flag)
      else
        newname = @filename + ':2,' + tag
      end

      File.link @filename, newname
      File.unlink @filename
      @filename = newname
    end

    def get_status( tag )
      m = MAIL_FILE.match(File.basename(@filename)) or return false
      m[2] == '2' and m[3].to_s.include?(tag[0])
    end
  
  end


  ###
  ###  StringPort
  ###

  class StringPort < Port

    def initialize( str = '' )
      @buffer = str
      super()
    end

    def string
      @buffer
    end

    def to_s
      @buffer.dup
    end

    alias read_all to_s

    def size
      @buffer.size
    end

    def ==( other )
      StringPort === other and @buffer.equal? other.string
    end

    alias eql? ==

    def hash
      @buffer.object_id.hash
    end

    def inspect
      "#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
    end

    def reproducible?
      true
    end

    def ropen( &block )
      @buffer or raise Errno::ENOENT, "#{inspect} is already removed"
      StringInput.open(@buffer, &block)
    end

    def wopen( &block )
      @buffer = ''
      StringOutput.new(@buffer, &block)
    end

    def aopen( &block )
      @buffer ||= ''
      StringOutput.new(@buffer, &block)
    end

    def remove
      @buffer = nil
    end

    alias rm remove

    def copy_to( port )
      port.wopen {|f|
          f.write @buffer
      }
    end

    alias cp copy_to

    def move_to( port )
      if StringPort === port
        str = @buffer
        port.instance_eval { @buffer = str }
      else
        copy_to port
      end
      remove
    end

  end

end   # module TMail
module TMail
  class Mail
    def subject(to_charset = 'utf-8')
      Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
    end

    def unquoted_body(to_charset = 'utf-8')
      from_charset = sub_header("content-type", "charset")
      case (content_transfer_encoding || "7bit").downcase
        when "quoted-printable"
          Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
            to_charset, from_charset, true)
        when "base64"
          Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
            from_charset)
        when "7bit", "8bit"
          Unquoter.convert_to(quoted_body, to_charset, from_charset)
        when "binary"
          quoted_body
        else
          quoted_body
      end
    end

    def body(to_charset = 'utf-8', &block)
      attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
    
      if multipart?
        parts.collect { |part| 
          header = part["content-type"]

          if part.multipart?
            part.body(to_charset, &attachment_presenter)
          elsif header.nil?
            ""
          elsif !attachment?(part)
            part.unquoted_body(to_charset)
          else
            attachment_presenter.call(header["name"] || "(unnamed)")
          end
        }.join
      else
        unquoted_body(to_charset)
      end
    end
  end

  class Unquoter
    class << self
      def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
        return "" if text.nil?
        if text =~ /^=\?(.*?)\?(.)\?(.*)\?=$/
          from_charset = $1
          quoting_method = $2
          text = $3
          case quoting_method.upcase
            when "Q" then
              unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
            when "B" then
              unquote_base64_and_convert_to(text, to_charset, from_charset)
            else
              raise "unknown quoting method #{quoting_method.inspect}"
          end
        else
          convert_to(text, to_charset, from_charset)
        end
      end
 
      def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
        text = text.gsub(/_/, " ") unless preserve_underscores
        convert_to(text.unpack("M*").first, to, from)
      end
 
      def unquote_base64_and_convert_to(text, to, from)
        convert_to(Base64.decode(text).first, to, from)
      end

      begin
        require 'iconv'
        def convert_to(text, to, from)
          return text unless to && from
          text ? Iconv.iconv(to, from, text).first : ""
        rescue Iconv::IllegalSequence, Errno::EINVAL
          # the 'from' parameter specifies a charset other than what the text
          # actually is...not much we can do in this case but just return the
          # unconverted text.
          #
          # Ditto if either parameter represents an unknown charset, like
          # X-UNKNOWN.
          text
        end
      rescue LoadError
        # Not providing quoting support
        def convert_to(text, to, from)
          warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
          text
        end
      end
    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
#
# scanner.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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
  require 'tmail/scanner_r.rb'
  begin
    raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
    require 'tmail/scanner_c.so'
    Scanner = Scanner_C
  rescue LoadError
    Scanner = Scanner_R
  end
end
#
# scanner_r.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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/config'


module TMail

  class Scanner_R

    Version = '0.10.7'
    Version.freeze

    MIME_HEADERS = {
      :CTYPE        => true,
      :CENCODING    => true,
      :CDISPOSITION => true
    }

    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])+'

    quoted_with_iso2022  = /\A(?:[^\\\e"]+|#{iso2022str})+/n
    domlit_with_iso2022  = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
    comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n

    quoted_without_iso2022  = /\A[^\\"]+/n
    domlit_without_iso2022  = /\A[^\\\]]+/n
    comment_without_iso2022 = /\A[^\\()]+/n

    PATTERN_TABLE = {}
    PATTERN_TABLE['EUC'] =
      [
        /\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
        /\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
        quoted_with_iso2022,
        domlit_with_iso2022,
        comment_with_iso2022
      ]
    PATTERN_TABLE['SJIS'] =
      [
        /\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
        /\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
        quoted_with_iso2022,
        domlit_with_iso2022,
        comment_with_iso2022
      ]
    PATTERN_TABLE['UTF8'] =
      [
        /\A(?:[#{atomchars}]+|#{utf8str})+/n,
        /\A(?:[#{tokenchars}]+|#{utf8str})+/n,
        quoted_without_iso2022,
        domlit_without_iso2022,
        comment_without_iso2022
      ]
    PATTERN_TABLE['NONE'] =
      [
        /\A[#{atomchars}]+/n,
        /\A[#{tokenchars}]+/n,
        quoted_without_iso2022,
        domlit_without_iso2022,
        comment_without_iso2022
      ]


    def initialize( str, scantype, comments )
      init_scanner str
      @comments = comments || []
      @debug    = false

      # fix scanner mode
      @received  = (scantype == :RECEIVED)
      @is_mime_header = MIME_HEADERS[scantype]

      atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
      @word_re = (MIME_HEADERS[scantype] ? token : atom)
    end

    attr_accessor :debug

    def scan( &block )
      if @debug
        scan_main do |arr|
          s, v = arr
          printf "%7d %-10s %s\n",
                 rest_size(),
                 s.respond_to?(:id2name) ? s.id2name : s.inspect,
                 v.inspect
          yield arr
        end
      else
        scan_main(&block)
      end
    end

    private

    RECV_TOKEN = {
      'from' => :FROM,
      'by'   => :BY,
      'via'  => :VIA,
      'with' => :WITH,
      'id'   => :ID,
      'for'  => :FOR
    }

    def scan_main
      until eof?
        if skip(/\A[\n\r\t ]+/n)   # LWSP
          break if eof?
        end

        if s = readstr(@word_re)
          if @is_mime_header
            yield :TOKEN, s
          else
            # atom
            if /\A\d+\z/ === s
              yield :DIGIT, s
            elsif @received
              yield RECV_TOKEN[s.downcase] || :ATOM, s
            else
              yield :ATOM, s
            end
          end

        elsif skip(/\A"/)
          yield :QUOTED, scan_quoted_word()

        elsif skip(/\A\[/)
          yield :DOMLIT, scan_domain_literal()

        elsif skip(/\A\(/)
          @comments.push scan_comment()

        else
          c = readchar()
          yield c, c
        end
      end

      yield false, '$'
    end

    def scan_quoted_word
      scan_qstr(@quoted_re, /\A"/, 'quoted-word')
    end

    def scan_domain_literal
      '[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
    end

    def scan_qstr( pattern, terminal, type )
      result = ''
      until eof?
        if    s = readstr(pattern) then result << s
        elsif skip(terminal)       then return result
        elsif skip(/\A\\/)         then result << readchar()
        else
          raise "TMail FATAL: not match in #{type}"
        end
      end
      scan_error! "found unterminated #{type}"
    end

    def scan_comment
      result = ''
      nest = 1
      content = @comment_re

      until eof?
        if s = readstr(content) then result << s
        elsif skip(/\A\)/)      then nest -= 1
                                     return result if nest == 0
                                     result << ')'
        elsif skip(/\A\(/)      then nest += 1
                                     result << '('
        elsif skip(/\A\\/)      then result << readchar()
        else
          raise 'TMail FATAL: not match in comment'
        end
      end
      scan_error! 'found unterminated comment'
    end

    # string scanner

    def init_scanner( str )
      @src = str
    end

    def eof?
      @src.empty?
    end

    def rest_size
      @src.size
    end

    def readstr( re )
      if m = re.match(@src)
        @src = m.post_match
        m[0]
      else
        nil
      end
    end

    def readchar
      readstr(/\A./)
    end

    def skip( re )
      if m = re.match(@src)
        @src = m.post_match
        true
      else
        false
      end
    end

    def scan_error!( msg )
      raise SyntaxError, msg
    end

  end

end   # module TMail
#
# stringio.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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.
#++

class StringInput#:nodoc:

  include Enumerable

  class << self

    def new( str )
      if block_given?
        begin
          f = super
          yield f
        ensure
          f.close if f
        end
      else
        super
      end
    end

    alias open new
  
  end

  def initialize( str )
    @src = str
    @pos = 0
    @closed = false
    @lineno = 0
  end

  attr_reader :lineno

  def string
    @src
  end

  def inspect
    "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
  end

  def close
    stream_check!
    @pos = nil
    @closed = true
  end

  def closed?
    @closed
  end

  def pos
    stream_check!
    [@pos, @src.size].min
  end

  alias tell pos

  def seek( offset, whence = IO::SEEK_SET )
    stream_check!
    case whence
    when IO::SEEK_SET
      @pos = offset
    when IO::SEEK_CUR
      @pos += offset
    when IO::SEEK_END
      @pos = @src.size - offset
    else
      raise ArgumentError, "unknown seek flag: #{whence}"
    end
    @pos = 0 if @pos < 0
    @pos = [@pos, @src.size + 1].min
    offset
  end

  def rewind
    stream_check!
    @pos = 0
  end

  def eof?
    stream_check!
    @pos > @src.size
  end

  def each( &block )
    stream_check!
    begin
      @src.each(&block)
    ensure
      @pos = 0
    end
  end

  def gets
    stream_check!
    if idx = @src.index(?\n, @pos)
      idx += 1  # "\n".size
      line = @src[ @pos ... idx ]
      @pos = idx
      @pos += 1 if @pos == @src.size
    else
      line = @src[ @pos .. -1 ]
      @pos = @src.size + 1
    end
    @lineno += 1

    line
  end

  def getc
    stream_check!
    ch = @src[@pos]
    @pos += 1
    @pos += 1 if @pos == @src.size
    ch
  end

  def read( len = nil )
    stream_check!
    return read_all unless len
    str = @src[@pos, len]
    @pos += len
    @pos += 1 if @pos == @src.size
    str
  end

  alias sysread read

  def read_all
    stream_check!
    return nil if eof?
    rest = @src[@pos ... @src.size]
    @pos = @src.size + 1
    rest
  end

  def stream_check!
    @closed and raise IOError, 'closed stream'
  end

end


class StringOutput#:nodoc:

  class << self

    def new( str = '' )
      if block_given?
        begin
          f = super
          yield f
        ensure
          f.close if f
        end
      else
        super
      end
    end

    alias open new
  
  end

  def initialize( str = '' )
    @dest = str
    @closed = false
  end

  def close
    @closed = true
  end

  def closed?
    @closed
  end

  def string
    @dest
  end

  alias value string
  alias to_str string

  def size
    @dest.size
  end

  alias pos size

  def inspect
    "#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
  end

  def print( *args )
    stream_check!
    raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
    args.each do |s|
      raise ArgumentError, 'nil not allowed' if s.nil?
      @dest << s.to_s
    end
    nil
  end

  def puts( *args )
    stream_check!
    args.each do |str|
      @dest << (s = str.to_s)
      @dest << "\n" unless s[-1] == ?\n
    end
    @dest << "\n" if args.empty?
    nil
  end

  def putc( ch )
    stream_check!
    @dest << ch.chr
    nil
  end

  def printf( *args )
    stream_check!
    @dest << sprintf(*args)
    nil
  end

  def write( str )
    stream_check!
    s = str.to_s
    @dest << s
    s.size
  end

  alias syswrite write

  def <<( str )
    stream_check!
    @dest << str.to_s
    self
  end

  private

  def stream_check!
    @closed and raise IOError, 'closed stream'
  end

end
require 'tmail'
#
# utils.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# 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

  class SyntaxError < StandardError; end


  def TMail.new_boundary
    'mimepart_' + random_tag
  end

  def TMail.new_message_id( fqdn = nil )
    fqdn ||= ::Socket.gethostname
    "<#{random_tag()}@#{fqdn}.tmail>"
  end

  def TMail.random_tag
    @uniq += 1
    t = Time.now
    sprintf('%x%x_%x%x%d%x',
            t.to_i, t.tv_usec,
            $$, Thread.current.object_id, @uniq, rand(255))
  end
  private_class_method :random_tag

  @uniq = 0


  module TextUtils

    aspecial     = '()<>[]:;.\\,"'
    tspecial     = '()<>[];:\\,"/?='
    lwsp         = " \t\r\n"
    control      = '\x00-\x1f\x7f-\xff'

    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

    def atom_safe?( str )
      not ATOM_UNSAFE === str
    end

    def quote_atom( str )
      (ATOM_UNSAFE === str) ? dquote(str) : str
    end

    def quote_phrase( str )
      (PHRASE_UNSAFE === str) ? dquote(str) : str
    end

    def token_safe?( str )
      not TOKEN_UNSAFE === str
    end

    def quote_token( str )
      (TOKEN_UNSAFE === str) ? dquote(str) : str
    end

    def dquote( str )
      '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
    end
    private :dquote


    def join_domain( arr )
      arr.map {|i|
          if /\A\[.*\]\z/ === i
            i
          else
            quote_atom(i)
          end
      }.join('.')
    end


    ZONESTR_TABLE = {
      'jst' =>   9 * 60,
      'eet' =>   2 * 60,
      'bst' =>   1 * 60,
      'met' =>   1 * 60,
      'gmt' =>   0,
      'utc' =>   0,
      'ut'  =>   0,
      'nst' => -(3 * 60 + 30),
      'ast' =>  -4 * 60,
      'edt' =>  -4 * 60,
      'est' =>  -5 * 60,
      'cdt' =>  -5 * 60,
      'cst' =>  -6 * 60,
      'mdt' =>  -6 * 60,
      'mst' =>  -7 * 60,
      'pdt' =>  -7 * 60,
      'pst' =>  -8 * 60,
      'a'   =>  -1 * 60,
      'b'   =>  -2 * 60,
      'c'   =>  -3 * 60,
      'd'   =>  -4 * 60,
      'e'   =>  -5 * 60,
      'f'   =>  -6 * 60,
      'g'   =>  -7 * 60,
      'h'   =>  -8 * 60,
      'i'   =>  -9 * 60,
      # j not use
      'k'   => -10 * 60,
      'l'   => -11 * 60,
      'm'   => -12 * 60,
      'n'   =>   1 * 60,
      'o'   =>   2 * 60,
      'p'   =>   3 * 60,
      'q'   =>   4 * 60,
      'r'   =>   5 * 60,
      's'   =>   6 * 60,
      't'   =>   7 * 60,
      'u'   =>   8 * 60,
      'v'   =>   9 * 60,
      'w'   =>  10 * 60,
      'x'   =>  11 * 60,
      'y'   =>  12 * 60,
      'z'   =>   0 * 60
    }

    def timezone_string_to_unixtime( str )
      if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
        sec = (m[2].to_i * 60 + m[3].to_i) * 60
        m[1] == '-' ? -sec : sec
      else
        min = ZONESTR_TABLE[str.downcase] or
                raise SyntaxError, "wrong timezone format '#{str}'"
        min * 60
      end
    end


    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 )

    def time2str( tm )
      # [ruby-list:7928]
      gmt = Time.at(tm.to_i)
      gmt.gmtime
      offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i

      # DO NOT USE strftime: setlocale() breaks it
      sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
              WDAY[tm.wday], tm.mday, MONTH[tm.month],
              tm.year, tm.hour, tm.min, tm.sec,
              *(offset / 60).divmod(60)
    end


    MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/

    def message_id?( str )
      MESSAGE_ID === str
    end


    MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i

    def mime_encoded?( str )
      MIME_ENCODED === str
    end
  

    def decode_params( hash )
      new = Hash.new
      encoded = nil
      hash.each do |key, value|
        if m = /\*(?:(\d+)\*)?\z/.match(key)
          ((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
        else
          new[key] = to_kcode(value)
        end
      end
      if encoded
        encoded.each do |key, strings|
          new[key] = decode_RFC2231(strings.join(''))
        end
      end

      new
    end

    NKF_FLAGS = {
      'EUC'  => '-e -m',
      'SJIS' => '-s -m'
    }

    def to_kcode( str )
      flag = NKF_FLAGS[$KCODE] or return str
      NKF.nkf(flag, str)
    end

    RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in

    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 })
      rescue
        m.post_match.gsub(/%[\da-f]{2}/in, "")
      end
    end

  end

end
require 'tmail/info'
require 'tmail/mail'
require 'tmail/mailbox'
module ActionMailer
  module VERSION #:nodoc:
    MAJOR = 1
    MINOR = 2
    TINY  = 5

    STRING = [MAJOR, MINOR, TINY].join('.')
  end
end
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# 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.
#++

begin
  require 'action_controller'
rescue LoadError
  begin
    require File.dirname(__FILE__) + '/../../actionpack/lib/action_controller'
  rescue LoadError
    require 'rubygems'
    require_gem 'actionpack', '>= 1.9.1'
  end
end

$:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/")

require 'action_mailer/base'
require 'action_mailer/helpers'
require 'action_mailer/mail_helper'
require 'action_mailer/quoting'
require 'tmail'
require 'net/smtp'

ActionMailer::Base.class_eval do
  include ActionMailer::Quoting
  include ActionMailer::Helpers

  helper MailHelper
end

silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }module TestHelper
  def test_format(text)
    "<em><strong><small>#{text}</small></strong></em>"
  end
end
$:.unshift(File.dirname(__FILE__) + "/../lib/")
$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"

require 'test/unit'
require 'action_mailer'

module MailerHelper
  def person_name
    "Mr. Joe Person"
  end
end

class HelperMailer < ActionMailer::Base
  helper MailerHelper
  helper :test

  def use_helper(recipient)
    recipients recipient
    subject    "using helpers"
    from       "tester@example.com"
  end

  def use_test_helper(recipient)
    recipients recipient
    subject    "using helpers"
    from       "tester@example.com"
    self.body = { :text => "emphasize me!" }
  end

  def use_mail_helper(recipient)
    recipients recipient
    subject    "using mailing helpers"
    from       "tester@example.com"
    self.body = { :text => 
      "But soft! What light through yonder window breaks? It is the east, " +
      "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
      "which is sick and pale with grief that thou, her maid, art far more " +
      "fair than she. Be not her maid, for she is envious! Her vestal " +
      "livery is but sick and green, and none but fools do wear it. Cast " +
      "it off!"
    }
  end

  def use_helper_method(recipient)
    recipients recipient
    subject    "using helpers"
    from       "tester@example.com"
    self.body = { :text => "emphasize me!" }
  end

  private

    def name_of_the_mailer_class
      self.class.name
    end
    helper_method :name_of_the_mailer_class
end

HelperMailer.template_root = File.dirname(__FILE__) + "/fixtures"

class MailerHelperTest < Test::Unit::TestCase
  def new_mail( charset="utf-8" )
    mail = TMail::Mail.new
    mail.set_content_type "text", "plain", { "charset" => charset } if charset
    mail
  end

  def setup
    ActionMailer::Base.delivery_method = :test
    ActionMailer::Base.perform_deliveries = true
    ActionMailer::Base.deliveries = []

    @recipient = 'test@localhost'
  end

  def test_use_helper
    mail = HelperMailer.create_use_helper(@recipient)
    assert_match %r{Mr. Joe Person}, mail.encoded
  end

  def test_use_test_helper
    mail = HelperMailer.create_use_test_helper(@recipient)
    assert_match %r{<em><strong><small>emphasize me!}, mail.encoded
  end

  def test_use_helper_method
    mail = HelperMailer.create_use_helper_method(@recipient)
    assert_match %r{HelperMailer}, mail.encoded
  end

  def test_use_mail_helper
    mail = HelperMailer.create_use_mail_helper(@recipient)
    assert_match %r{  But soft!}, mail.encoded
    assert_match %r{east, and\n  Juliet}, mail.encoded
  end
end

$:.unshift(File.dirname(__FILE__) + "/../lib/")

require 'test/unit'
require 'action_mailer'

class RenderMailer < ActionMailer::Base
  def inline_template(recipient)
    recipients recipient
    subject    "using helpers"
    from       "tester@example.com"
    body       render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
  end

  def file_template(recipient)
    recipients recipient
    subject    "using helpers"
    from       "tester@example.com"
    body       render(:file => "signed_up", :body => { :recipient => recipient })
  end

  def initialize_defaults(method_name)
    super
    mailer_name "test_mailer"
  end
end

RenderMailer.template_root = File.dirname(__FILE__) + "/fixtures"

class RenderHelperTest < Test::Unit::TestCase
  def setup
    ActionMailer::Base.delivery_method = :test
    ActionMailer::Base.perform_deliveries = true
    ActionMailer::Base.deliveries = []

    @recipient = 'test@localhost'
  end

  def test_inline_template
    mail = RenderMailer.create_inline_template(@recipient)
    assert_equal "Hello, Earth", mail.body.strip
  end

  def test_file_template
    mail = RenderMailer.create_file_template(@recipient)
    assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
  end
end

$:.unshift(File.dirname(__FILE__) + "/../lib/")

require 'test/unit'
require 'action_mailer'

class MockSMTP
  def self.deliveries
    @@deliveries
  end

  def initialize
    @@deliveries = []
  end

  def sendmail(mail, from, to)
    @@deliveries << [mail, from, to]
  end
end

class Net::SMTP
  def self.start(*args)
    yield MockSMTP.new
  end
end

class FunkyPathMailer < ActionMailer::Base
  self.template_root = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"

  def multipart_with_template_path_with_dots(recipient)
    recipients recipient
    subject    "Have a lovely picture"
    from       "Chad Fowler <chad@chadfowler.com>"
    attachment :content_type => "image/jpeg",
      :body => "not really a jpeg, we're only testing, after all"
  end

  def template_path
    "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
  end
end

class TestMailer < ActionMailer::Base

  def signed_up(recipient)
    @recipients   = recipient
    @subject      = "[Signed up] Welcome #{recipient}"
    @from         = "system@loudthinking.com"
    @sent_on      = Time.local(2004, 12, 12)
    @body["recipient"] = recipient
  end

  def cancelled_account(recipient)
    self.recipients = recipient
    self.subject    = "[Cancelled] Goodbye #{recipient}"
    self.from       = "system@loudthinking.com"
    self.sent_on    = Time.local(2004, 12, 12)
    self.body       = "Goodbye, Mr. #{recipient}"
  end

  def cc_bcc(recipient)
    recipients recipient
    subject    "testing bcc/cc"
    from       "system@loudthinking.com"
    sent_on    Time.local(2004, 12, 12)
    cc         "nobody@loudthinking.com"
    bcc        "root@loudthinking.com"
    body       "Nothing to see here."
  end

  def iso_charset(recipient)
    @recipients = recipient
    @subject    = "testing isø charsets"
    @from       = "system@loudthinking.com"
    @sent_on    = Time.local 2004, 12, 12
    @cc         = "nobody@loudthinking.com"
    @bcc        = "root@loudthinking.com"
    @body       = "Nothing to see here."
    @charset    = "iso-8859-1"
  end

  def unencoded_subject(recipient)
    @recipients = recipient
    @subject    = "testing unencoded subject"
    @from       = "system@loudthinking.com"
    @sent_on    = Time.local 2004, 12, 12
    @cc         = "nobody@loudthinking.com"
    @bcc        = "root@loudthinking.com"
    @body       = "Nothing to see here."
  end

  def extended_headers(recipient)
    @recipients = recipient
    @subject    = "testing extended headers"
    @from       = "Grytøyr <stian1@example.net>"
    @sent_on    = Time.local 2004, 12, 12
    @cc         = "Grytøyr <stian2@example.net>"
    @bcc        = "Grytøyr <stian3@example.net>"
    @body       = "Nothing to see here."
    @charset    = "iso-8859-1"
  end

  def utf8_body(recipient)
    @recipients = recipient
    @subject    = "testing utf-8 body"
    @from       = "Foo áëô îü <extended@example.net>"
    @sent_on    = Time.local 2004, 12, 12
    @cc         = "Foo áëô îü <extended@example.net>"
    @bcc        = "Foo áëô îü <extended@example.net>"
    @body       = "åœö blah"
    @charset    = "utf-8"
  end

  def multipart_with_mime_version(recipient)
    recipients   recipient
    subject      "multipart with mime_version"
    from         "test@example.com"
    sent_on      Time.local(2004, 12, 12)
    mime_version "1.1"
    content_type "multipart/alternative"

    part "text/plain" do |p|
      p.body = "blah"
    end

    part "text/html" do |p|
      p.body = "<b>blah</b>"
    end
  end

  def multipart_with_utf8_subject(recipient)
    recipients   recipient
    subject      "Foo áëô îü"
    from         "test@example.com"
    charset      "utf-8"

    part "text/plain" do |p|
      p.body = "blah"
    end

    part "text/html" do |p|
      p.body = "<b>blah</b>"
    end
  end

  def explicitly_multipart_example(recipient, ct=nil)
    recipients   recipient
    subject      "multipart example"
    from         "test@example.com"
    sent_on      Time.local(2004, 12, 12)
    body         "plain text default"
    content_type ct if ct

    part "text/html" do |p|
      p.charset = "iso-8859-1"
      p.body = "blah"
    end

    attachment :content_type => "image/jpeg", :filename => "foo.jpg",
      :body => "123456789"
  end

  def implicitly_multipart_example(recipient, cs = nil, order = nil)
    @recipients = recipient
    @subject    = "multipart example"
    @from       = "test@example.com"
    @sent_on    = Time.local 2004, 12, 12
    @body       = { "recipient" => recipient }
    @charset    = cs if cs
    @implicit_parts_order = order if order
  end

  def implicitly_multipart_with_utf8
    recipients "no.one@nowhere.test"
    subject    "Foo áëô îü"
    from       "some.one@somewhere.test"
    template   "implicitly_multipart_example"
    body       ({ "recipient" => "no.one@nowhere.test" })
  end

  def html_mail(recipient)
    recipients   recipient
    subject      "html mail"
    from         "test@example.com"
    body         "<em>Emphasize</em> <strong>this</strong>"
    content_type "text/html"
  end

  def html_mail_with_underscores(recipient)
    subject      "html mail with underscores"
    body         %{<a href="http://google.com" target="_blank">_Google</a>}
  end

  def custom_template(recipient)
    recipients recipient
    subject    "[Signed up] Welcome #{recipient}"
    from       "system@loudthinking.com"
    sent_on    Time.local(2004, 12, 12)
    template   "signed_up"

    body["recipient"] = recipient
  end

  def various_newlines(recipient)
    recipients   recipient
    subject      "various newlines"
    from         "test@example.com"
    body         "line #1\nline #2\rline #3\r\nline #4\r\r" +
                 "line #5\n\nline#6\r\n\r\nline #7"
  end

  def various_newlines_multipart(recipient)
    recipients   recipient
    subject      "various newlines multipart"
    from         "test@example.com"
    content_type "multipart/alternative"
    part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r"
    part :content_type => "text/html", :body => "<p>line #1</p>\n<p>line #2</p>\r<p>line #3</p>\r\n<p>line #4</p>\r\r"
  end

  def nested_multipart(recipient)
    recipients   recipient
    subject      "nested multipart"
    from         "test@example.com"
    content_type "multipart/mixed"
    part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
      p.part :content_type => "text/plain", :body => "test text\nline #2"
      p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
    end
    attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
  end
  
  def attachment_with_custom_header(recipient)
    recipients   recipient
    subject      "custom header in attachment"
    from         "test@example.com"
    content_type "multipart/related"
    part :content_type => "text/html", :body => 'yo'
    attachment :content_type => "image/jpeg",:filename => "test.jpeg", :body => "i am not a real picture", :headers => { 'Content-ID' => '<test@test.com>' }
  end

  def unnamed_attachment(recipient)
    recipients   recipient
    subject      "nested multipart"
    from         "test@example.com"
    content_type "multipart/mixed"
    part :content_type => "text/plain", :body => "hullo"
    attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz"
  end

  def headers_with_nonalpha_chars(recipient)
    recipients   recipient
    subject      "nonalpha chars"
    from         "One: Two <test@example.com>"
    cc           "Three: Four <test@example.com>"
    bcc          "Five: Six <test@example.com>"
    body         "testing"
  end

  def custom_content_type_attributes
    recipients   "no.one@nowhere.test"
    subject      "custom content types"
    from         "some.one@somewhere.test"
    content_type "text/plain; format=flowed"
    body         "testing"
  end

  class <<self
    attr_accessor :received_body
  end

  def receive(mail)
    self.class.received_body = mail.body
  end
end

TestMailer.template_root = File.dirname(__FILE__) + "/fixtures"

class ActionMailerTest < Test::Unit::TestCase
  include ActionMailer::Quoting

  def encode( text, charset="utf-8" )
    quoted_printable( text, charset )
  end

  def new_mail( charset="utf-8" )
    mail = TMail::Mail.new
    if charset
      mail.set_content_type "text", "plain", { "charset" => charset }
    end
    mail
  end

  def setup
    ActionMailer::Base.delivery_method = :test
    ActionMailer::Base.perform_deliveries = true
    ActionMailer::Base.deliveries = []

    @recipient = 'test@localhost'
  end

  def test_nested_parts
    created = nil
    assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
    assert_equal 2,created.parts.size
    assert_equal 2,created.parts.first.parts.size
    
    assert_equal "multipart/mixed", created.content_type
    assert_equal "multipart/alternative", created.parts.first.content_type
    assert_equal "text/plain", created.parts.first.parts.first.content_type
    assert_equal "text/html", created.parts.first.parts[1].content_type
    assert_equal "application/octet-stream", created.parts[1].content_type
  end

  def test_attachment_with_custom_header
    created = nil
    assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient)}
    assert_equal "<test@test.com>", created.parts[1].header['content-id'].to_s
  end

  def test_signed_up
    expected = new_mail
    expected.to      = @recipient
    expected.subject = "[Signed up] Welcome #{@recipient}"
    expected.body    = "Hello there, \n\nMr. #{@recipient}"
    expected.from    = "system@loudthinking.com"
    expected.date    = Time.local(2004, 12, 12)
    expected.mime_version = nil

    created = nil
    assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
    assert_not_nil created
    assert_equal expected.encoded, created.encoded

    assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
    assert_not_nil ActionMailer::Base.deliveries.first
    assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  end
  
  def test_custom_template
    expected = new_mail
    expected.to      = @recipient
    expected.subject = "[Signed up] Welcome #{@recipient}"
    expected.body    = "Hello there, \n\nMr. #{@recipient}"
    expected.from    = "system@loudthinking.com"
    expected.date    = Time.local(2004, 12, 12)

    created = nil
    assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) }
    assert_not_nil created
    assert_equal expected.encoded, created.encoded
  end
  
  def test_cancelled_account
    expected = new_mail
    expected.to      = @recipient
    expected.subject = "[Cancelled] Goodbye #{@recipient}"
    expected.body    = "Goodbye, Mr. #{@recipient}"
    expected.from    = "system@loudthinking.com"
    expected.date    = Time.local(2004, 12, 12)

    created = nil
    assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) }
    assert_not_nil created
    assert_equal expected.encoded, created.encoded

    assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) }
    assert_not_nil ActionMailer::Base.deliveries.first
    assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  end
  
  def test_cc_bcc
    expected = new_mail
    expected.to      = @recipient
    expected.subject = "testing bcc/cc"
    expected.body    = "Nothing to see here."
    expected.from    = "system@loudthinking.com"
    expected.cc      = "nobody@loudthinking.com"
    expected.bcc     = "root@loudthinking.com"
    expected.date    = Time.local 2004, 12, 12

    created = nil
    assert_nothing_raised do
      created = TestMailer.create_cc_bcc @recipient
    end
    assert_not_nil created
    assert_equal expected.encoded, created.encoded

    assert_nothing_raised do
      TestMailer.deliver_cc_bcc @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
    expected.subject = encode "testing isø charsets", "iso-8859-1"
    expected.body    = "Nothing to see here."
    expected.from    = "system@loudthinking.com"
    expected.cc      = "nobody@loudthinking.com"
    expected.bcc     = "root@loudthinking.com"
    expected.date    = Time.local 2004, 12, 12

    created = nil
    assert_nothing_raised do
      created = TestMailer.create_iso_charset @recipient
    end
    assert_not_nil created
    assert_equal expected.encoded, created.encoded

    assert_nothing_raised do
      TestMailer.deliver_iso_charset @recipient
    end

    assert_not_nil ActionMailer::Base.deliveries.first
    assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  end

  def test_unencoded_subject
    expected = new_mail
    expected.to      = @recipient
    expected.subject = "testing unencoded subject"
    expected.body    = "Nothing to see here."
    expected.from    = "system@loudthinking.com"
    expected.cc      = "nobody@loudthinking.com"
    expected.bcc     = "root@loudthinking.com"
    expected.date    = Time.local 2004, 12, 12

    created = nil
    assert_nothing_raised do
      created = TestMailer.create_unencoded_subject @recipient
    end
    assert_not_nil created
    assert_equal expected.encoded, created.encoded

    assert_nothing_raised do
      TestMailer.deliver_unencoded_subject @recipient
    end

    assert_not_nil ActionMailer::Base.deliveries.first
    assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  end

  def test_instances_are_nil
    assert_nil ActionMailer::Base.new
    assert_nil TestMailer.new
  end

  def test_deliveries_array
    assert_not_nil ActionMailer::Base.deliveries
    assert_equal 0, ActionMailer::Base.deliveries.size
    TestMailer.deliver_signed_up(@recipient)
    assert_equal 1, ActionMailer::Base.deliveries.size
    assert_not_nil ActionMailer::Base.deliveries.first
  end

  def test_perform_deliveries_flag
    ActionMailer::Base.perform_deliveries = false
    TestMailer.deliver_signed_up(@recipient)
    assert_equal 0, ActionMailer::Base.deliveries.size
    ActionMailer::Base.perform_deliveries = true
    TestMailer.deliver_signed_up(@recipient)
    assert_equal 1, ActionMailer::Base.deliveries.size
  end

  def test_unquote_quoted_printable_subject
    msg = <<EOF
From: me@example.com
Subject: =?utf-8?Q?testing_testing_=D6=A4?=
Content-Type: text/plain; charset=iso-8859-1

The body
EOF
    mail = TMail::Mail.parse(msg)
    assert_equal "testing testing \326\244", mail.subject
    assert_equal "=?utf-8?Q?testing_testing_=D6=A4?=", mail.quoted_subject
  end

  def test_unquote_7bit_subject
    msg = <<EOF
From: me@example.com
Subject: this == working?
Content-Type: text/plain; charset=iso-8859-1

The body
EOF
    mail = TMail::Mail.parse(msg)
    assert_equal "this == working?", mail.subject
    assert_equal "this == working?", mail.quoted_subject
  end

  def test_unquote_7bit_body
    msg = <<EOF
From: me@example.com
Subject: subject
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 7bit

The=3Dbody
EOF
    mail = TMail::Mail.parse(msg)
    assert_equal "The=3Dbody", mail.body.strip
    assert_equal "The=3Dbody", mail.quoted_body.strip
  end

  def test_unquote_quoted_printable_body
    msg = <<EOF
From: me@example.com
Subject: subject
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable

The=3Dbody
EOF
    mail = TMail::Mail.parse(msg)
    assert_equal "The=body", mail.body.strip
    assert_equal "The=3Dbody", mail.quoted_body.strip
  end

  def test_unquote_base64_body
    msg = <<EOF
From: me@example.com
Subject: subject
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: base64

VGhlIGJvZHk=
EOF
    mail = TMail::Mail.parse(msg)
    assert_equal "The body", mail.body.strip
    assert_equal "VGhlIGJvZHk=", mail.quoted_body.strip
  end

  def test_extended_headers
    @recipient = "Grytøyr <test@localhost>"

    expected = new_mail "iso-8859-1"
    expected.to      = quote_address_if_necessary @recipient, "iso-8859-1"
    expected.subject = "testing extended headers"
    expected.body    = "Nothing to see here."
    expected.from    = quote_address_if_necessary "Grytøyr <stian1@example.net>", "iso-8859-1"
    expected.cc      = quote_address_if_necessary "Grytøyr <stian2@example.net>", "iso-8859-1"
    expected.bcc     = quote_address_if_necessary "Grytøyr <stian3@example.net>", "iso-8859-1"
    expected.date    = Time.local 2004, 12, 12

    created = nil
    assert_nothing_raised do
      created = TestMailer.create_extended_headers @recipient
    end

    assert_not_nil created
    assert_equal expected.encoded, created.encoded

    assert_nothing_raised do
      TestMailer.deliver_extended_headers @recipient
    end

    assert_not_nil ActionMailer::Base.deliveries.first
    assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  end
  
  def test_utf8_body_is_not_quoted
    @recipient = "Foo áëô îü <extended@example.net>"
    expected = new_mail "utf-8"
    expected.to      = quote_address_if_necessary @recipient, "utf-8"
    expected.subject = "testing utf-8 body"
    expected.body    = "åœö blah"
    expected.from    = quote_address_if_necessary @recipient, "utf-8"
    expected.cc      = quote_address_if_necessary @recipient, "utf-8"
    expected.bcc     = quote_address_if_necessary @recipient, "utf-8"
    expected.date    = Time.local 2004, 12, 12

    created = TestMailer.create_utf8_body @recipient
    assert_match(/åœö blah/, created.encoded)
  end

  def test_multiple_utf8_recipients
    @recipient = ["\"Foo áëô îü\" <extended@example.net>", "\"Example Recipient\" <me@example.com>"]
    expected = new_mail "utf-8"
    expected.to      = quote_address_if_necessary @recipient, "utf-8"
    expected.subject = "testing utf-8 body"
    expected.body    = "åœö blah"
    expected.from    = quote_address_if_necessary @recipient.first, "utf-8"
    expected.cc      = quote_address_if_necessary @recipient, "utf-8"
    expected.bcc     = quote_address_if_necessary @recipient, "utf-8"
    expected.date    = Time.local 2004, 12, 12

    created = TestMailer.create_utf8_body @recipient
    assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>\r/, created.encoded)
    assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>, Example Recipient <me/, created.encoded)
  end

  def test_receive_decodes_base64_encoded_mail
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
    TestMailer.receive(fixture)
    assert_match(/Jamis/, TestMailer.received_body)
  end

  def test_receive_attachments
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email2")
    mail = TMail::Mail.parse(fixture)
    attachment = mail.attachments.last
    assert_equal "smime.p7s", attachment.original_filename
    assert_equal "application/pkcs7-signature", attachment.content_type
  end

  def test_decode_attachment_without_charset
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email3")
    mail = TMail::Mail.parse(fixture)
    attachment = mail.attachments.last
    assert_equal 1026, attachment.read.length
  end

  def test_attachment_using_content_location
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email12")
    mail = TMail::Mail.parse(fixture)
    assert_equal 1, mail.attachments.length
    assert_equal "Photo25.jpg", mail.attachments.first.original_filename
  end

  def test_attachment_with_text_type
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email13")
    mail = TMail::Mail.parse(fixture)
    assert mail.has_attachments?
    assert_equal 1, mail.attachments.length
    assert_equal "hello.rb", mail.attachments.first.original_filename
  end

  def test_decode_part_without_content_type
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email4")
    mail = TMail::Mail.parse(fixture)
    assert_nothing_raised { mail.body }
  end

  def test_decode_message_without_content_type
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email5")
    mail = TMail::Mail.parse(fixture)
    assert_nothing_raised { mail.body }
  end

  def test_decode_message_with_incorrect_charset
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email6")
    mail = TMail::Mail.parse(fixture)
    assert_nothing_raised { mail.body }
  end

  def test_multipart_with_mime_version
    mail = TestMailer.create_multipart_with_mime_version(@recipient)
    assert_equal "1.1", mail.mime_version
  end
  
  def test_multipart_with_utf8_subject
    mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
    assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
  end

  def test_implicitly_multipart_with_utf8
    mail = TestMailer.create_implicitly_multipart_with_utf8
    assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
  end

  def test_explicitly_multipart_messages
    mail = TestMailer.create_explicitly_multipart_example(@recipient)
    assert_equal 3, mail.parts.length
    assert_nil mail.content_type
    assert_equal "text/plain", mail.parts[0].content_type

    assert_equal "text/html", mail.parts[1].content_type
    assert_equal "iso-8859-1", mail.parts[1].sub_header("content-type", "charset")
    assert_equal "inline", mail.parts[1].content_disposition

    assert_equal "image/jpeg", mail.parts[2].content_type
    assert_equal "attachment", mail.parts[2].content_disposition
    assert_equal "foo.jpg", mail.parts[2].sub_header("content-disposition", "filename")
    assert_equal "foo.jpg", mail.parts[2].sub_header("content-type", "name")
    assert_nil mail.parts[2].sub_header("content-type", "charset")
  end

  def test_explicitly_multipart_with_content_type
    mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative")
    assert_equal 3, mail.parts.length
    assert_equal "multipart/alternative", mail.content_type
  end

  def test_explicitly_multipart_with_invalid_content_type
    mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml")
    assert_equal 3, mail.parts.length
    assert_nil mail.content_type
  end

  def test_implicitly_multipart_messages
    mail = TestMailer.create_implicitly_multipart_example(@recipient)
    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[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")
  end

  def test_implicitly_multipart_messages_with_custom_order
    mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
    assert_equal 3, mail.parts.length
    assert_equal "text/html", mail.parts[0].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
    mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')

    assert_equal "multipart/alternative", mail.header['content-type'].body
    
    assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
    assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
    assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
  end

  def test_html_mail
    mail = TestMailer.create_html_mail(@recipient)
    assert_equal "text/html", mail.content_type
  end

  def test_html_mail_with_underscores
    mail = TestMailer.create_html_mail_with_underscores(@recipient)
    assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body
  end

  def test_various_newlines
    mail = TestMailer.create_various_newlines(@recipient)
    assert_equal("line #1\nline #2\nline #3\nline #4\n\n" +
                 "line #5\n\nline#6\n\nline #7", mail.body)
  end

  def test_various_newlines_multipart
    mail = TestMailer.create_various_newlines_multipart(@recipient)
    assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
    assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
  end
  
  def test_headers_removed_on_smtp_delivery
    ActionMailer::Base.delivery_method = :smtp
    TestMailer.deliver_cc_bcc(@recipient)
    assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com")
    assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com")
    assert MockSMTP.deliveries[0][2].include?(@recipient)
    assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0]
    assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0]
    assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
  end

  def test_recursive_multipart_processing
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
    mail = TMail::Mail.parse(fixture)
    assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
  end

  def test_decode_encoded_attachment_filename
    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
  end

  def test_wrong_mail_header
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
    assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
  end

  def test_decode_message_with_unknown_charset
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
    mail = TMail::Mail.parse(fixture)
    assert_nothing_raised { mail.body }
  end

  def test_decode_message_with_unquoted_atchar_in_header
    fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email11")
    mail = TMail::Mail.parse(fixture)
    assert_not_nil mail.from
  end

  def test_empty_header_values_omitted
    result = TestMailer.create_unnamed_attachment(@recipient).encoded
    assert_match %r{Content-Type: application/octet-stream[^;]}, result
    assert_match %r{Content-Disposition: attachment[^;]}, result
  end

  def test_headers_with_nonalpha_chars
    mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
    assert !mail.from_addrs.empty?
    assert !mail.cc_addrs.empty?
    assert !mail.bcc_addrs.empty?
    assert_match(/:/, mail.from_addrs.to_s)
    assert_match(/:/, mail.cc_addrs.to_s)
    assert_match(/:/, mail.bcc_addrs.to_s)
  end

  def test_deliver_with_mail_object
    mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
    assert_nothing_raised { TestMailer.deliver(mail) }
    assert_equal 1, TestMailer.deliveries.length
  end

  def test_multipart_with_template_path_with_dots
    mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
    assert_equal 2, mail.parts.length
  end

  def test_custom_content_type_attributes
    mail = TestMailer.create_custom_content_type_attributes
    assert_match %r{format=flowed}, mail['content-type'].to_s
    assert_match %r{charset=utf-8}, mail['content-type'].to_s
  end
end

class InheritableTemplateRootTest < Test::Unit::TestCase
  def test_attr
    expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
    assert_equal expected, FunkyPathMailer.template_root

    sub = Class.new(FunkyPathMailer)
    sub.template_root = 'test/path'

    assert_equal 'test/path', sub.template_root
    assert_equal expected, FunkyPathMailer.template_root
  end
end
$:.unshift(File.dirname(__FILE__) + "/../lib/")
$:.unshift(File.dirname(__FILE__) + "/../lib/action_mailer/vendor")

require 'test/unit'
require 'tmail'
require 'tempfile'

class QuotingTest < Test::Unit::TestCase
  def test_quote_multibyte_chars
    original = "\303\246 \303\270 and \303\245"

    result = execute_in_sandbox(<<-CODE)
      $:.unshift(File.dirname(__FILE__) + "/../lib/")
      $KCODE = 'u'
      require 'jcode'
      require 'action_mailer/quoting'
      include ActionMailer::Quoting
      quoted_printable(#{original.inspect}, "UTF-8")
    CODE

    unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
    assert_equal unquoted, original
  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)
      test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
      res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"

      File.open(test_name, "w+") do |file|
        file.write(<<-CODE)
          block = Proc.new do
            #{code}
          end
          puts block.call
        CODE
      end

      system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
      File.read(res_name)
    ensure
      File.delete(test_name) rescue nil
      File.delete(res_name) rescue nil
    end
end
$:.unshift(File.dirname(__FILE__) + "/../lib/")
$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"

require 'test/unit'
require 'action_mailer'

class TMailMailTest < Test::Unit::TestCase
  def test_body
    m = TMail::Mail.new
    expected = 'something_with_underscores'
    m.encoding = 'quoted-printable'
    quoted_body = [expected].pack('*M')
    m.body = quoted_body
    assert_equal "something_with_underscores=\n", m.quoted_body
    assert_equal expected, m.body
  end
end
$:.unshift(File.dirname(__FILE__) + "/../lib")

require "action_controller"
require "action_controller/test_process"

Person = Struct.new("Person", :id, :name, :email_address, :phone_number)

class AddressBookService
  attr_reader :people

  def initialize()          @people = [] end
  def create_person(data)   people.unshift(Person.new(next_person_id, data["name"], data["email_address"], data["phone_number"])) end
  def find_person(topic_id) people.select { |person| person.id == person.to_i }.first end
  def next_person_id()      people.first.id + 1 end
end

class AddressBookController < ActionController::Base
  layout "address_book/layout"
  
  before_filter :initialize_session_storage
  
  # Could also have used a proc
  # before_filter proc { |c| c.instance_variable_set("@address_book", c.session["address_book"] ||= AddressBookService.new) } 

  def index
    @title  = "Address Book"
    @people = @address_book.people
  end
  
  def person
    @person = @address_book.find_person(@params["id"])
  end
  
  def create_person
    @address_book.create_person(@params["person"])
    redirect_to :action => "index"
  end
    
  private
    def initialize_session_storage
      @address_book = @session["address_book"] ||= AddressBookService.new
    end
end

ActionController::Base.template_root = File.dirname(__FILE__)
# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir

begin
  AddressBookController.process_cgi(CGI.new) if $0 == __FILE__
rescue => e
  CGI.new.out { "#{e.class}: #{e.message}" }
end$:.unshift(File.dirname(__FILE__) + "/../lib")

require "action_controller"
require 'action_controller/test_process'

Person = Struct.new("Person", :name, :address, :age)

class BenchmarkController < ActionController::Base
  def message
    render_text "hello world"
  end

  def list
    @people = [ Person.new("David"), Person.new("Mary") ]
    render_template "hello: <% for person in @people %>Name: <%= person.name %><% end %>"
  end
  
  def form_helper
    @person = Person.new "david", "hyacintvej", 24
    render_template(
      "<% person = Person.new 'Mary', 'hyacintvej', 22 %> " +
      "change the name <%= text_field 'person', 'name' %> and  <%= text_field 'person', 'address' %> and <%= text_field 'person', 'age' %>"
    )
  end
end

#ActionController::Base.template_root = File.dirname(__FILE__)

require "benchmark"

RUNS = ARGV[0] ? ARGV[0].to_i : 50

require "profile" if ARGV[1]

runtime = Benchmark.measure {
  RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "list" })) }
}

puts "List: #{RUNS / runtime.real}"


runtime = Benchmark.measure {
  RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "message" })) }
}

puts "Message: #{RUNS / runtime.real}"

runtime = Benchmark.measure {
  RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "form_helper" })) }
}

puts "Form helper: #{RUNS / runtime.real}"
require 'rbconfig'
require 'find'
require 'ftools'

include Config

# this was adapted from rdoc's install.rb by ways of Log4r

$sitedir = CONFIG["sitelibdir"]
unless $sitedir
  version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  $libdir = File.join(CONFIG["libdir"], "ruby", version)
  $sitedir = $:.find {|x| x =~ /site_ruby/ }
  if !$sitedir
    $sitedir = File.join($libdir, "site_ruby")
  elsif $sitedir !~ Regexp.quote(version)
    $sitedir = File.join($sitedir, version)
  end
end

# the acual gruntwork
Dir.chdir("lib")

Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f|
  if f[-3..-1] == ".rb"
    File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  else
    File::makedirs(File.join($sitedir, *f.split(/\//)))
  end
}require 'test/unit'
require 'test/unit/assertions'
require 'rexml/document'
require File.dirname(__FILE__) + "/vendor/html-scanner/html/document"

module Test #:nodoc:
  module Unit #:nodoc:
    # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
    # can be used against. These collections are:
    #
    # * assigns: Instance variables assigned in the action that are available for the view.
    # * session: Objects being saved in the session.
    # * flash: The flash objects currently in the session.
    # * cookies: Cookies being sent to the user on this request.
    # 
    # These collections can be used just like any other hash:
    #
    #   assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
    #   assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
    #   assert flash.empty? # makes sure that there's nothing in the flash
    #
    # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
    # appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
    # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
    #
    # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
    #
    # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
    # action call which can then be asserted against.
    #
    # == Manipulating the request collections
    #
    # The collections described above link to the response, so you can test if what the actions were expected to do happened. But
    # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
    # and cookies, though. For sessions, you just do:
    #
    #   @request.session[:key] = "value"
    #
    # For cookies, you need to manually create the cookie, like this:
    #
    #   @request.cookies["key"] = CGI::Cookie.new("key", "value")
    #
    # == Testing named routes
    #
    # If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
    # Example: 
    #
    #  assert_redirected_to page_url(:title => 'foo')
    module Assertions
      # Asserts that the response is one of the following types:
      # 
      # * <tt>:success</tt>: Status code was 200
      # * <tt>:redirect</tt>: Status code was in the 300-399 range
      # * <tt>:missing</tt>: Status code was 404
      # * <tt>:error</tt>:  Status code was in the 500-599 range
      #
      # You can also pass an explicit status code number as the type, like assert_response(501)
      def assert_response(type, message = nil)
        clean_backtrace do
          if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
            assert_block("") { true } # to count the assertion
          elsif type.is_a?(Fixnum) && @response.response_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 }
          end               
        end
      end

      # Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
      # such that assert_redirected_to(:controller => "weblog") will also match the redirection of 
      # redirect_to(:controller => "weblog", :action => "show") and so on.
      def assert_redirected_to(options = {}, message=nil)
        clean_backtrace do
          assert_response(:redirect, message)

          if options.is_a?(String)
            msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
            url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
            eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
              u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
              [u, (p[0..0] == '/') ? p : '/' + p]
            end.flatten

            assert_equal(eurl, url, msg) if eurl && url
            assert_equal(epath, path, msg) if epath && path 
          else
            @response_diff = options.diff(@response.redirected_to) if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
            msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)#{', difference: <?>' if @response_diff}", 
                                @response.redirected_to || @response.redirect_url, @response_diff)

            assert_block(msg) do
              if options.is_a?(Symbol)
                @response.redirected_to == options
              else
                options.keys.all? do |k|
                  if k == :controller then options[k] == ActionController::Routing.controller_relative_to(@response.redirected_to[k], @controller.class.controller_path)
                  else options[k] == (@response.redirected_to[k].respond_to?(:to_param) ? @response.redirected_to[k].to_param : @response.redirected_to[k] unless @response.redirected_to[k].nil?)
                  end
                end
              end
            end
          end
        end
      end

      # Asserts that the request was rendered with the appropriate template file.
      def assert_template(expected = nil, message=nil)
        clean_backtrace do
          rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
          msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
          assert_block(msg) do
            if expected.nil?
              !@response.rendered_with_file?
            else
              expected == rendered
            end
          end               
        end
      end

      # Asserts that the routing of the given path was handled correctly and that the parsed options match.
      def assert_recognizes(expected_options, path, extras={}, message=nil)
        clean_backtrace do 
          path = "/#{path}" unless path[0..0] == '/'
          # Load routes.rb if it hasn't been loaded.
          ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? 
      
          # Assume given controller
          request = ActionController::TestRequest.new({}, {}, nil)
          request.path = path
          ActionController::Routing::Routes.recognize!(request)
      
          expected_options = expected_options.clone
          extras.each_key { |key| expected_options.delete key } unless extras.nil?
      
          expected_options.stringify_keys!
          msg = build_message(message, "The recognized options <?> did not match <?>", 
              request.path_parameters, expected_options)
          assert_block(msg) { request.path_parameters == expected_options }
        end
      end

      # Asserts that the provided options can be used to generate the provided path.
      def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
        clean_backtrace do 
          expected_path = "/#{expected_path}" unless expected_path[0] == ?/
          # Load routes.rb if it hasn't been loaded.
          ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? 
      
          generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras)
          found_extras = options.reject {|k, v| ! extra_keys.include? k}

          msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
          assert_block(msg) { found_extras == extras }
      
          msg = build_message(message, "The generated path <?> did not match <?>", generated_path, 
              expected_path)
          assert_block(msg) { expected_path == generated_path }
        end
      end

      # Asserts that path and options match both ways; in other words, the URL generated from 
      # options is the same as path, and also that the options recognized from path are the same as options
      def assert_routing(path, options, defaults={}, extras={}, message=nil)
        assert_recognizes(options, path, extras, message)
        
        controller, default_controller = options[:controller], defaults[:controller] 
        if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
          options[:controller] = "/#{controller}"
        end
         
        assert_generates(path, options, defaults, extras, message)
      end

      # Asserts that there is a tag/node/element in the body of the response
      # that meets all of the given conditions. The +conditions+ parameter must
      # be a hash of any of the following keys (all are optional):
      #
      # * <tt>:tag</tt>: the node type must match the corresponding value
      # * <tt>:attributes</tt>: a hash. The node's attributes must match the
      #   corresponding values in the hash.
      # * <tt>:parent</tt>: a hash. The node's parent must match the
      #   corresponding hash.
      # * <tt>:child</tt>: a hash. At least one of the node's immediate children
      #   must meet the criteria described by the hash.
      # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
      #   meet the criteria described by the hash.
      # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
      #   must meet the criteria described by the hash.
      # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
      #   meet the criteria described by the hash.
      # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
      #   the criteria described by the hash, and at least one sibling must match.
      # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
      #   the criteria described by the hash, and at least one sibling must match.
      # * <tt>:children</tt>: a hash, for counting children of a node. Accepts
      #   the keys:
      #   * <tt>:count</tt>: either a number or a range which must equal (or
      #     include) the number of children that match.
      #   * <tt>:less_than</tt>: the number of matching children must be less
      #     than this number.
      #   * <tt>:greater_than</tt>: the number of matching children must be
      #     greater than this number.
      #   * <tt>:only</tt>: another hash consisting of the keys to use
      #     to match on the children, and only matching children will be
      #     counted.
      # * <tt>:content</tt>: the textual content of the node must match the
      #     given value. This will not match HTML tags in the body of a
      #     tag--only text.
      #
      # Conditions are matched using the following algorithm:
      #
      # * if the condition is a string, it must be a substring of the value.
      # * if the condition is a regexp, it must match the value.
      # * if the condition is a number, the value must match number.to_s.
      # * if the condition is +true+, the value must not be +nil+.
      # * if the condition is +false+ or +nil+, the value must be +nil+.
      #
      # Usage:
      #
      #   # assert that there is a "span" tag
      #   assert_tag :tag => "span"
      #
      #   # assert that there is a "span" tag with id="x"
      #   assert_tag :tag => "span", :attributes => { :id => "x" }
      #
      #   # assert that there is a "span" tag using the short-hand
      #   assert_tag :span
      #
      #   # assert that there is a "span" tag with id="x" using the short-hand
      #   assert_tag :span, :attributes => { :id => "x" }
      #
      #   # assert that there is a "span" inside of a "div"
      #   assert_tag :tag => "span", :parent => { :tag => "div" }
      #
      #   # assert that there is a "span" somewhere inside a table
      #   assert_tag :tag => "span", :ancestor => { :tag => "table" }
      #
      #   # assert that there is a "span" with at least one "em" child
      #   assert_tag :tag => "span", :child => { :tag => "em" }
      #
      #   # assert that there is a "span" containing a (possibly nested)
      #   # "strong" tag.
      #   assert_tag :tag => "span", :descendant => { :tag => "strong" }
      #
      #   # assert that there is a "span" containing between 2 and 4 "em" tags
      #   # as immediate children
      #   assert_tag :tag => "span",
      #              :children => { :count => 2..4, :only => { :tag => "em" } } 
      #
      #   # get funky: assert that there is a "div", with an "ul" ancestor
      #   # and an "li" parent (with "class" = "enum"), and containing a 
      #   # "span" descendant that contains text matching /hello world/
      #   assert_tag :tag => "div",
      #              :ancestor => { :tag => "ul" },
      #              :parent => { :tag => "li",
      #                           :attributes => { :class => "enum" } },
      #              :descendant => { :tag => "span",
      #                               :child => /hello world/ }
      #
      # <strong>Please note</strong: #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). <em>You must explicitly
      # close all of your tags to use these assertions.</em>
      def assert_tag(*opts)
        clean_backtrace do
          opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
          tag = find_tag(opts)
          assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
        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.)
      def assert_no_tag(*opts)
        clean_backtrace do
          opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
          tag = find_tag(opts)
          assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
        end
      end

      # test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
      def assert_dom_equal(expected, actual, message="")
        clean_backtrace do
          expected_dom = HTML::Document.new(expected).root
          actual_dom = HTML::Document.new(actual).root
          full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
          assert_block(full_message) { expected_dom == actual_dom }
        end
      end
      
      # negated form of +assert_dom_equivalent+
      def assert_dom_not_equal(expected, actual, message="")
        clean_backtrace do
          expected_dom = HTML::Document.new(expected).root
          actual_dom = HTML::Document.new(actual).root
          full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
          assert_block(full_message) { expected_dom != actual_dom }
        end
      end

      # ensures that the passed record is valid by active record standards. returns the error messages if not
      def assert_valid(record)
        clean_backtrace do
          assert record.valid?, record.errors.full_messages.join("\n")
        end
      end             
      
      def clean_backtrace(&block)
        yield
      rescue AssertionFailedError => e         
        path = File.expand_path(__FILE__)
        raise AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
      end
    end
  end
end
require 'action_controller/mime_type'
require 'action_controller/request'
require 'action_controller/response'
require 'action_controller/routing'
require 'action_controller/code_generation'
require 'action_controller/url_rewriter'
require 'drb'
require 'set'

module ActionController #:nodoc:
  class ActionControllerError < StandardError #:nodoc:
  end
  class SessionRestoreError < ActionControllerError #:nodoc:
  end
  class MissingTemplate < ActionControllerError #:nodoc:
  end
  class RoutingError < ActionControllerError #:nodoc:
    attr_reader :failures
    def initialize(message, failures=[])
      super(message)
      @failures = failures
    end
  end
  class UnknownController < ActionControllerError #:nodoc:
  end
  class UnknownAction < ActionControllerError #:nodoc:
  end
  class MissingFile < ActionControllerError #:nodoc:
  end
  class SessionOverflowError < ActionControllerError #:nodoc:
    DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'

    def initialize(message = nil)
      super(message || DEFAULT_MESSAGE)
    end
  end
  class DoubleRenderError < ActionControllerError #:nodoc:
    DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and only once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\". Finally, note that to cause a before filter to halt execution of the rest of the filter chain, the filter must return false, explicitly, so \"render(...) and return false\"." 

    def initialize(message = nil)
      super(message || DEFAULT_MESSAGE)
    end
  end
  class RedirectBackError < ActionControllerError #:nodoc:
    DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify @request.env["HTTP_REFERER"].'
  
    def initialize(message = nil)
      super(message || DEFAULT_MESSAGE)
    end
  end

  # Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed 
  # on request and then either render a template or redirect to another action. An action is defined as a public method
  # on the controller, which will automatically be made accessible to the web-server through Rails Routes. 
  #
  # A sample controller could look like this:
  #
  #   class GuestBookController < ActionController::Base
  #     def index
  #       @entries = Entry.find(:all)
  #     end
  #     
  #     def sign
  #       Entry.create(params[:entry])
  #       redirect_to :action => "index"
  #     end
  #   end
  #
  # Actions, by default, render a template in the <tt>app/views</tt> 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 
  # template <tt>app/views/guestbook/index.rhtml</tt> by default after populating the <tt>@entries</tt> instance variable.
  #
  # Unlike index, the sign action will not render a template. After performing its main purpose (creating a 
  # new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external 
  # "302 Moved" HTTP response that takes the user to the index action.
  #
  # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
  # Most actions are variations of these themes.
  #
  # == Requests
  #
  # 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
  # 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
  # are made by accessing the environment hash, like this:
  #
  #   def server_ip
  #     location = request.env["SERVER_ADDR"]
  #     render :text => "This server hosted at #{location}"
  #   end
  #
  # == Parameters
  #
  # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
  # which returns a hash. For example, an action that was performed through <tt>/weblog/list?category=All&limit=5</tt> will include 
  # <tt>{ "category" => "All", "limit" => 5 }</tt> in params.
  #
  # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
  #
  #   <input type="text" name="post[name]" value="david">
  #   <input type="text" name="post[address]" value="hyacintvej">
  #
  # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
  # If the address input had been named "post[address][street]", the params would have included 
  # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
  #
  # == Sessions
  #
  # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
  # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
  # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely 
  # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
  #
  # You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
  #
  #   session[:person] = Person.authenticate(user_name, password)
  #
  # And retrieved again through the same hash:
  #
  #   Hello #{session[:person]}
  #
  # For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
  # remove the entire session with reset_session.
  #
  # By default, sessions are stored on the file system in <tt>RAILS_ROOT/tmp/sessions</tt>. Any object can be placed in the session 
  # (as long as it can be Marshalled). But remember that 1000 active sessions each storing a 50kb object could lead to a 50MB store on the filesystem.
  # In other words, think carefully about size and caching before resorting to the use of the session on the filesystem.
  #
  # An alternative to storing sessions on disk is to use ActiveRecordStore to store sessions in your database, which can solve problems
  # caused by storing sessions in the file system and may speed up your application. To use ActiveRecordStore, uncomment the line:
  #   
  #   config.action_controller.session_store = :active_record_store
  #
  # in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
  #
  # == Responses
  #
  # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
  # object is generated automatically through the use of renders and redirects and requires no user intervention.
  #
  # == Renders
  #
  # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
  # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
  # The controller passes objects to the view by assigning instance variables:
  #
  #   def show
  #     @post = Post.find(params[:id])
  #   end
  #
  # Which are then automatically available to the view:
  #
  #   Title: <%= @post.title %>
  #
  # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use
  # the manual rendering methods:
  #
  #   def search
  #     @results = Search.find(params[:query])
  #     case @results
  #       when 0 then render :action => "no_results"
  #       when 1 then render :action => "show"
  #       when 2..10 then render :action => "show_many"
  #     end
  #   end
  #
  # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html.
  #
  # == Redirects
  #
  # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database,
  # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to)
  # a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
  #
  #   def create
  #     @entry = Entry.new(params[:entry])
  #     if @entry.save
  #       # The entry was saved correctly, redirect to show
  #       redirect_to :action => 'show', :id => @entry.id
  #     else
  #       # things didn't go so well, do something else
  #     end
  #   end
  #
  # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed.
  #
  # == Calling multiple redirects or renders
  #
  # An action should conclude with a single render or redirect. Attempting to try to do either again will result in a DoubleRenderError:
  #
  #   def do_something
  #     redirect_to :action => "elsewhere"
  #     render :action => "overthere" # raises DoubleRenderError
  #   end
  #
  # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
  #
  #   def do_something
  #     redirect_to(:action => "elsewhere") and return if monkeys.nil?
  #     render :action => "overthere" # won't be called unless monkeys is nil
  #   end
  #
  class Base
    DEFAULT_RENDER_STATUS_CODE = "200 OK"
    
    include Reloadable::Subclasses
    
    # 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

    # 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: 
    #   ActionController::Base.asset_host = "http://assets.example.com"
    @@asset_host = ""
    cattr_accessor :asset_host

    # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
    # When the application is ready to go public, this should be set to false, and the protected method <tt>local_request?</tt>
    # should instead be implemented in the controller to determine when debugging screens should be shown.
    @@consider_all_requests_local = true
    cattr_accessor :consider_all_requests_local
    
    # Enable or disable the collection of failure information for RoutingErrors.
    # This information can be extremely useful when tweaking custom routes, but is
    # pointless once routes have been tested and verified.
    @@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.
    @@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 wich will process the http body and add parameters to the 
    # <tt>params</tt> 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 instanciated 
    # in the <tt>params</tt>. 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.
    # 
    # Example of doing your own parser for a custom content type:
    #
    #   ActionController::Base.param_parsers[Mime::Type.lookup('application/atom+xml')] = Proc.new do |data| 
    #      node = REXML::Document.new(post) 
    #     { node.root.name => node.root }
    #   end
    #
    # 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 "<r><name>David</name></r>" results
    # 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] = 
    #     Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) }
    #
    # A YAML parser is also available and can be turned on with:
    #
    #   ActionController::Base.param_parsers[Mime::YAML] = :yaml
    @@param_parsers = { Mime::XML => :xml_simple }
    cattr_accessor :param_parsers 

    # Template root determines the base from which template references will be made. So a call to render("test/template")
    # will be converted to "#{template_root}/test/template.rhtml".
    class_inheritable_accessor :template_root
    
    # 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

    # Holds the request object that's primarily used to get environment variables through access like
    # <tt>request.env["REQUEST_URI"]</tt>.
    attr_accessor :request
    
    # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like <tt>params["post_id"]</tt>
    # to get the post_id. No type casts are made, so all values are returned as strings.
    attr_accessor :params
    
    # Holds the response object that's primarily used to set additional HTTP headers through access like 
    # <tt>response.headers["Cache-Control"] = "no-cache"</tt>. Can also be used to access the final body HTML after a template
    # has been rendered through response.body -- useful for <tt>after_filter</tt>s that wants to manipulate the output,
    # such as a OutputCompressionFilter.
    attr_accessor :response
    
    # Holds a hash of objects in the session. Accessed like <tt>session[:person]</tt> to get the object tied to the "person"
    # key. The session will hold any type of object as values, but the key should be a string or symbol.
    attr_accessor :session
    
    # Holds a hash of header names and values. Accessed like <tt>headers["Cache-Control"]</tt> to get the value of the Cache-Control
    # directive. Values should always be specified as strings.
    attr_accessor :headers
    
    # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
    # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
    attr_accessor :assigns

    # Returns the name of the action this controller is processing.
    attr_accessor :action_name
    
    class << self
      # Factory for the standard create, process loop where the controller is discarded after processing.
      def process(request, response) #:nodoc:
        new.process(request, response)
      end
      
      # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
      def controller_class_name
        @controller_class_name ||= name.demodulize
      end

      # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
      def controller_name
        @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore
      end
      
      # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
      def controller_path
        @controller_path ||= name.gsub(/Controller$/, '').underscore
      end

      # Return an array containing the names of public methods that have been marked hidden from the action processor.
      # By default, all methods defined in ActionController::Base and included modules are hidden.
      # More methods can be hidden using <tt>hide_actions</tt>.
      def hidden_actions
        write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods) unless read_inheritable_attribute(:hidden_actions)
        read_inheritable_attribute(:hidden_actions)
      end

      # Hide each of the given methods from being callable as actions.
      def hide_action(*names)
        write_inheritable_attribute(:hidden_actions, hidden_actions | names.collect { |n| n.to_s })
      end
      
      # Replace sensitive paramater data from the request log.
      # Filters paramaters that have any of the arguments as a substring.
      # Looks in all subhashes of the param hash for keys to filter.
      # If a block is given, each key and value of the paramater hash and all
      # subhashes is passed to it, the value or key
      # can be replaced using String#replace or similar method.
      #
      # Examples:
      #   filter_parameter_logging
      #   => Does nothing, just slows the logging process down
      #
      #   filter_parameter_logging :password
      #   => replaces the value to all keys matching /password/i with "[FILTERED]"
      #
      #   filter_parameter_logging :foo, "bar"
      #   => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
      #
      #   filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
      #   => reverses the value to all keys matching /secret/i
      #
      #   filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
      #   => reverses the value to all keys matching /secret/i, and 
      #      replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
      def filter_parameter_logging(*filter_words, &block)
        parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0

        define_method(:filter_parameters) do |unfiltered_parameters|
          filtered_parameters = {}

          unfiltered_parameters.each do |key, value|
            if key =~ parameter_filter
              filtered_parameters[key] = '[FILTERED]'
            elsif value.is_a?(Hash)
              filtered_parameters[key] = filter_parameters(value) 
            elsif block_given?
              key, value = key.dup, value.dup
              yield key, value
              filtered_parameters[key] = value
            else 
              filtered_parameters[key] = value
            end
          end

          filtered_parameters
        end
      end
    end

    public      
      # Extracts the action_name from the request parameters and performs that action.
      def process(request, response, method = :perform_action, *arguments) #:nodoc:
        initialize_template_class(response)
        assign_shortcuts(request, response)
        initialize_current_url
        assign_names
        forget_variables_added_to_assigns
        
        log_processing
        send(method, *arguments)
        
        response
      ensure
        process_cleanup
      end

      # Returns a URL that has been rewritten according to the options hash and the defined Routes. 
      # (For doing a complete redirect, use redirect_to).
      #  
      # <tt>url_for</tt> is used to:
      #  
      # All keys given to url_for are forwarded to the Route module, save for the following:
      # * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example, 
      #   <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt> 
      #   will produce "/posts/show/10#comments".
      # * <tt>:only_path</tt> --  if true, returns the absolute URL (omitting the protocol, host name, and port)
      # * <tt>:trailing_slash</tt> --  if true, adds a trailing slash, as in "/archive/2005/". Note that this
      #   is currently not recommended since it breaks caching.
      # * <tt>:host</tt> -- overrides the default (current) host if provided
      # * <tt>:protocol</tt> -- overrides the default (current) protocol if provided
      #
      # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
      # Routes composes a query string as the key/value pairs not included in the <base>.
      #
      # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with
      # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs:
      #  
      #   url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
      #   url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
      #   url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
      #
      # When generating a new URL, missing values may be filled in from the current request's parameters. For example,
      # <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to
      # other parameters, including <tt>:controller</tt>, <tt>:id</tt>, and any other parameters that are placed into a Route's
      # path.
      #  
      # The URL helpers such as <tt>url_for</tt> have a limited form of memory: when generating a new URL, they can look for
      # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
      # taken from the defaults. There are a few simple rules on how this is performed:
      #
      # * If the controller name begins with a slash, no defaults are used: <tt>url_for :controller => '/home'</tt>
      # * If the controller changes, the action will default to index unless provided
      #
      # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
      # route given by <tt>map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'</tt>.
      #
      # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated
      # from this page.
      #
      # * <tt>url_for :action => 'bio'</tt> -- During the generation of this URL, default values will be used for the first and
      # last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
      # * <tt>url_for :first => 'davids-little-brother'</tt> This generates the URL 'people/hh/davids-little-brother' -- note
      #   that this URL leaves out the assumed action of 'bio'.
      #
      # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The
      # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the
      # value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using
      # defaults. On it's own, this rule can account for much of the typical Rails URL behavior.
      #  
      # Although a convienence, defaults can occasionaly get in your way. In some cases a default persists longer than desired.
      # The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options.
      # This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the
      # helper is used from. The following line will redirect to PostController's default action, regardless of the page it is
      # displayed on:
      #
      #   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.
      # Then, in the show view, you get the URL for the print view like this
      #
      #   url_for :overwrite_params => { :action => 'print' }
      #
      # This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
      # would have slashed-off the path components after the changed action.
      def url_for(options = {}, *parameters_for_method_reference) #:doc:
        case options
          when String then options
          when Symbol then send(options, *parameters_for_method_reference)
          when Hash   then @url.rewrite(rewrite_options(options))
        end
      end

      # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
      def controller_class_name
        self.class.controller_class_name
      end

      # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
      def controller_name
        self.class.controller_name
      end

      def session_enabled?
        request.session_options[:disabled] != false
      end

    protected
      # Renders the content that will be returned to the browser as the response body.
      #
      # === Rendering an action
      # 
      # Action rendering is the most common form and the type used automatically by Action Controller when nothing else is
      # specified. By default, actions are rendered within the current layout (if one exists).
      #
      #   # Renders the template for the action "goal" within the current controller
      #   render :action => "goal"
      #
      #   # Renders the template for the action "short_goal" within the current controller,
      #   # but without the current active layout
      #   render :action => "short_goal", :layout => false
      #
      #   # Renders the template for the action "long_goal" within the current controller,
      #   # but with a custom layout
      #   render :action => "long_goal", :layout => "spectacular"
      #
      # _Deprecation_ _notice_: This used to have the signatures <tt>render_action("action", status = 200)</tt>,
      # <tt>render_without_layout("controller/action", status = 200)</tt>, and 
      # <tt>render_with_layout("controller/action", status = 200, layout)</tt>.
      #
      # === Rendering partials
      # 
      # Partial rendering is most commonly used together with Ajax calls that only update one or a few elements on a page
      # without reloading. Rendering of partials from the controller makes it possible to use the same partial template in
      # both the full-page rendering (by calling it from within the template) and when sub-page updates happen (from the
      # controller action responding to Ajax calls). By default, the current layout is not used.
      #
      #   # Renders the partial located at app/views/controller/_win.r(html|xml)
      #   render :partial => "win"
      #
      #   # Renders the partial with a status code of 500 (internal error)
      #   render :partial => "broken", :status => 500
      #
      #   # Renders the same partial but also makes a local variable available to it
      #   render :partial => "win", :locals => { :name => "david" }
      #
      #   # Renders a collection of the same partial by making each element of @wins available through 
      #   # the local variable "win" as it builds the complete response
      #   render :partial => "win", :collection => @wins
      #
      #   # Renders the same collection of partials, but also renders the win_divider partial in between
      #   # each win partial.
      #   render :partial => "win", :collection => @wins, :spacer_template => "win_divider"
      #
      # _Deprecation_ _notice_: This used to have the signatures 
      # <tt>render_partial(partial_path = default_template_name, object = nil, local_assigns = {})</tt> and
      # <tt>render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})</tt>.
      #
      # === Rendering a template
      # 
      # Template rendering works just like action rendering except that it takes a path relative to the template root. 
      # The current layout is automatically applied.
      #
      #   # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.rhtml)
      #   render :template => "weblog/show"
      #
      # === Rendering a file
      # 
      # File rendering works just like action rendering except that it takes a filesystem path. By default, the path
      # is assumed to be absolute, and the current layout is not applied.
      #
      #   # Renders the template located at the absolute filesystem path
      #   render :file => "/path/to/some/template.rhtml"
      #   render :file => "c:/path/to/some/template.rhtml"
      #
      #   # Renders a template within the current layout, and with a 404 status code
      #   render :file => "/path/to/some/template.rhtml", :layout => true, :status => 404
      #   render :file => "c:/path/to/some/template.rhtml", :layout => true, :status => 404
      #
      #   # Renders a template relative to the template root and chooses the proper file extension
      #   render :file => "some/template", :use_full_path => true
      #
      # _Deprecation_ _notice_: This used to have the signature <tt>render_file(path, status = 200)</tt>
      #
      # === Rendering text
      # 
      # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
      # rendering is not done within the active layout.
      #
      #   # Renders the clear text "hello world" with status code 200
      #   render :text => "hello world!"
      #
      #   # Renders the clear text "Explosion!"  with status code 500
      #   render :text => "Explosion!", :status => 500 
      #
      #   # Renders the clear text "Hi there!" within the current active layout (if one exists)
      #   render :text => "Explosion!", :layout => true
      #
      #   # Renders the clear text "Hi there!" within the layout 
      #   # placed in "app/views/layouts/special.r(html|xml)"
      #   render :text => "Explosion!", :layout => "special"
      #
      # _Deprecation_ _notice_: This used to have the signature <tt>render_text("text", status = 200)</tt>
      #
      # === Rendering an inline template
      #
      # Rendering of an inline template works as a cross between text and action rendering where the source for the template
      # is supplied inline, like text, but its interpreted with ERb or Builder, like action. By default, ERb is used for rendering
      # and the current layout is not used.
      #
      #   # Renders "hello, hello, hello, again"
      #   render :inline => "<%= 'hello, ' * 3 + 'again' %>" 
      #
      #   # Renders "<p>Good seeing you!</p>" using Builder
      #   render :inline => "xml.p { 'Good seeing you!' }", :type => :rxml
      #
      #   # Renders "hello david"
      #   render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" }
      #
      # _Deprecation_ _notice_: This used to have the signature <tt>render_template(template, status = 200, type = :rhtml)</tt>
      #
      # === Rendering inline JavaScriptGenerator page updates
      #
      # In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details),
      # you can also pass the <tt>:update</tt> parameter to +render+, along with a block, to render page updates inline.
      #
      #   render :update do |page|
      #     page.replace_html  'user_list', :partial => 'user', :collection => @users
      #     page.visual_effect :highlight, 'user_list'
      #   end
      #
      # === Rendering nothing
      #
      # Rendering nothing is often convenient in combination with Ajax calls that perform their effect client-side or
      # when you just want to communicate a status code. Due to a bug in Safari, nothing actually means a single space.
      #
      #   # Renders an empty response with status code 200
      #   render :nothing => true
      #
      #   # Renders an empty response with status code 401 (access denied)
      #   render :nothing => true, :status => 401
      def render(options = nil, deprecated_status = nil, &block) #:doc:
        raise DoubleRenderError, "Can only render or redirect once per action" if performed?

        # Backwards compatibility
        unless options.is_a?(Hash)
          if options == :update
            options = {:update => true}
          else
            return render_file(options || default_template_name, deprecated_status, true)
          end
        end

        if content_type = options[:content_type]
          headers["Content-Type"] = content_type
        end

        if text = options[:text]
          render_text(text, options[:status])

        else
          if file = options[:file]
            render_file(file, options[:status], options[:use_full_path], options[:locals] || {})

          elsif template = options[:template]
            render_file(template, options[:status], true)
            
          elsif inline = options[:inline]
            render_template(inline, options[:status], options[:type], options[:locals] || {})
            
          elsif action_name = options[:action]
            render_action(action_name, options[:status], options[:layout]) 

          elsif xml = options[:xml]
            render_xml(xml, options[:status])

          elsif partial = options[:partial]
            partial = default_template_name if partial == true
            if collection = options[:collection]
              render_partial_collection(partial, collection, options[:spacer_template], options[:locals], options[:status])
            else
              render_partial(partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals], options[:status])
            end

          elsif options[:update]
            add_variables_to_assigns
            @template.send :evaluate_assigns
            
            generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
            render_javascript(generator.to_s)

          elsif options[:nothing]
            # Safari doesn't pass the headers of the return if the response is zero length
            render_text(" ", options[:status])
            
          else
            render_file(default_template_name, options[:status], true)
            
          end
        end
      end

      # Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
      # of sending it as the response body to the browser.
      def render_to_string(options = nil, &block) #:doc:
        result = render(options, &block)

        erase_render_results
        forget_variables_added_to_assigns
        reset_variables_added_to_assigns

        result
      end    

      def render_action(action_name, status = nil, with_layout = true) #:nodoc:
        template = default_template_name(action_name.to_s)
        if with_layout && !template_exempt_from_layout?(template) 
          render_with_layout(template, status)
        else
          render_without_layout(template, status)
        end
      end

      def render_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_text(@template.render_file(template_path, use_full_path, locals), status)
      end

      def render_template(template, status = nil, type = :rhtml, local_assigns = {}) #:nodoc:
        add_variables_to_assigns
        render_text(@template.render_template(type, template, nil, local_assigns), status)
      end

      def render_text(text = nil, status = nil) #:nodoc:
        @performed_render = true
        @response.headers['Status'] = (status || DEFAULT_RENDER_STATUS_CODE).to_s
        @response.body = text
      end

      def render_javascript(javascript, status = nil) #:nodoc:
        @response.headers['Content-Type'] = 'text/javascript; charset=UTF-8'
        render_text(javascript, status)
      end

      def render_xml(xml, status = nil) #:nodoc:
        @response.headers['Content-Type'] = 'application/xml'
        render_text(xml, status)
      end

      def render_nothing(status = nil) #:nodoc:
        render_text(' ', status)
      end

      def render_partial(partial_path = default_template_name, object = nil, local_assigns = nil, status = nil) #:nodoc:
        add_variables_to_assigns
        render_text(@template.render_partial(partial_path, object, local_assigns), status)
      end

      def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil, status = nil) #:nodoc:
        add_variables_to_assigns
        render_text(@template.render_partial_collection(partial_name, collection, partial_spacer_template, local_assigns), status)
      end

      def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
        render_with_a_layout(template_name, status, layout)
      end

      def render_without_layout(template_name = default_template_name, status = nil) #:nodoc:
        render_with_no_layout(template_name, status)
      end


      # Clears the rendered results, allowing for another render to be performed.
      def erase_render_results #:nodoc:
        @response.body = nil
        @performed_render = false
      end
      
      # Clears the redirected results from the headers, resets the status to 200 and returns 
      # the URL that was used to redirect or nil if there was no redirected URL
      # Note that +redirect_to+ will change the body of the response to indicate a redirection.
      # The response body is not reset here, see +erase_render_results+
      def erase_redirect_results #:nodoc:
        @performed_redirect = false
        response.redirected_to = nil
        response.redirected_to_method_params = nil
        response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
        response.headers.delete('location')
      end

      # Erase both render and redirect results
      def erase_results #:nodoc:
        erase_render_results
        erase_redirect_results
      end

      def rewrite_options(options) #:nodoc:
        if defaults = default_url_options(options)
          defaults.merge(options)
        else
          options
        end
      end
      
      # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
      # the form of a hash, just like the one you would use for url_for directly. Example:
      #
      #   def default_url_options(options)
      #     { :project => @project.active? ? @project.url_name : "unknown" }
      #   end
      #
      # 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:
      end
      
      # Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
      #
      # * <tt>Hash</tt>: The URL will be generated by calling url_for with the +options+.
      # * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through as the target for redirection.
      # * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string.
      # * <tt>:back</tt>: Back to the page that issued the request. Useful for forms that are triggered from multiple places.
      #   Short-hand for redirect_to(request.env["HTTP_REFERER"])
      # 
      # Examples:
      #   redirect_to :action => "show", :id => 5
      #   redirect_to "http://www.rubyonrails.org"
      #   redirect_to "/images/screenshot.jpg"
      #   redirect_to :back
      #
      # The redirection happens as a "302 Moved" header.
      #
      # When using <tt>redirect_to :back</tt>, if there is no referrer,
      # RedirectBackError will be raised. You may specify some fallback
      # behavior for this case by rescueing RedirectBackError.
      def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
        case options
          when %r{^\w+://.*}
            raise DoubleRenderError if performed?
            logger.info("Redirected to #{options}") if logger
            response.redirect(options)
            response.redirected_to = options
            @performed_redirect = true

          when String
            redirect_to(request.protocol + request.host_with_port + options)
          
          when :back
            request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"]) : raise(RedirectBackError)

          else
            if parameters_for_method_reference.empty?
              redirect_to(url_for(options))
              response.redirected_to = options
            else
              redirect_to(url_for(options, *parameters_for_method_reference))
              response.redirected_to, response.redirected_to_method_params = options, parameters_for_method_reference
            end
        end
      end
      
      # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
      # intermediate caches shouldn't cache the response.
      #
      # Examples:
      #   expires_in 20.minutes
      #   expires_in 3.hours, :private => false
      #   expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true
      # 
      # This method will overwrite an existing Cache-Control header.
      # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
      def expires_in(seconds, options = {}) #:doc:
        cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
        cache_options.delete_if { |k,v| v.nil? or v == false }
        cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
        @response.headers["Cache-Control"] = cache_control.join(', ')
      end
      
      # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
      # intermediate caches (like caching proxy servers).
      def expires_now #:doc:
        @response.headers["Cache-Control"] = "no-cache"
      end

      # Resets the session by clearing out all the objects stored within and initializing a new session object.
      def reset_session #:doc:
        @request.reset_session
        @session = @request.session
        @response.session = @session
      end
    
    private
      def self.view_class
        @view_class ||=
          # create a new class based on the default template class and include helper methods
          returning Class.new(ActionView::Base) do |view_class|
            view_class.send(:include, master_helper_module)
          end
      end

      def self.view_root
        @view_root ||= template_root
      end

      def initialize_template_class(response)
        raise "You must assign a template class through ActionController.template_class= before processing a request" unless @@template_class
        
        response.template = self.class.view_class.new(self.class.view_root, {}, self)
        response.redirected_to = nil
        @performed_render = @performed_redirect = false
      end
    
      def assign_shortcuts(request, response)
        @request, @params, @cookies = request, request.parameters, request.cookies

        @response         = response
        @response.session = request.session

        @session  = @response.session
        @template = @response.template
        @assigns  = @response.template.assigns
  
        @headers  = @response.headers
      end
      
      def initialize_current_url
        @url = UrlRewriter.new(@request, @params.clone())
      end

      def log_processing
        if logger
          logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
          logger.info "  Session ID: #{@session.session_id}" if @session and @session.respond_to?(:session_id)
          logger.info "  Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(@params).inspect : @params.inspect}"
        end
      end
    
      def perform_action
        if self.class.action_methods.include?(action_name) || self.class.action_methods.include?('method_missing')
          send(action_name)
          render unless performed?
        elsif template_exists? && template_public?
          render
        else
          raise UnknownAction, "No action responded to #{action_name}", caller
        end
      end
      
      def performed?
        @performed_render || @performed_redirect
      end

      def assign_names
        @action_name = (params['action'] || 'index')
      end
      
      def action_methods
        self.class.action_methods
      end

      def self.action_methods
        @action_methods ||= Set.new(public_instance_methods - hidden_actions)
      end

      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
      
      def forget_variables_added_to_assigns
        @variables_added = nil
      end
      
      def reset_variables_added_to_assigns
        @template.instance_variable_set("@assigns_added", nil)
      end

      def add_instance_variables_to_assigns
        @@protected_variables_cache ||= protected_instance_variables.inject({}) { |h, k| h[k] = true; h }
        instance_variables.each do |var|
          next if @@protected_variables_cache.include?(var)
          @assigns[var[1..-1]] = instance_variable_get(var)
        end
      end

      def add_class_variables_to_assigns
        %w( template_root logger template_class ignore_missing_templates ).each do |cvar|
          @assigns[cvar] = self.send(cvar)
        end
      end

      def protected_instance_variables
        if view_controller_internals
          [ "@assigns", "@performed_redirect", "@performed_render" ]
        else
          [ "@assigns", "@performed_redirect", "@performed_render", "@request", "@response", "@session", "@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
        @request_origin ||= "#{@request.remote_ip} at #{Time.now.to_s(:db)}"
      end
      
      def complete_request_uri
        "#{@request.protocol}#{@request.host}#{@request.request_uri}"
      end

      def close_session
        @session.close unless @session.nil? || Hash === @session
      end
      
      def template_exists?(template_name = default_template_name)
        @template.file_exists?(template_name)
      end

      def template_public?(template_name = default_template_name)
        @template.file_public?(template_name)
      end

      def template_exempt_from_layout?(template_name = default_template_name)
        template_name =~ /\.rjs$/ || (@template.pick_template_extension(template_name) == :rjs rescue false)
      end

      def assert_existence_of_template_file(template_name)
        unless template_exists?(template_name) || ignore_missing_templates
          full_template_path = @template.send(:full_template_path, template_name, 'rhtml')
          template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
          raise(MissingTemplate, "Missing #{template_type} #{full_template_path}")
        end
      end

      def default_template_name(action_name = self.action_name)
        if action_name
          action_name = action_name.to_s
          if action_name.include?('/') && template_path_includes_controller?(action_name)
            action_name = strip_out_controller(action_name)
          end
        end
        "#{self.class.controller_path}/#{action_name}"
      end
      
      def strip_out_controller(path)
        path.split('/', 2).last
      end
      
      def template_path_includes_controller?(path)
        self.class.controller_path.split('/')[-1] == path.split('/')[0]
      end

      def process_cleanup
        close_session
      end
  end
end
require 'benchmark'

module ActionController #:nodoc:
  # The benchmarking module times the performance of actions and reports to the logger. If the Active Record
  # package has been included, a separate timing section for database calls will be added as well.
  module Benchmarking #:nodoc:
    def self.included(base)
      base.extend(ClassMethods)

      base.class_eval do
        alias_method :perform_action_without_benchmark, :perform_action
        alias_method :perform_action, :perform_action_with_benchmark

        alias_method :render_without_benchmark, :render
        alias_method :render, :render_with_benchmark
      end
    end

    module ClassMethods
      # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it 
      # (unless <tt>use_silence</tt> is set to false).
      #
      # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
      # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
      # will only be conducted if the log level is low enough.
      def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
        if logger && logger.level == log_level
          result = nil
          seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
          logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
          result
        else
          yield
        end
      end

      # Silences the logger for the duration of the block.
      def silence
        old_logger_level, logger.level = logger.level, Logger::ERROR if logger
        yield
      ensure
        logger.level = old_logger_level if logger
      end
    end

    def render_with_benchmark(options = nil, deprecated_status = nil, &block)
      unless logger
        render_without_benchmark(options, deprecated_status, &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, deprecated_status, &block) }.real

        if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
          @db_rt_before_render = db_runtime
          @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
          @rendering_runtime -= @db_rt_after_render
        end

        render_output
      end
    end    

    def perform_action_with_benchmark
      unless logger
        perform_action_without_benchmark
      else
        runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
        log_message  = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
        log_message << rendering_runtime(runtime) if @rendering_runtime
        log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
        log_message << " | #{headers["Status"]}"
        log_message << " [#{complete_request_uri rescue "unknown"}]"
        logger.info(log_message)
      end
    end
    
    private
      def rendering_runtime(runtime)
        " | Rendering: #{sprintf("%.5f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime * 100) / runtime)}%)"
      end

      def active_record_runtime(runtime)
        db_runtime    = ActiveRecord::Base.connection.reset_runtime
        db_runtime    += @db_rt_before_render if @db_rt_before_render
        db_runtime    += @db_rt_after_render if @db_rt_after_render
        db_percentage = (db_runtime * 100) / runtime
        " | DB: #{sprintf("%.5f", db_runtime)} (#{sprintf("%d", db_percentage)}%)"
      end
  end
end
require 'fileutils'

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.
  #
  # 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.
  module Caching
    def self.included(base) #:nodoc:
      base.send(:include, Pages, Actions, Fragments, Sweeping)

      base.class_eval do
        @@perform_caching = true
        cattr_accessor :perform_caching
      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 <tt>caches</tt> 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.exists?(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.each do |action|
            class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
          end
        end

        private
          def page_cache_file(path)
            name = ((path.empty? || path == "/") ? "/index" : URI.unescape(path))
            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[: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
      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 current +options+ for this action is used. Example:
      #   cache_page "I'm the cached content", :controller => "lists", :action => "show"
      def cache_page(content = nil, options = {})
        return unless perform_caching && caching_allowed
        self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
      end

      private
        def caching_allowed
          !@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400
        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.
    module Actions
      def self.append_features(base) #:nodoc:
        super
        base.extend(ClassMethods)
        base.send(:attr_accessor, :rendered_action_cache)
      end

      module ClassMethods #:nodoc:
        def caches_action(*actions)
          return unless perform_caching
          around_filter(ActionCacheFilter.new(*actions))
        end
      end

      def expire_action(options = {})
        return unless perform_caching
        if options[:action].is_a?(Array)
          options[:action].dup.each do |action|
            expire_fragment(url_for(options.merge({ :action => action })).split("://").last)
          end
        else
          expire_fragment(url_for(options).split("://").last)
        end
      end

      class ActionCacheFilter #:nodoc:
        def initialize(*actions)
          @actions = actions
        end

        def before(controller)
          return unless @actions.include?(controller.action_name.intern)
          if cache = controller.read_fragment(controller.url_for.split("://").last)
            controller.rendered_action_cache = true
            controller.send(:render_text, cache)
            false
          end
        end

        def after(controller)
          return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
          controller.write_fragment(controller.url_for.split("://").last, controller.response.body)
        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:
    #
    #   <b>Hello <%= @name %></b>
    #   <% cache do %>
    #     All the topics in the system:
    #     <%= render_collection_of_partials "topic", Topic.find_all %>
    #   <% end %>
    #
    # This cache will bind to the name of action that called it. So you would be able to invalidate it using
    # <tt>expire_fragment(:controller => "topics", :action => "list")</tt> -- if that was the controller/action used. This is not too helpful
    # if you need to cache multiple fragments per action or if the action itself is cached using <tt>caches_action</tt>. So instead we should
    # qualify the name of the action used with something like:
    #
    #   <% cache(:action => "list", :action_suffix => "all_topics") do %>
    #
    # That would result in a name such as "/topics/list/all_topics", which wouldn't conflict with any action cache and neither with another
    # fragment using a different suffix. Note that the URL doesn't have to really exist or be callable. We're just using the url_for system
    # to generate unique cache names that we can refer to later for expirations. The expiration call for this example would be
    # <tt>expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")</tt>.
    #
    # == Fragment stores
    #
    # In order to use the fragment caching, you need to designate where the caches should be stored. This is done by assigning a fragment store
    # of which there are four different kinds:
    #
    # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and shares the fragments for
    #   all the web server processes running off the same application directory.
    # * 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.append_features(base) #:nodoc:
        super
        base.class_eval do
          @@fragment_cache_store = MemoryStore.new
          cattr_reader :fragment_cache_store

          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

      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("_erbout", 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

      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

      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
      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
        end
      end

      # Deprecated -- just call expire_fragment with a regular expression
      def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
        expire_fragment(matcher, options)
      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
      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.send(: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

      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

      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 Object => 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.exists?(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.send(: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 <tt>cache_sweeper</tt> 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.append_features(base) #:nodoc:
        super
        base.extend(ClassMethods)
      end

      module ClassMethods #:nodoc:
        def cache_sweeper(*sweepers)
          return unless perform_caching
          configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
          sweepers.each do |sweeper|
            observer(sweeper)

            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

        # ActiveRecord::Observer will mark this class as reloadable even though it should not be.
        # However, subclasses of ActionController::Caching::Sweeper should be Reloadable
        include Reloadable::Subclasses
        
        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

        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)
            send(action_callback_method_name)     if respond_to?(action_callback_method_name)
          end

          def method_missing(method, *arguments)
            return if @controller.nil?
            @controller.send(method, *arguments)
          end
      end
    end
  end
end
require 'cgi'
require 'cgi/session'
require 'cgi/session/pstore'
require 'action_controller/cgi_ext/cgi_methods'

# Wrapper around the CGIMethods that have been secluded to allow testing without 
# an instantiated CGI object
class CGI #:nodoc:
  class << self
    alias :escapeHTML_fail_on_nil :escapeHTML

    def escapeHTML(string)
      escapeHTML_fail_on_nil(string) unless string.nil?
    end
  end
  
  # Returns a parameter hash including values from both the request (POST/GET)
  # and the query string with the latter taking precedence.
  def parameters
    request_parameters.update(query_parameters)
  end

  def query_parameters
    CGIMethods.parse_query_parameters(query_string)
  end

  def request_parameters
    CGIMethods.parse_request_parameters(params, env_table)
  end

  def redirect(where)
     header({ 
       "Status" => "302 Moved", 
       "location" => "#{where}"
    })
  end
  
  def session(parameters = nil)
    parameters = {} if parameters.nil?
    parameters['database_manager'] = CGI::Session::PStore
    CGI::Session.new(self, parameters)
  end
end
require 'cgi'
require 'action_controller/vendor/xml_simple'
require 'action_controller/vendor/xml_node'

# Static methods for parsing the query and request parameters that can be used in
# a CGI extension class or testing in isolation.
class CGIMethods #:nodoc:
  public
    # Returns a hash with the pairs from the query string. The implicit hash construction that is done in
    # parse_request_params is not done here.
    def CGIMethods.parse_query_parameters(query_string)
      parsed_params = {}
  
      query_string.split(/[&;]/).each { |p| 
        # Ignore repeated delimiters.
        next if p.empty?

        k, v = p.split('=',2)
        v = nil if (v && v.empty?)

        k = CGI.unescape(k) if k
        v = CGI.unescape(v) if v

        unless k.include?(?[)
          parsed_params[k] = v
        else
          keys = split_key(k)
          last_key = keys.pop
          last_key = keys.pop if (use_array = last_key.empty?)
          parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
          
          if use_array then (parent[last_key] ||= []) << v
          else parent[last_key] = v
          end
        end
      }
  
      parsed_params
    end

    # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" / 
    # "Somewhere cool!" are translated into a full hash hierarchy, like
    # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
    def CGIMethods.parse_request_parameters(params)
      parsed_params = {}

      for key, value in params
        value = [value] if key =~ /.*\[\]$/
        unless key.include?('[')
          # much faster to test for the most common case first (GET)
          # and avoid the call to build_deep_hash
          parsed_params[key] = get_typed_value(value[0])
        else
          build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
        end
      end
    
      parsed_params
    end

    def self.parse_formatted_request_parameters(mime_type, raw_post_data)
      params = case strategy = ActionController::Base.param_parsers[mime_type]
        when Proc
          strategy.call(raw_post_data)
        when :xml_simple
          raw_post_data.blank? ? nil :
            typecast_xml_value(XmlSimple.xml_in(raw_post_data,
              'forcearray'   => false,
              'forcecontent' => true,
              'keeproot'     => true,
              'contentkey'   => '__content__'))
        when :yaml
          YAML.load(raw_post_data)
        when :xml_node
          node = XmlNode.from_xml(raw_post_data)
          { node.node_name => node }
      end
      
      dasherize_keys(params || {})
    rescue Object => e
      { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace, 
        "raw_post_data" => raw_post_data, "format" => mime_type }
    end

    def self.typecast_xml_value(value)
      case value
      when Hash
        if value.has_key?("__content__")
          content = translate_xml_entities(value["__content__"])
          case value["type"]
          when "integer"  then content.to_i
          when "boolean"  then content == "true"
          when "datetime" then Time.parse(content)
          when "date"     then Date.parse(content)
          else                 content
          end
        else
          value.empty? ? nil : value.inject({}) do |h,(k,v)|
            h[k] = typecast_xml_value(v)
            h
          end
        end
      when Array
        value.map! { |i| typecast_xml_value(i) }
        case value.length
        when 0 then nil
        when 1 then value.first
        else value
        end
      else
        raise "can't typecast #{value.inspect}"
      end
    end

  private

    def self.translate_xml_entities(value)
      value.gsub(/&lt;/,   "<").
            gsub(/&gt;/,   ">").
            gsub(/&quot;/, '"').
            gsub(/&apos;/, "'").
            gsub(/&amp;/,  "&")
    end

    def self.dasherize_keys(params)
      case params.class.to_s
      when "Hash"
        params.inject({}) do |h,(k,v)|
          h[k.to_s.tr("-", "_")] = dasherize_keys(v)
          h
        end
      when "Array"
        params.map { |v| dasherize_keys(v) }
      else
        params
      end
    end

    # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
    # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
    # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
    def CGIMethods.split_key(key)
      if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
        keys = [$1]
        
        keys.concat($2[1..-2].split(']['))
        keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
        
        keys
      else
        [key]
      end
    end
    
    def CGIMethods.get_typed_value(value)
      # test most frequent case first
      if value.is_a?(String)
        value
      elsif value.respond_to?(:content_type) && ! value.content_type.blank?
        # Uploaded file
        unless value.respond_to?(:full_original_filename)
          class << value
            alias_method :full_original_filename, :original_filename

            # Take the basename of the upload's original filename.
            # This handles the full Windows paths given by Internet Explorer
            # (and perhaps other broken user agents) without affecting
            # those which give the lone filename.
            # The Windows regexp is adapted from Perl's File::Basename.
            def original_filename
              if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
                md.captures.first
              else
                File.basename full_original_filename
              end
            end
          end
        end

        # Return the same value after overriding original_filename.
        value

      elsif value.respond_to?(:read)
        # Value as part of a multipart request
        value.read
      elsif value.class == Array
        value.collect { |v| CGIMethods.get_typed_value(v) }
      else
        # other value (neither string nor a multipart request)
        value.to_s
      end
    end
  
    PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
    def CGIMethods.get_levels(key)
      all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
      if main.nil?
        []
      elsif trailing
        [key]
      elsif bracketed
        [main] + bracketed.slice(1...-1).split('][')
      else
        [main]
      end
    end

    def CGIMethods.build_deep_hash(value, hash, levels)
      if levels.length == 0
        value
      elsif hash.nil?
        { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
      else
        hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
      end
    end
end
CGI.module_eval { remove_const "Cookie" }

class CGI #:nodoc:
  # This is a cookie class that fixes the performance problems with the default one that ships with 1.8.1 and below.
  # It replaces the inheritance on SimpleDelegator with DelegateClass(Array) following the suggestion from Matz on
  # http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
  class Cookie < DelegateClass(Array)
    # Create 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.
    #
    # These keywords correspond to attributes of the cookie object.
    def initialize(name = '', *value)
      if name.kind_of?(String)
        @name = name
        @value = Array(value)
        @domain = nil
        @expires = nil
        @secure = false
        @path = nil
      else
        @name = name['name']
        @value = Array(name['value'])
        @domain = name['domain']
        @expires = name['expires']
        @secure = name['secure'] || false
        @path = name['path']
      end
      
      unless @name
        raise ArgumentError, "`name' required"
      end

      # simple support for IE
      unless @path
        %r|^(.*/)|.match(ENV['SCRIPT_NAME'])
        @path = ($1 or '')
      end

      super(@value)
    end

    def __setobj__(obj)
      @_dc_obj = obj
    end

    attr_accessor("name", "value", "path", "domain", "expires")
    attr_reader("secure")

    # Set whether the Cookie is a secure cookie or not.
    #
    # +val+ must be a boolean.
    def secure=(val)
      @secure = val if val == true or val == false
      @secure
    end

    # Convert the Cookie to its string representation.
    def to_s
      buf = ""
      buf << @name << '='

      if @value.kind_of?(String)
        buf << CGI::escape(@value)
      else
        buf << @value.collect{|v| CGI::escape(v) }.join("&")
      end

      if @domain
        buf << '; domain=' << @domain
      end

      if @path
        buf << '; path=' << @path
      end

      if @expires
        buf << '; expires=' << CGI::rfc1123_date(@expires)
      end

      if @secure == true
        buf << '; secure'
      end

      buf
    end

    # Parse a raw cookie string into a hash of cookie-name=>Cookie
    # pairs.
    #
    #   cookies = CGI::Cookie::parse("raw_cookie_string")
    #     # { "name1" => cookie1, "name2" => cookie2, ... }
    #
    def self.parse(raw_cookie)
      cookies = Hash.new([])

      if raw_cookie
        raw_cookie.split(/; ?/).each do |pairs|
          name, values = pairs.split('=',2)
          next unless name and values
          name = CGI::unescape(name)
          values = values.split('&').collect!{|v| CGI::unescape(v) }
          unless cookies.has_key?(name)
            cookies[name] = new(name, *values)
          end
        end
      end

      cookies
    end
  end # class Cookie
end
class CGI #:nodoc:
  # Add @request.env['RAW_POST_DATA'] for the vegans.
  module QueryExtension
    # Initialize the data from the query.
    #
    # Handles multipart forms (in particular, forms that involve file uploads).
    # Reads query parameters in the @params field, and cookies into @cookies.
    def initialize_query()
      @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])

      #fix some strange request environments
      if method = env_table['REQUEST_METHOD']
        method = method.to_s.downcase.intern
      else
        method = :get
      end

      if method == :post && (boundary = multipart_form_boundary)
        @multipart = true
        @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
      else
        @multipart = false
        @params = CGI::parse(read_query_params(method) || "")
      end
    end

    private
      unless defined?(MULTIPART_FORM_BOUNDARY_RE)
        MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #"
      end

      def multipart_form_boundary
        MULTIPART_FORM_BOUNDARY_RE.match(env_table['CONTENT_TYPE']).to_a.pop
      end

      if defined? MOD_RUBY
        def read_params_from_query
          Apache::request.args || ''
        end
      else
        def read_params_from_query
          # fixes CGI querystring parsing for lighttpd
          env_qs = env_table['QUERY_STRING']
          if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
            uri.split('?', 2)[1] || ''
          else
            env_qs
          end
        end
      end

      def read_params_from_post
        stdinput.binmode if stdinput.respond_to?(:binmode)
        content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || ''
        # fix for Safari Ajax postings that always append \000
        content.chop! if content[-1] == 0
        content.gsub! /&_=$/, ''
        env_table['RAW_POST_DATA'] = content.freeze
      end

      def read_query_params(method)
        case method
          when :get
            read_params_from_query
          when :post, :put
            read_params_from_post
          when :cmd
            read_from_cmdline
          else # when :head, :delete, :options
            read_params_from_query
        end
      end
  end # module QueryExtension
end
require 'action_controller/cgi_ext/cgi_ext'
require 'action_controller/cgi_ext/cookie_performance_fix'
require 'action_controller/cgi_ext/raw_post_data_fix'

module ActionController #:nodoc:
  class Base
    # Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
    # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
    #
    # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
    #   (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in 
    #   lib/action_controller/session.
    # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
    # * <tt>:session_id</tt> - the session id to use.  If not provided, then it is retrieved from the +session_key+ parameter
    #   of the request, or automatically generated for a new session.
    # * <tt>:new_session</tt> - 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.
    # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object.  If not set, the session will continue
    #   indefinitely.
    # * <tt>:session_domain</tt> -  the hostname domain for which this session is valid. If not set, defaults to the hostname of the
    #   server.
    # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
    # * <tt>:session_path</tt> - the path for which this session applies.  Defaults to the directory of the CGI script.
    def self.process_cgi(cgi = CGI.new, session_options = {}) 
      new.process_cgi(cgi, session_options)
    end
  
    def process_cgi(cgi, session_options = {}) #:nodoc:
      process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
    end
  end

  class CgiRequest < AbstractRequest #:nodoc:
    attr_accessor :cgi, :session_options

    DEFAULT_SESSION_OPTIONS = {
      :database_manager => CGI::Session::PStore,
      :prefix           => "ruby_sess.",
      :session_path     => "/"
    } unless const_defined?(:DEFAULT_SESSION_OPTIONS)

    def initialize(cgi, session_options = {})
      @cgi = cgi
      @session_options = session_options
      @env = @cgi.send(:env_table)
      super()
    end

    def query_string
      if (qs = @cgi.query_string) && !qs.empty?
        qs
      elsif uri = @env['REQUEST_URI']
        parts = uri.split('?')  
        parts.shift
        parts.join('?')
      else
        @env['QUERY_STRING'] || ''
      end
    end

    def query_parameters
      (qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
    end

    def request_parameters
      @request_parameters ||=
        if ActionController::Base.param_parsers.has_key?(content_type)
          CGIMethods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA'])
        else
          CGIMethods.parse_request_parameters(@cgi.params)
        end
    end
   
    def cookies
      @cgi.cookies.freeze
    end

    def host_with_port
      if forwarded = env["HTTP_X_FORWARDED_HOST"]
        forwarded.split(/,\s?/).last
      elsif http_host = env['HTTP_HOST']
        http_host
      elsif server_name = env['SERVER_NAME']
        server_name
      else
        "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
      end
    end

    def host
      host_with_port[/^[^:]+/]
    end

    def port
      if host_with_port =~ /:(\d+)$/
        $1.to_i
      else
        standard_port
      end
    end

    def session
      unless @session
        if @session_options == false
          @session = Hash.new
        else
          stale_session_check! do
            if session_options_with_string_keys['new_session'] == true
              @session = new_session
            else
              @session = CGI::Session.new(@cgi, session_options_with_string_keys)
            end
            @session['__valid_session']
          end
        end
      end
      @session
    end

    def reset_session
      @session.delete if CGI::Session === @session
      @session = new_session
    end

    def method_missing(method_id, *arguments)
      @cgi.send(method_id, *arguments) rescue super
    end

    private
      # Delete an old session if it exists then create a new one.
      def new_session
        if @session_options == false
          Hash.new
        else
          CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
          CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
        end
      end

      def stale_session_check!
        yield
      rescue ArgumentError => argument_error
        if argument_error.message =~ %r{undefined class/module (\w+)}
          begin
            Module.const_missing($1)
          rescue LoadError, NameError => const_error
            raise ActionController::SessionRestoreError, <<end_msg
Session contains objects whose class definition isn\'t available.
Remember to require the classes for all objects kept in the session.
(Original exception: #{const_error.message} [#{const_error.class}])
end_msg
          end

          retry
        else
          raise
        end
      end

      def session_options_with_string_keys
        @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
      end
  end

  class CgiResponse < AbstractResponse #:nodoc:
    def initialize(cgi)
      @cgi = cgi
      super()
    end

    def out(output = $stdout)
      convert_content_type!(@headers)
      output.binmode      if output.respond_to?(:binmode)
      output.sync = false if output.respond_to?(:sync=)
      
      begin
        output.write(@cgi.header(@headers))

        if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
          return
        elsif @body.respond_to?(:call)
          @body.call(self, output)
        else
          output.write(@body)
        end

        output.flush if output.respond_to?(:flush)
      rescue Errno::EPIPE => e
        # lost connection to the FCGI process -- ignore the output, then
      end
    end

    private
      def convert_content_type!(headers)
        if header = headers.delete("Content-Type")
          headers["type"] = header
        end
        if header = headers.delete("Content-type")
          headers["type"] = header
        end
        if header = headers.delete("content-type")
          headers["type"] = header
        end
      end
  end
end
module ActionController
  module CodeGeneration #:nodoc:
    class GenerationError < StandardError #:nodoc:
    end
  
    class Source #:nodoc:
      attr_reader :lines, :indentation_level
      IndentationString = '  '
      def initialize
        @lines, @indentation_level = [], 0
      end
      def line(line)
        @lines << (IndentationString * @indentation_level + line)
      end
      alias :<< :line
    
      def indent
        @indentation_level += 1
        yield
        ensure
        @indentation_level -= 1
      end
    
      def to_s() lines.join("\n") end
    end

    class CodeGenerator #:nodoc:
      attr_accessor :source, :locals
      def initialize(source = nil)
        @locals = []
        @source = source || Source.new
      end
    
      BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym}
      ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym}
      Keywords = BeginKeywords + ResumeKeywords
    
      def method_missing(keyword, *text)
        if Keywords.include? keyword
          if ResumeKeywords.include? keyword
            raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/ 
            source.lines.pop # Remove the 'end'
          end
      
          line "#{keyword} #{text.join ' '}"
          begin source.indent { yield(self.dup) }
          ensure line 'end'
          end
        else
          super(keyword, *text)
        end
      end
    
      def line(*args) self.source.line(*args) end
      alias :<< :line
      def indent(*args, &block) source(*args, &block) end
      def to_s() source.to_s end
    
      def share_locals_with(other)
        other.locals = self.locals = (other.locals | locals) 
      end
    
      FieldsToDuplicate = [:locals]
      def dup
        copy = self.class.new(source)
        self.class::FieldsToDuplicate.each do |sym|
          value = self.send(sym)
          value = value.dup unless value.nil? || value.is_a?(Numeric)
          copy.send("#{sym}=", value)
        end
        return copy
      end
    end

    class RecognitionGenerator < CodeGenerator #:nodoc:
      Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement]
      attr_accessor(*Attributes)
      FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
    
      def initialize(*args)
        super(*args)
        @after, @before = [], []
        @current = nil
        @results, @constants = {}, {}
        @depth = 0
        @move_ahead = nil
        @finish_statement = Proc.new {|hash_expr| hash_expr}
      end
    
      def if_next_matches(string, &block)
        test = Routing.test_condition(next_segment(true), string)
        self.if(test, &block)
      end
    
      def move_forward(places = 1)
        dup = self.dup
        dup.depth += 1
        dup.move_ahead = places
        yield dup
      end
    
      def next_segment(assign_inline = false, default = nil)
        if locals.include?(segment_name)
          code = segment_name
        else
          code = "#{segment_name} = #{path_name}[#{index_name}]"
          if assign_inline
            code = "(#{code})"
          else
            line(code)
            code = segment_name
          end
        
          locals << segment_name
        end
        code = "(#{code} || #{default.inspect})" if default 
      
        return code.to_s
      end
    
      def segment_name() "segment#{depth}".to_sym end
      def path_name() :path end
      def index_name
        move_ahead, @move_ahead = @move_ahead, nil
        move_ahead ? "index += #{move_ahead}" : 'index'
      end
    
      def continue
        dup = self.dup
        dup.before << dup.current
        dup.current = dup.after.shift
        dup.go
      end
    
      def go
        if current then current.write_recognition(self)
        else self.finish
        end
      end 
    
      def result(key, expression, delay = false)
        unless delay
          line "#{key}_value = #{expression}"
          expression = "#{key}_value"
        end
        results[key] = expression
      end
      def constant_result(key, object)
        constants[key] = object
      end
  
      def finish(ensure_traversal_finished = true)
        pairs = [] 
        (results.keys + constants.keys).uniq.each do |key|
          pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}"
        end
        hash_expr = "{#{pairs.join(', ')}}"
      
        statement = finish_statement.call(hash_expr)
        if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement}
        else self << statement
        end
      end
    end
  
    class GenerationGenerator < CodeGenerator #:nodoc:
      Attributes = [:after, :before, :current, :segments]
      attr_accessor(*Attributes)
      FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
    
      def initialize(*args)
        super(*args)
        @after, @before = [], []
        @current = nil
        @segments = []
      end
    
      def hash_name() 'hash' end
      def local_name(key) "#{key}_value" end
    
      def hash_value(key, assign = true, default = nil)
        if locals.include?(local_name(key)) then code = local_name(key)
        else
          code = "hash[#{key.to_sym.inspect}]"
          if assign
            code = "(#{local_name(key)} = #{code})"
            locals << local_name(key)
          end
        end
        code = "(#{code} || (#{default.inspect}))" if default
        return code
      end 
    
      def expire_for_keys(*keys)
        return if keys.empty?
        conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"}
        line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}"
      end
    
      def add_segment(*segments)
        d = dup
        d.segments.concat segments
        yield d
      end
    
      def go
        if current then current.write_generation(self)
        else self.finish
        end
      end
    
      def continue
        d = dup
        d.before << d.current
        d.current = d.after.shift
        d.go
      end
    
      def finish
        line %("/#{segments.join('/')}")
      end

      def check_conditions(conditions)
        tests = []
        generator = nil
        conditions.each do |key, condition|
          tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp
          tests << Routing.test_condition((generator || self).hash_value(key, false), condition)
          generator = self.dup unless generator
        end
        return tests.join(' && ')
      end
    end
  end
end
module ActionController #:nodoc:
  # Components allow you to call other actions for their rendered response while executing another action. You can either delegate
  # the entire response rendering or you can mix a partial response in with your other content.
  #
  #   class WeblogController < ActionController::Base
  #     # Performs a method and then lets hello_world output its render
  #     def delegate_action
  #       do_other_stuff_before_hello_world
  #       render_component :controller => "greeter",  :action => "hello_world", :params => { :person => "david" }
  #     end
  #   end
  #
  #   class GreeterController < ActionController::Base
  #     def hello_world
  #       render :text => "#{params[:person]} says, Hello World!"
  #     end
  #   end
  #
  # The same can be done in a view to do a partial rendering:
  # 
  #   Let's see a greeting: 
  #   <%= render_component :controller => "greeter", :action => "hello_world" %>
  #
  # It is also possible to specify the controller as a class constant, bypassing the inflector
  # code to compute the controller class at runtime:
  # 
  # <%= render_component :controller => GreeterController, :action => "hello_world" %>
  #
  # == When to use components
  #
  # Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
  # conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
  # reserve components to those rare cases where you truly have reusable view and controller elements that can be employed 
  # across many applications at once.
  #
  # So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
  module Components
    def self.included(base) #:nodoc:
      base.send :include, InstanceMethods
      base.extend(ClassMethods)

      base.helper do
        def render_component(options) 
          @controller.send(:render_component_as_string, options)
        end
      end
            
      # If this controller was instantiated to process a component request,
      # +parent_controller+ points to the instantiator of this controller.
      base.send :attr_accessor, :parent_controller
      
      base.class_eval do
        alias_method :process_cleanup_without_components, :process_cleanup
        alias_method :process_cleanup, :process_cleanup_with_components
        
        alias_method :set_session_options_without_components, :set_session_options
        alias_method :set_session_options, :set_session_options_with_components
        
        alias_method :flash_without_components, :flash
        alias_method :flash, :flash_with_components

        alias_method :component_request?, :parent_controller       
      end
    end

    module ClassMethods
      # Track parent controller to identify component requests
      def process_with_components(request, response, parent_controller = nil) #:nodoc:
        controller = new
        controller.parent_controller = parent_controller
        controller.process(request, response)
      end

      # Set the template root to be one directory behind the root dir of the controller. Examples:
      #   /code/weblog/components/admin/users_controller.rb with Admin::UsersController 
      #     will use /code/weblog/components as template root 
      #     and find templates in /code/weblog/components/admin/users/
      #
      #   /code/weblog/components/admin/parties/users_controller.rb with Admin::Parties::UsersController 
      #     will also use /code/weblog/components as template root 
      #     and find templates in /code/weblog/components/admin/parties/users/
      def uses_component_template_root
        path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/).first)
        path_of_controller_root    = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode)

        self.template_root = path_of_controller_root
      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:
        flash.discard if component_request?
        process_without_components(request, response, method, *arguments)
      end
      
      protected
        # Renders the component specified as the response for the current method
        def render_component(options) #:doc:
          component_logging(options) do
            render_text(component_response(options, true).body, response.headers["Status"])
          end
        end

        # Returns the component response as a string
        def render_component_as_string(options) #:doc:
          component_logging(options) do
            response = component_response(options, false)

            if redirected = response.redirected_to
              render_component_as_string(redirected)
            else
              response.body
            end
          end
        end

        def flash_with_components(refresh = false) #:nodoc:
          if @flash.nil? || refresh
            @flash = 
              if @parent_controller
                @parent_controller.flash
              else
                flash_without_components
              end
          end
          
          @flash
        end

      private
        def component_response(options, reuse_response)
          klass    = component_class(options)
          request  = request_for_component(klass.controller_name, options)
          response = reuse_response ? @response : @response.dup

          klass.process_with_components(request, response, self)
        end
        
        # determine the controller class for the component request
        def component_class(options)
          if controller = options[:controller]
            controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize
          else
            self.class
          end
        end
        
        # Create a new request object based on the current request.
        # The new request inherits the session from the current request,
        # bypassing any session options set for the component controller's class
        def request_for_component(controller_name, options)
          request         = @request.dup
          request.session = @request.session
        
          request.instance_variable_set(
            :@parameters,
            (options[:params] || {}).with_indifferent_access.update(
              "controller" => controller_name, "action" => options[:action], "id" => options[:id]
            )
          )
          
          request
        end

        def component_logging(options)
          if logger
            logger.info "Start rendering component (#{options.inspect}): "
            result = yield
            logger.info "\n\nEnd of component rendering"
            result
          else
            yield
          end
        end

        def set_session_options_with_components(request)
          set_session_options_without_components(request) unless component_request?
        end

        def process_cleanup_with_components
          process_cleanup_without_components unless component_request?
        end
    end
  end
end
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[:user_name] = "david" # => Will set a simple session cookie
  #   cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour
  #   
  # Examples for reading:
  #
  #   cookies[:user_name] # => "david"
  #   cookies.size         # => 2
  # 
  # Example for deleting:
  #
  #   cookies.delete :user_name
  #
  # All the option symbols for setting cookies are:
  #
  # * <tt>value</tt> - the cookie's value or list of values (as an array).
  # * <tt>path</tt> - the path for which this cookie applies.  Defaults to the root of the application.
  # * <tt>domain</tt> - the domain for which this cookie applies.
  # * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
  # * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
  #   Secure cookies are only transmitted to HTTPS servers.
  module Cookies
    protected
      # Returns the cookie container, which operates as described above.
      def cookies
        CookieJar.new(self)
      end

      # Deprecated cookie writer method
      def cookie(*options)
        @response.headers["cookie"] << CGI::Cookie.new(*options)
      end
  end
  
  class CookieJar < Hash #:nodoc:
    def initialize(controller)
      @controller, @cookies = controller, controller.instance_variable_get("@cookies")
      super()
      update(@cookies)
    end

    # Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using either the cookie method
    # or cookies[]= (for simple name/value cookies without options).
    def [](name)
      @cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
    end
    
    def []=(name, options)
      if options.is_a?(Hash)
        options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
        options["name"] = name.to_s
      else
        options = { "name" => name.to_s, "value" => options }
      end
      
      set_cookie(options)
    end
    
    # Removes the cookie on the client machine by setting the value to an empty string
    # and setting its expiration date into the past
    def delete(name)
      set_cookie("name" => name.to_s, "value" => "", "expires" => Time.at(0))
    end

    private
      def set_cookie(options) #:doc:
        options["path"] = "/" unless options["path"]
        cookie = CGI::Cookie.new(options)
        @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
        @controller.response.headers["cookie"] << cookie
      end
  end
end
module ActionController #:nodoc:
  module Dependencies #:nodoc:
    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    # Dependencies control what classes are needed for the controller to run its course. This is an alternative to doing explicit
    # +require+ statements that bring a number of benefits. It's more succinct, communicates what type of dependency we're talking about,
    # can trigger special behavior (as in the case of +observer+), and enables Rails to be clever about reloading in cached environments
    # like FCGI. Example:
    #
    #   class ApplicationController < ActionController::Base
    #     model    :account, :company, :person, :project, :category
    #     helper   :access_control
    #     service  :notifications, :billings
    #     observer :project_change_observer
    #   end
    #
    # Please note that a controller like ApplicationController will automatically attempt to require_dependency on a model of its 
    # singuralized name and a helper of its name. If nothing is found, no error is raised. This is especially useful for concrete 
    # controllers like PostController:
    #
    #   class PostController < ApplicationController
    #     # model  :post (already required)
    #     # helper :post (already required)
    #   end
    #
    # Also note, that if the models follow the pattern of just 1 class per file in the form of MyClass => my_class.rb, then these
    # classes don't have to be required as Active Support will auto-require them.
    module ClassMethods #:nodoc:
      # Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
      # backend for modelling entity classes.
      def model(*models)
        require_dependencies(:model, models)
        depend_on(:model, models)
      end

      # Specifies a variable number of services that this controller depends on. Services are normally singletons or factories, like
      # Action Mailer service or a Payment Gateway service.
      def service(*services)
        require_dependencies(:service, services)
        depend_on(:service, services)
      end
      
      # Specifies a variable number of observers that are to govern when this controller is handling actions. The observers will
      # automatically have .instance called on them to make them active on assignment.
      def observer(*observers)
        require_dependencies(:observer, observers)
        depend_on(:observer, observers)
        instantiate_observers(observers)
      end

      # Returns an array of symbols that specify the dependencies on a given layer. For the example at the top, calling
      # <tt>ApplicationController.dependencies_on(:model)</tt> would return <tt>[:account, :company, :person, :project, :category]</tt>
      def dependencies_on(layer)
        read_inheritable_attribute("#{layer}_dependencies")
      end
    
      def depend_on(layer, dependencies) #:nodoc:
        write_inheritable_array("#{layer}_dependencies", dependencies)
      end

      private
        def instantiate_observers(observers)
          observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance }
        end
        
        def require_dependencies(layer, dependencies)
          dependencies.flatten.each do |dependency|
            begin
              require_dependency(dependency.to_s)
            rescue LoadError => e
              raise LoadError.new("Missing #{layer} #{dependency}.rb").copy_blame!(e)
            rescue Object => exception
              exception.blame_file! "=> #{layer} #{dependency}.rb"
              raise
            end
          end
        end
    end
  end
end
require 'test/unit'
require 'test/unit/assertions'
require 'rexml/document'

module Test #:nodoc:
  module Unit #:nodoc:
    module Assertions
      def assert_success(message=nil) #:nodoc:
        assert_response(:success, message)
      end

      def assert_redirect(message=nil) #:nodoc:
        assert_response(:redirect, message)
      end

      def assert_rendered_file(expected=nil, message=nil) #:nodoc:
        assert_template(expected, message)
      end

      # ensure that the session has an object with the specified name
      def assert_session_has(key=nil, message=nil) #:nodoc:
        msg = build_message(message, "<?> is not in the session <?>", key, @response.session)
        assert_block(msg) { @response.has_session_object?(key) }
      end

      # ensure that the session has no object with the specified name
      def assert_session_has_no(key=nil, message=nil) #:nodoc:
        msg = build_message(message, "<?> is in the session <?>", key, @response.session)
        assert_block(msg) { !@response.has_session_object?(key) }
      end

      def assert_session_equal(expected = nil, key = nil, message = nil) #:nodoc:
        msg = build_message(message, "<?> expected in session['?'] but was <?>", expected, key, @response.session[key])
        assert_block(msg) { expected == @response.session[key] }
      end

      # -- cookie assertions ---------------------------------------------------

      def assert_no_cookie(key = nil, message = nil) #:nodoc:
        actual = @response.cookies[key]
        msg = build_message(message, "<?> not expected in cookies['?']", actual, key)
        assert_block(msg) { actual.nil? or actual.empty? }
      end
    
      def assert_cookie_equal(expected = nil, key = nil, message = nil) #:nodoc:
        actual = @response.cookies[key]
        actual = actual.first if actual
        msg = build_message(message, "<?> expected in cookies['?'] but was <?>", expected, key, actual)
        assert_block(msg) { expected == actual }
      end
    
      # -- flash assertions ---------------------------------------------------

      # ensure that the flash has an object with the specified name
      def assert_flash_has(key=nil, message=nil) #:nodoc:
        msg = build_message(message, "<?> is not in the flash <?>", key, @response.flash)
        assert_block(msg) { @response.has_flash_object?(key) }
      end

      # ensure that the flash has no object with the specified name
      def assert_flash_has_no(key=nil, message=nil) #:nodoc:
        msg = build_message(message, "<?> is in the flash <?>", key, @response.flash)
        assert_block(msg) { !@response.has_flash_object?(key) }
      end

      # ensure the flash exists
      def assert_flash_exists(message=nil) #:nodoc:
        msg = build_message(message, "the flash does not exist <?>", @response.session['flash'] )
        assert_block(msg) { @response.has_flash? }
      end

      # ensure the flash does not exist
      def assert_flash_not_exists(message=nil) #:nodoc:
        msg = build_message(message, "the flash exists <?>", @response.flash)
        assert_block(msg) { !@response.has_flash? }
      end
    
      # ensure the flash is empty but existent
      def assert_flash_empty(message=nil) #:nodoc:
        msg = build_message(message, "the flash is not empty <?>", @response.flash)
        assert_block(msg) { !@response.has_flash_with_contents? }
      end

      # ensure the flash is not empty
      def assert_flash_not_empty(message=nil) #:nodoc:
        msg = build_message(message, "the flash is empty")
        assert_block(msg) { @response.has_flash_with_contents? }
      end
    
      def assert_flash_equal(expected = nil, key = nil, message = nil) #:nodoc:
        msg = build_message(message, "<?> expected in flash['?'] but was <?>", expected, key, @response.flash[key])
        assert_block(msg) { expected == @response.flash[key] }
      end
    

      # ensure our redirection url is an exact match
      def assert_redirect_url(url=nil, message=nil) #:nodoc:
        assert_redirect(message)
        msg = build_message(message, "<?> is not the redirected location <?>", url, @response.redirect_url)
        assert_block(msg) { @response.redirect_url == url }
      end

      # ensure our redirection url matches a pattern
      def assert_redirect_url_match(pattern=nil, message=nil) #:nodoc:
        assert_redirect(message)
        msg = build_message(message, "<?> was not found in the location: <?>", pattern, @response.redirect_url)
        assert_block(msg) { @response.redirect_url_match?(pattern) }
      end

    
      # -- template assertions ------------------------------------------------

      # ensure that a template object with the given name exists
      def assert_template_has(key=nil, message=nil) #:nodoc:
        msg = build_message(message, "<?> is not a template object", key )
        assert_block(msg) { @response.has_template_object?(key) }
      end

      # ensure that a template object with the given name does not exist
      def assert_template_has_no(key=nil,message=nil) #:nodoc:
        msg = build_message(message, "<?> is a template object <?>", key, @response.template_objects[key])
        assert_block(msg) { !@response.has_template_object?(key) }
      end

      # ensures that the object assigned to the template on +key+ is equal to +expected+ object.
      def assert_template_equal(expected = nil, key = nil, message = nil) #:nodoc:
        msg = build_message(message, "<?> expected in assigns['?'] but was <?>", expected, key, @response.template.assigns[key.to_s])
        assert_block(msg) { expected == @response.template.assigns[key.to_s] }
      end
      alias_method :assert_assigned_equal, :assert_template_equal

      # Asserts that the template returns the +expected+ string or array based on the XPath +expression+.
      # This will only work if the template rendered a valid XML document.
      def assert_template_xpath_match(expression=nil, expected=nil, message=nil) #:nodoc:
        xml, matches = REXML::Document.new(@response.body), []
        xml.elements.each(expression) { |e| matches << e.text }
        if matches.empty? then
          msg = build_message(message, "<?> not found in document", expression)
          flunk(msg)
          return
        elsif matches.length < 2 then
          matches = matches.first
        end

        msg = build_message(message, "<?> found <?>, not <?>", expression, matches, expected)
        assert_block(msg) { matches == expected }
      end

      # Assert the template object with the given name is an Active Record descendant and is valid.
      def assert_valid_record(key = nil, message = nil) #:nodoc:
        record = find_record_in_template(key)
        msg = build_message(message, "Active Record is invalid <?>)", record.errors.full_messages)
        assert_block(msg) { record.valid? }
      end

      # Assert the template object with the given name is an Active Record descendant and is invalid.
      def assert_invalid_record(key = nil, message = nil) #:nodoc:
        record = find_record_in_template(key)
        msg = build_message(message, "Active Record is valid)")
        assert_block(msg) { !record.valid? }
      end

      # Assert the template object with the given name is an Active Record descendant and the specified column(s) are valid.
      def assert_valid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
        record = find_record_in_template(key)
        record.send(:validate)

        cols = glue_columns(columns)
        cols.delete_if { |col| !record.errors.invalid?(col) }
        msg = build_message(message, "Active Record has invalid columns <?>)", cols.join(",") )
        assert_block(msg) { cols.empty? }
      end

      # Assert the template object with the given name is an Active Record descendant and the specified column(s) are invalid.
      def assert_invalid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
        record = find_record_in_template(key)
        record.send(:validate)

        cols = glue_columns(columns)
        cols.delete_if { |col| record.errors.invalid?(col) }
        msg = build_message(message, "Active Record has valid columns <?>)", cols.join(",") )
        assert_block(msg) { cols.empty? }
      end

      private
        def glue_columns(columns)
          cols = []
          cols << columns if columns.class == String
          cols += columns if columns.class == Array
          cols
        end

        def find_record_in_template(key = nil)
          assert_template_has(key)
          record = @response.template_objects[key]

          assert_not_nil(record)
          assert_kind_of ActiveRecord::Base, record

          return record
        end      
    end
  end
endmodule ActionController
  class Base
    protected
      # Deprecated in favor of calling redirect_to directly with the path.
      def redirect_to_path(path) #:nodoc:
        redirect_to(path)
      end

      # Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass
      # true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through
      # just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to.
      def redirect_to_url(url, permanently = false) #:nodoc:
        headers["Status"] = "301 Moved Permanently" if permanently
        redirect_to(url)
      end
  end
end
module ActionController
  class AbstractRequest
    # Determine whether the body of a HTTP call is URL-encoded (default)
    # or matches one of the registered param_parsers. 
    #
    # For backward compatibility, the post format is extracted from the
    # X-Post-Data-Format HTTP header if present.
    def post_format
      case content_type.to_s
      when 'application/xml'
        :xml
      when 'application/x-yaml'
        :yaml
      else
        :url_encoded
      end
    end

    # Is this a POST request formatted as XML or YAML?
    def formatted_post?
      post? && (post_format == :yaml || post_format == :xml)
    end

    # Is this a POST request formatted as XML?
    def xml_post?
      post? && post_format == :xml
    end

    # Is this a POST request formatted as YAML?
    def yaml_post?
      post? && post_format == :yaml
    end
  end
end
module ActionController #:nodoc:
  module Filters #:nodoc:
    def self.included(base)
      base.extend(ClassMethods)
      base.send(:include, ActionController::Filters::InstanceMethods)
    end

    # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do 
    # authentication, caching, or auditing before the intended action is performed. Or to do localization or output 
    # compression after the action has been performed.
    #
    # Filters have access to the request, response, and all the instance variables set by other filters in the chain
    # or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
    # to halt the processing before the intended action is processed by returning false or performing a redirect or render. 
    # This is especially useful for filters like authentication where you're not interested in allowing the action to be 
    # performed if the proper credentials are not in order.
    #
    # == Filter inheritance
    #
    # Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
    # affecting the superclass. For example:
    #
    #   class BankController < ActionController::Base
    #     before_filter :audit
    #
    #     private
    #       def audit
    #         # record the action and parameters in an audit log
    #       end
    #   end
    #
    #   class VaultController < BankController
    #     before_filter :verify_credentials
    #
    #     private
    #       def verify_credentials
    #         # make sure the user is allowed into the vault
    #       end
    #   end
    #
    # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
    # first the audit method is called, then the verify_credentials method. If the audit method returns false, then 
    # verify_credentials and the intended action are never called.
    #
    # == Filter types
    #
    # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
    # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
    # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
    #
    # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
    # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
    #
    #   class OutputCompressionFilter
    #     def self.filter(controller)
    #       controller.response.body = compress(controller.response.body)
    #     end
    #   end
    #
    #   class NewspaperController < ActionController::Base
    #     after_filter OutputCompressionFilter
    #   end
    #
    # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
    # manipulate them as it sees fit.
    #
    # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation. 
    # Or just as a quick test. It works like this:
    #
    #   class WeblogController < ActionController::Base
    #     before_filter { |controller| false if controller.params["stop_action"] }
    #   end
    #
    # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
    # This means that the block has access to both the request and response objects complete with convenience methods for params,
    # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
    # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
    #
    # == Filter chain ordering
    #
    # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
    # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
    # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
    # beginning of their respective chain and executed before the rest. For example:
    #
    #   class ShoppingController
    #     before_filter :verify_open_shop
    #
    #   class CheckoutController
    #     prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
    #
    # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
    # <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop 
    # is open or not.
    #
    # You may pass multiple filter arguments of each type as well as a filter block.
    # If a block is given, it is treated as the last argument.
    #
    # == Around filters
    #
    # In addition to the individual before and after filters, it's also possible to specify that a single object should handle
    # both the before and after call. That's especially useful when you need to keep state active between the before and after,
    # such as the example of a benchmark filter below:
    # 
    #   class WeblogController < ActionController::Base
    #     around_filter BenchmarkingFilter.new
    #     
    #     # Before this action is performed, BenchmarkingFilter#before(controller) is executed
    #     def index
    #     end
    #     # After this action has been performed, BenchmarkingFilter#after(controller) is executed
    #   end
    #
    #   class BenchmarkingFilter
    #     def initialize
    #       @runtime
    #     end
    #     
    #     def before
    #       start_timer
    #     end
    #     
    #     def after
    #       stop_timer
    #       report_result
    #     end
    #   end
    #
    # == Filter chain skipping
    #
    # Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the 
    # subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
    # they would like to be relieved of. Examples
    #
    #   class ApplicationController < ActionController::Base
    #     before_filter :authenticate
    #   end
    #
    #   class WeblogController < ApplicationController
    #     # will run the :authenticate filter
    #   end
    #
    #   class SignupController < ApplicationController
    #     # will not run the :authenticate filter
    #     skip_before_filter :authenticate
    #   end
    #
    # == Filter conditions
    #
    # Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
    # exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both 
    # of which accept an arbitrary number of method references. For example:
    #
    #   class Journal < ActionController::Base
    #     # only require authentication if the current action is edit or delete
    #     before_filter :authorize, :only => [ :edit, :delete ]
    #    
    #     private
    #       def authorize
    #         # redirect to login unless authenticated
    #       end
    #   end
    # 
    # When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.
    #
    #   class UserPreferences < ActionController::Base
    #     before_filter(:except => :new) { # some proc ... }
    #     # ...
    #   end
    #
    module ClassMethods
      # The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
      # on this controller are performed.
      def append_before_filter(*filters, &block)
        conditions = extract_conditions!(filters)
        filters << block if block_given?
        add_action_conditions(filters, conditions)
        append_filter_to_chain('before', filters)
      end

      # The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
      # on this controller are performed.
      def prepend_before_filter(*filters, &block)
        conditions = extract_conditions!(filters) 
        filters << block if block_given?
        add_action_conditions(filters, conditions)
        prepend_filter_to_chain('before', filters)
      end

      # Short-hand for append_before_filter since that's the most common of the two.
      alias :before_filter :append_before_filter
      
      # The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
      # on this controller are performed.
      def append_after_filter(*filters, &block)
        conditions = extract_conditions!(filters) 
        filters << block if block_given?
        add_action_conditions(filters, conditions)
        append_filter_to_chain('after', filters)
      end

      # The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
      # on this controller are performed.
      def prepend_after_filter(*filters, &block)
        conditions = extract_conditions!(filters) 
        filters << block if block_given?
        add_action_conditions(filters, conditions)
        prepend_filter_to_chain("after", filters)
      end

      # Short-hand for append_after_filter since that's the most common of the two.
      alias :after_filter :append_after_filter
      
      # The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
      # on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all 
      # respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
      #
      #   B#before
      #     A#before
      #     A#after
      #   B#after
      def append_around_filter(*filters)
        conditions = extract_conditions!(filters) 
        for filter in filters.flatten
          ensure_filter_responds_to_before_and_after(filter)
          append_before_filter(conditions || {}) { |c| filter.before(c) }
          prepend_after_filter(conditions || {}) { |c| filter.after(c) }
        end
      end        

      # The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
      # on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all 
      # respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
      #
      #   A#before
      #     B#before
      #     B#after
      #   A#after
      def prepend_around_filter(*filters)
        for filter in filters.flatten
          ensure_filter_responds_to_before_and_after(filter)
          prepend_before_filter { |c| filter.before(c) }
          append_after_filter   { |c| filter.after(c) }
        end
      end     

      # Short-hand for append_around_filter since that's the most common of the two.
      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 <tt>:only</tt> and <tt>:except</tt> options, 
      # just like when you apply the filters.
      def skip_before_filter(*filters)
        if conditions = extract_conditions!(filters)
          remove_contradicting_conditions!(filters, conditions)
          conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
          add_action_conditions(filters, conditions)
        else
          for filter in filters.flatten
            write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ])
          end
        end
      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 <tt>:only</tt> and <tt>:except</tt> options, 
      # just like when you apply the filters.
      def skip_after_filter(*filters)
        if conditions = extract_conditions!(filters)
          remove_contradicting_conditions!(filters, conditions)
          conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
          add_action_conditions(filters, conditions)
        else
          for filter in filters.flatten
            write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ])
          end
        end
      end
      
      # Returns all the before filters for this class and all its ancestors.
      def before_filters #:nodoc:
        @before_filters ||= read_inheritable_attribute("before_filters") || []
      end
      
      # Returns all the after filters for this class and all its ancestors.
      def after_filters #:nodoc:
        @after_filters ||= read_inheritable_attribute("after_filters") || []
      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
      
      private
        def append_filter_to_chain(condition, filters)
          write_inheritable_array("#{condition}_filters", filters)
        end

        def prepend_filter_to_chain(condition, filters)
          old_filters = read_inheritable_attribute("#{condition}_filters") || []
          write_inheritable_attribute("#{condition}_filters", filters + old_filters)
        end

        def ensure_filter_responds_to_before_and_after(filter)
          unless filter.respond_to?(:before) && filter.respond_to?(:after)
            raise ActionControllerError, "Filter object must respond to both before and after"
          end
        end

        def extract_conditions!(filters)
          return nil unless filters.last.is_a? Hash
          filters.pop
        end

        def add_action_conditions(filters, conditions)
          return unless conditions
          included, excluded = conditions[:only], conditions[:except]
          write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included
          write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded
        end

        def condition_hash(filters, *actions)
          filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})}
        end
        
        def remove_contradicting_conditions!(filters, conditions)
          return unless conditions[:only]
          filters.each do |filter|
            next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter]
            [*conditions[:only]].each do |conditional_action|
              conditional_action = conditional_action.to_s
              included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action)
            end
          end
        end
    end

    module InstanceMethods # :nodoc:
      def self.included(base)
        base.class_eval do
          alias_method :perform_action_without_filters, :perform_action
          alias_method :perform_action, :perform_action_with_filters

          alias_method :process_without_filters, :process
          alias_method :process, :process_with_filters

          alias_method :process_cleanup_without_filters, :process_cleanup
          alias_method :process_cleanup, :process_cleanup_with_filters
        end
      end

      def perform_action_with_filters
        before_action_result = before_action

        unless before_action_result == false || performed?
          perform_action_without_filters
          after_action
        end

        @before_filter_chain_aborted = (before_action_result == false)
      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

      # Calls all the defined before-filter filters, which are added by using "before_filter :method".
      # If any of the filters return false, no more filters will be executed and the action is aborted.
      def before_action #:doc:
        call_filters(self.class.before_filters)
      end

      # Calls all the defined after-filter filters, which are added by using "after_filter :method".
      # If any of the filters return false, no more filters will be executed.
      def after_action #:doc:
        call_filters(self.class.after_filters)
      end
      
      private
        def call_filters(filters)
          filters.each do |filter| 
            next if action_exempted?(filter)

            filter_result = case
              when filter.is_a?(Symbol)
                self.send(filter)
              when filter_block?(filter)
                filter.call(self)
              when filter_class?(filter)
                filter.filter(self)
              else
                raise(
                  ActionControllerError, 
                  'Filters need to be either a symbol, proc/method, or class implementing a static filter method'
                )
            end

            if filter_result == false
              logger.info "Filter chain halted as [#{filter}] returned false" if logger
              return false 
            end
          end
        end
        
        def filter_block?(filter)
          filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)
        end
        
        def filter_class?(filter)
          filter.respond_to?('filter')
        end

        def action_exempted?(filter)
          case
            when ia = self.class.included_actions[filter]
              !ia.include?(action_name)
            when ea = self.class.excluded_actions[filter] 
              ea.include?(action_name)
          end
        end

        def process_cleanup_with_filters
          if @before_filter_chain_aborted
            close_session
          else
            process_cleanup_without_filters
          end
        end
    end
  end
end
module ActionController #:nodoc:
  # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
  # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
  # that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can then expose 
  # the flash to its template. Actually, that exposure is automatically done. Example:
  #
  #   class WeblogController < ActionController::Base
  #     def create
  #       # save post
  #       flash[:notice] = "Successfully created post"
  #       redirect_to :action => "display", :params => { :id => post.id }
  #     end
  #
  #     def display
  #       # doesn't need to assign the flash notice to the template, that's done automatically
  #     end
  #   end
  #
  #   display.rhtml
  #     <% if @flash[:notice] %><div class="notice"><%= @flash[:notice] %></div><% end %>
  #
  # This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
  # as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
  #
  # See docs on the FlashHash class for more details about the flash.
  module Flash
    def self.included(base)
      base.send :include, InstanceMethods

      base.class_eval do
        alias_method :assign_shortcuts_without_flash, :assign_shortcuts
        alias_method :assign_shortcuts, :assign_shortcuts_with_flash

        alias_method :process_cleanup_without_flash, :process_cleanup
        alias_method :process_cleanup, :process_cleanup_with_flash
      end
    end
    
    
    class FlashNow #:nodoc:
      def initialize(flash)
        @flash = flash
      end
      
      def []=(k, v)
        @flash[k] = v
        @flash.discard(k)
        v
      end
      
      def [](k)
        @flash[k]
      end
    end
    
    class FlashHash < Hash
      def initialize #:nodoc:
        super
        @used = {}
      end
      
      def []=(k, v) #:nodoc:
        keep(k)
        super
      end
      
      def update(h) #:nodoc:
        h.keys.each{ |k| discard(k) }
        super
      end
      
      alias :merge! :update
      
      def replace(h) #:nodoc:
        @used = {}
        super
      end
    
      # Sets a flash that will not be available to the next action, only to the current.
      #
      #     flash.now[:message] = "Hello current action"
      # 
      # This method enables you to use the flash as a central messaging system in your app.
      # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
      # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
      # vanish when the current action is done.
      #
      # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
      def now
        FlashNow.new self
      end
    
      # Keeps either the entire current flash or a specific flash entry available for the next action:
      #
      #    flash.keep            # keeps the entire flash
      #    flash.keep(:notice)   # keeps only the "notice" entry, the rest of the flash is discarded
      def keep(k=nil)
        use(k, false)
      end
    
      # Marks the entire flash or a single flash entry to be discarded by the end of the current action
      #
      #     flash.keep                 # keep entire flash available for the next action
      #     flash.discard(:warning)    # discard the "warning" entry (it'll still be available for the current action)
      def discard(k=nil)
        use(k)
      end
    
      # Mark for removal entries that were kept, and delete unkept ones.
      #
      # This method is called automatically by filters, so you generally don't need to care about it.
      def sweep #:nodoc:
        keys.each do |k| 
          unless @used[k]
            use(k)
          else
            delete(k)
            @used.delete(k)
          end
        end
        (@used.keys - keys).each{|k| @used.delete k } # clean up after keys that could have been left over by calling reject! or shift on the flash
      end
    
      private
        # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
        #     use()               # marks the entire flash as used
        #     use('msg')          # marks the "msg" entry as used
        #     use(nil, false)     # marks the entire flash as unused (keeps it around for one more action)
        #     use('msg', false)   # marks the "msg" entry as unused (keeps it around for one more action)
        def use(k=nil, v=true)
          unless k.nil?
            @used[k] = v
          else
            keys.each{|key| use key, v }
          end
        end
    end

    module InstanceMethods #: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
      end
      
      protected 
        # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or 
        # <tt>flash["notice"] = "hello"</tt> to put a new one.
        # Note that if sessions are disabled only flash.now will work.
        def flash(refresh = false) #:doc:
          if @flash.nil? || refresh
            @flash = 
              if @session.is_a?(Hash)
                # @session is a Hash, if sessions are disabled
                # we don't put the flash in the session in this case
                FlashHash.new
              else
                # otherwise, @session is a CGI::Session or a TestSession
                # so make sure it gets retrieved from/saved to session storage after request processing
                @session["flash"] ||= FlashHash.new
              end
          end
          
          @flash
        end

        # deprecated. use <tt>flash.keep</tt> instead
        def keep_flash #:doc:
          warn 'keep_flash is deprecated; use flash.keep instead.'
          flash.keep
        end
    end
  end
endmodule ActionController #:nodoc:
  module Helpers #:nodoc:
    def self.append_features(base)
      super

      # Initialize the base module to aggregate its helpers.
      base.class_inheritable_accessor :master_helper_module
      base.master_helper_module = Module.new

      # Extend base with class methods to declare helpers.
      base.extend(ClassMethods)

      base.class_eval do
        # Wrap inherited to create a new master helper module for subclasses.
        class << self
          alias_method :inherited_without_helper, :inherited
          alias_method :inherited, :inherited_with_helper
        end
      end
    end

    # The template helpers serve to relieve the templates from including the same inline code again and again. It's a
    # set of standardized methods for working with forms (FormHelper), dates (DateHelper), texts (TextHelper), and 
    # Active Records (ActiveRecordHelper) that's available to all templates by default.
    #
    # It's also really easy to make your own helpers and it's much encouraged to keep the template files free
    # from complicated logic. It's even encouraged to bundle common compositions of methods from other helpers 
    # (often the common helpers) as they're used by the specific application.
    # 
    #   module MyHelper
    #     def hello_world() "hello world" end
    #   end
    # 
    # MyHelper can now be included in a controller, like this:
    # 
    #   class MyController < ActionController::Base
    #     helper :my_helper
    #   end
    # 
    # ...and, same as above, used in any template rendered from MyController, like this:
    # 
    # Let's hear what the helper has to say: <tt><%= hello_world %></tt>
    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 
      # available to the templates.
      def add_template_helper(helper_module) #:nodoc:
        master_helper_module.send(:include, helper_module)
      end

      # Declare a helper:
      #   helper :foo
      # requires 'foo_helper' and includes FooHelper in the template class.
      #   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.
      #   helper(:three, BlindHelper) { def mice() 'mice' end }
      # does all three.
      def helper(*args, &block)
        args.flatten.each do |arg|
          case arg
            when Module
              add_template_helper(arg)
            when String, Symbol
              file_name  = arg.to_s.underscore + '_helper'
              class_name = file_name.camelize
                
              begin
                require_dependency(file_name)
              rescue LoadError => load_error
                requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
                msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
                raise LoadError.new(msg).copy_blame!(load_error)
              end

              add_template_helper(class_name.constantize)
            else
              raise ArgumentError, 'helper expects String, Symbol, or Module argument'
          end
        end

        # Evaluate block in template class if given.
        master_helper_module.module_eval(&block) if block_given?
      end

      # Declare a controller method as a helper.  For example,
      #   helper_method :link_to
      #   def link_to(name, options) ... end
      # makes the link_to controller method available in the view.
      def helper_method(*methods)
        methods.flatten.each do |method|
          master_helper_module.module_eval <<-end_eval
            def #{method}(*args, &block)
              controller.send(%(#{method}), *args, &block)
            end
          end_eval
        end
      end

      # Declare a controller attribute as a helper.  For example,
      #   helper_attr :name
      #   attr_accessor :name
      # makes the name and name= controller methods available in the view.
      # The is a convenience wrapper for helper_method.
      def helper_attr(*attrs)
        attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
      end

      private 
        def default_helper_module!
          module_name = name.sub(/Controller$|$/, 'Helper')
          module_path = module_name.split('::').map { |m| m.underscore }.join('/')
          require_dependency module_path
          helper module_name.constantize
        rescue LoadError
          logger.debug("#{name}: missing default helper path #{module_path}") if logger
        rescue NameError
          logger.debug("#{name}: missing default helper module #{module_name}") if logger
        end

        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.send :default_helper_module!
          rescue MissingSourceFile => e
            raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
          end
        end
    end
  end
end
require 'dispatcher'
require 'stringio'
require 'uri'

module ActionController
  module Integration #:nodoc:
    # An integration Session instance represents a set of requests and responses
    # performed sequentially by some virtual user. Becase you can instantiate
    # multiple sessions and run them side-by-side, you can also mimic (to some
    # limited extent) multiple simultaneous users interacting with your system.
    #
    # Typically, you will instantiate a new session using IntegrationTest#open_session,
    # rather than instantiating Integration::Session directly.
    class Session
      include Test::Unit::Assertions
      include ActionController::TestProcess

      # The integer HTTP status code of the last request.
      attr_reader :status

      # The status message that accompanied the status code of the last request.
      attr_reader :status_message

      # The URI of the last request.
      attr_reader :path

      # The hostname used in the last request.
      attr_accessor :host

      # The remote_addr used in the last request.
      attr_accessor :remote_addr

      # The Accept header to send.
      attr_accessor :accept

      # A map of the cookies returned by the last response, and which will be
      # sent with the next request.
      attr_reader :cookies

      # A map of the headers returned by the last response.
      attr_reader :headers

      # A reference to the controller instance used by the last request.
      attr_reader :controller

      # A reference to the request instance used by the last request.
      attr_reader :request

      # A reference to the response instance used by the last request.
      attr_reader :response

      # Create an initialize a new Session instance.
      def initialize
        reset!
      end

      # Resets the instance. This can be used to reset the state information
      # in an existing session instance, so it can be used from a clean-slate
      # condition.
      #
      #   session.reset!
      def reset!
        @status = @path = @headers = nil
        @result = @status_message = nil
        @https = false
        @cookies = {}
        @controller = @request = @response = nil
      
        self.host        = "www.example.com"
        self.remote_addr = "127.0.0.1"
        self.accept      = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"

        unless @named_routes_configured
          # install the named routes in this session instance.
          klass = class<<self; self; end
          Routing::NamedRoutes.install(klass)

          # the helpers are made protected by default--we make them public for
          # easier access during testing and troubleshooting.
          klass.send(:public, *Routing::NamedRoutes::Helpers)
          @named_routes_configured = true
        end
      end

      # Specify whether or not the session should mimic a secure HTTPS request.
      #
      #   session.https!
      #   session.https!(false)
      def https!(flag=true)
        @https = flag        
      end

      # Return +true+ if the session is mimicing a secure HTTPS request.
      #
      #   if session.https?
      #     ...
      #   end
      def https?
        @https
      end

      # Set the host name to use in the next request.
      #
      #   session.host! "www.example.com"
      def host!(name)
        @host = name
      end

      # Follow a single redirect response. If the last response was not a
      # redirect, an exception will be raised. Otherwise, the redirect is
      # performed on the location header.
      def follow_redirect!
        raise "not a redirect! #{@status} #{@status_message}" unless redirect?
        get(interpret_uri(headers["location"].first))
        status
      end

      # Performs a GET request, following any subsequent redirect. Note that
      # the redirects are followed until the response is not a redirect--this
      # means you may run into an infinite loop if your redirect loops back to
      # itself.
      def get_via_redirect(path, args={})
        get path, args
        follow_redirect! while redirect?
        status
      end

      # Performs a POST request, following any subsequent redirect. This is
      # vulnerable to infinite loops, the same as #get_via_redirect.
      def post_via_redirect(path, args={})
        post path, args
        follow_redirect! while redirect?
        status
      end

      # Returns +true+ if the last response was a redirect.
      def redirect?
        status/100 == 3
      end

      # 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 
      # prefix 'HTTP_' added if needed.
      def get(path, parameters=nil, headers=nil)
        process :get, path, parameters, headers
      end

      # Performs a POST 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 
      # prefix 'HTTP_' added if needed.
      def post(path, parameters=nil, headers=nil)
        process :post, path, parameters, headers
      end

      # Performs an XMLHttpRequest request with the given parameters, mimicing
      # the request environment created by the Prototype library. 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 
      # prefix 'HTTP_' added if needed.
      def xml_http_request(path, parameters=nil, headers=nil)
        headers = (headers || {}).merge("X-Requested-With" => "XMLHttpRequest")
        post(path, parameters, headers)
      end

      # Returns the URL for the given options, according to the rules specified
      # in the application's routes.
      def url_for(options)
        controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
      end

      private

        class MockCGI < CGI #:nodoc:
          attr_accessor :stdinput, :stdoutput, :env_table

          def initialize(env, input=nil)
            self.env_table = env
            self.stdinput = StringIO.new(input || "")
            self.stdoutput = StringIO.new

            super()
          end
        end

        # Tailors the session based on the given URI, setting the HTTPS value
        # and the hostname.
        def interpret_uri(path)
          location = URI.parse(path)
          https! URI::HTTPS === location if location.scheme
          host! location.host if location.host
          location.query ? "#{location.path}?#{location.query}" : location.path
        end

        # Performs the actual request.
        def process(method, path, parameters=nil, headers=nil)
          data = requestify(parameters)
          path = interpret_uri(path) if path =~ %r{://}
          path = "/#{path}" unless path[0] == ?/
          @path = path
          env = {}

          if method == :get
            env["QUERY_STRING"] = data
            data = nil
          end

          env.update(
            "REQUEST_METHOD" => method.to_s.upcase,
            "REQUEST_URI"    => path,
            "HTTP_HOST"      => host,
            "REMOTE_ADDR"    => remote_addr,
            "SERVER_PORT"    => (https? ? "443" : "80"),
            "CONTENT_TYPE"   => "application/x-www-form-urlencoded",
            "CONTENT_LENGTH" => data ? data.length.to_s : nil,
            "HTTP_COOKIE"    => encode_cookies,
            "HTTPS"          => https? ? "on" : "off",
            "HTTP_ACCEPT"    => accept
          )

          (headers || {}).each do |key, value|
            key = key.to_s.upcase.gsub(/-/, "_")
            key = "HTTP_#{key}" unless env.has_key?(key) || env =~ /^X|HTTP/
            env[key] = value
          end

          unless ActionController::Base.respond_to?(:clear_last_instantiation!)
            ActionController::Base.send(:include, ControllerCapture)
          end

          ActionController::Base.clear_last_instantiation!

          cgi = MockCGI.new(env, data)
          Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
          @result = cgi.stdoutput.string

          @controller = ActionController::Base.last_instantiation
          @request = @controller.request
          @response = @controller.response

          # Decorate the response with the standard behavior of the TestResponse
          # so that things like assert_response can be used in integration
          # tests.
          @response.extend(TestResponseBehavior)

          parse_result
          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)

          @headers = Hash.new { |h,k| h[k] = [] }
          headers.each_line do |line|
            key, value = line.strip.split(/:\s*/, 2)
            @headers[key.downcase] << value
          end

          (@headers['set-cookie'] || [] ).each do |string|
            name, value = string.match(/^(.*?)=(.*?);/)[1,2]
            @cookies[name] = value
          end

          @status, @status_message = @headers["status"].first.split(/ /)
          @status = @status.to_i
        end

        # Encode the cookies hash in a format suitable for passing to a 
        # request.
        def encode_cookies
          cookies.inject("") do |string, (name, value)|
            string << "#{name}=#{value}; "
          end
        end

        # Get a temporarly URL writer object
        def generic_url_rewriter
          cgi = MockCGI.new('REQUEST_METHOD' => "GET",
                            'QUERY_STRING'   => "",
                            "REQUEST_URI"    => "/",
                            "HTTP_HOST"      => host,
                            "SERVER_PORT"    => https? ? "443" : "80",
                            "HTTPS"          => https? ? "on" : "off")                          
          ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
        end

        def name_with_prefix(prefix, name)
          prefix ? "#{prefix}[#{name}]" : name.to_s
        end

        # 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
            return nil if parameters.empty?
            parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
          elsif Array === parameters
            parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
          elsif prefix.nil?
            parameters
          else
            "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
          end
        end

    end

    # A module used to extend ActionController::Base, so that integration tests
    # can capture the controller used to satisfy a request.
    module ControllerCapture #:nodoc:
      def self.included(base)
        base.extend(ClassMethods)
        base.class_eval do
          class <<self
            alias_method :new_without_capture, :new
            alias_method :new, :new_with_capture
          end
        end
      end

      module ClassMethods #:nodoc:
        mattr_accessor :last_instantiation

        def clear_last_instantiation!
          self.last_instantiation = nil
        end
    
        def new_with_capture(*args)
          self.last_instantiation ||= new_without_capture(*args)
        end
      end
    end
  end

  # An IntegrationTest is one that spans multiple controllers and actions,
  # tying them all together to ensure they work together as expected. It tests
  # more completely than either unit or functional tests do, exercising the
  # entire stack, from the dispatcher to the database.
  #
  # At its simplest, you simply extend IntegrationTest and write your tests
  # using the get/post methods:
  #
  #   require "#{File.dirname(__FILE__)}/test_helper"
  #
  #   class ExampleTest < ActionController::IntegrationTest
  #     fixtures :people
  #
  #     def test_login
  #       # get the login page
  #       get "/login"
  #       assert_equal 200, status
  #
  #       # post the login and follow through to the home page
  #       post "/login", :username => people(:jamis).username,
  #         :password => people(:jamis).password
  #       follow_redirect!
  #       assert_equal 200, status
  #       assert_equal "/home", path
  #     end
  #   end
  #
  # However, you can also have multiple session instances open per test, and
  # even extend those instances with assertions and methods to create a very
  # powerful testing DSL that is specific for your application. You can even
  # reference any named routes you happen to have defined!
  #
  #   require "#{File.dirname(__FILE__)}/test_helper"
  #
  #   class AdvancedTest < ActionController::IntegrationTest
  #     fixtures :people, :rooms
  #
  #     def test_login_and_speak
  #       jamis, david = login(:jamis), login(:david)
  #       room = rooms(:office)
  #
  #       jamis.enter(room)
  #       jamis.speak(room, "anybody home?")
  #
  #       david.enter(room)
  #       david.speak(room, "hello!")
  #     end
  #
  #     private
  #
  #       module CustomAssertions
  #         def enter(room)
  #           # reference a named route, for maximum internal consistency!
  #           get(room_url(:id => room.id))
  #           assert(...)
  #           ...
  #         end
  #
  #         def speak(room, message)
  #           xml_http_request "/say/#{room.id}", :message => message
  #           assert(...)
  #           ...
  #         end
  #       end
  #
  #       def login(who)
  #         open_session do |sess|
  #           sess.extend(CustomAssertions)
  #           who = people(who)
  #           sess.post "/login", :username => who.username,
  #             :password => who.password
  #           assert(...)
  #         end
  #       end
  #   end
  class IntegrationTest < Test::Unit::TestCase
    # Work around a bug in test/unit caused by the default test being named
    # as a symbol (:default_test), which causes regex test filters
    # (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
    # symbols.
    def initialize(name) #:nodoc:
      super(name.to_s)
    end

    # Work around test/unit's requirement that every subclass of TestCase have
    # at least one test method. Note that this implementation extends to all
    # subclasses, as well, so subclasses of IntegrationTest may also exist
    # without any test methods.
    def run(*args) #:nodoc:
      return if @method_name == "default_test"
      super   
    end

    # Because of how use_instantiated_fixtures and use_transactional_fixtures
    # are defined, we need to treat them as special cases. Otherwise, users
    # would potentially have to set their values for both Test::Unit::TestCase
    # ActionController::IntegrationTest, since by the time the value is set on
    # TestCase, IntegrationTest has already been defined and cannot inherit
    # changes to those variables. So, we make those two attributes copy-on-write.

    class << self
      def use_transactional_fixtures=(flag) #:nodoc:
        @_use_transactional_fixtures = true
        @use_transactional_fixtures = flag
      end

      def use_instantiated_fixtures=(flag) #:nodoc:
        @_use_instantiated_fixtures = true
        @use_instantiated_fixtures = flag
      end

      def use_transactional_fixtures #:nodoc:
        @_use_transactional_fixtures ?
          @use_transactional_fixtures :
          superclass.use_transactional_fixtures
      end

      def use_instantiated_fixtures #:nodoc:
        @_use_instantiated_fixtures ?
          @use_instantiated_fixtures :
          superclass.use_instantiated_fixtures
      end
    end

    # Reset the current session. This is useful for testing multiple sessions
    # in a single test case.
    def reset!
      @integration_session = open_session
    end

    %w(get post cookies assigns xml_http_request).each do |method|
      define_method(method) do |*args|
        reset! unless @integration_session
        returning @integration_session.send(method, *args) do
          copy_session_variables!
        end
      end
    end

    # Open a new session instance. If a block is given, the new session is
    # yielded to the block before being returned.
    #
    #   session = open_session do |sess|
    #     sess.extend(CustomAssertions)
    #   end
    #
    # By default, a single session is automatically created for you, but you
    # can use this method to open multiple sessions that ought to be tested
    # simultaneously.
    def open_session
      session = Integration::Session.new

      # delegate the fixture accessors back to the test instance
      extras = Module.new { attr_accessor :delegate, :test_result }
      self.class.fixture_table_names.each do |table_name|
        name = table_name.tr(".", "_")
        next unless respond_to?(name)
        extras.send(:define_method, name) { |*args| delegate.send(name, *args) }
      end

      # delegate add_assertion to the test case
      extras.send(:define_method, :add_assertion) { test_result.add_assertion }
      session.extend(extras)
      session.delegate = self
      session.test_result = @_result

      yield session if block_given?
      session
    end

    # Copy the instance variables from the current session instance into the
    # test instance.
    def copy_session_variables! #:nodoc:
      return unless @integration_session
      %w(controller response request).each do |var|
        instance_variable_set("@#{var}", @integration_session.send(var))
      end
    end

    # Delegate unhandled messages to the current session instance.
    def method_missing(sym, *args, &block)
      reset! unless @integration_session
      returning @integration_session.send(sym, *args, &block) do
        copy_session_variables!
      end
    end
  end
end
module ActionController #:nodoc:
  module Layout #:nodoc:
    def self.included(base)
      base.extend(ClassMethods)
      base.class_eval do
        alias_method :render_with_no_layout, :render
        alias_method :render, :render_with_a_layout

        class << self
          alias_method :inherited_without_layout, :inherited
          alias_method :inherited, :inherited_with_layout
        end
      end
    end

    # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
    # repeated setups. The inclusion pattern has pages that look like this:
    #
    #   <%= render "shared/header" %>
    #   Hello World
    #   <%= render "shared/footer" %>
    #
    # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
    # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
    #
    # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
    # that the header and footer are only mentioned in one place, like this:
    #
    #   <!-- The header part of this layout -->
    #   <%= yield %>
    #   <!-- 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:
    #
    #   <!-- The header part of this layout -->
    #   hello world
    #   <!-- The footer part of this layout -->
    #
    # == Accessing shared variables
    #
    # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
    # references that won't materialize before rendering time:
    #
    #   <h1><%= @page_title %></h1>
    #   <%= yield %>
    #
    # ...and content pages that fulfill these references _at_ rendering time:
    #
    #    <% @page_title = "Welcome" %>
    #    Off-world colonies offers you a chance to start a new life
    #
    # The result after rendering is:
    #
    #   <h1>Welcome</h1>
    #   Off-world colonies offers you a chance to start a new life
    #
    # == Automatic layout assignment
    #
    # If there is a template in <tt>app/views/layouts/</tt> 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 
    # <tt>app/views/layouts/weblog.rhtml</tt> or <tt>app/views/layouts/weblog.rxml</tt> exists then it will be automatically set as
    # the layout for your WeblogController. You can create a layout with the name <tt>application.rhtml</tt> or <tt>application.rxml</tt>
    # 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 <tt>app/views/layouts/admin/weblog.rhtml</tt>.
    # 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 assignement if the child
    # class has a layout with the same name. 
    #
    # == Inheritance for layouts
    #
    # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
    #
    #   class BankController < ActionController::Base
    #     layout "bank_standard"
    #
    #   class InformationController < BankController
    #
    #   class VaultController < BankController
    #     layout :access_level_layout
    #
    #   class EmployeeController < BankController
    #     layout nil
    #
    # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
    # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
    #
    # == Types of layouts
    #
    # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
    # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
    # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
    #
    # The method reference is the preferred approach to variable layouts and is used like this:
    #
    #   class WeblogController < ActionController::Base
    #     layout :writers_and_readers
    #
    #     def index
    #       # fetching posts
    #     end
    #
    #     private
    #       def writers_and_readers
    #         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 
    # is logged in or not.
    #
    # If you want to use an inline method, such as a proc, do something like this:
    #
    #   class WeblogController < ActionController::Base
    #     layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
    #
    # Of course, the most common way of specifying a layout is still just as a plain template name:
    #
    #   class WeblogController < ActionController::Base
    #     layout "weblog_standard"
    #
    # If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+.
    #
    # == 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 
    # <tt>:only</tt> and <tt>:except</tt> 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 
    # around the rendered view.
    #
    # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so 
    # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
    #
    # == 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 <tt>render</tt> 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:
    #
    #   class WeblogController < ActionController::Base
    #     def help
    #       render :action => "help/index", :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 <tt>@content_for_layout</tt> instance 
    # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
    module ClassMethods
      # If a layout is specified, all rendered actions will have their result rendered  
      # when the layout<tt>yield</tt>'s. 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 = {})
        add_layout_conditions(conditions)
        write_inheritable_attribute "layout", template_name
      end

      def layout_conditions #:nodoc:
        @layout_conditions ||= read_inheritable_attribute("layout_conditions")
      end
      
      def default_layout #:nodoc:
        @default_layout ||= read_inheritable_attribute("layout")
      end

      private
        def inherited_with_layout(child)
          inherited_without_layout(child)
          child.send :include, Reloadable
          layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
          child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty?
        end

        def layout_list
          Dir.glob("#{template_root}/layouts/**/*")
        end

        def add_layout_conditions(conditions)
          write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
        end

        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
    end

    # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
    # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
    # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
    # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
    def active_layout(passed_layout = nil)
      layout = passed_layout || self.class.default_layout

      active_layout = case layout
        when String then layout
        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.
      if active_layout
        if active_layout.include?('/') && ! layout_directory?(active_layout)
          active_layout
        else
          "layouts/#{active_layout}"
        end
      end
    end

    def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc:
      template_with_options = options.is_a?(Hash)

      if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
        options = options.merge :layout => false if template_with_options
        logger.info("Rendering #{options} within #{layout}") if logger

        if template_with_options
          content_for_layout = render_with_no_layout(options, &block)
          deprecated_status = options[:status] || deprecated_status
        else
          content_for_layout = render_with_no_layout(options, deprecated_status, &block)
        end

        erase_render_results
        add_variables_to_assigns
        @template.instance_variable_set("@content_for_layout", content_for_layout)
        render_text(@template.render_file(layout, true), deprecated_status)
      else
        render_with_no_layout(options, deprecated_status, &block)
      end
    end

    private
    
      def apply_layout?(template_with_options, options)
        return false if options == :update
        template_with_options ?  candidate_for_layout?(options) : !template_exempt_from_layout?
      end

      def candidate_for_layout?(options)
        (options.has_key?(:layout) && options[:layout] != false) || 
        options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
        !template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
      end

      def pick_layout(template_with_options, options, deprecated_layout)
        if deprecated_layout
          deprecated_layout
        elsif template_with_options
          case layout = options[:layout]
            when FalseClass
              nil
            when NilClass, TrueClass
              active_layout if action_has_layout?
            else
              active_layout(layout)
          end
        else
          active_layout if action_has_layout?
        end
      end

      def action_has_layout?
        if conditions = self.class.layout_conditions
          case
            when only = conditions[:only]
              only.include?(action_name)
            when except = conditions[:except]
              !except.include?(action_name) 
            else
              true
          end
        else
          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)
        template_path = File.join(self.class.view_root, 'layouts', layout_name)
        dirname = File.dirname(template_path)
        self.class.send(:layout_directory_exists_cache)[dirname]
      end
  end
end
module ActionController
  # Macros are class-level calls that add pre-defined actions to the controller based on the parameters passed in.
  # Currently, they're used to bridge the JavaScript macros, like autocompletion and in-place editing, with the controller
  # backing.
  module Macros
    module AutoComplete #:nodoc:
      def self.append_features(base) #:nodoc:
        super
        base.extend(ClassMethods)
      end

      # Example:
      #
      #   # Controller
      #   class BlogController < ApplicationController
      #     auto_complete_for :post, :title
      #   end
      #
      #   # View
      #   <%= text_field_with_auto_complete :post, title %>
      #
      # By default, auto_complete_for limits the results to 10 entries,
      # and sorts by the given field.
      # 
      # auto_complete_for takes a third parameter, an options hash to
      # the find method used to search for the records:
      #
      #   auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC'
      #
      # For help on defining text input fields with autocompletion, 
      # see ActionView::Helpers::JavaScriptHelper.
      #
      # For more examples, see script.aculo.us:
      # * http://script.aculo.us/demos/ajax/autocompleter
      # * http://script.aculo.us/demos/ajax/autocompleter_customized
      module ClassMethods
        def auto_complete_for(object, method, options = {})
          define_method("auto_complete_for_#{object}_#{method}") do
            find_options = { 
              :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ], 
              :order => "#{method} ASC",
              :limit => 10 }.merge!(options)
            
            @items = object.to_s.camelize.constantize.find(:all, find_options)

            render :inline => "<%= auto_complete_result @items, '#{method}' %>"
          end
        end
      end
    end
  end
endmodule ActionController
  module Macros
    module InPlaceEditing #:nodoc:
      def self.append_features(base) #:nodoc:
        super
        base.extend(ClassMethods)
      end

      # Example:
      #
      #   # Controller
      #   class BlogController < ApplicationController
      #     in_place_edit_for :post, :title
      #   end
      #
      #   # View
      #   <%= in_place_editor_field :post, 'title' %>
      #
      # For help on defining an in place editor in the browser,
      # see ActionView::Helpers::JavaScriptHelper.
      module ClassMethods
        def in_place_edit_for(object, attribute, options = {})
          define_method("set_#{object}_#{attribute}") do
            @item = object.to_s.camelize.constantize.find(params[:id])
            @item.update_attribute(attribute, params[:value])
            render :text => @item.send(attribute)
          end
        end
      end
    end
  end
end
module ActionController #:nodoc:
  module MimeResponds #:nodoc:
    def self.included(base)
      base.send(:include, ActionController::MimeResponds::InstanceMethods)
    end

    module InstanceMethods
      # Without web-service support, an action which collects the data for displaying a list of people
      # might look something like this:
      #
      #   def list
      #     @people = Person.find(:all)
      #   end
      # 
      # Here's the same action, with web-service support baked in:
      # 
      #   def list
      #     @people = Person.find(:all)
      # 
      #     respond_to do |wants|
      #       wants.html
      #       wants.xml { render :xml => @people.to_xml }
      #     end
      #   end
      # 
      # What that says is, "if the client wants HTML in response to this action, just respond as we 
      # would have before, but if the client wants XML, return them the list of people in XML format." 
      # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
      # 
      # Supposing you have an action that adds a new person, optionally creating their company 
      # (by name) if it does not already exist, without web-services, it might look like this:
      # 
      #   def add
      #     @company = Company.find_or_create_by_name(params[:company][:name])
      #     @person  = @company.people.create(params[:person])
      # 
      #     redirect_to(person_list_url)
      #   end
      # 
      # Here's the same action, with web-service support baked in:
      # 
      #   def add
      #     company  = params[:person].delete(:company)
      #     @company = Company.find_or_create_by_name(company[:name])
      #     @person  = @company.people.create(params[:person])
      # 
      #     respond_to do |wants|
      #       wants.html { redirect_to(person_list_url) }
      #       wants.js
      #       wants.xml  { render :xml => @person.to_xml(:include => @company) }
      #     end
      #   end
      # 
      # If the client wants HTML, we just redirect them back to the person list. If they want Javascript 
      # (wants.js), then it is an RJS request and we render the RJS template associated with this action. 
      # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also 
      # include the person’s company in the rendered XML, so you get something like this:
      # 
      #   <person>
      #     <id>...</id>
      #     ...
      #     <company>
      #       <id>...</id>
      #       <name>...</name>
      #       ...
      #     </company>
      #   </person>
      # 
      # Note, however, the extra bit at the top of that action:
      # 
      #   company  = params[:person].delete(:company)
      #   @company = Company.find_or_create_by_name(company[:name])
      # 
      # This is because the incoming XML document (if a web-service request is in process) can only contain a 
      # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
      # 
      #   person[name]=...&person[company][name]=...&...
      # 
      # And, like this (xml-encoded):
      # 
      #   <person>
      #     <name>...</name>
      #     <company>
      #       <name>...</name>
      #     </company>
      #   </person>
      # 
      # In other words, we make the request so that it operates on a single entity—a person. Then, in the action, 
      # we extract the company data from the request, find or create the company, and then create the new person 
      # 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 
      # 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
      # environment.rb as follows.
      # 
      #   Mime::Type.register "image/jpg", :jpg
      # 
      def respond_to(*types, &block)
        raise ArgumentError, "respond_to takes either types or a block, never bot" unless types.any? ^ block
        block ||= lambda { |responder| types.each { |type| responder.send(type) } }
        responder = Responder.new(block.binding)
        block.call(responder)
        responder.respond
      end
    end
    
    class Responder #:nodoc:
      DEFAULT_BLOCKS = {
        :html    => 'Proc.new { render }',
        :js      => 'Proc.new { render :action => "#{action_name}.rjs" }',
        :xml     => 'Proc.new { render :action => "#{action_name}.rxml" }'
      }
      
      def initialize(block_binding)
        @block_binding = block_binding
        @mime_type_priority = eval("request.accepts", block_binding)
        @order     = []
        @responses = {}
      end

      def custom(mime_type, &block)
        mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s)
        
        @order << mime_type
        
        if block_given?
          @responses[mime_type] = block
        else
          @responses[mime_type] = eval(DEFAULT_BLOCKS[mime_type.to_sym], @block_binding)
        end
      end
      
      for mime_type in %w( all html js xml rss atom yaml )
        eval <<-EOT
          def #{mime_type}(&block)
            custom(Mime::#{mime_type.upcase}, &block)
          end
        EOT
      end

      def any(*args, &block)
        args.each { |type| send(type, &block) }
      end
      
      def respond
        for priority in @mime_type_priority
          if priority == Mime::ALL
            @responses[@order.first].call
            return
          else
            if priority === @order
              @responses[priority].call
              return # mime type match found, be happy and return
            end
          end
        end
        
        if @order.include?(Mime::ALL)
          @responses[Mime::ALL].call
        else
          eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding
        end
      end
    end
  end
end
module Mime
  class Type #:nodoc:
    # A simple helper class used in parsing the accept header
    class AcceptItem #:nodoc:
      attr_accessor :order, :name, :q

      def initialize(order, name, q=nil)
        @order = order
        @name = name.strip
        q ||= 0.0 if @name == "*/*" # default "*/*" to end of list
        @q = ((q || 1.0).to_f * 100).to_i
      end

      def to_s
        @name
      end

      def <=>(item)
        result = item.q <=> q
        result = order <=> item.order if result == 0
        result
      end

      def ==(item)
        name == (item.respond_to?(:name) ? item.name : item)
      end
    end

    class << self
      def lookup(string)
        LOOKUP[string]
      end

      def parse(accept_header)
        # keep track of creation order to keep the subsequent sort stable
        index = 0
        list = accept_header.split(/,/).
          map! { |i| AcceptItem.new(index += 1, *i.split(/;\s*q=/)) }.sort!

        # Take care of the broken text/xml entry by renaming or deleting it
  
        text_xml = list.index("text/xml")
        app_xml = list.index("application/xml")

        if text_xml && app_xml
          # set the q value to the max of the two
          list[app_xml].q = [list[text_xml].q, list[app_xml].q].max

          # make sure app_xml is ahead of text_xml in the list
          if app_xml > text_xml
            list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
            app_xml, text_xml = text_xml, app_xml
          end

          # delete text_xml from the list
          list.delete_at(text_xml)
  
        elsif text_xml
          list[text_xml].name = "application/xml"
        end

        # Look for more specific xml-based types and sort them ahead of app/xml

        if app_xml
          idx = app_xml
          app_xml_type = list[app_xml]

          while(idx < list.length)
            type = list[idx]
            break if type.q < app_xml_type.q
            if type.name =~ /\+xml$/
              list[app_xml], list[idx] = list[idx], list[app_xml]
              app_xml = idx
            end
            idx += 1
          end
        end

        list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
        list
      end
    end
    
    def initialize(string, symbol = nil, synonyms = [])
      @symbol, @synonyms = symbol, synonyms
      @string = string
    end
    
    def to_s
      @string
    end
    
    def to_str
      to_s
    end
    
    def to_sym
      @symbol || @string.to_sym
    end

    def ===(list)
      if list.is_a?(Array)
        (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
      else
        super
      end
    end
    
    def ==(mime_type)
      (@synonyms + [ self ]).any? { |synonym| synonym.to_s == mime_type.to_s } if mime_type
    end
  end

  ALL   = Type.new "*/*", :all
  HTML  = Type.new "text/html", :html, %w( application/xhtml+xml )
  JS    = Type.new "text/javascript", :js, %w( application/javascript application/x-javascript )
  XML   = Type.new "application/xml", :xml, %w( text/xml application/x-xml )
  RSS   = Type.new "application/rss+xml", :rss
  ATOM  = Type.new "application/atom+xml", :atom
  YAML  = Type.new "application/x-yaml", :yaml, %w( text/yaml )

  LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) }

  LOOKUP["*/*"]                      = ALL

  LOOKUP["text/html"]                = HTML
  LOOKUP["application/xhtml+xml"]    = HTML

  LOOKUP["application/xml"]          = XML
  LOOKUP["text/xml"]                 = XML
  LOOKUP["application/x-xml"]        = XML

  LOOKUP["text/javascript"]          = JS
  LOOKUP["application/javascript"]   = JS
  LOOKUP["application/x-javascript"] = JS

  LOOKUP["text/yaml"]                = YAML
  LOOKUP["application/x-yaml"]       = YAML

  LOOKUP["application/rss+xml"]      = RSS
  LOOKUP["application/atom+xml"]     = ATOM
endmodule ActionController
  # === Action Pack pagination for Active Record collections
  #
  # The Pagination module aids in the process of paging large collections of
  # Active Record objects. It offers macro-style automatic fetching of your
  # model for multiple views, or explicit fetching for single actions. And if
  # the magic isn't flexible enough for your needs, you can create your own
  # paginators with a minimal amount of code.
  #
  # The Pagination module can handle as much or as little as you wish. In the
  # controller, have it automatically query your model for pagination; or,
  # if you prefer, create Paginator objects yourself.
  #
  # Pagination is included automatically for all controllers.
  #
  # For help rendering pagination links, see 
  # ActionView::Helpers::PaginationHelper.
  #
  # ==== Automatic pagination for every action in a controller
  #
  #   class PersonController < ApplicationController   
  #     model :person
  #
  #     paginate :people, :order => 'last_name, first_name',
  #              :per_page => 20
  #     
  #     # ...
  #   end
  #
  # Each action in this controller now has access to a <tt>@people</tt>
  # instance variable, which is an ordered collection of model objects for the
  # current page (at most 20, sorted by last name and first name), and a 
  # <tt>@person_pages</tt> Paginator instance. The current page is determined
  # by the <tt>params[:page]</tt> variable.
  #
  # ==== Pagination for a single action
  #
  #   def list
  #     @person_pages, @people =
  #       paginate :people, :order => 'last_name, first_name'
  #   end
  #
  # Like the previous example, but explicitly creates <tt>@person_pages</tt>
  # and <tt>@people</tt> for a single action, and uses the default of 10 items
  # per page.
  #
  # ==== Custom/"classic" pagination 
  #
  #   def list
  #     @person_pages = Paginator.new self, Person.count, 10, params[:page]
  #     @people = Person.find :all, :order => 'last_name, first_name', 
  #                           :limit  =>  @person_pages.items_per_page,
  #                           :offset =>  @person_pages.current.offset
  #   end
  # 
  # Explicitly creates the paginator from the previous example and uses 
  # Paginator#to_sql to retrieve <tt>@people</tt> from the model.
  #
  module Pagination
    unless const_defined?(:OPTIONS)
      # A hash holding options for controllers using macro-style pagination
      OPTIONS = Hash.new
  
      # The default options for pagination
      DEFAULT_OPTIONS = {
        :class_name => nil,
        :singular_name => nil,
        :per_page   => 10,
        :conditions => nil,
        :order_by   => nil,
        :order      => nil,
        :join       => nil,
        :joins      => nil,
        :count      => nil,
        :include    => nil,
        :select     => nil,
        :parameter  => 'page'
      }
    end
      
    def self.included(base) #:nodoc:
      super
      base.extend(ClassMethods)
    end
  
    def self.validate_options!(collection_id, options, in_action) #:nodoc:
      options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}

      valid_options = DEFAULT_OPTIONS.keys
      valid_options << :actions unless in_action
    
      unknown_option_keys = options.keys - valid_options
      raise ActionController::ActionControllerError,
            "Unknown options: #{unknown_option_keys.join(', ')}" unless
              unknown_option_keys.empty?

      options[:singular_name] ||= Inflector.singularize(collection_id.to_s)
      options[:class_name]  ||= Inflector.camelize(options[:singular_name])
    end

    # Returns a paginator and a collection of Active Record model instances
    # for the paginator's current page. This is designed to be used in a
    # single action; to automatically paginate multiple actions, consider
    # ClassMethods#paginate.
    #
    # +options+ are:
    # <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by
    #                        singularizing the collection name
    # <tt>:class_name</tt>:: the class name to use, if it can't be inferred by
    #                        camelizing the singular name
    # <tt>:per_page</tt>::   the maximum number of items to include in a 
    #                        single page. Defaults to 10
    # <tt>:conditions</tt>:: optional conditions passed to Model.find(:all, *params) and
    #                        Model.count
    # <tt>:order</tt>::      optional order parameter passed to Model.find(:all, *params)
    # <tt>:order_by</tt>::   (deprecated, used :order) optional order parameter passed to Model.find(:all, *params)
    # <tt>:joins</tt>::      optional joins parameter passed to Model.find(:all, *params)
    #                        and Model.count
    # <tt>:join</tt>::       (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params)
    #                        and Model.count
    # <tt>:include</tt>::    optional eager loading parameter passed to Model.find(:all, *params)
    #                        and Model.count
    # <tt>:select</tt>::     :select parameter passed to Model.find(:all, *params)
    #
    # <tt>:count</tt>::      parameter passed as :select option to Model.count(*params)
    #
    def paginate(collection_id, options={})
      Pagination.validate_options!(collection_id, options, true)
      paginator_and_collection_for(collection_id, options)
    end

    # These methods become class methods on any controller 
    module ClassMethods
      # Creates a +before_filter+ which automatically paginates an Active
      # Record model for all actions in a controller (or certain actions if
      # specified with the <tt>:actions</tt> option).
      #
      # +options+ are the same as PaginationHelper#paginate, with the addition 
      # of:
      # <tt>:actions</tt>:: an array of actions for which the pagination is
      #                     active. Defaults to +nil+ (i.e., every action)
      def paginate(collection_id, options={})
        Pagination.validate_options!(collection_id, options, false)
        module_eval do
          before_filter :create_paginators_and_retrieve_collections
          OPTIONS[self] ||= Hash.new
          OPTIONS[self][collection_id] = options
        end
      end
    end

    def create_paginators_and_retrieve_collections #:nodoc:
      Pagination::OPTIONS[self.class].each do |collection_id, options|
        next unless options[:actions].include? action_name if
          options[:actions]

        paginator, collection = 
          paginator_and_collection_for(collection_id, options)

        paginator_name = "@#{options[:singular_name]}_pages"
        self.instance_variable_set(paginator_name, paginator)

        collection_name = "@#{collection_id.to_s}"
        self.instance_variable_set(collection_name, collection)     
      end
    end
  
    # Returns the total number of items in the collection to be paginated for
    # the +model+ and given +conditions+. Override this method to implement a
    # custom counter.
    def count_collection_for_pagination(model, options)
      model.count(:conditions => options[:conditions],
                  :joins => options[:join] || options[:joins],
                  :include => options[:include],
                  :select => options[:count])
    end
    
    # Returns a collection of items for the given +model+ and +options[conditions]+,
    # ordered by +options[order]+, for the current page in the given +paginator+.
    # Override this method to implement a custom finder.
    def find_collection_for_pagination(model, options, paginator)
      model.find(:all, :conditions => options[:conditions],
                 :order => options[:order_by] || options[:order],
                 :joins => options[:join] || options[:joins], :include => options[:include],
                 :select => options[:select], :limit => options[:per_page],
                 :offset => paginator.current.offset)
    end
  
    protected :create_paginators_and_retrieve_collections,
              :count_collection_for_pagination,
              :find_collection_for_pagination

    def paginator_and_collection_for(collection_id, options) #:nodoc:
      klass = options[:class_name].constantize
      page  = @params[options[:parameter]]
      count = count_collection_for_pagination(klass, options)
      paginator = Paginator.new(self, count, options[:per_page], page)
      collection = find_collection_for_pagination(klass, options, paginator)
    
      return paginator, collection 
    end
      
    private :paginator_and_collection_for

    # A class representing a paginator for an Active Record collection.
    class Paginator
      include Enumerable

      # Creates a new Paginator on the given +controller+ for a set of items
      # of size +item_count+ and having +items_per_page+ items per page.
      # Raises ArgumentError if items_per_page is out of bounds (i.e., less
      # than or equal to zero). The page CGI parameter for links defaults to
      # "page" and can be overridden with +page_parameter+.
      def initialize(controller, item_count, items_per_page, current_page=1)
        raise ArgumentError, 'must have at least one item per page' if
          items_per_page <= 0

        @controller = controller
        @item_count = item_count || 0
        @items_per_page = items_per_page
        @pages = {}
        
        self.current_page = current_page
      end
      attr_reader :controller, :item_count, :items_per_page
      
      # Sets the current page number of this paginator. If +page+ is a Page
      # object, its +number+ attribute is used as the value; if the page does 
      # not belong to this Paginator, an ArgumentError is raised.
      def current_page=(page)
        if page.is_a? Page
          raise ArgumentError, 'Page/Paginator mismatch' unless
            page.paginator == self
        end
        page = page.to_i
        @current_page_number = has_page_number?(page) ? page : 1
      end

      # Returns a Page object representing this paginator's current page.
      def current_page
        @current_page ||= self[@current_page_number]
      end
      alias current :current_page

      # Returns a new Page representing the first page in this paginator.
      def first_page
        @first_page ||= self[1]
      end
      alias first :first_page

      # Returns a new Page representing the last page in this paginator.
      def last_page
        @last_page ||= self[page_count] 
      end
      alias last :last_page

      # Returns the number of pages in this paginator.
      def page_count
        @page_count ||= @item_count.zero? ? 1 :
                          (q,r=@item_count.divmod(@items_per_page); r==0? q : q+1)
      end

      alias length :page_count

      # Returns true if this paginator contains the page of index +number+.
      def has_page_number?(number)
        number >= 1 and number <= page_count
      end

      # Returns a new Page representing the page with the given index
      # +number+.
      def [](number)
        @pages[number] ||= Page.new(self, number)
      end

      # Successively yields all the paginator's pages to the given block.
      def each(&block)
        page_count.times do |n|
          yield self[n+1]
        end
      end

      # A class representing a single page in a paginator.
      class Page
        include Comparable

        # Creates a new Page for the given +paginator+ with the index
        # +number+. If +number+ is not in the range of valid page numbers or
        # is not a number at all, it defaults to 1.
        def initialize(paginator, number)
          @paginator = paginator
          @number = number.to_i
          @number = 1 unless @paginator.has_page_number? @number
        end
        attr_reader :paginator, :number
        alias to_i :number

        # Compares two Page objects and returns true when they represent the 
        # same page (i.e., their paginators are the same and they have the
        # same page number).
        def ==(page)
          return false if page.nil?
          @paginator == page.paginator and 
            @number == page.number
        end

        # Compares two Page objects and returns -1 if the left-hand page comes
        # before the right-hand page, 0 if the pages are equal, and 1 if the
        # left-hand page comes after the right-hand page. Raises ArgumentError
        # if the pages do not belong to the same Paginator object.
        def <=>(page)
          raise ArgumentError unless @paginator == page.paginator
          @number <=> page.number
        end

        # Returns the item offset for the first item in this page.
        def offset
          @paginator.items_per_page * (@number - 1)
        end
        
        # Returns the number of the first item displayed.
        def first_item
          offset + 1
        end
        
        # Returns the number of the last item displayed.
        def last_item
          [@paginator.items_per_page * @number, @paginator.item_count].min
        end

        # Returns true if this page is the first page in the paginator.
        def first?
          self == @paginator.first
        end

        # Returns true if this page is the last page in the paginator.
        def last?
          self == @paginator.last
        end

        # Returns a new Page object representing the page just before this
        # page, or nil if this is the first page.
        def previous
          if first? then nil else @paginator[@number - 1] end
        end

        # Returns a new Page object representing the page just after this
        # page, or nil if this is the last page.
        def next
          if last? then nil else @paginator[@number + 1] end
        end

        # Returns a new Window object for this page with the specified 
        # +padding+.
        def window(padding=2)
          Window.new(self, padding)
        end

        # Returns the limit/offset array for this page.
        def to_sql
          [@paginator.items_per_page, offset]
        end
        
        def to_param #:nodoc:
          @number.to_s
        end
      end

      # A class for representing ranges around a given page.
      class Window
        # Creates a new Window object for the given +page+ with the specified
        # +padding+.
        def initialize(page, padding=2)
          @paginator = page.paginator
          @page = page
          self.padding = padding
        end
        attr_reader :paginator, :page

        # Sets the window's padding (the number of pages on either side of the
        # window page).
        def padding=(padding)
          @padding = padding < 0 ? 0 : padding
          # Find the beginning and end pages of the window
          @first = @paginator.has_page_number?(@page.number - @padding) ?
            @paginator[@page.number - @padding] : @paginator.first
          @last =  @paginator.has_page_number?(@page.number + @padding) ?
            @paginator[@page.number + @padding] : @paginator.last
        end
        attr_reader :padding, :first, :last

        # Returns an array of Page objects in the current window.
        def pages
          (@first.number..@last.number).to_a.collect! {|n| @paginator[n]}
        end
        alias to_a :pages
      end
    end

  end
end
module ActionController
  # Subclassing AbstractRequest makes these methods available to the request objects used in production and testing,
  # CgiRequest and TestRequest
  class AbstractRequest
    cattr_accessor :relative_url_root

    # Returns the hash of environment variables for this request,
    # such as { 'RAILS_ENV' => 'production' }.
    attr_reader :env

    # Returns both GET and POST parameters in a single hash.
    def parameters
      @parameters ||= request_parameters.update(query_parameters).update(path_parameters).with_indifferent_access
    end

    # Returns the HTTP request method as a lowercase symbol (:get, for example)
    def method
      @request_method ||= @env['REQUEST_METHOD'].downcase.to_sym
    end

    # Is this a GET request?  Equivalent to request.method == :get
    def get?
      method == :get
    end

    # Is this a POST request?  Equivalent to request.method == :post
    def post?
      method == :post
    end

    # Is this a PUT request?  Equivalent to request.method == :put
    def put?
      method == :put
    end

    # Is this a DELETE request?  Equivalent to request.method == :delete
    def delete?
      method == :delete
    end

    # Is this a HEAD request?  Equivalent to request.method == :head
    def head?
      method == :head
    end

    # Determine whether the body of a HTTP call is URL-encoded (default)
    # or matches one of the registered param_parsers. 
    #
    # For backward compatibility, the post format is extracted from the
    # X-Post-Data-Format HTTP header if present.
    def content_type
      @content_type ||=
        begin
          content_type = @env['CONTENT_TYPE'].to_s.downcase
          
          if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
            case x_post_format.to_s.downcase
            when 'yaml'
              content_type = 'application/x-yaml'
            when 'xml'
              content_type = 'application/xml'
            end
          end
          
          Mime::Type.lookup(content_type)
        end
    end

    # Returns the accepted MIME type for the request
    def accepts
      @accepts ||=
        if @env['HTTP_ACCEPT'].to_s.strip.empty?
          [ content_type, Mime::ALL ]
        else
          Mime::Type.parse(@env['HTTP_ACCEPT'])
        end
    end

    # Returns true if the request's "X-Requested-With" header contains
    # "XMLHttpRequest". (The Prototype Javascript library sends this header with
    # every Ajax request.)
    def xml_http_request?
      not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
    end
    alias xhr? :xml_http_request?

    # 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.
    def remote_ip
      return @env['HTTP_CLIENT_IP'] if @env.include? 'HTTP_CLIENT_IP'

      if @env.include? 'HTTP_X_FORWARDED_FOR' then
        remote_ips = @env['HTTP_X_FORWARDED_FOR'].split(',').reject do |ip|
            ip =~ /^unknown$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
        end

        return remote_ips.first.strip unless remote_ips.empty?
      end

      @env['REMOTE_ADDR']
    end

    # Returns the domain part of a host, such as rubyonrails.org in "www.rubyonrails.org". You can specify
    # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk".
    def domain(tld_length = 1)
      return nil if !/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/.match(host).nil? or host.nil?

      host.split('.').last(1 + tld_length).join('.')
    end

    # Returns all the subdomains as an array, so ["dev", "www"] would be returned for "dev.www.rubyonrails.org".
    # You can specify a different <tt>tld_length</tt>, such as 2 to catch ["www"] instead of ["www", "rubyonrails"]
    # in "www.rubyonrails.co.uk".
    def subdomains(tld_length = 1)
      return [] unless host
      parts = host.split('.')
      parts[0..-(tld_length+2)]
    end

    # Receive the raw post data.
    # This is useful for services such as REST, XMLRPC and SOAP
    # which communicate over HTTP POST but don't use the traditional parameter format.
    def raw_post
      @env['RAW_POST_DATA']
    end

    # Returns the request URI correctly, taking into account the idiosyncracies
    # of the various servers.
    def request_uri
      if uri = @env['REQUEST_URI']
        (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri # Remove domain, which webrick puts into the request_uri.
      else  # REQUEST_URI is blank under IIS - get this from PATH_INFO and SCRIPT_NAME
        script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
        uri = @env['PATH_INFO']
        uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
        unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
          uri << '?' << env_qs
        end
        uri
      end
    end

    # Return 'https://' if this is an SSL request and 'http://' otherwise.
    def protocol
      ssl? ? 'https://' : 'http://'
    end

    # Is this an SSL request?
    def ssl?
      @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
    end

    # Returns the interpreted path to requested resource after all the installation directory of this application was taken into account
    def path
      path = (uri = request_uri) ? uri.split('?').first : ''

      # Cut off the path to the installation directory if given
      root = relative_url_root
      path[0, root.length] = '' if root
      path || ''
    end

    # Returns the path minus the web server relative installation directory.
    # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT.
    # It can be automatically extracted for Apache setups. If the server is not
    # Apache, this method returns an empty string.
    def relative_url_root
      @@relative_url_root ||= case
        when @env["RAILS_RELATIVE_URL_ROOT"]
          @env["RAILS_RELATIVE_URL_ROOT"]
        when server_software == 'apache'
          @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '')
        else
          ''
      end
    end

    # Returns the port number of this request as an integer.
    def port
      @port_as_int ||= @env['SERVER_PORT'].to_i
    end

    # Returns the standard port number for this request's protocol
    def standard_port
      case protocol
        when 'https://' then 443
        else 80
      end
    end

    # Returns a port suffix like ":8080" if the port number of this request
    # is not the default HTTP port 80 or HTTPS port 443.
    def port_string
      (port == standard_port) ? '' : ":#{port}"
    end

    # Returns a host:port string for this request, such as example.com or
    # example.com:8080.
    def host_with_port
      host + port_string
    end

    def path_parameters=(parameters) #:nodoc:
      @path_parameters = parameters
      @symbolized_path_parameters = @parameters = nil
    end

    # The same as <tt>path_parameters</tt> with explicitly symbolized keys 
    def symbolized_path_parameters 
      @symbolized_path_parameters ||= path_parameters.symbolize_keys
    end

    # Returns a hash with the parameters used to form the path of the request 
    #
    # Example: 
    #
    #   {:action => 'my_action', :controller => 'my_controller'}
    def path_parameters
      @path_parameters ||= {}
    end

    # Returns the lowercase name of the HTTP server software.
    def server_software
      (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
    end

    #--
    # Must be implemented in the concrete request
    #++
    def query_parameters #:nodoc:
    end

    def request_parameters #:nodoc:
    end

    # Returns the host for this request, such as example.com.
    def host
    end

    def cookies #:nodoc:
    end

    def session #:nodoc:
    end

    def session=(session) #:nodoc:
      @session = session
    end

    def reset_session #:nodoc:
    end
  end
end
module ActionController #:nodoc:
  # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view 
  # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
  # is already implemented by the Action Controller, but the public view should be tailored to your specific application. So too
  # could the decision on whether something is a public or a developer request.
  #
  # You can tailor the rescuing behavior and appearance by overwriting the following two stub methods.
  module Rescue
    def self.append_features(base) #:nodoc:
      super
      base.extend(ClassMethods)
      base.class_eval do
        alias_method :perform_action_without_rescue, :perform_action
        alias_method :perform_action, :perform_action_with_rescue
      end
    end

    module ClassMethods #:nodoc:
      def process_with_exception(request, response, exception)
        new.process(request, response, :rescue_action, exception)
      end
    end

    protected
      # Exception handler called when the performance of an action raises an exception.
      def rescue_action(exception)
        log_error(exception) if logger
        erase_results if performed?

        if consider_all_requests_local || local_request?
          rescue_action_locally(exception)
        else
          rescue_action_in_public(exception)
        end
      end

      # Overwrite to implement custom logging of errors. By default logs as fatal.
      def log_error(exception) #:doc:
        if ActionView::TemplateError === exception
          logger.fatal(exception.to_s)
        else
          logger.fatal(
            "\n\n#{exception.class} (#{exception.message}):\n    " + 
            clean_backtrace(exception).join("\n    ") + 
            "\n\n"
          )
        end
      end

      # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
      def rescue_action_in_public(exception) #:doc:
        case exception
          when RoutingError, UnknownAction then
            render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
          else render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
        end
      end

      # Overwrite to expand the meaning of a local request in order to show local rescues on other occurrences than
      # the remote IP being 127.0.0.1. For example, this could include the IP of the developer machine when debugging
      # remotely.
      def local_request? #:doc:
        [@request.remote_addr, @request.remote_ip] == ["127.0.0.1"] * 2
      end

      # Renders a detailed diagnostics screen on action exceptions. 
      def rescue_action_locally(exception)
        add_variables_to_assigns
        @template.instance_variable_set("@exception", exception)
        @template.instance_variable_set("@rescues_path", File.dirname(__FILE__) + "/templates/rescues/")    
        @template.send(:assign_variables_from_controller)

        @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
    
        @headers["Content-Type"] = "text/html"
        render_file(rescues_path("layout"), response_code_for_rescue(exception))
      end
    
    private
      def perform_action_with_rescue #:nodoc:
        begin
          perform_action_without_rescue
        rescue Object => exception
          if defined?(Breakpoint) && @params["BP-RETRY"]
            msg = exception.backtrace.first
            if md = /^(.+?):(\d+)(?::in `(.+)')?$/.match(msg) then
              origin_file, origin_line = md[1], md[2].to_i

              set_trace_func(lambda do |type, file, line, method, context, klass|
                if file == origin_file and line == origin_line then
                  set_trace_func(nil)
                  @params["BP-RETRY"] = false

                  callstack = caller
                  callstack.slice!(0) if callstack.first["rescue.rb"]
                  file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures

                  message = "Exception at #{file}:#{line}#{" in `#{method}'" if method}." # `' ( for ruby-mode)

                  Breakpoint.handle_breakpoint(context, message, file, line)
                end
              end)

              retry
            end
          end

          rescue_action(exception)
        end
      end

      def rescues_path(template_name)
        File.dirname(__FILE__) + "/templates/rescues/#{template_name}.rhtml"
      end

      def template_path_for_local_rescue(exception)
        rescues_path(
          case exception
            when MissingTemplate then "missing_template"
            when RoutingError then "routing_error"
            when UnknownAction   then "unknown_action"
            when ActionView::TemplateError then "template_error"
            else "diagnostics"
          end
        )
      end
      
      def response_code_for_rescue(exception)
        case exception
          when UnknownAction, RoutingError then "404 Page Not Found"
          else "500 Internal Error"
        end
      end
      
      def clean_backtrace(exception)
        exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line }
      end
  end
end
module ActionController
  class AbstractResponse #:nodoc:
    DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
    attr_accessor :body, :headers, :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params

    def initialize
      @body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
    end

    def redirect(to_url, permanently = false)
      @headers["Status"]   = "302 Found" unless @headers["Status"] == "301 Moved Permanently"
      @headers["location"] = to_url

      @body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
    end
  end
endmodule ActionController
  module Routing #:nodoc:
    class << self
      def expiry_hash(options, recall)
        k = v = nil
        expire_on = {}
        options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))}
        expire_on
      end

      def extract_parameter_value(parameter) #:nodoc:
        CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s) 
      end
      def controller_relative_to(controller, previous)
        if controller.nil?           then previous
        elsif controller[0] == ?/    then controller[1..-1]
        elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
        else controller
        end
      end

      def treat_hash(hash, keys_to_delete = [])
        k = v = nil
        hash.each do |k, v|
          if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
          else
            hash.delete k
            keys_to_delete << k
          end
        end
        hash
      end
      
      def test_condition(expression, condition)
        case condition
          when String then "(#{expression} == #{condition.inspect})"
          when Regexp then
            condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source 
            "(#{condition.inspect} =~ #{expression})"
          when Array then
            conds = condition.collect do |condition|
              cond = test_condition(expression, condition)
              (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})"
            end
            "(#{conds.join(' || ')})"
          when true then expression
          when nil then "! #{expression}"
          else
            raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil"
        end
      end
    end

    class Component #:nodoc:
      def dynamic?()  false end
      def optional?() false end

      def key() nil end
  
      def self.new(string, *args)
        return super(string, *args) unless self == Component
        case string
          when ':controller' then ControllerComponent.new(:controller, *args)
          when /^:(\w+)$/    then DynamicComponent.new($1, *args)
          when /^\*(\w+)$/   then PathComponent.new($1, *args)
          else StaticComponent.new(string, *args)
        end
      end 
    end

    class StaticComponent < Component #:nodoc:
      attr_reader :value
  
      def initialize(value)
        @value = value
      end

      def write_recognition(g)
        g.if_next_matches(value) do |gp|
          gp.move_forward {|gpp| gpp.continue}
        end
      end

      def write_generation(g)
        g.add_segment(value) {|gp| gp.continue }
      end
    end

    class DynamicComponent < Component #:nodoc:
      attr_reader :key, :default
      attr_accessor :condition
  
      def dynamic?()  true      end
      def optional?() @optional end

      def default=(default)
        @optional = true
        @default = default
      end
    
      def initialize(key, options = {})
        @key = key.to_sym
        @optional = false
        default, @condition = options[:default], options[:condition]
        self.default = default if options.key?(:default)
      end

      def default_check(g)
        presence = "#{g.hash_value(key, !! default)}"
        if default
           "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})"
        else
          "! #{presence}"
        end
      end
  
      def write_generation(g)
        wrote_dropout = write_dropout_generation(g)
        write_continue_generation(g, wrote_dropout)
      end

      def write_dropout_generation(g)
        return false unless optional? && g.after.all? {|c| c.optional?}
    
        check = [default_check(g)]
        gp = g.dup # Use another generator to write the conditions after the first &&
        # We do this to ensure that the generator will not assume x_value is set. It will
        # not be set if it follows a false condition -- for example, false && (x = 2)
        
        check += gp.after.map {|c| c.default_check gp}
        gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here
        true 
      end

      def write_continue_generation(g, use_else)
        test  = Routing.test_condition(g.hash_value(key, true, default), condition || true)
        check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test]
    
        g.send(*check) do |gp|
          gp.expire_for_keys(key) unless gp.after.empty?
          add_segments_to(gp) {|gpp| gpp.continue}
        end
      end

      def add_segments_to(g)
        g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp}
      end
  
      def recognition_check(g)
        test_type = [true, nil].include?(condition) ? :presence : :constraint
    
        prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : ''
        check = prefix + Routing.test_condition(g.next_segment(true), condition || true)
    
        g.if(check) {|gp| yield gp, test_type}
      end
  
      def write_recognition(g)
        test_type = nil
        recognition_check(g) do |gp, test_type|
          assign_result(gp) {|gpp| gpp.continue}
        end
    
        if optional? && g.after.all? {|c| c.optional?}
          call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"]
       
          g.send(*call) do |gp|
            assign_default(gp)
            gp.after.each {|c| c.assign_default(gp)}
            gp.finish(false)
          end
        end
      end

      def assign_result(g, with_default = false)
        g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})"
        g.move_forward {|gp| yield gp}
      end

      def assign_default(g)
        g.constant_result key, default unless default.nil?
      end
    end

    class ControllerComponent < DynamicComponent #:nodoc:
      def key() :controller end

      def add_segments_to(g)
        g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp}
      end
    
      def recognition_check(g)
        g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})" 
        g.if('controller_result') do |gp|
          gp << 'controller_value, segments_to_controller = controller_result'
          if condition
            gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')"
            gp.if(Routing.test_condition("controller_path", condition)) do |gpp|
              gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint}
            end
          else
            gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
          end
        end
      end

      def assign_result(g)
        g.result key, 'controller_value'
        yield g
      end

      def assign_default(g)
        ControllerComponent.assign_controller(g, default)
      end
  
      class << self
        def assign_controller(g, controller)
          expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller"
          g.result :controller, expr, true
        end

        def traverse_to_controller(segments, start_at = 0)
          mod = ::Object
          length = segments.length
          index = start_at
          mod_name = controller_name = segment = nil
          while index < length
            return nil unless /\A[A-Za-z][A-Za-z\d_]*\Z/ =~ (segment = segments[index])
            index += 1
            
            mod_name = segment.camelize
            controller_name = "#{mod_name}Controller"
            path_suffix = File.join(segments[start_at..(index - 1)])
            next_mod = nil
            
            # If the controller is already present, or if we load it, return it.
            if mod.const_defined?(controller_name) || attempt_load(mod, controller_name, path_suffix + "_controller") == :defined
              controller = mod.const_get(controller_name)
              return nil unless controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) # it's not really a controller?
              return [controller, (index - start_at)]
            end
            
            # No controller? Look for the module
            if mod.const_defined? mod_name
              next_mod = mod.send(:const_get, mod_name)
              next_mod = nil unless next_mod.is_a?(Module)
            else
              # Try to load a file that defines the module we want.
              case attempt_load(mod, mod_name, path_suffix)
                when :defined then next_mod = mod.const_get mod_name
                when :dir then # We didn't find a file, but there's a dir.
                  next_mod = Module.new # So create a module for the directory
                  mod.send :const_set, mod_name, next_mod
                else
                  return nil
              end
            end
            mod = next_mod
            
            return nil unless mod && mod.is_a?(Module)
          end
          nil
        end
        
      protected
        def safe_load_paths #:nodoc:
          if defined?(RAILS_ROOT)
            $LOAD_PATH.select do |base|
              base = File.expand_path(base)
              extended_root = File.expand_path(RAILS_ROOT)
              # Exclude all paths that are not nested within app, lib, or components.
              base.match(/\A#{Regexp.escape(extended_root)}\/*(app|lib|components)\/[a-z]/) || base =~ %r{rails-[\d.]+/builtin}
            end
          else
            $LOAD_PATH
          end
        end
        
        def attempt_load(mod, const_name, path)
          has_dir = false
          safe_load_paths.each do |load_path|
            full_path = File.join(load_path, path)
            file_path = full_path + '.rb'
            if File.file?(file_path) # Found a .rb file? Load it up
              require_dependency(file_path)
              return :defined if mod.const_defined? const_name
            else
              has_dir ||= File.directory?(full_path)
            end
          end
          return (has_dir ? :dir : nil)
        end
      end
    end

    class PathComponent < DynamicComponent #:nodoc:
      def optional?() true end
      def default()   []  end
      def condition() nil  end

      def default=(value)
        raise RoutingError, "All path components have an implicit default of []" unless value == []
      end
  
      def write_generation(g)
        raise RoutingError, 'Path components must occur last' unless g.after.empty?
        g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do
          g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)"
          g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish }
        end
        g.else { g.finish }
      end
  
      def write_recognition(g)
        raise RoutingError, "Path components must occur last" unless g.after.empty?
    
        start = g.index_name
        start = "(#{start})" unless /^\w+$/ =~ start
    
        value_expr = "#{g.path_name}[#{start}..-1] || []"
        g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})"
        g.finish(false)
      end
  
      class Result < ::Array #:nodoc:
        def to_s() join '/' end
        def self.new_escaped(strings)
          new strings.collect {|str| CGI.unescape str}
        end
      end
    end

    class Route #:nodoc:
      attr_accessor :components, :known
      attr_reader :path, :options, :keys, :defaults
  
      def initialize(path, options = {})
        @path, @options = path, options
    
        initialize_components path
        defaults, conditions = initialize_hashes options.dup
        @defaults = defaults.dup
        configure_components(defaults, conditions)
        add_default_requirements
        initialize_keys
      end
  
      def inspect
        "<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>"
      end
  
      def write_generation(generator = CodeGeneration::GenerationGenerator.new)
        generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])

        if known.empty? then generator.go
        else
          # Alter the conditions to allow :action => 'index' to also catch :action => nil
          altered_known = known.collect do |k, v|
            if k == :action && v== 'index' then [k, [nil, 'index']]
            else [k, v]
            end
          end
          generator.if(generator.check_conditions(altered_known)) {|gp| gp.go }
        end
        
        generator
      end
  
      def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
        g = generator.dup
        g.share_locals_with generator
        g.before, g.current, g.after = [], components.first, (components[1..-1] || [])
    
        known.each do |key, value|
          if key == :controller then ControllerComponent.assign_controller(g, value)
          else g.constant_result(key, value)
          end
        end
    
        g.go
    
        generator
      end

      def initialize_keys
        @keys = (components.collect {|c| c.key} + known.keys).compact
        @keys.freeze
      end
  
      def extra_keys(options)
        options.keys - @keys
      end
    
      def matches_controller?(controller)
        if known[:controller] then known[:controller] == controller
        else
          c = components.find {|c| c.key == :controller}
          return false unless c
          return c.condition.nil? || eval(Routing.test_condition('controller', c.condition))
        end
      end
  
      protected
        def initialize_components(path)
          path = path.split('/') if path.is_a? String
          path.shift if path.first.blank?
          self.components = path.collect {|str| Component.new str}
        end
    
        def initialize_hashes(options)
          path_keys = components.collect {|c| c.key }.compact 
          self.known = {}
          defaults = options.delete(:defaults) || {}
          conditions = options.delete(:require) || {}
          conditions.update(options.delete(:requirements) || {})
      
          options.each do |k, v|
            if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v
            else known[k] = v
            end
          end
          [defaults, conditions]
        end
    
        def configure_components(defaults, conditions)
          components.each do |component|
            if defaults.key?(component.key) then component.default = defaults[component.key]
            elsif component.key == :action  then component.default = 'index'
            elsif component.key == :id      then component.default = nil
            end
        
            component.condition = conditions[component.key] if conditions.key?(component.key)
          end
        end
        
        def add_default_requirements
          component_keys = components.collect {|c| c.key}
          known[:action] ||= 'index' unless component_keys.include? :action
        end
    end

    class RouteSet #:nodoc:
      attr_reader :routes, :categories, :controller_to_selector
      def initialize
        @routes = []
        @generation_methods = Hash.new(:generate_default_path)
      end
      
      def generate(options, request_or_recall_hash = {})
        recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters
        use_recall = true
        
        controller = options[:controller]
        options[:action] ||= 'index' if controller
        recall_controller = recall[:controller]
        if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/)) 
          recall = {} if controller && controller[0] == ?/
          options[:controller] = Routing.controller_relative_to(controller, recall_controller)
        end
        options = recall.dup if options.empty? # XXX move to url_rewriter?
        
        keys_to_delete = []
        Routing.treat_hash(options, keys_to_delete)
        
        merged = recall.merge(options)
        keys_to_delete.each {|key| merged.delete key}
        expire_on = Routing.expiry_hash(options, recall)
    
        generate_path(merged, options, expire_on)
      end
      
      def generate_path(merged, options, expire_on)
        send @generation_methods[merged[:controller]], merged, options, expire_on
      end
      def generate_default_path(*args)
        write_generation
        generate_default_path(*args)
      end
  
      def write_generation
        method_sources = []
        @generation_methods = Hash.new(:generate_default_path)
        categorize_routes.each do |controller, routes|
          next unless routes.length < @routes.length
      
          ivar = controller.gsub('/', '__')
          method_name = "generate_path_for_#{ivar}".to_sym
          instance_variable_set "@#{ivar}", routes
          code = generation_code_for(ivar, method_name).to_s
          method_sources << code
          
          filename = "generated_code/routing/generation_for_controller_#{controller}.rb"
          eval(code, nil, filename)
      
          @generation_methods[controller.to_s]   = method_name
          @generation_methods[controller.to_sym] = method_name
        end
        
        code = generation_code_for('routes', 'generate_default_path').to_s
        eval(code, nil, 'generated_code/routing/generation.rb')
        
        return (method_sources << code)
      end

      def recognize(request)
        string_path = request.path  
        string_path.chomp! if string_path[0] == ?/  
        path = string_path.split '/'  
        path.shift  
   
        hash = recognize_path(path)  
        return recognition_failed(request) unless hash && hash['controller']  
   
        controller = hash['controller']  
        hash['controller'] = controller.controller_path  
        request.path_parameters = hash  
        controller.new 
      end
      alias :recognize! :recognize
  
      def recognition_failed(request)
        raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}"
      end

      def write_recognition
        g = generator = CodeGeneration::RecognitionGenerator.new
        g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"}
    
        g.def "self.recognize_path(path)" do
          each do |route|
            g << 'index = 0'
            route.write_recognition(g)
          end
        end
        
        eval g.to_s, nil, 'generated/routing/recognition.rb'
        return g.to_s
      end
        
      def generation_code_for(ivar = 'routes', method_name = nil)
        routes = instance_variable_get('@' + ivar)
        key_ivar = "@keys_for_#{ivar}"
        instance_variable_set(key_ivar, routes.collect {|route| route.keys})
    
        g = generator = CodeGeneration::GenerationGenerator.new
        g.def "self.#{method_name}(merged, options, expire_on)" do
          g << 'unused_count = options.length + 1'
          g << "unused_keys = keys = options.keys"
          g << 'path = nil'
      
          routes.each_with_index do |route, index|
            g << "new_unused_keys = keys - #{key_ivar}[#{index}]"
            g << 'new_path = ('
            g.source.indent do
              if index.zero?
                g << "new_unused_count = new_unused_keys.length"
                g << "hash = merged; not_expired = true"
                route.write_generation(g.dup)
              else
                g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp|
                  gp << "hash = merged; not_expired = true"
                  route.write_generation(gp)
                end
              end
            end
            g.source.lines.last << ' )' # Add the closing brace to the end line
            g.if 'new_path' do
              g << 'return new_path, [] if new_unused_count.zero?'
              g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count'
            end
          end
        
          g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path"
          g << "return path, unused_keys"
        end
        
        return g
      end
      
      def categorize_routes
        @categorized_routes = by_controller = Hash.new(self)
      
        known_controllers.each do |name|
          set = by_controller[name] = []
          each do |route|
            set << route if route.matches_controller? name
          end
        end
    
        @categorized_routes
      end
      
      def known_controllers
        @routes.inject([]) do |known, route|
          if (controller = route.known[:controller])
            if controller.is_a?(Regexp)
              known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word} 
            else known << controller
            end
          end
          known
        end.uniq
      end

      def reload
        NamedRoutes.clear
        
        if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb'))
        else connect(':controller/:action/:id', :action => 'index', :id => nil)
        end

        NamedRoutes.install
      end

      def connect(*args)
        new_route = Route.new(*args)
        @routes << new_route
        return new_route
      end

      def draw
        old_routes = @routes
        @routes = []
        
        begin yield self
        rescue
          @routes = old_routes
          raise
        end
        write_generation
        write_recognition
      end
      
      def empty?() @routes.empty? end
  
      def each(&block) @routes.each(&block) end
      
      # Defines a new named route with the provided name and arguments.
      # This method need only be used when you wish to use a name that a RouteSet instance
      # method exists for, such as categories.
      #
      # For example, map.categories '/categories', :controller => 'categories' will not work
      # due to RouteSet#categories.
      def named_route(name, path, hash = {})
        route = connect(path, hash)
        NamedRoutes.name_route(route, name)
        route
      end
      
      def method_missing(name, *args)
        (1..2).include?(args.length) ? named_route(name, *args) : super(name, *args)
      end

      def extra_keys(options, recall = {})
        generate(options.dup, recall).last
      end
    end

    module NamedRoutes #:nodoc:
      Helpers = []
      class << self
        def clear() Helpers.clear end
  
        def hash_access_name(name)
          "hash_for_#{name}_url"
        end

        def url_helper_name(name)
          "#{name}_url"
        end
        
        def known_hash_for_route(route)
          hash = route.known.symbolize_keys
          route.defaults.each do |key, value|
            hash[key.to_sym] ||= value if value
          end
          hash[:controller] = "/#{hash[:controller]}"
          
          hash
        end
        
        def define_hash_access_method(route, name)
          hash = known_hash_for_route(route)
          define_method(hash_access_name(name)) do |*args|
            args.first ? hash.merge(args.first) : hash
          end
        end
        
        def name_route(route, name)
          define_hash_access_method(route, name)
          
          module_eval(%{def #{url_helper_name name}(options = {})
            url_for(#{hash_access_name(name)}.merge(options))
          end}, "generated/routing/named_routes/#{name}.rb")
      
          protected url_helper_name(name), hash_access_name(name)
      
          Helpers << url_helper_name(name).to_sym
          Helpers << hash_access_name(name).to_sym
          Helpers.uniq!
        end
    
        def install(cls = ActionController::Base)
          cls.send :include, self
          if cls.respond_to? :helper_method
            Helpers.each do |helper_name|
              cls.send :helper_method, helper_name
            end
          end
        end
      end
    end

    Routes = RouteSet.new
  end
end
module ActionController
  module Scaffolding # :nodoc:
    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    # Scaffolding is a way to quickly put an Active Record class online by providing a series of standardized actions
    # for listing, showing, creating, updating, and destroying objects of the class. These standardized actions come
    # with both controller logic and default templates that through introspection already know which fields to display
    # and which input types to use. Example:
    #
    #  class WeblogController < ActionController::Base
    #    scaffold :entry
    #  end
    #
    # This tiny piece of code will add all of the following methods to the controller:
    #
    #  class WeblogController < ActionController::Base
    #    verify :method => :post, :only => [ :destroy, :create, :update ],
    #           :redirect_to => { :action => :list }
    #
    #    def index
    #      list
    #    end
    #
    #    def list
    #      @entries = Entry.find_all
    #      render_scaffold "list"
    #    end
    #  
    #    def show
    #      @entry = Entry.find(params[:id])
    #      render_scaffold
    #    end
    #    
    #    def destroy
    #      Entry.find(params[:id]).destroy
    #      redirect_to :action => "list"
    #    end
    #    
    #    def new
    #      @entry = Entry.new
    #      render_scaffold
    #    end
    #    
    #    def create
    #      @entry = Entry.new(params[:entry])
    #      if @entry.save
    #        flash[:notice] = "Entry was successfully created"
    #        redirect_to :action => "list"
    #      else
    #        render_scaffold('new')
    #      end
    #    end
    #    
    #    def edit
    #      @entry = Entry.find(params[:id])
    #      render_scaffold
    #    end
    #    
    #    def update
    #      @entry = Entry.find(params[:id])
    #      @entry.attributes = params[:entry]
    #  
    #      if @entry.save
    #        flash[:notice] = "Entry was successfully updated"
    #        redirect_to :action => "show", :id => @entry
    #      else
    #        render_scaffold('edit')
    #      end
    #    end
    #  end
    #
    # The <tt>render_scaffold</tt> method will first check to see if you've made your own template (like "weblog/show.rhtml" for 
    # the show action) and if not, then render the generic template for that action. This gives you the possibility of using the 
    # scaffold while you're building your specific application. Start out with a totally generic setup, then replace one template 
    # and one action at a time while relying on the rest of the scaffolded templates and actions.
    module ClassMethods
      # Adds a swath of generic CRUD actions to the controller. The +model_id+ is automatically converted into a class name unless
      # one is specifically provide through <tt>options[:class_name]</tt>. So <tt>scaffold :post</tt> would use Post as the class
      # and @post/@posts for the instance variables.
      # 
      # It's possible to use more than one scaffold in a single controller by specifying <tt>options[:suffix] = true</tt>. This will
      # make <tt>scaffold :post, :suffix => true</tt> use method names like list_post, show_post, and create_post 
      # instead of just list, show, and post. If suffix is used, then no index method is added.
      def scaffold(model_id, options = {})
        options.assert_valid_keys(:class_name, :suffix)

        singular_name = model_id.to_s
        class_name    = options[:class_name] || singular_name.camelize
        plural_name   = singular_name.pluralize
        suffix        = options[:suffix] ? "_#{singular_name}" : ""

        unless options[:suffix]
          module_eval <<-"end_eval", __FILE__, __LINE__
            def index
              list
            end
          end_eval
        end
        
        module_eval <<-"end_eval", __FILE__, __LINE__
          
          verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ],
                 :redirect_to => { :action => :list#{suffix} }
          
        
          def list#{suffix}
            @#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10
            render#{suffix}_scaffold "list#{suffix}"
          end

          def show#{suffix}
            @#{singular_name} = #{class_name}.find(params[:id])
            render#{suffix}_scaffold
          end
          
          def destroy#{suffix}
            #{class_name}.find(params[:id]).destroy
            redirect_to :action => "list#{suffix}"
          end
          
          def new#{suffix}
            @#{singular_name} = #{class_name}.new
            render#{suffix}_scaffold
          end
          
          def create#{suffix}
            @#{singular_name} = #{class_name}.new(params[:#{singular_name}])
            if @#{singular_name}.save
              flash[:notice] = "#{class_name} was successfully created"
              redirect_to :action => "list#{suffix}"
            else
              render#{suffix}_scaffold('new')
            end
          end
          
          def edit#{suffix}
            @#{singular_name} = #{class_name}.find(params[:id])
            render#{suffix}_scaffold
          end
          
          def update#{suffix}
            @#{singular_name} = #{class_name}.find(params[:id])
            @#{singular_name}.attributes = params[:#{singular_name}]

            if @#{singular_name}.save
              flash[:notice] = "#{class_name} was successfully updated"
              redirect_to :action => "show#{suffix}", :id => @#{singular_name}
            else
              render#{suffix}_scaffold('edit')
            end
          end
          
          private
            def render#{suffix}_scaffold(action=nil)
              action ||= caller_method_name(caller)
              # logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger
              
              if template_exists?("\#{self.class.controller_path}/\#{action}")
                render_action(action)
              else
                @scaffold_class = #{class_name}
                @scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
                @scaffold_suffix = "#{suffix}"
                add_instance_variables_to_assigns

                @template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))

                if !active_layout.nil?
                  render_file(active_layout, nil, true)
                else
                  render_file(scaffold_path("layout"))
                end
              end
            end

            def scaffold_path(template_name)
              File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
            end
            
            def caller_method_name(caller)
              caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
            end
        end_eval
      end      
    end
  end
end
require 'cgi'
require 'cgi/session'
require 'digest/md5'
require 'base64'

class CGI
  class Session
    # Return this session's underlying Session instance. Useful for the DB-backed session stores.
    def model
      @dbman.model if @dbman
    end


    # 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
    # with text +session_id+ and +data+ attributes is sufficient.
    #
    # The default assumes a +sessions+ tables with columns:
    #   +id+ (numeric primary key),
    #   +session_id+ (text, or longtext if your session data exceeds 65K), and
    #   +data+ (text or longtext; careful if your session data exceeds 65KB).
    # The +session_id+ column should always be indexed for speedy lookups.
    # Session data is marshaled to the +data+ column in Base64 format.
    # If the data you write is larger than the column's size limit,
    # 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:
    #   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
    # on ApplicationController is a good place.
    #
    # Since the default class is a simple Active Record, you get timestamps
    # for free if you add +created_at+ and +updated_at+ datetime columns to
    # the +sessions+ table, making periodic session expiration a snap.
    #
    # 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+
    # You must implement these methods:
    #   self.find_by_session_id(session_id)
    #   initialize(hash_of_session_id_and_data)
    #   attr_reader :session_id
    #   attr_accessor :data
    #   save
    #   destroy
    #
    # The example SqlBypass class is a generic SQL session store.  You may
    # use it as a basis for high-performance database-specific stores.
    class ActiveRecordStore
      # The default Active Record class.
      class Session < ActiveRecord::Base
        # Customizable data column name.  Defaults to 'data'.
        cattr_accessor :data_column_name
        self.data_column_name = 'data'

        before_save :marshal_data!
        before_save :raise_on_session_data_overflow!

        class << self
          # Don't try to reload ARStore::Session in dev mode.
          def reloadable? #:nodoc:
            false
          end

          def data_column_size_limit
            @data_column_size_limit ||= columns_hash[@@data_column_name].limit
          end

          # Hook to set up sessid compatibility.
          def find_by_session_id(session_id)
            setup_sessid_compatibility!
            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 create_table!
            connection.execute <<-end_sql
              CREATE TABLE #{table_name} (
                id INTEGER PRIMARY KEY,
                #{connection.quote_column_name('session_id')} TEXT UNIQUE,
                #{connection.quote_column_name(@@data_column_name)} TEXT(255)
              )
            end_sql
          end

          def drop_table!
            connection.execute "DROP TABLE #{table_name}"
          end

          private
            # Compatibility with tables using sessid instead of session_id.
            def setup_sessid_compatibility!
              # Reset column info since it may be stale.
              reset_column_information
              if columns_hash['sessid']
                def self.find_by_session_id(*args)
                  find_by_sessid(*args)
                end

                define_method(:session_id)  { sessid }
                define_method(:session_id=) { |session_id| self.sessid = session_id }
              else
                def self.find_by_session_id(session_id)
                  find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
                end
              end
            end
        end

        # Lazy-unmarshal session state.
        def data
          @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
        end

        # Has the session been loaded yet?
        def loaded?
          !! @data
        end

        private
          attr_writer :data

          def marshal_data!
            return false if !loaded?
            write_attribute(@@data_column_name, self.class.marshal(self.data))
          end

          # Ensures that the data about to be stored in the database is not
          # larger than the data storage column. Raises
          # ActionController::SessionOverflowError.
          def raise_on_session_data_overflow!
            return false if !loaded?
            limit = self.class.data_column_size_limit
            if loaded? and limit and read_attribute(@@data_column_name).size > limit
              raise ActionController::SessionOverflowError
            end
          end
      end

      # A barebones session store which duck-types with the default session
      # store but bypasses Active Record and issues SQL directly.  This is
      # an example session model class meant as a basis for your own classes.
      #
      # 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))+.
      #
      # This marshaling behavior is intended to store the widest range of
      # binary session data in a +text+ column.  For higher performance,
      # store in a +blob+ column instead and forgo the Base64 encoding.
      class SqlBypass
        # Use the ActiveRecord::Base.connection by default.
        cattr_accessor :connection

        # The table name defaults to 'sessions'.
        cattr_accessor :table_name
        @@table_name = 'sessions'

        # The session id field defaults to 'session_id'.
        cattr_accessor :session_id_column
        @@session_id_column = 'session_id'

        # The data field defaults to 'data'.
        cattr_accessor :data_column
        @@data_column = 'data'

        class << self

          def connection
            @@connection ||= ActiveRecord::Base.connection
          end

          # Look up a session by id and unmarshal its data if found.
          def find_by_session_id(session_id)
            if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
              new(:session_id => session_id, :marshaled_data => record['data'])
            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 create_table!
            @@connection.execute <<-end_sql
              CREATE TABLE #{table_name} (
                id INTEGER PRIMARY KEY,
                #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
                #{@@connection.quote_column_name(data_column)} TEXT
              )
            end_sql
          end

          def drop_table!
            @@connection.execute "DROP TABLE #{table_name}"
          end
        end

        attr_reader :session_id
        attr_writer :data

        # Look for normal and marshaled data, self.find_by_session_id's way of
        # telling us to postpone unmarshaling until the data is requested.
        # We need to handle a normal data attribute in case of a new record.
        def initialize(attributes)
          @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
          @new_record = @marshaled_data.nil?
        end

        def new_record?
          @new_record
        end

        # Lazy-unmarshal session state.
        def data
          unless @data
            if @marshaled_data
              @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
            else
              @data = {}
            end
          end
          @data
        end

        def loaded?
          !! @data
        end

        def save
          return false if !loaded?
          marshaled_data = self.class.marshal(data)

          if @new_record
            @new_record = false
            @@connection.update <<-end_sql, 'Create session'
              INSERT INTO #{@@table_name} (
                #{@@connection.quote_column_name(@@session_id_column)},
                #{@@connection.quote_column_name(@@data_column)} )
              VALUES (
                #{@@connection.quote(session_id)},
                #{@@connection.quote(marshaled_data)} )
            end_sql
          else
            @@connection.update <<-end_sql, 'Update session'
              UPDATE #{@@table_name}
              SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
              WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
            end_sql
          end
        end

        def destroy
          unless @new_record
            @@connection.delete <<-end_sql, 'Destroy session'
              DELETE FROM #{@@table_name}
              WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
            end_sql
          end
        end
      end


      # The class used for session storage.  Defaults to
      # CGI::Session::ActiveRecordStore::Session.
      cattr_accessor :session_class
      self.session_class = Session

      # Find or instantiate a session given a CGI::Session.
      def initialize(session, option = nil)
        session_id = session.session_id
        unless @session = ActiveRecord::Base.silence { @@session_class.find_by_session_id(session_id) }
          unless session.new_session
            raise CGI::Session::NoSession, 'uninitialized session'
          end
          @session = @@session_class.new(:session_id => session_id, :data => {})
          # session saving can be lazy again, because of improved component implementation
          # therefore next line gets commented out:
          # @session.save
        end
      end

      # Access the underlying session model.
      def model
        @session
      end

      # Restore session state.  The session model handles unmarshaling.
      def restore
        if @session
          @session.data
        end
      end

      # Save session store.
      def update
        if @session
          ActiveRecord::Base.silence { @session.save }
        end
      end

      # Save and close the session store.
      def close
        if @session
          update
          @session = nil
        end
      end

      # Delete and close the session store.
      def delete
        if @session
          ActiveRecord::Base.silence { @session.destroy }
          @session = nil
        end
      end

      protected
        def logger
          ActionController::Base.logger rescue nil
        end
    end
  end
end
#!/usr/local/bin/ruby -w
                                                                                
# This is a really simple session storage daemon, basically just a hash, 
# which is enabled for DRb access.
                                                                                
require 'drb'

session_hash = Hash.new
session_hash.instance_eval { @mutex = Mutex.new }

class <<session_hash
  def []=(key, value)
    @mutex.synchronize do
      super(key, value)
    end
  end
  
  def [](key)
    @mutex.synchronize do
      super(key)
    end
  end
  
  def delete(key)
    @mutex.synchronize do
      super(key)
    end
  end
end

DRb.start_service('druby://127.0.0.1:9192', session_hash)
DRb.thread.joinrequire 'cgi'
require 'cgi/session'
require 'drb'
 
class CGI #:nodoc:all
  class Session
    class DRbStore
      @@session_data = DRbObject.new(nil, 'druby://localhost:9192')
 
      def initialize(session, option=nil)
        @session_id = session.session_id
      end
 
      def restore
        @h = @@session_data[@session_id] || {}
      end
 
      def update
        @@session_data[@session_id] = @h
      end
 
      def close
        update
      end
 
      def delete
        @@session_data.delete(@session_id)
      end
    end
  end
end
# cgi/session/memcached.rb - persistent storage of marshalled session data
#
# == Overview
#
# This file provides the CGI::Session::MemCache class, which builds
# persistence of storage data on top of the MemCache library.  See
# cgi/session.rb for more details on session storage managers.
#

begin
  require 'cgi/session'
  require 'memcache'

  class CGI
    class Session
      # MemCache-based session storage class.
      #
      # This builds upon the top-level MemCache class provided by the
      # library file memcache.rb.  Session data is marshalled and stored
      # in a memcached cache.
      class MemCacheStore
        def check_id(id) #:nodoc:#
          /[^0-9a-zA-Z]+/ =~ id.to_s ? false : true
        end

        # Create a new CGI::Session::MemCache instance
        #
        # This constructor is used internally by CGI::Session. The
        # user does not generally need to call it directly.
        #
        # +session+ is the session for which this instance is being
        # created. The session id must only contain alphanumeric
        # characters; automatically generated session ids observe
        # this requirement.
        #
        # +options+ is a hash of options for the initializer. The
        # following options are recognized:
        #
        # cache::  an instance of a MemCache client to use as the
        #      session cache.
        #
        # expires:: an expiry time value to use for session entries in
        #     the session cache. +expires+ is interpreted in seconds
        #     relative to the current time if it's less than 60*60*24*30
        #     (30 days), or as an absolute Unix time (e.g., Time#to_i) if
        #     greater. If +expires+ is +0+, or not passed on +options+,
        #     the entry will never expire.
        #
        # This session's memcache entry will be created if it does
        # not exist, or retrieved if it does.
        def initialize(session, options = {})
          id = session.session_id
          unless check_id(id)
            raise ArgumentError, "session_id '%s' is invalid" % id
          end
          @cache = options['cache'] || MemCache.new('localhost')
          @expires = options['expires'] || 0
          @session_key = "session:#{id}"
          @session_data = {}
        end

        # Restore session state from the session's memcache entry.
        #
        # Returns the session state as a hash.
        def restore
          begin
            @session_data = @cache[@session_key] || {}
          rescue
            @session_data = {}
          end
        end

        # Save session state to the session's memcache entry.
        def update
          begin
            @cache.set(@session_key, @session_data, @expires)
          rescue
            # Ignore session update failures.
          end
        end
      
        # Update and close the session's memcache entry.
        def close
          update
        end

        # Delete the session's memcache entry.
        def delete
          begin
            @cache.delete(@session_key)
          rescue
            # Ignore session delete failures.
          end
          @session_data = {}
        end
      end
    end
  end
rescue LoadError
  # MemCache wasn't available so neither can the store be
end
require 'action_controller/session/drb_store'
require 'action_controller/session/mem_cache_store'
if Object.const_defined?(:ActiveRecord)
  require 'action_controller/session/active_record_store'
end

module ActionController #:nodoc:
  module SessionManagement #:nodoc:
    def self.included(base)
      base.extend(ClassMethods)

      base.send :alias_method, :process_without_session_management_support, :process
      base.send :alias_method, :process, :process_with_session_management_support

      base.send :alias_method, :process_cleanup_without_session_management_support, :process_cleanup
      base.send :alias_method, :process_cleanup, :process_cleanup_with_session_management_support
    end

    module ClassMethods
      # Set the session store to be used for keeping the session data between requests. The default is using the
      # file system, but you can also specify one of the other included stores (:active_record_store, :drb_store, 
      # :mem_cache_store, or :memory_store) or use your own 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
      end

      # Returns the session store class currently used.
      def session_store
        ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
      end

      # Returns the hash used to configure the session. Example use:
      #
      #   ActionController::Base.session_options[:session_secure] = true # session only available over HTTPS
      def session_options
        ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
      end
      
      # Specify how sessions ought to be managed for a subset of the actions on
      # the controller. Like filters, you can specify <tt>:only</tt> and
      # <tt>:except</tt> clauses to restrict the subset, otherwise options
      # apply to all actions on this controller.
      #
      # The session options are inheritable, as well, so if you specify them in
      # a parent controller, they apply to controllers that extend the parent.
      #
      # Usage:
      #
      #   # turn off session management for all actions.
      #   session :off
      #
      #   # turn off session management for all actions _except_ foo and bar.
      #   session :off, :except => %w(foo bar)
      #
      #   # turn off session management for only the foo and bar actions.
      #   session :off, :only => %w(foo bar)
      #
      #   # the session will only work over HTTPS, but only for the foo action
      #   session :only => :foo, :session_secure => true
      #
      #   # the session will only be disabled for 'foo', and only if it is
      #   # requested as a web service
      #   session :off, :only => :foo,
      #           :if => Proc.new { |req| req.parameters[:ws] }
      #
      # All session options described for ActionController::Base.process_cgi
      # are valid arguments.
      def session(*args)
        options = Hash === args.last ? args.pop : {}

        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]
        if options[:only] && options[:except]
          raise ArgumentError, "only one of either :only or :except are allowed"
        end

        write_inheritable_array("session_options", [options])
      end

      def cached_session_options #:nodoc:
        @session_options ||= read_inheritable_attribute("session_options") || []
      end

      def session_options_for(request, action) #:nodoc:
        if (session_options = cached_session_options).empty?
          {}
        else
          options = {}

          action = action.to_s
          session_options.each do |opts|
            next if opts[:if] && !opts[:if].call(request)
            if opts[:only] && opts[:only].include?(action)
              options.merge!(opts)
            elsif opts[:except] && !opts[:except].include?(action)
              options.merge!(opts)
            elsif !opts[:only] && !opts[:except]
              options.merge!(opts)
            end
          end
          
          if options.empty? then options
          else
            options.delete :only
            options.delete :except
            options.delete :if
            options[:disabled] ? false : options
          end
        end
      end
    end

    def process_with_session_management_support(request, response, method = :perform_action, *arguments) #:nodoc:
      set_session_options(request)
      process_without_session_management_support(request, response, method, *arguments)
    end

    private
      def set_session_options(request)
        request.session_options = self.class.session_options_for(request, request.parameters["action"] || "index")
      end
      
      def process_cleanup_with_session_management_support
        process_cleanup_without_session_management_support
        clear_persistent_model_associations
      end

      # Clear cached associations in session data so they don't overflow
      # the database field.  Only applies to ActiveRecordStore since there
      # is not a standard way to iterate over session data.
      def clear_persistent_model_associations #:doc:
        if defined?(@session) && @session.instance_variables.include?('@data')
          session_data = @session.instance_variable_get('@data')

          if session_data && session_data.respond_to?(:each_value)
            session_data.each_value do |obj|
              obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
            end
          end
        end
      end
  end
end
module ActionController #:nodoc:
  # Methods for sending files and streams to the browser instead of rendering.
  module Streaming
    DEFAULT_SEND_FILE_OPTIONS = {
      :type         => 'application/octet-stream'.freeze,
      :disposition  => 'attachment'.freeze,
      :stream       => true, 
      :buffer_size  => 4096
    }.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
      # download any file on your server.
      #
      # Options:
      # * <tt>:filename</tt> - suggests a filename for the browser to use.
      #   Defaults to File.basename(path).
      # * <tt>:type</tt> - specifies an HTTP content type.
      #   Defaults to 'application/octet-stream'.
      # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.  
      #   Valid values are 'inline' and 'attachment' (default).
      # * <tt>:stream</tt> - 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.
      # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
      #   Defaults to 4096.
      # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
      #
      # The default Content-Type and Content-Disposition headers are
      # set to download arbitrary binary files in as many browsers as
      # possible.  IE versions 4, 5, 5.5, and 6 are all known to have
      # 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
      #
      # 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
      # by intermediaries.  They default to require clients to validate with
      # the server before releasing cached responses.  See
      # http://www.mnot.net/cache_docs/ for an overview of web caching and
      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
      # for the Cache-Control header spec.
      def send_file(path, options = {}) #:doc:
        raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)

        options[:length]   ||= File.size(path)
        options[:filename] ||= File.basename(path)
        send_file_headers! options

        @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|
              if output.respond_to?(:syswrite)
                begin
                  while true
                    output.syswrite(file.sysread(len))
                  end
                rescue EOFError
                end
              else
                while buf = file.read(len)
                  output.write(buf)
                end
              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

      # Send binary data to the user as a file download.  May set content type, apparent file name,
      # and specify whether to show data inline or download as an attachment.
      #
      # Options:
      # * <tt>:filename</tt> - Suggests a filename for the browser to use.
      # * <tt>:type</tt> - specifies an HTTP content type.
      #   Defaults to 'application/octet-stream'.
      # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.  
      # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
      #   Valid values are 'inline' and 'attachment' (default).
      #
      # 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?
        send_file_headers! options.merge(:length => data.size)
        @performed_render = false
        render :status => options[:status], :text => data
      end

    private
      def send_file_headers!(options)
        options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
        [:length, :type, :disposition].each do |arg|
          raise ArgumentError, ":#{arg} option required" if options[arg].nil?
        end

        disposition = options[:disposition].dup || 'attachment'
        
        disposition <<= %(; filename="#{options[:filename]}") if options[:filename]

        @headers.update(
          'Content-Length'            => options[:length],
          'Content-Type'              => options[:type].strip,  # fixes a problem with extra '\r' with some browsers
          'Content-Disposition'       => disposition,
          'Content-Transfer-Encoding' => 'binary'
        )

        # 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 
        # is called for handling the download is run, so let's workaround that
        @headers['Cache-Control'] = 'private' if @headers['Cache-Control'] == 'no-cache'
      end
  end
end
require File.dirname(__FILE__) + '/assertions'
require File.dirname(__FILE__) + '/deprecated_assertions'

module ActionController #:nodoc:
  class Base
    # Process a test request called with a +TestRequest+ object.
    def self.process_test(request)
      new.process_test(request)
    end

    def process_test(request) #:nodoc:
      process(request, TestResponse.new)
    end

    def process_with_test(*args)
      returning process_without_test(*args) do
        add_variables_to_assigns
      end
    end

    alias_method :process_without_test, :process
    alias_method :process, :process_with_test
  end

  class TestRequest < AbstractRequest #:nodoc:
    attr_accessor :cookies, :session_options
    attr_accessor :query_parameters, :request_parameters, :path, :session, :env
    attr_accessor :host

    def initialize(query_parameters = nil, request_parameters = nil, session = nil)
      @query_parameters   = query_parameters || {}
      @request_parameters = request_parameters || {}
      @session            = session || TestSession.new

      initialize_containers
      initialize_default_values

      super()
    end

    def reset_session
      @session = {}
    end              

    def raw_post
      if raw_post = env['RAW_POST_DATA']
        raw_post
      else
        params = self.request_parameters.dup
        %w(controller action only_path).each do |k|
          params.delete(k)
          params.delete(k.to_sym)
        end
    
        params.map { |k,v| [ CGI.escape(k.to_s), CGI.escape(v.to_s) ].join('=') }.sort.join('&')
      end
    end

    def port=(number)
      @env["SERVER_PORT"] = number.to_i
      @port_as_int = nil
    end

    def action=(action_name)
      @query_parameters.update({ "action" => action_name })
      @parameters = nil
    end

    # Used to check AbstractRequest's request_uri functionality.
    # Disables the use of @path and @request_uri so superclass can handle those.
    def set_REQUEST_URI(value)
      @env["REQUEST_URI"] = value
      @request_uri = nil
      @path = nil
    end

    def request_uri=(uri)
      @request_uri = uri
      @path = uri.split("?").first
    end

    def remote_addr=(addr)
      @env['REMOTE_ADDR'] = addr
    end

    def remote_addr
      @env['REMOTE_ADDR']
    end

    def request_uri
      @request_uri || super()
    end

    def path
      @path || super()
    end

    def assign_parameters(controller_path, action, parameters)
      parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
      extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
      non_path_parameters = get? ? query_parameters : request_parameters
      parameters.each do |key, value|
        if value.is_a? Fixnum
          value = value.to_s
        elsif value.is_a? Array
          value = ActionController::Routing::PathComponent::Result.new(value)
        end

        if extra_keys.include?(key.to_sym)
          non_path_parameters[key] = value
        else
          path_parameters[key.to_s] = value
        end
      end
    end                        
    
    def recycle!
      self.request_parameters = {}
      self.query_parameters   = {}
      self.path_parameters    = {}
      @request_method, @accepts, @content_type = nil, nil, nil
    end    

    private
      def initialize_containers
        @env, @cookies = {}, {}
      end

      def initialize_default_values
        @host                    = "test.host"
        @request_uri             = "/"
        self.remote_addr         = "0.0.0.0"        
        @env["SERVER_PORT"]      = 80
        @env['REQUEST_METHOD']   = "GET"
      end
  end

  # 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
    def response_code
      headers['Status'][0,3].to_i rescue 0
    end
    
    # returns a String to ensure compatibility with Net::HTTPResponse
    def code
      headers['Status'].to_s.split(' ')[0]
    end

    def message
      headers['Status'].to_s.split(' ',2)[1]
    end

    # was the response successful?
    def success?
      response_code == 200
    end

    # was the URL not found?
    def missing?
      response_code == 404
    end

    # were we redirected?
    def redirect?
      (300..399).include?(response_code)
    end

    # 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
    def redirect_url
      redirect? ? headers['location'] : nil
    end

    # 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
      p = pattern if pattern.class == Regexp
      return false if p.nil?
      p.match(redirect_url) != nil
    end

    # 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?
        unless with_controller
          template.first_render
        else
          template.first_render.split('/').last || template.first_render
        end
      end
    end

    # 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!)
    def flash
      session['flash'] || {}
    end

    # do we have a flash? 
    def has_flash?
      !session['flash'].empty?
    end

    # do we have a flash that has contents?
    def has_flash_with_contents?
      !flash.empty?
    end

    # does the specified flash object exist?
    def has_flash_object?(name=nil)
      !flash[name].nil?
    end

    # does the specified object exist in the session?
    def has_session_object?(name=nil)
      !session[name].nil?
    end

    # a shortcut to the template.assigns
    def template_objects
      template.assigns || {}
    end

    # 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
    def cookies
      headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
    end

    # Returns binary content (downloadable file), converted to a String
    def binary_content
      raise "Response body is not a Proc: #{body.inspect}" unless body.kind_of?(Proc)
      require 'stringio'

      sio = StringIO.new

      begin 
        $stdout = sio
        body.call
      ensure
        $stdout = STDOUT
      end

      sio.rewind
      sio.read
    end
  end

  class TestResponse < AbstractResponse #:nodoc:
    include TestResponseBehavior
  end

  class TestSession #:nodoc:
    def initialize(attributes = {})
      @attributes = attributes
    end

    def [](key)
      @attributes[key]
    end

    def []=(key, value)
      @attributes[key] = value
    end

    def session_id
      ""
    end

    def update() end
    def close() end
    def delete() @attributes = {} end
  end
  
  # Essentially generates a modified Tempfile object similar to the object
  # you'd get from the standard library CGI module in a multipart
  # request. This means you can use an ActionController::TestUploadedFile
  # object in the params of a test request in order to simulate
  # a file upload.
  #
  # Usage example, within a functional test:
  #   post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
  class TestUploadedFile
    # The filename, *not* including the path, of the "uploaded" file
    attr_reader :original_filename
    
    # The content type of the "uploaded" file
    attr_reader :content_type
    
    def initialize(path, content_type = 'text/plain')
      raise "file does not exist" unless File.exist?(path)
      @content_type = content_type
      @original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
      @tempfile = Tempfile.new(@original_filename)
      FileUtils.copy_file(path, @tempfile.path)
    end
    
    def path #:nodoc:
      @tempfile.path
    end
    
    alias local_path path
    
    def method_missing(method_name, *args, &block) #:nodoc:
      @tempfile.send(method_name, *args, &block)
    end
  end
  
  module TestProcess
    def self.included(base)
      # 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)
            @request.env['REQUEST_METHOD'] = "#{method.upcase}" if @request
            process(action, parameters, session, flash)
          end
        EOV
      end
    end

    # execute the request and set/volley the response
    def process(action, parameters = nil, session = nil, flash = nil)
      # Sanity check for required instance variables so we can give an
      # understandable error message.
      %w(controller request response).each do |iv_name|
        raise "@#{iv_name} is nil: make sure you set it in your test's setup method." if instance_variable_get("@#{iv_name}").nil?
      end

      @request.recycle!

      @html_document = nil
      @request.env['REQUEST_METHOD'] ||= "GET"
      @request.action = action.to_s

      parameters ||= {}
      @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)

      @request.session = ActionController::TestSession.new(session) unless session.nil?
      @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
      build_request_uri(action, parameters)
      @controller.process(@request, @response)
    end

    def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
      @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
      @request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
      returning self.send(request_method, action, parameters, session, flash) do
        @request.env.delete 'HTTP_X_REQUESTED_WITH'
        @request.env.delete 'HTTP_ACCEPT'
      end
    end
    alias xhr :xml_http_request

    def follow_redirect
      if @response.redirected_to[:controller]
        raise "Can't follow redirects outside of current controller (#{@response.redirected_to[:controller]})"
      end

      get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
    end

    def assigns(key = nil) 
      if key.nil? 
        @response.template.assigns 
      else 
        @response.template.assigns[key.to_s] 
      end 
    end 

    def session
      @response.session
    end

    def flash
      @response.flash
    end

    def cookies
      @response.cookies
    end

    def redirect_to_url
      @response.redirect_url
    end

    def build_request_uri(action, parameters)
      unless @request.env['REQUEST_URI']
        options = @controller.send(:rewrite_options, parameters)
        options.update(:only_path => true, :action => action)

        url = ActionController::UrlRewriter.new(@request, parameters)
        @request.set_REQUEST_URI(url.rewrite(options))
      end
    end

    def html_document
      @html_document ||= HTML::Document.new(@response.body)
    end

    def find_tag(conditions)
      html_document.find(conditions)
    end

    def find_all_tag(conditions)
      html_document.find_all(conditions)
    end

    def method_missing(selector, *args)
      return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector)
      return super
    end
    
    # Shortcut for ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type). Example:
    #   post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
    def fixture_file_upload(path, mime_type = nil)
      ActionController::TestUploadedFile.new(
        Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path, 
        mime_type
      )
    end

    # A helper to make it easier to test different route configurations.
    # This method temporarily replaces ActionController::Routing::Routes
    # 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 ... }:
    #
    #   with_routing do |set|
    #     set.draw { set.connect ':controller/:id/:action' }
    #     assert_equal(
    #        ['/content/10/show', {}],
    #        set.generate(:controller => 'content', :id => 10, :action => 'show')
    #     )
    #   end
    #
    def with_routing
      real_routes = ActionController::Routing::Routes
      ActionController::Routing.send :remove_const, :Routes

      temporary_routes = ActionController::Routing::RouteSet.new
      ActionController::Routing.send :const_set, :Routes, temporary_routes
  
      yield temporary_routes
    ensure
      if ActionController::Routing.const_defined? :Routes
        ActionController::Routing.send(:remove_const, :Routes) 
      end
      ActionController::Routing.const_set(:Routes, real_routes) if real_routes
    end
  end
end

module Test
  module Unit
    class TestCase #:nodoc:
      include ActionController::TestProcess
    end
  end
end
module ActionController
  # Rewrites URLs for Base.redirect_to and Base.url_for in the controller.

  class UrlRewriter #:nodoc:
    RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :trailing_slash, :skip_relative_url_root]
    def initialize(request, parameters)
      @request, @parameters = request, parameters
    end
    
    def rewrite(options = {})      
      rewrite_url(rewrite_path(options), options)
    end

    def to_str
      "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}"
    end

    alias_method :to_s, :to_str

    private
      def rewrite_url(path, options)
        rewritten_url = ""
        unless options[:only_path]
          rewritten_url << (options[:protocol] || @request.protocol)
          rewritten_url << (options[:host] || @request.host_with_port)
        end

        rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
        rewritten_url << path
        rewritten_url << '/' if options[:trailing_slash]
        rewritten_url << "##{options[:anchor]}" if options[:anchor]

        rewritten_url
      end

      def rewrite_path(options)
        options = options.symbolize_keys
        options.update(options[:params].symbolize_keys) if options[:params]
        if (overwrite = options.delete(:overwrite_params))
          options.update(@parameters.symbolize_keys)
          options.update(overwrite)
        end
        RESERVED_OPTIONS.each {|k| options.delete k}
        path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash

        path << build_query_string(options, extra_keys) unless extra_keys.empty?
        
        path
      end

      # Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
      # be added as a path element instead of a regular parameter pair.
      def build_query_string(hash, only_keys = nil)
        elements = []
        query_string = ""

        only_keys ||= hash.keys
        
        only_keys.each do |key|
          value = hash[key] 
          key = CGI.escape key.to_s
          if value.class == Array
            key <<  '[]'
          else
            value = [ value ]
          end
          value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" }
        end
        
        query_string << ("?" + elements.join("&")) unless elements.empty?
        query_string
      end
  end
end
require File.dirname(__FILE__) + '/tokenizer'
require File.dirname(__FILE__) + '/node'

module HTML #:nodoc:
  
  # A top-level HTMl document. You give it a body of text, and it will parse that
  # text into a tree of nodes.
  class Document #:nodoc:

    # The root of the parsed document.
    attr_reader :root

    # Create a new Document from the given text.
    def initialize(text, strict=false, xml=false)
      tokenizer = Tokenizer.new(text)
      @root = Node.new(nil)
      node_stack = [ @root ]
      while token = tokenizer.next
        node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token)

        node_stack.last.children << node unless node.tag? && node.closing == :close
        if node.tag?
          if node_stack.length > 1 && node.closing == :close
            if node_stack.last.name == node.name
              node_stack.pop
            else
              open_start = node_stack.last.position - 20
              open_start = 0 if open_start < 0
              close_start = node.position - 20
              close_start = 0 if close_start < 0
              msg = <<EOF.strip
ignoring attempt to close #{node_stack.last.name} with #{node.name}
  opened at byte #{node_stack.last.position}, line #{node_stack.last.line}
  closed at byte #{node.position}, line #{node.line}
  attributes at open: #{node_stack.last.attributes.inspect}
  text around open: #{text[open_start,40].inspect}
  text around close: #{text[close_start,40].inspect}
EOF
              strict ? raise(msg) : warn(msg)
            end
          elsif !node.childless?(xml) && node.closing != :close
            node_stack.push node
          end
        end
      end
    end
  
    # Search the tree for (and return) the first node that matches the given
    # conditions. The conditions are interpreted differently for different node
    # types, see HTML::Text#find and HTML::Tag#find.
    def find(conditions)
      @root.find(conditions)
    end

    # Search the tree for (and return) all nodes that match the given
    # conditions. The conditions are interpreted differently for different node
    # types, see HTML::Text#find and HTML::Tag#find.
    def find_all(conditions)
      @root.find_all(conditions)
    end
    
  end

end
require 'strscan'

module HTML #:nodoc:
  
  class Conditions < Hash #:nodoc:
    def initialize(hash)
      super()
      hash = { :content => hash } unless Hash === hash
      hash = keys_to_symbols(hash)
      hash.each do |k,v|
        case k
          when :tag, :content then
            # keys are valid, and require no further processing
          when :attributes then
            hash[k] = keys_to_strings(v)
          when :parent, :child, :ancestor, :descendant, :sibling, :before,
                  :after
            hash[k] = Conditions.new(v)
          when :children
            hash[k] = v = keys_to_symbols(v)
            v.each do |k,v2|
              case k
                when :count, :greater_than, :less_than
                  # keys are valid, and require no further processing
                when :only
                  v[k] = Conditions.new(v2)
                else
                  raise "illegal key #{k.inspect} => #{v2.inspect}"
              end
            end
          else
            raise "illegal key #{k.inspect} => #{v.inspect}"
        end
      end
      update hash
    end

    private

      def keys_to_strings(hash)
        hash.keys.inject({}) do |h,k|
          h[k.to_s] = hash[k]
          h
        end
      end

      def keys_to_symbols(hash)
        hash.keys.inject({}) do |h,k|
          raise "illegal key #{k.inspect}" unless k.respond_to?(:to_sym)
          h[k.to_sym] = hash[k]
          h
        end
      end
  end

  # The base class of all nodes, textual and otherwise, in an HTML document.
  class Node #:nodoc:
    # The array of children of this node. Not all nodes have children.
    attr_reader :children
    
    # The parent node of this node. All nodes have a parent, except for the
    # root node.
    attr_reader :parent
    
    # The line number of the input where this node was begun
    attr_reader :line
    
    # The byte position in the input where this node was begun
    attr_reader :position
    
    # Create a new node as a child of the given parent.
    def initialize(parent, line=0, pos=0)
      @parent = parent
      @children = []
      @line, @position = line, pos
    end

    # Return a textual representation of the node.
    def to_s
      s = ""
      @children.each { |child| s << child.to_s }
      s
    end

    # Return false (subclasses must override this to provide specific matching
    # behavior.) +conditions+ may be of any type.
    def match(conditions)
      false
    end

    # Search the children of this node for the first node for which #find
    # returns non +nil+. Returns the result of the #find call that succeeded.
    def find(conditions)
      conditions = validate_conditions(conditions)

      @children.each do |child|        
        node = child.find(conditions)
        return node if node
      end
      nil
    end

    # Search for all nodes that match the given conditions, and return them
    # as an array.
    def find_all(conditions)
      conditions = validate_conditions(conditions)

      matches = []
      matches << self if match(conditions)
      @children.each do |child|
        matches.concat child.find_all(conditions)
      end
      matches
    end

    # Returns +false+. Subclasses may override this if they define a kind of
    # tag.
    def tag?
      false
    end

    def validate_conditions(conditions)
      Conditions === conditions ? conditions : Conditions.new(conditions)
    end

    def ==(node)
      return false unless self.class == node.class && children.size == node.children.size

      equivalent = true

      children.size.times do |i|
        equivalent &&= children[i] == node.children[i]
      end

      equivalent
    end
    
    class <<self
      def parse(parent, line, pos, content, strict=true)
        if content !~ /^<\S/
          Text.new(parent, line, pos, content)
        else
          scanner = StringScanner.new(content)

          unless scanner.skip(/</)
            if strict
              raise "expected <"
            else
              return Text.new(parent, line, pos, content)
            end
          end

          if scanner.skip(/!\[CDATA\[/)
            scanner.scan_until(/\]\]>/)
            return CDATA.new(parent, line, pos, scanner.pre_match)
          end
          
          closing = ( scanner.scan(/\//) ? :close : nil )
          return Text.new(parent, line, pos, content) unless name = scanner.scan(/[\w:]+/)
          name.downcase!
  
          unless closing
            scanner.skip(/\s*/)
            attributes = {}
            while attr = scanner.scan(/[-\w:]+/)
              value = true
              if scanner.scan(/\s*=\s*/)
                if delim = scanner.scan(/['"]/)
                  value = ""
                  while text = scanner.scan(/[^#{delim}\\]+|./)
                    case text
                      when "\\" then
                        value << text
                        value << scanner.getch
                      when delim
                        break
                      else value << text
                    end
                  end
                else
                  value = scanner.scan(/[^\s>\/]+/)
                end
              end
              attributes[attr.downcase] = value
              scanner.skip(/\s*/)
            end
    
            closing = ( scanner.scan(/\//) ? :self : nil )
          end
          
          unless scanner.scan(/\s*>/)
            if strict
              raise "expected > (got #{scanner.rest.inspect} for #{content}, #{attributes.inspect})" 
            else
              # throw away all text until we find what we're looking for
              scanner.skip_until(/>/) or scanner.terminate
            end
          end

          Tag.new(parent, line, pos, name, attributes, closing)
        end
      end
    end
  end

  # A node that represents text, rather than markup.
  class Text < Node #:nodoc:
    
    attr_reader :content
    
    # Creates a new text node as a child of the given parent, with the given
    # content.
    def initialize(parent, line, pos, content)
      super(parent, line, pos)
      @content = content
    end

    # Returns the content of this node.
    def to_s
      @content
    end

    # Returns +self+ if this node meets the given conditions. Text nodes support
    # conditions of the following kinds:
    #
    # * if +conditions+ is a string, it must be a substring of the node's
    #   content
    # * if +conditions+ is a regular expression, it must match the node's
    #   content
    # * if +conditions+ is a hash, it must contain a <tt>:content</tt> key that
    #   is either a string or a regexp, and which is interpreted as described
    #   above.
    def find(conditions)
      match(conditions) && self
    end
    
    # Returns non-+nil+ if this node meets the given conditions, or +nil+
    # otherwise. See the discussion of #find for the valid conditions.
    def match(conditions)
      case conditions
        when String
          @content.index(conditions)
        when Regexp
          @content =~ conditions
        when Hash
          conditions = validate_conditions(conditions)

          # Text nodes only have :content, :parent, :ancestor
          unless (conditions.keys - [:content, :parent, :ancestor]).empty?
            return false
          end

          match(conditions[:content])
        else
          nil
      end
    end

    def ==(node)
      return false unless super
      content == node.content
    end
  end
  
  # A CDATA node is simply a text node with a specialized way of displaying
  # itself.
  class CDATA < Text #:nodoc:
    def to_s
      "<![CDATA[#{super}]>"
    end
  end

  # A Tag is any node that represents markup. It may be an opening tag, a
  # closing tag, or a self-closing tag. It has a name, and may have a hash of
  # attributes.
  class Tag < Node #:nodoc:
    
    # Either +nil+, <tt>:close</tt>, or <tt>:self</tt>
    attr_reader :closing
    
    # Either +nil+, or a hash of attributes for this node.
    attr_reader :attributes

    # The name of this tag.
    attr_reader :name
        
    # Create a new node as a child of the given parent, using the given content
    # to describe the node. It will be parsed and the node name, attributes and
    # closing status extracted.
    def initialize(parent, line, pos, name, attributes, closing)
      super(parent, line, pos)
      @name = name
      @attributes = attributes
      @closing = closing
    end

    # A convenience for obtaining an attribute of the node. Returns +nil+ if
    # the node has no attributes.
    def [](attr)
      @attributes ? @attributes[attr] : nil
    end

    # Returns non-+nil+ if this tag can contain child nodes.
    def childless?(xml = false)
      return false if xml && @closing.nil?
      !@closing.nil? ||
        @name =~ /^(img|br|hr|link|meta|area|base|basefont|
                    col|frame|input|isindex|param)$/ox
    end

    # Returns a textual representation of the node
    def to_s
      if @closing == :close
        "</#{@name}>"
      else
        s = "<#{@name}"
        @attributes.each do |k,v|
          s << " #{k}"
          s << "='#{v.gsub(/'/,"\\\\'")}'" if String === v
        end
        s << " /" if @closing == :self
        s << ">"
        @children.each { |child| s << child.to_s }
        s << "</#{@name}>" if @closing != :self && !@children.empty?
        s
      end
    end

    # If either the node or any of its children meet the given conditions, the
    # matching node is returned. Otherwise, +nil+ is returned. (See the
    # description of the valid conditions in the +match+ method.)
    def find(conditions)
      match(conditions) && self || super
    end

    # Returns +true+, indicating that this node represents an HTML tag.
    def tag?
      true
    end
    
    # Returns +true+ if the node meets any of the given conditions. The
    # +conditions+ parameter must be a hash of any of the following keys
    # (all are optional):
    #
    # * <tt>:tag</tt>: the node name must match the corresponding value
    # * <tt>:attributes</tt>: a hash. The node's values must match the
    #   corresponding values in the hash.
    # * <tt>:parent</tt>: a hash. The node's parent must match the
    #   corresponding hash.
    # * <tt>:child</tt>: a hash. At least one of the node's immediate children
    #   must meet the criteria described by the hash.
    # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
    #   meet the criteria described by the hash.
    # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
    #   must meet the criteria described by the hash.
    # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
    #   meet the criteria described by the hash.
    # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
    #   the criteria described by the hash, and at least one sibling must match.
    # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
    #   the criteria described by the hash, and at least one sibling must match.
    # * <tt>:children</tt>: a hash, for counting children of a node. Accepts the
    #   keys:
    # ** <tt>:count</tt>: either a number or a range which must equal (or
    #    include) the number of children that match.
    # ** <tt>:less_than</tt>: the number of matching children must be less than
    #    this number.
    # ** <tt>:greater_than</tt>: the number of matching children must be
    #    greater than this number.
    # ** <tt>:only</tt>: another hash consisting of the keys to use
    #    to match on the children, and only matching children will be
    #    counted.
    #
    # Conditions are matched using the following algorithm:
    #
    # * if the condition is a string, it must be a substring of the value.
    # * if the condition is a regexp, it must match the value.
    # * if the condition is a number, the value must match number.to_s.
    # * if the condition is +true+, the value must not be +nil+.
    # * if the condition is +false+ or +nil+, the value must be +nil+.
    #
    # Usage:
    #
    #   # test if the node is a "span" tag
    #   node.match :tag => "span"
    #
    #   # test if the node's parent is a "div"
    #   node.match :parent => { :tag => "div" }
    #
    #   # test if any of the node's ancestors are "table" tags
    #   node.match :ancestor => { :tag => "table" }
    #
    #   # test if any of the node's immediate children are "em" tags
    #   node.match :child => { :tag => "em" }
    #
    #   # test if any of the node's descendants are "strong" tags
    #   node.match :descendant => { :tag => "strong" }
    #
    #   # test if the node has between 2 and 4 span tags as immediate children
    #   node.match :children => { :count => 2..4, :only => { :tag => "span" } } 
    #
    #   # get funky: test to see if the node is a "div", has a "ul" ancestor
    #   # and an "li" parent (with "class" = "enum"), and whether or not it has
    #   # a "span" descendant that contains # text matching /hello world/:
    #   node.match :tag => "div",
    #              :ancestor => { :tag => "ul" },
    #              :parent => { :tag => "li",
    #                           :attributes => { :class => "enum" } },
    #              :descendant => { :tag => "span",
    #                               :child => /hello world/ }
    def match(conditions)
      conditions = validate_conditions(conditions)

      # check content of child nodes
      if conditions[:content]
        if children.empty?
          return false unless match_condition("", conditions[:content])
        else
          return false unless children.find { |child| child.match(conditions[:content]) }
        end
      end

      # test the name
      return false unless match_condition(@name, conditions[:tag]) if conditions[:tag]

      # test attributes
      (conditions[:attributes] || {}).each do |key, value|
        return false unless match_condition(self[key], value)
      end

      # test parent
      return false unless parent.match(conditions[:parent]) if conditions[:parent]

      # test children
      return false unless children.find { |child| child.match(conditions[:child]) } if conditions[:child]
   
      # test ancestors
      if conditions[:ancestor]
        return false unless catch :found do
          p = self
          throw :found, true if p.match(conditions[:ancestor]) while p = p.parent
        end
      end

      # test descendants
      if conditions[:descendant]
        return false unless children.find do |child|
          # test the child
          child.match(conditions[:descendant]) ||
          # test the child's descendants
          child.match(:descendant => conditions[:descendant])
        end
      end
      
      # count children
      if opts = conditions[:children]
        matches = children.select do |c|
          c.match(/./) or
          (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
        end
        
        matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
        opts.each do |key, value|
          next if key == :only
          case key
            when :count
              if Integer === value
                return false if matches.length != value
              else
                return false unless value.include?(matches.length)
              end
            when :less_than
              return false unless matches.length < value
            when :greater_than
              return false unless matches.length > value
            else raise "unknown count condition #{key}"
          end
        end
      end

      # test siblings
      if conditions[:sibling] || conditions[:before] || conditions[:after]
        siblings = parent ? parent.children : []
        self_index = siblings.index(self)

        if conditions[:sibling]
          return false unless siblings.detect do |s| 
            s != self && s.match(conditions[:sibling])
          end
        end

        if conditions[:before]
          return false unless siblings[self_index+1..-1].detect do |s| 
            s != self && s.match(conditions[:before])
          end
        end

        if conditions[:after]
          return false unless siblings[0,self_index].detect do |s| 
            s != self && s.match(conditions[:after])
          end
        end
      end
  
      true
    end

    def ==(node)
      return false unless super
      return false unless closing == node.closing && self.name == node.name
      attributes == node.attributes
    end
    
    private
      # Match the given value to the given condition.
      def match_condition(value, condition)
        case condition
          when String
            value && value == condition
          when Regexp
            value && value.match(condition)
          when Numeric
            value == condition.to_s
          when true
            !value.nil?
          when false, nil
            value.nil?
          else
            false
        end
      end
  end
end
require 'strscan'

module HTML #:nodoc:
  
  # A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each
  # token is a string. Each string represents either "text", or an HTML element.
  #
  # This currently assumes valid XHTML, which means no free < or > characters.
  #
  # Usage:
  #
  #   tokenizer = HTML::Tokenizer.new(text)
  #   while token = tokenizer.next
  #     p token
  #   end
  class Tokenizer #:nodoc:
    
    # The current (byte) position in the text
    attr_reader :position
    
    # The current line number
    attr_reader :line
    
    # Create a new Tokenizer for the given text.
    def initialize(text)
      @scanner = StringScanner.new(text)
      @position = 0
      @line = 0
      @current_line = 1
    end

    # Return the next token in the sequence, or +nil+ if there are no more tokens in
    # the stream.
    def next
      return nil if @scanner.eos?
      @position = @scanner.pos
      @line = @current_line
      if @scanner.check(/<\S/)
        update_current_line(scan_tag)
      else
        update_current_line(scan_text)
      end
    end
  
    private

      # Treat the text at the current position as a tag, and scan it. Supports
      # comments, doctype tags, and regular tags, and ignores less-than and
      # greater-than characters within quoted strings.
      def scan_tag
        tag = @scanner.getch
        if @scanner.scan(/!--/) # comment
          tag << @scanner.matched
          tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
        elsif @scanner.scan(/!\[CDATA\[/)
          tag << @scanner.matched
          tag << @scanner.scan_until(/\]\]>/)
        elsif @scanner.scan(/!/) # doctype
          tag << @scanner.matched
          tag << consume_quoted_regions
        else
          tag << consume_quoted_regions
        end
        tag
      end

      # Scan all text up to the next < character and return it.
      def scan_text
        "#{@scanner.getch}#{@scanner.scan(/[^<]*/)}"
      end
      
      # Counts the number of newlines in the text and updates the current line
      # accordingly.
      def update_current_line(text)
        text.scan(/\r?\n/) { @current_line += 1 }
      end
      
      # Skips over quoted strings, so that less-than and greater-than characters
      # within the strings are ignored.
      def consume_quoted_regions
        text = ""
        loop do
          match = @scanner.scan_until(/['"<>]/) or break

          delim = @scanner.matched
          if delim == "<"
            match = match.chop
            @scanner.pos -= 1
          end

          text << match
          break if delim == "<" || delim == ">"

          # consume the quoted region
          while match = @scanner.scan_until(/[\\#{delim}]/)
            text << match
            break if @scanner.matched == delim
            text << @scanner.getch # skip the escaped character
          end
        end
        text
      end
  end
  
end
module HTML #:nodoc:
  module Version #:nodoc:

    MAJOR = 0
    MINOR = 5
    TINY  = 3

    STRING = [ MAJOR, MINOR, TINY ].join(".")

  end
end
require 'rexml/document'

# SimpleXML like xml parser. Written by leon breet from the ruby on rails Mailing list
class XmlNode #:nodoc:
  attr :node

  def initialize(node, options = {})
    @node = node
    @children = {}
    @raise_errors = options[:raise_errors]
  end

  def self.from_xml(xml_or_io)
    document = REXML::Document.new(xml_or_io)
    if document.root 
      XmlNode.new(document.root) 
    else
      XmlNode.new(document) 
    end
  end

  def node_encoding
    @node.encoding
  end

  def node_name
    @node.name
  end

  def node_value
    @node.text
  end

  def node_value=(value)
    @node.text = value
  end

  def xpath(expr)
    matches = nil
    REXML::XPath.each(@node, expr) do |element|
      matches ||= XmlNodeList.new
      matches << (@children[element] ||= XmlNode.new(element))
    end
    matches
  end

  def method_missing(name, *args)
    name = name.to_s
    nodes = nil
    @node.each_element(name) do |element|
      nodes ||= XmlNodeList.new
      nodes << (@children[element] ||= XmlNode.new(element))
    end
    nodes
  end

  def <<(node)
    if node.is_a? REXML::Node
      child = node
    elsif node.respond_to? :node
      child = node.node
    end
    @node.add_element child
    @children[child] ||= XmlNode.new(child)
  end

  def [](name)
    @node.attributes[name.to_s]
  end

  def []=(name, value)
    @node.attributes[name.to_s] = value
  end

  def to_s
    @node.to_s
  end

  def to_i
    to_s.to_i
  end
end

class XmlNodeList < Array #:nodoc:
  def [](i)
    i.is_a?(String) ? super(0)[i] : super(i)
  end

  def []=(i, value)
    i.is_a?(String) ? self[0][i] = value : super(i, value)
  end

  def method_missing(name, *args)
    name = name.to_s
    self[0].__send__(name, *args)
  end
end# = XmlSimple
#
# Author::    Maik Schmidt <contact@maik-schmidt.de>
# Copyright:: Copyright (c) 2003 Maik Schmidt
# License::   Distributes under the same terms as Ruby.
#
require 'rexml/document'

# Easy API to maintain XML (especially configuration files).
class XmlSimple #:nodoc:
  include REXML

  @@VERSION = '1.0.2'

  # A simple cache for XML documents that were already transformed
  # by xml_in.
  class Cache #:nodoc:
    # Creates and initializes a new Cache object.
    def initialize
      @mem_share_cache = {}
      @mem_copy_cache  = {}
    end

    # Saves a data structure into a file.
    # 
    # data::
    #   Data structure to be saved.
    # filename::
    #   Name of the file belonging to the data structure.
    def save_storable(data, filename)
      cache_file = get_cache_filename(filename)
      File.open(cache_file, "w+") { |f| Marshal.dump(data, f) }
    end

    # Restores a data structure from a file. If restoring the data
    # structure failed for any reason, nil will be returned.
    #
    # filename::
    #   Name of the file belonging to the data structure.
    def restore_storable(filename)
      cache_file = get_cache_filename(filename)
      return nil unless File::exist?(cache_file)
      return nil unless File::mtime(cache_file).to_i > File::mtime(filename).to_i
      data = nil
      File.open(cache_file) { |f| data = Marshal.load(f) }
      data
    end

    # Saves a data structure in a shared memory cache.
    #
    # data::
    #   Data structure to be saved.
    # filename::
    #   Name of the file belonging to the data structure.
    def save_mem_share(data, filename)
      @mem_share_cache[filename] = [Time::now.to_i, data]
    end

    # Restores a data structure from a shared memory cache. You
    # should consider these elements as "read only". If restoring
    # the data structure failed for any reason, nil will be
    # returned.
    #
    # filename::
    #   Name of the file belonging to the data structure.
    def restore_mem_share(filename)
      get_from_memory_cache(filename, @mem_share_cache)
    end

    # Copies a data structure to a memory cache.
    #
    # data::
    #   Data structure to be copied.
    # filename::
    #   Name of the file belonging to the data structure.
    def save_mem_copy(data, filename)
      @mem_share_cache[filename] = [Time::now.to_i, Marshal.dump(data)]
    end

    # Restores a data structure from a memory cache. If restoring
    # the data structure failed for any reason, nil will be
    # returned.
    #
    # filename::
    #   Name of the file belonging to the data structure.
    def restore_mem_copy(filename)
      data = get_from_memory_cache(filename, @mem_share_cache)
      data = Marshal.load(data) unless data.nil?
      data
    end

    private

    # Returns the "cache filename" belonging to a filename, i.e.
    # the extension '.xml' in the original filename will be replaced
    # by '.stor'. If filename does not have this extension, '.stor'
    # will be appended.
    #
    # filename::
    #   Filename to get "cache filename" for.
    def get_cache_filename(filename)
      filename.sub(/(\.xml)?$/, '.stor')
    end

    # Returns a cache entry from a memory cache belonging to a
    # certain filename. If no entry could be found for any reason,
    # nil will be returned.
    #
    # filename::
    #   Name of the file the cache entry belongs to.
    # cache::
    #   Memory cache to get entry from.
    def get_from_memory_cache(filename, cache)
      return nil unless cache[filename]
      return nil unless cache[filename][0] > File::mtime(filename).to_i
      return cache[filename][1]
    end
  end

  # Create a "global" cache.
  @@cache = Cache.new

  # Creates and intializes a new XmlSimple object.
  # 
  # defaults::
  #   Default values for options.
  def initialize(defaults = nil)
    unless defaults.nil? || defaults.instance_of?(Hash)
      raise ArgumentError, "Options have to be a Hash."
    end
    @default_options = normalize_option_names(defaults, KNOWN_OPTIONS['in'] & KNOWN_OPTIONS['out'])
    @options = Hash.new
    @_var_values = nil
  end

  # Converts an XML document in the same way as the Perl module XML::Simple.
  #
  # string::
  #   XML source. Could be one of the following:
  #
  #   - nil: Tries to load and parse '<scriptname>.xml'.
  #   - filename: Tries to load and parse filename.
  #   - IO object: Reads from object until EOF is detected and parses result.
  #   - XML string: Parses string.
  #   
  # options::
  #   Options to be used.
  def xml_in(string = nil, options = nil)
    handle_options('in', options)

    # If no XML string or filename was supplied look for scriptname.xml.
    if string.nil?
      string = File::basename($0)
      string.sub!(/\.[^.]+$/, '')
      string += '.xml'

      directory = File::dirname($0)
      @options['searchpath'].unshift(directory) unless directory.nil?
    end

    if string.instance_of?(String)
      if string =~ /<.*?>/m
        @doc = parse(string)
      elsif string == '-'
        @doc = parse($stdin.readlines.to_s)
      else
        filename = find_xml_file(string, @options['searchpath'])

        if @options.has_key?('cache')
          @options['cache'].each { |scheme|
            case(scheme)
            when 'storable'
              content = @@cache.restore_storable(filename)
            when 'mem_share'
              content = @@cache.restore_mem_share(filename)
            when 'mem_copy'
              content = @@cache.restore_mem_copy(filename)
            else
              raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
            end
            return content if content
          }
        end
        
        @doc = load_xml_file(filename)
      end
    elsif string.kind_of?(IO)
      @doc = parse(string.readlines.to_s)
    else
      raise ArgumentError, "Could not parse object of type: <#{string.type}>."
    end

    result = collapse(@doc.root)
    result = @options['keeproot'] ? merge({}, @doc.root.name, result) : result
    put_into_cache(result, filename)
    result
  end

  # This is the functional version of the instance method xml_in.
  def XmlSimple.xml_in(string = nil, options = nil)
    xml_simple = XmlSimple.new
    xml_simple.xml_in(string, options)
  end
  
  # Converts a data structure into an XML document.
  #
  # ref::
  #   Reference to data structure to be converted into XML.
  # options::
  #   Options to be used.
  def xml_out(ref, options = nil)
    handle_options('out', options)
    if ref.instance_of?(Array)
      ref = { @options['anonymoustag'] => ref }
    end

    if @options['keeproot']
      keys = ref.keys
      if keys.size == 1
        ref = ref[keys[0]]
        @options['rootname'] = keys[0]
      end
    elsif @options['rootname'] == ''
      if ref.instance_of?(Hash)
        refsave = ref
        ref = {}
        refsave.each { |key, value|
          if !scalar(value)
            ref[key] = value
          else
            ref[key] = [ value.to_s ]
          end
        }
      end
    end

    @ancestors = []
    xml = value_to_xml(ref, @options['rootname'], '')
    @ancestors = nil

    if @options['xmldeclaration']
      xml = @options['xmldeclaration'] + "\n" + xml
    end

    if @options.has_key?('outputfile')
      if @options['outputfile'].kind_of?(IO)
        return @options['outputfile'].write(xml)
      else
        File.open(@options['outputfile'], "w") { |file| file.write(xml) }
      end
    end
    xml
  end

  # This is the functional version of the instance method xml_out.
  def XmlSimple.xml_out(hash, options = nil)
    xml_simple = XmlSimple.new
    xml_simple.xml_out(hash, options)
  end
  
  private

  # Declare options that are valid for xml_in and xml_out.
  KNOWN_OPTIONS = {
    'in'  => %w(
      keyattr keeproot forcecontent contentkey noattr
      searchpath forcearray suppressempty anonymoustag
      cache grouptags normalisespace normalizespace
      variables varattr
    ),
    'out' => %w(
      keyattr keeproot contentkey noattr rootname
      xmldeclaration outputfile noescape suppressempty
      anonymoustag indent grouptags noindent
    )
  }

  # Define some reasonable defaults.
  DEF_KEY_ATTRIBUTES  = []
  DEF_ROOT_NAME       = 'opt'
  DEF_CONTENT_KEY     = 'content'
  DEF_XML_DECLARATION = "<?xml version='1.0' standalone='yes'?>"
  DEF_ANONYMOUS_TAG   = 'anon'
  DEF_FORCE_ARRAY     = true
  DEF_INDENTATION     = '  '
  
  # Normalizes option names in a hash, i.e., turns all
  # characters to lower case and removes all underscores.
  # Additionally, this method checks, if an unknown option
  # was used and raises an according exception.
  #
  # options::
  #   Hash to be normalized.
  # known_options::
  #   List of known options.
  def normalize_option_names(options, known_options)
    return nil if options.nil?
    result = Hash.new
    options.each { |key, value|
      lkey = key.downcase
      lkey.gsub!(/_/, '')
      if !known_options.member?(lkey)
        raise ArgumentError, "Unrecognised option: #{lkey}."
      end
      result[lkey] = value
    }
    result
  end
  
  # Merges a set of options with the default options.
  # 
  # direction::
  #  'in':  If options should be handled for xml_in.
  #  'out': If options should be handled for xml_out.
  # options::
  #   Options to be merged with the default options.
  def handle_options(direction, options)
    @options = options || Hash.new

    raise ArgumentError, "Options must be a Hash!" unless @options.instance_of?(Hash)

    unless KNOWN_OPTIONS.has_key?(direction)
      raise ArgumentError, "Unknown direction: <#{direction}>."
    end

    known_options = KNOWN_OPTIONS[direction]
    @options = normalize_option_names(@options, known_options)

    unless @default_options.nil?
      known_options.each { |option|
        unless @options.has_key?(option)
          if @default_options.has_key?(option)
            @options[option] = @default_options[option]
          end
        end
      }
    end

    unless @options.has_key?('noattr')
        @options['noattr'] = false
    end

    if @options.has_key?('rootname')
      @options['rootname'] = '' if @options['rootname'].nil?
    else
      @options['rootname'] = DEF_ROOT_NAME
    end

    if @options.has_key?('xmldeclaration') && @options['xmldeclaration'] == true
      @options['xmldeclaration'] = DEF_XML_DECLARATION
    end

    if @options.has_key?('contentkey')
      if @options['contentkey'] =~ /^-(.*)$/
        @options['contentkey']    = $1
        @options['collapseagain'] = true
      end
    else
      @options['contentkey'] = DEF_CONTENT_KEY
    end

    unless @options.has_key?('normalisespace')
      @options['normalisespace'] = @options['normalizespace']
    end
    @options['normalisespace'] = 0 if @options['normalisespace'].nil?

    if @options.has_key?('searchpath')
      unless @options['searchpath'].instance_of?(Array)
        @options['searchpath'] = [ @options['searchpath'] ]
      end
    else
      @options['searchpath'] = []
    end

    if @options.has_key?('cache') && scalar(@options['cache'])
      @options['cache'] = [ @options['cache'] ]
    end

    @options['anonymoustag'] = DEF_ANONYMOUS_TAG unless @options.has_key?('anonymoustag')

    if !@options.has_key?('indent') || @options['indent'].nil?
      @options['indent'] = DEF_INDENTATION
    end

    @options['indent'] = '' if @options.has_key?('noindent')

    # Special cleanup for 'keyattr' which could be an array or
    # a hash or left to default to array.
    if @options.has_key?('keyattr')
      if !scalar(@options['keyattr'])
        # Convert keyattr => { elem => '+attr' }
        #      to keyattr => { elem => ['attr', '+'] }
        if @options['keyattr'].instance_of?(Hash)
          @options['keyattr'].each { |key, value|
            if value =~ /^([-+])?(.*)$/
              @options['keyattr'][key] = [$2, $1 ? $1 : '']
            end
          }
        elsif !@options['keyattr'].instance_of?(Array)
          raise ArgumentError, "'keyattr' must be String, Hash, or Array!"
        end
      else
        @options['keyattr'] = [ @options['keyattr'] ]
      end
    else
      @options['keyattr'] = DEF_KEY_ATTRIBUTES
    end

    if @options.has_key?('forcearray')
      if @options['forcearray'].instance_of?(Regexp)
        @options['forcearray'] = [ @options['forcearray'] ]
      end

      if @options['forcearray'].instance_of?(Array)
        force_list = @options['forcearray']
        unless force_list.empty?
          @options['forcearray'] = {}
          force_list.each { |tag|
            if tag.instance_of?(Regexp)
              unless @options['forcearray']['_regex'].instance_of?(Array)
                @options['forcearray']['_regex'] = []
              end
              @options['forcearray']['_regex'] << tag
            else
              @options['forcearray'][tag] = true
            end
          }
        else
          @options['forcearray'] = false
        end
      else
        @options['forcearray'] = @options['forcearray'] ? true : false
      end
    else
      @options['forcearray'] = DEF_FORCE_ARRAY
    end

    if @options.has_key?('grouptags') && !@options['grouptags'].instance_of?(Hash)
      raise ArgumentError, "Illegal value for 'GroupTags' option - expected a Hash."
    end

    if @options.has_key?('variables') && !@options['variables'].instance_of?(Hash)
      raise ArgumentError, "Illegal value for 'Variables' option - expected a Hash."
    end

    if @options.has_key?('variables')
      @_var_values = @options['variables']
    elsif @options.has_key?('varattr')
      @_var_values = {}
    end
  end

  # Actually converts an XML document element into a data structure.
  #
  # element::
  #   The document element to be collapsed.
  def collapse(element)
    result = @options['noattr'] ? {} : get_attributes(element)

    if @options['normalisespace'] == 2
      result.each { |k, v| result[k] = normalise_space(v) }
    end

    if element.has_elements?
      element.each_element { |child|
        value = collapse(child)
        if empty(value) && (element.attributes.empty? || @options['noattr'])
          next if @options.has_key?('suppressempty') && @options['suppressempty'] == true
        end
        result = merge(result, child.name, value)
      }
      if has_mixed_content?(element)
        # normalisespace?
        content = element.texts.map { |x| x.to_s }
        content = content[0] if content.size == 1
        result[@options['contentkey']] = content
      end
    elsif element.has_text? # i.e. it has only text.
      return collapse_text_node(result, element)
    end

    # Turn Arrays into Hashes if key fields present.
    count = fold_arrays(result)

    # Disintermediate grouped tags.
    if @options.has_key?('grouptags')
      result.each { |key, value|
        next unless (value.instance_of?(Hash) && (value.size == 1))
        child_key, child_value = value.to_a[0]
        if @options['grouptags'][key] == child_key
          result[key] = child_value
        end
      }
    end
    
    # Fold Hases containing a single anonymous Array up into just the Array.
    if count == 1 
      anonymoustag = @options['anonymoustag']
      if result.has_key?(anonymoustag) && result[anonymoustag].instance_of?(Array)
        return result[anonymoustag]
      end
    end

    if result.empty? && @options.has_key?('suppressempty')
      return @options['suppressempty'] == '' ? '' : nil
    end

    result
  end

  # Collapses a text node and merges it with an existing Hash, if
  # possible.
  # Thanks to Curtis Schofield for reporting a subtle bug.
  #
  # hash::
  #   Hash to merge text node value with, if possible.
  # element::
  #   Text node to be collapsed.
  def collapse_text_node(hash, element)
    value = node_to_text(element)
    if empty(value) && !element.has_attributes?
      return {}
    end

    if element.has_attributes? && !@options['noattr']
      return merge(hash, @options['contentkey'], value)
    else
      if @options['forcecontent']
        return merge(hash, @options['contentkey'], value)
      else
        return value
      end
    end
  end

  # Folds all arrays in a Hash.
  # 
  # hash::
  #   Hash to be folded.
  def fold_arrays(hash)
    fold_amount = 0
    keyattr = @options['keyattr']
    if (keyattr.instance_of?(Array) || keyattr.instance_of?(Hash))
      hash.each { |key, value|
        if value.instance_of?(Array)
          if keyattr.instance_of?(Array)
            hash[key] = fold_array(value)
          else
            hash[key] = fold_array_by_name(key, value)
          end
          fold_amount += 1
        end
      }
    end
    fold_amount
  end

  # Folds an Array to a Hash, if possible. Folding happens
  # according to the content of keyattr, which has to be
  # an array.
  #
  # array::
  #   Array to be folded.
  def fold_array(array)
    hash = Hash.new
    array.each { |x|
      return array unless x.instance_of?(Hash)
      key_matched = false
      @options['keyattr'].each { |key|
        if x.has_key?(key)
          key_matched = true
          value = x[key]
          return array if value.instance_of?(Hash) || value.instance_of?(Array)
          value = normalise_space(value) if @options['normalisespace'] == 1
          x.delete(key)
          hash[value] = x
          break
        end
      }
      return array unless key_matched
    }
    hash = collapse_content(hash) if @options['collapseagain']
    hash
  end
  
  # Folds an Array to a Hash, if possible. Folding happens
  # according to the content of keyattr, which has to be
  # a Hash.
  #
  # name::
  #   Name of the attribute to be folded upon.
  # array::
  #   Array to be folded.
  def fold_array_by_name(name, array)
    return array unless @options['keyattr'].has_key?(name)
    key, flag = @options['keyattr'][name]

    hash = Hash.new
    array.each { |x|
      if x.instance_of?(Hash) && x.has_key?(key)
        value = x[key]
        return array if value.instance_of?(Hash) || value.instance_of?(Array)
        value = normalise_space(value) if @options['normalisespace'] == 1
        hash[value] = x
        hash[value]["-#{key}"] = hash[value][key] if flag == '-'
        hash[value].delete(key) unless flag == '+'
      else
        $stderr.puts("Warning: <#{name}> element has no '#{key}' attribute.")
        return array
      end
    }
    hash = collapse_content(hash) if @options['collapseagain']
    hash
  end

  # Tries to collapse a Hash even more ;-)
  #
  # hash::
  #   Hash to be collapsed again.
  def collapse_content(hash)
    content_key = @options['contentkey']
    hash.each_value { |value|
      return hash unless value.instance_of?(Hash) && value.size == 1 && value.has_key?(content_key)
      hash.each_key { |key| hash[key] = hash[key][content_key] }
    }
    hash
  end
  
  # Adds a new key/value pair to an existing Hash. If the key to be added
  # does already exist and the existing value associated with key is not
  # an Array, it will be converted into an Array. Then the new value is
  # appended to that Array.
  #
  # hash::
  #   Hash to add key/value pair to.
  # key::
  #   Key to be added.
  # value::
  #   Value to be associated with key.
  def merge(hash, key, value)
    if value.instance_of?(String)
      value = normalise_space(value) if @options['normalisespace'] == 2

      # do variable substitutions
      unless @_var_values.nil? || @_var_values.empty?
        value.gsub!(/\$\{(\w+)\}/) { |x| get_var($1) }
      end
      
      # look for variable definitions
      if @options.has_key?('varattr')
        varattr = @options['varattr']
        if hash.has_key?(varattr)
          set_var(hash[varattr], value)
        end
      end
    end
    if hash.has_key?(key)
      if hash[key].instance_of?(Array)
        hash[key] << value
      else
        hash[key] = [ hash[key], value ]
      end
    elsif value.instance_of?(Array) # Handle anonymous arrays.
      hash[key] = [ value ]
    else
      if force_array?(key)
        hash[key] = [ value ]
      else
        hash[key] = value
      end
    end
    hash
  end
  
  # Checks, if the 'forcearray' option has to be used for
  # a certain key.
  def force_array?(key)
    return false if key == @options['contentkey']
    return true if @options['forcearray'] == true
    forcearray = @options['forcearray']
    if forcearray.instance_of?(Hash)
      return true if forcearray.has_key?(key) 
      return false unless forcearray.has_key?('_regex')
      forcearray['_regex'].each { |x| return true if key =~ x }
    end
    return false
  end
  
  # Converts the attributes array of a document node into a Hash.
  # Returns an empty Hash, if node has no attributes.
  #
  # node::
  #   Document node to extract attributes from.
  def get_attributes(node)
    attributes = {}
    node.attributes.each { |n,v| attributes[n] = v }
    attributes
  end
  
  # Determines, if a document element has mixed content.
  #
  # element::
  #   Document element to be checked.
  def has_mixed_content?(element)
    if element.has_text? && element.has_elements?
      return true if element.texts.join('') !~ /^\s*$/s
    end
    false
  end
  
  # Called when a variable definition is encountered in the XML.
  # A variable definition looks like
  #    <element attrname="name">value</element>
  # where attrname matches the varattr setting.
  def set_var(name, value)
    @_var_values[name] = value
  end

  # Called during variable substitution to get the value for the
  # named variable.
  def get_var(name)
    if @_var_values.has_key?(name)
      return @_var_values[name]
    else
      return "${#{name}}"
    end
  end
  
  # Recurses through a data structure building up and returning an
  # XML representation of that structure as a string.
  #
  # ref::
  #   Reference to the data structure to be encoded.
  # name::
  #   The XML tag name to be used for this item.
  # indent::
  #   A string of spaces for use as the current indent level.
  def value_to_xml(ref, name, indent)
    named = !name.nil? && name != ''
    nl    = @options.has_key?('noindent') ? '' : "\n"

    if !scalar(ref)
      if @ancestors.member?(ref)
        raise ArgumentError, "Circular data structures not supported!"
      end
      @ancestors << ref
    else
      if named
        return [indent, '<', name, '>', @options['noescape'] ? ref.to_s : escape_value(ref.to_s), '</', name, '>', nl].join('')
      else
        return ref.to_s + nl
      end
    end

    # Unfold hash to array if possible.
    if ref.instance_of?(Hash) && !ref.empty? && !@options['keyattr'].empty? && indent != ''
      ref = hash_to_array(name, ref)
    end

    result = []
    if ref.instance_of?(Hash)
      # Reintermediate grouped values if applicable.
      if @options.has_key?('grouptags')
        ref.each { |key, value|
          if @options['grouptags'].has_key?(key)
            ref[key] = { @options['grouptags'][key] => value }
          end
        }
      end
      
      nested = []
      text_content = nil
      if named
        result << indent << '<' << name
      end

      if !ref.empty?
        ref.each { |key, value|
          next if !key.nil? && key[0, 1] == '-'
          if value.nil?
            unless @options.has_key?('suppressempty') && @options['suppressempty'].nil?
              raise ArgumentError, "Use of uninitialized value!"
            end
            value = {}
          end

          if !scalar(value) || @options['noattr']
            nested << value_to_xml(value, key, indent + @options['indent'])
          else
            value = value.to_s
            value = escape_value(value) unless @options['noescape']
            if key == @options['contentkey']
              text_content = value
            else
              result << ' ' << key << '="' << value << '"'
            end
          end
        }
      else
        text_content = ''
      end

      if !nested.empty? || !text_content.nil?
        if named
          result << '>'
          if !text_content.nil?
            result << text_content
            nested[0].sub!(/^\s+/, '') if !nested.empty?
          else
            result << nl
          end
          if !nested.empty?
            result << nested << indent
          end
          result << '</' << name << '>' << nl
        else
          result << nested
        end
      else
        result << ' />' << nl
      end
    elsif ref.instance_of?(Array)
      ref.each { |value|
        if scalar(value)
          result << indent << '<' << name << '>'
          result << (@options['noescape'] ? value.to_s : escape_value(value.to_s))
          result << '</' << name << '>' << nl
        elsif value.instance_of?(Hash)
          result << value_to_xml(value, name, indent)
        else
          result << indent << '<' << name << '>' << nl
          result << value_to_xml(value, @options['anonymoustag'], indent + @options['indent'])
          result << indent << '</' << name << '>' << nl
        end
      }
    else
      # Probably, this is obsolete.
      raise ArgumentError, "Can't encode a value of type: #{ref.type}."
    end
    @ancestors.pop if !scalar(ref)
    result.join('')
  end
  
  # Checks, if a certain value is a "scalar" value. Whatever
  # that will be in Ruby ... ;-)
  # 
  # value::
  #   Value to be checked.
  def scalar(value)
    return false if value.instance_of?(Hash) || value.instance_of?(Array)
    return true
  end

  # Attempts to unfold a hash of hashes into an array of hashes. Returns
  # a reference to th array on success or the original hash, if unfolding
  # is not possible.
  # 
  # parent::
  #   
  # hashref::
  #   Reference to the hash to be unfolded.
  def hash_to_array(parent, hashref)
    arrayref = []
    hashref.each { |key, value|
      return hashref unless value.instance_of?(Hash)

      if @options['keyattr'].instance_of?(Hash)
        return hashref unless @options['keyattr'].has_key?(parent)
        arrayref << { @options['keyattr'][parent][0] => key }.update(value)
      else
        arrayref << { @options['keyattr'][0] => key }.update(value)
      end
    }
    arrayref
  end
  
  # Replaces XML markup characters by their external entities.
  #
  # data::
  #   The string to be escaped.
  def escape_value(data)
    return data if data.nil? || data == ''
    result = data.dup
    result.gsub!('&', '&amp;')
    result.gsub!('<', '&lt;')
    result.gsub!('>', '&gt;')
    result.gsub!('"', '&quot;')
    result.gsub!("'", '&apos;')
    result
  end
  
  # Removes leading and trailing whitespace and sequences of
  # whitespaces from a string.
  #
  # text::
  #   String to be normalised.
  def normalise_space(text)
    text.sub!(/^\s+/, '')
    text.sub!(/\s+$/, '')
    text.gsub!(/\s\s+/, ' ')
    text
  end

  # Checks, if an object is nil, an empty String or an empty Hash.
  # Thanks to Norbert Gawor for a bugfix.
  #
  # value::
  #   Value to be checked for emptyness.
  def empty(value)
    case value
      when Hash
        return value.empty?
      when String
        return value !~ /\S/m
      else
        return value.nil?
    end
  end
  
  # Converts a document node into a String.
  # If the node could not be converted into a String
  # for any reason, default will be returned.
  #
  # node::
  #   Document node to be converted.
  # default::
  #   Value to be returned, if node could not be converted.
  def node_to_text(node, default = nil)
    if node.instance_of?(Element) 
      return node.texts.join('')
    elsif node.instance_of?(Attribute)
      return node.value.nil? ? default : node.value.strip
    elsif node.instance_of?(Text)
      return node.to_s.strip
    else
      return default
    end
  end

  # Parses an XML string and returns the according document.
  #
  # xml_string::
  #   XML string to be parsed.
  #
  # The following exception may be raised:
  #
  # REXML::ParseException::
  #   If the specified file is not wellformed.
  def parse(xml_string)
    Document.new(xml_string)
  end
  
  # Searches in a list of paths for a certain file. Returns
  # the full path to the file, if it could be found. Otherwise,
  # an exception will be raised.
  #
  # filename::
  #   Name of the file to search for.
  # searchpath::
  #   List of paths to search in.
  def find_xml_file(file, searchpath)
    filename = File::basename(file)

    if filename != file
      return file if File::file?(file)
    else
      searchpath.each { |path|
        full_path = File::join(path, filename)
        return full_path if File::file?(full_path)
      }
    end

    if searchpath.empty?
      return file if File::file?(file)
      raise ArgumentError, "File does not exist: #{file}."
    end
    raise ArgumentError, "Could not find <#{filename}> in <#{searchpath.join(':')}>"
  end
  
  # Loads and parses an XML configuration file.
  #
  # filename::
  #   Name of the configuration file to be loaded.
  #
  # The following exceptions may be raised:
  # 
  # Errno::ENOENT::
  #   If the specified file does not exist.
  # REXML::ParseException::
  #   If the specified file is not wellformed.
  def load_xml_file(filename)
    parse(File.readlines(filename).to_s)
  end

  # Caches the data belonging to a certain file.
  #
  # data::
  #   Data to be cached.
  # filename::
  #   Name of file the data was read from.
  def put_into_cache(data, filename)
    if @options.has_key?('cache')
      @options['cache'].each { |scheme|
        case(scheme)
        when 'storable'
          @@cache.save_storable(data, filename)
        when 'mem_share'
          @@cache.save_mem_share(data, filename)
        when 'mem_copy'
          @@cache.save_mem_copy(data, filename)
        else
          raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
        end
      }
    end
  end
end

# vim:sw=2
module ActionController #:nodoc:
  module Verification #:nodoc:
    def self.append_features(base) #:nodoc:
      super
      base.extend(ClassMethods)
    end

    # This module provides a class-level method for specifying that certain
    # actions are guarded against being called without certain prerequisites
    # being met. This is essentially a special kind of before_filter.
    #
    # An action may be guarded against being invoked without certain request
    # parameters being set, or without certain session values existing.
    #
    # When a verification is violated, values may be inserted into the flash, and
    # a specified redirection is triggered.
    #
    # Usage:
    #
    #   class GlobalController < ActionController::Base
    #     # prevent the #update_settings action from being invoked unless
    #     # the 'admin_privileges' request parameter exists.
    #     verify :params => "admin_privileges", :only => :update_post,
    #            :redirect_to => { :action => "settings" }
    #
    #     # disallow a post from being updated if there was no information
    #     # submitted with the post, and if there is no active post in the
    #     # session, and if there is no "note" key in the flash.
    #     verify :params => "post", :session => "post", "flash" => "note",
    #            :only => :update_post,
    #            :add_flash => { "alert" => "Failed to create your message" },
    #            :redirect_to => :category_url
    #
    module ClassMethods
      # Verify the given actions so that if certain prerequisites are not met,
      # the user is redirected to a different action. The +options+ parameter
      # is a hash consisting of the following key/value pairs:
      #
      # * <tt>:params</tt>: a single key or an array of keys that must
      #   be in the <tt>params</tt> hash in order for the action(s) to be safely
      #   called.
      # * <tt>:session</tt>: a single key or an array of keys that must
      #   be in the @session in order for the action(s) to be safely called.
      # * <tt>:flash</tt>: a single key or an array of keys that must
      #   be in the flash in order for the action(s) to be safely called.
      # * <tt>:method</tt>: 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: <tt>:get</tt> or
      #   <tt>:post</tt>, for example.)
      # * <tt>:xhr</tt>: true/false option to ensure that the request is coming
      #   from an Ajax call or not. 
      # * <tt>:add_flash</tt>: a hash of name/value pairs that should be merged
      #   into the session's flash if the prerequisites cannot be satisfied.
      # * <tt>:redirect_to</tt>: the redirection parameters to be used when
      #   redirecting if the prerequisites cannot be satisfied.
      # * <tt>:render</tt>: the render parameters to be used when
      #   the prerequisites cannot be satisfied.
      # * <tt>:only</tt>: only apply this verification to the actions specified
      #   in the associated array (may also be a single value).
      # * <tt>:except</tt>: 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|
          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]
        unless performed?
          render(options[:render]) if options[:render]
          redirect_to(options[:redirect_to]) if options[:redirect_to]
        end
        return false
      end

      true
    end
    private :verify_action
  end
end
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# 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.
#++

$:.unshift(File.dirname(__FILE__)) unless
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

unless defined?(ActiveSupport)
  begin
    $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
    require 'active_support'  
  rescue LoadError
    require 'rubygems'
    require_gem 'activesupport'
  end
end

require 'action_controller/base'
require 'action_controller/deprecated_redirects'
require 'action_controller/request'
require 'action_controller/deprecated_request_methods'
require 'action_controller/rescue'
require 'action_controller/benchmarking'
require 'action_controller/flash'
require 'action_controller/filters'
require 'action_controller/layout'
require 'action_controller/dependencies'
require 'action_controller/mime_responds'
require 'action_controller/pagination'
require 'action_controller/scaffolding'
require 'action_controller/helpers'
require 'action_controller/cookies'
require 'action_controller/cgi_process'
require 'action_controller/caching'
require 'action_controller/verification'
require 'action_controller/streaming'
require 'action_controller/session_management'
require 'action_controller/components'
require 'action_controller/macros/auto_complete'
require 'action_controller/macros/in_place_editing'

require 'action_view'
ActionController::Base.template_class = ActionView::Base

ActionController::Base.class_eval do
  include ActionController::Flash
  include ActionController::Filters
  include ActionController::Layout
  include ActionController::Benchmarking
  include ActionController::Rescue
  include ActionController::Dependencies
  include ActionController::MimeResponds
  include ActionController::Pagination
  include ActionController::Scaffolding
  include ActionController::Helpers
  include ActionController::Cookies
  include ActionController::Caching
  include ActionController::Verification
  include ActionController::Streaming
  include ActionController::SessionManagement
  include ActionController::Components
  include ActionController::Macros::AutoComplete
  include ActionController::Macros::InPlaceEditing
end
module ActionPack #:nodoc:
  module VERSION #:nodoc:
    MAJOR = 1
    MINOR = 12
    TINY  = 5
    
    STRING = [MAJOR, MINOR, TINY].join('.')
  end
end
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# 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.
#++

require 'action_pack/version'
require 'erb'

module ActionView #:nodoc:

  class ActionViewError < StandardError #:nodoc:
  end

  # Action View templates can be written in three ways. If the template file has a +.rhtml+ extension then it uses a mixture of ERb 
  # (included in Ruby) and HTML. If the template file has a +.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
  # 
  # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the 
  # following loop for names:
  #
  #   <b>Names of all the people</b>
  #   <% for person in @people %>
  #     Name: <%= person.name %><br/>
  #   <% end %>
  #
  # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
  # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong:
  #
  #   Hi, Mr. <% puts "Frodo" %>
  #
  # 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 %>.
  #
  # == Using sub templates
  #
  # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
  # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
  #
  #   <%= render "shared/header" %>
  #   Something really specific and terrific
  #   <%= render "shared/footer" %>
  #
  # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
  # result of the rendering. The output embedding writes it to the current template.
  #
  # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
  # variables defined using the regular embedding tags. Like this:
  #
  #   <% @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:
  #
  #   <title><%= @page_title %></title>
  #
  # == Passing local variables to sub templates
  # 
  # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
  #
  #   <%= render "shared/header", { "headline" => "Welcome", "person" => person } %>
  #
  # These can now be accessed in shared/header with:
  #
  #   Headline: <%= headline %>
  #   First name: <%= person.first_name %>
  #
  # == Template caching
  #
  # By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will
  # check the file's modification time and recompile it.
  #
  # == 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 +.rxml+ extension. 
  #
  # Here are some basic examples:
  #
  #   xml.em("emphasized")                              # => <em>emphasized</em>
  #   xml.em { xml.b("emp & bold") }                    # => <em><b>emph &amp; bold</b></em>
  #   xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
  #   xml.target("name"=>"compile", "option"=>"fast")   # => <target option="fast" name="compile"\>
  #                                                     # NOTE: order of attributes is not specified.
  # 
  # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
  #
  #   xml.div {
  #     xml.h1(@person.name)
  #     xml.p(@person.bio)
  #   }
  #
  # would produce something like:
  #
  #   <div>
  #     <h1>David Heinemeier Hansson</h1>
  #     <p>A product of Danish Design during the Winter of '79...</p>
  #   </div>
  #
  # A full-length RSS example actually used on Basecamp:
  #
  #   xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  #     xml.channel do
  #       xml.title(@feed_title)
  #       xml.link(@url)
  #       xml.description "Basecamp: Recent items"
  #       xml.language "en-us"
  #       xml.ttl "40"
  # 
  #       for item in @recent_items
  #         xml.item do
  #           xml.title(item_title(item))
  #           xml.description(item_description(item)) if item_description(item)
  #           xml.pubDate(item_pubDate(item))
  #           xml.guid(@person.firm.account.url + @recent_items.url(item))
  #           xml.link(@person.firm.account.url + @recent_items.url(item))
  #       
  #           xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
  #         end
  #       end
  #     end
  #   end
  #
  # More builder documentation can be found at http://builder.rubyforge.org.
  #
  # == JavaScriptGenerator
  #
  # 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:
  #
  #   link_to_remote :url => {:action => 'delete'}
  #
  # The subsequently rendered +delete.rjs+ might look like:
  #
  #   page.replace_html  'sidebar', :partial => 'sidebar'
  #   page.remove        "person-#{@person.id}"
  #   page.visual_effect :highlight, 'user-list' 
  #
  # This refreshes the sidebar, removes a person element and highlights the user list.
  # 
  # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator documentation for more details.
  class Base
    include ERB::Util

    attr_reader   :first_render
    attr_accessor :base_path, :assigns, :template_extension
    attr_accessor :controller

    attr_reader :logger, :params, :request, :response, :session, :headers, :flash

    # Specify trim mode for the ERB compiler. Defaults to '-'.
    # See ERB documentation for suitable values.
    @@erb_trim_mode = '-'
    cattr_accessor :erb_trim_mode

    # 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.
    # 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
    
    # 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
    cattr_accessor :debug_rjs

    @@template_handlers = HashWithIndifferentAccess.new
 
    module CompiledTemplates #:nodoc:
      # holds compiled template code
    end
    include CompiledTemplates

    # maps inline templates to their 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 = {}

    class ObjectWrapper < Struct.new(:value) #:nodoc:
    end

    def self.load_helpers(helper_dir)#:nodoc:
      Dir.foreach(helper_dir) do |helper_file| 
        next unless helper_file =~ /^([a-z][a-z_]*_helper).rb$/
        require File.join(helper_dir, $1)
        helper_module_name = $1.camelize
        class_eval("include ActionView::Helpers::#{helper_module_name}") if Helpers.const_defined?(helper_module_name)
      end
    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] = klass
    end

    def initialize(base_path = nil, assigns_for_first_render = {}, controller = nil)#:nodoc:
      @base_path, @assigns = base_path, assigns_for_first_render
      @assigns_added = nil
      @controller = controller
      @logger = controller && controller.logger 
    end

    # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true, 
    # it's relative to the template_root, otherwise it's absolute. The hash in <tt>local_assigns</tt> 
    # is made available as local variables.
    def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
      @first_render ||= template_path

      if use_full_path
        template_path_without_extension, template_extension = path_and_extension(template_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
          template_file_name = full_template_path(template_path, template_extension)
        end
      else
        template_file_name = template_path
        template_extension = template_path.split('.').last
      end

      template_source = nil # Don't read the source until we know that it is required

      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(@base_path, template_file_name, @assigns, template_source, e)
        end
      end
    end

    # Renders the template present at <tt>template_path</tt> (relative to the template_root). 
    # The hash in <tt>local_assigns</tt> is made available as local variables.
    def render(options = {}, old_local_assigns = {}, &block) #:nodoc:
      if options.is_a?(String)
        render_file(options, true, old_local_assigns)
      elsif options == :update
        update_page(&block)
      elsif options.is_a?(Hash)
        options[:locals] ||= {}
        options[:use_full_path] = options[:use_full_path].nil? ? true : options[:use_full_path]

        if options[:file]
          render_file(options[:file], options[:use_full_path], options[:locals])
        elsif options[:partial] && options[:collection]
          render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
        elsif options[:partial]
          render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
        elsif options[:inline]
          render_template(options[:type] || :rhtml, options[:inline], nil, options[:locals] || {})
        end
      end
    end

    # Renders the +template+ which is given as a string as either rhtml or rxml depending on <tt>template_extension</tt>.
    # The hash in <tt>local_assigns</tt> is made available as local variables.
    def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc:
      if handler = @@template_handlers[template_extension]
        template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded.
        delegate_render(handler, template, local_assigns)
      else
        compile_and_render_template(template_extension, template, file_path, local_assigns)
      end
    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(extension, template = nil, file_path = nil, local_assigns = {}) #:nodoc:
      # compile the given template, if necessary
      if compile_template?(template, file_path, local_assigns)
        template ||= read_template_file(file_path, extension)
        compile_template(extension, 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                                    

      local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys

      send(method_name, local_assigns) do |*name|
        instance_variable_get "@content_for_#{name.first || 'layout'}"
      end
    end

    def pick_template_extension(template_path)#:nodoc:
      if @@cache_template_extensions
        @@cached_template_extension[template_path] ||= find_template_extension_for(template_path)
      else
        find_template_extension_for(template_path)
      end
    end

    def delegate_template_exists?(template_path)#:nodoc:
      @@template_handlers.find { |k,| template_exists?(template_path, k) }
    end

    def erb_template_exists?(template_path)#:nodoc:
      template_exists?(template_path, :rhtml)
    end

    def builder_template_exists?(template_path)#:nodoc:
      template_exists?(template_path, :rxml)
    end
    
    def javascript_template_exists?(template_path)#:nodoc:
      template_exists?(template_path, :rjs)
    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
        cached_template_extension(template_path) ||
           %w(erb builder javascript delegate).any? do |template_type| 
             send("#{template_type}_template_exists?", template_path)
           end
      end
    end

    # Returns true is the file may be rendered implicitly.
    def file_public?(template_path)#:nodoc:
      template_path.split('/').last[0,1] != '_'
    end

    private
      def full_template_path(template_path, extension)
        "#{@base_path}/#{template_path}.#{extension}"
      end

      def template_exists?(template_path, extension)
        file_path = full_template_path(template_path, extension)
        @@method_names.has_key?(file_path) || FileTest.exists?(file_path)
      end

      def path_and_extension(template_path)
        template_path_without_extension = template_path.sub(/\.(\w+)$/, '')
        [ template_path_without_extension, $1 ]
      end
      
      def cached_template_extension(template_path)
        @@cache_template_extensions && @@cached_template_extension[template_path]
      end      
          
      def find_template_extension_for(template_path)
        if match = delegate_template_exists?(template_path)
          match.first.to_sym
        elsif erb_template_exists?(template_path):        :rhtml
        elsif builder_template_exists?(template_path):    :rxml
        elsif javascript_template_exists?(template_path): :rjs
        else
          raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path}"
        end
      end

      # This method reads a template file.
      def read_template_file(template_path, extension)
        File.read(template_path)
      end

      def evaluate_assigns
        unless @assigns_added
          assign_variables_from_controller
          @assigns_added = true
        end
      end

      def delegate_render(handler, template, local_assigns)
        handler.new(self).render(template, local_assigns)
      end

      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
      
      # Check whether compilation is necessary.
      # Compile if the inline template or file has not been compiled yet.
      # Or 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]

        if @@compile_time[render_symbol] && supports_local_assigns?(render_symbol, local_assigns)
          if file_name && !@@cache_template_loading 
            @@compile_time[render_symbol] < File.mtime(file_name) || (File.symlink?(file_name) ? 
              @@compile_time[render_symbol] < File.lstat(file_name).mtime : false)
          end
        else
          true
        end
      end

      # Create source code for given template
      def create_template_source(extension, template, render_symbol, locals)
        if template_requires_setup?(extension)
          body = case extension.to_sym
            when :rxml
              "xml = Builder::XmlMarkup.new(:indent => 2)\n" +
              "@controller.headers['Content-Type'] ||= 'application/xml'\n" +
              template
            when :rjs
              "@controller.headers['Content-Type'] ||= 'text/javascript'\n" +
              "update_page do |page|\n#{template}\nend"
          end
        else
          body = ERB.new(template, nil, @@erb_trim_mode).src
        end

        @@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}] if local_assigns.has_key?(:#{key})\n"
        end

        "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
      end

      def template_requires_setup?(extension)
        templates_requiring_setup.include? extension.to_s
      end

      def templates_requiring_setup
        %w(rxml rjs)
      end

      def assign_method_name(extension, template, file_name)
        method_name = '_run_'
        method_name << "#{extension}_" if extension

        if file_name
          file_path = File.expand_path(file_name)
          base_path = File.expand_path(@base_path)

          i = file_path.index(base_path)
          l = base_path.length

          method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone
          method_name_file_part.sub!(/\.r(html|xml|js)$/,'')
          method_name_file_part.tr!('/:-', '_')
          method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s}

          method_name += method_name_file_part
        else
          @@inline_template_count += 1
          method_name << @@inline_template_count.to_s
        end

        @@method_names[file_name || template] = method_name.intern
      end

      def compile_template(extension, template, file_name, local_assigns)
        method_key = file_name || template

        render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name)
        render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)

        line_offset = @@template_args[render_symbol].size
        if extension
          case extension.to_sym
          when :rxml, :rjs
            line_offset += 2
          end
        end
        
        begin
          unless file_name.blank?
            CompiledTemplates.module_eval(render_source, file_name, -line_offset)
          else
            CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset)
          end
        rescue Object => e
          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(@base_path, method_key, @assigns, template, e)
        end

        @@compile_time[render_symbol] = Time.now
        # logger.debug "Compiled template #{method_key}\n  ==> #{render_symbol}" if logger
      end
  end
end

require 'action_view/template_error'

module ActionView

  # CompiledTemplates modules hold methods that have been compiled.
  # Templates are compiled into these methods so that they do not need to be
  # re-read and re-parsed 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 #:nodoc:
    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 provded, 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 Object => e
        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
require 'cgi'
require File.dirname(__FILE__) + '/form_helper'

module ActionView
  class Base
    @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>" }
    cattr_accessor :field_error_proc
  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
    # method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
    # is a great 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
    module ActiveRecordHelper
      # Returns a default input tag for the type of object returned by the method. Example
      # (title is a VARCHAR column and holds "Hello World"):
      #   input("post", "title") =>
      #     <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
      def input(record_name, method, options = {})
        InstanceTag.new(record_name, method, self).to_tag(options)
      end

      # Returns an entire form with input tags and everything for a specified Active Record object. Example
      # (post is a new record that has a title using VARCHAR and a body using TEXT):
      #   form("post") =>
      #     <form action='/post/create' method='post'>
      #       <p>
      #         <label for="post_title">Title</label><br />
      #         <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
      #       </p>
      #       <p>
      #         <label for="post_body">Body</label><br />
      #         <textarea cols="40" id="post_body" name="post[body]" rows="20">
      #           Back to the hill and over it again!
      #         </textarea>
      #       </p>
      #       <input type='submit' value='Create' />
      #     </form>
      #
      # It's possible to specialize the form builder by using a different action name and by supplying another
      # block renderer. Example (entry is a new record that has a message attribute using VARCHAR):
      #
      #   form("entry", :action => "sign", :input_block =>
      #        Proc.new { |record, column| "#{column.human_name}: #{input(record, column.name)}<br />" }) =>
      #
      #     <form action='/post/sign' method='post'>
      #       Message:
      #       <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /><br />
      #       <input type='submit' value='Sign' />
      #     </form>
      #
      # It's also possible to add additional content to the form by giving it a block, such as:
      #
      #   form("entry", :action => "sign") do |form|
      #     form << content_tag("b", "Department")
      #     form << collection_select("department", "id", @departments, "id", "name")
      #   end
      def form(record_name, options = {})
        record = instance_variable_get("@#{record_name}")

        options = options.symbolize_keys
        options[:action] ||= record.new_record? ? "create" : "update"
        action = url_for(:action => options[:action], :id => record)

        submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize

        contents = ''
        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)
      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 specialized to include both a +prepend_text+ and +append_text+
      # to properly introduce the error and a +css_class+ to style it accordingly. Examples (post has an error message
      # "can't be empty" on the title attribute):
      #
      #   <%= error_message_on "post", "title" %> =>
      #     <div class="formError">can't be empty</div>
      #
      #   <%= error_message_on "post", "title", "Title simply ", " (or it won't work)", "inputError" %> =>
      #     <div class="inputError">Title simply can't be empty (or it won't work)</div>
      def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError")
        if errors = instance_variable_get("@#{object}").errors.on(method)
          content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class)
        end
      end

      # Returns a string with a div containing all the error messages for the object located as an instance variable by the name
      # of <tt>object_name</tt>. This div can be tailored by the following options:
      #
      # * <tt>header_tag</tt> - Used for the header of the error div (default: h2)
      # * <tt>id</tt> - The id of the error div (default: errorExplanation)
      # * <tt>class</tt> - The class of the error div (default: errorExplanation)
      #
      # 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
      # instance yourself and set it up. View the source of this method to see how easy it is.
      def error_messages_for(object_name, options = {})
        options = options.symbolize_keys
        object = instance_variable_get("@#{object_name}")
        if object && !object.errors.empty?
          content_tag("div",
            content_tag(
              options[:header_tag] || "h2",
              "#{pluralize(object.errors.count, "error")} prohibited this #{object_name.to_s.gsub("_", " ")} from being saved"
            ) +
            content_tag("p", "There were problems with the following fields:") +
            content_tag("ul", object.errors.full_messages.collect { |msg| content_tag("li", msg) }),
            "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
          )
        else
          ""
        end
      end

      private
        def all_input_tags(record, record_name, options)
          input_block = options[:input_block] || default_input_block
          record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
        end

        def default_input_block
          Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
        end
    end

    class InstanceTag #:nodoc:
      def to_tag(options = {})
        case column_type
          when :string
            field_type = @method_name.include?("password") ? "password" : "text"
            to_input_field_tag(field_type, options)
          when :text
            to_text_area_tag(options)
          when :integer, :float
            to_input_field_tag("text", options)
          when :date
            to_date_select_tag(options)
          when :datetime, :timestamp
            to_datetime_select_tag(options)
          when :boolean
            to_boolean_select_tag(options)
        end
      end

      alias_method :tag_without_error_wrapping, :tag
      def tag(name, options)
        if object.respond_to?("errors") && object.errors.respond_to?("on")
          error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
        else
          tag_without_error_wrapping(name, options)
        end
      end

      alias_method :content_tag_without_error_wrapping, :content_tag
      def content_tag(name, value, options)
        if object.respond_to?("errors") && object.errors.respond_to?("on")
          error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
        else
          content_tag_without_error_wrapping(name, value, options)
        end
      end

      alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
      def to_date_select_tag(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))
        else
          to_date_select_tag_without_error_wrapping(options)
        end
      end

      alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
      def to_datetime_select_tag(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))
          else
            to_datetime_select_tag_without_error_wrapping(options)
        end
      end

      def error_wrapping(html_tag, has_error)
        has_error ? Base.field_error_proc.call(html_tag, self) : html_tag
      end

      def error_message
        object.errors.on(@method_name)
      end

      def column_type
        object.send("column_for_attribute", @method_name).type
      end
    end
  end
end
require 'cgi'
require File.dirname(__FILE__) + '/url_helper'
require File.dirname(__FILE__) + '/tag_helper'

module ActionView
  module Helpers
    # Provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
    module AssetTagHelper
      # Returns a link tag that browsers and news readers can use to auto-detect a RSS or ATOM feed for this page. The +type+ can
      # either be <tt>:rss</tt> (default) or <tt>:atom</tt> and the +options+ follow the url_for style of declaring a link target.
      #
      # Examples:
      #   auto_discovery_link_tag # =>
      #     <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/action" />
      #   auto_discovery_link_tag(:atom) # =>
      #     <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.curenthost.com/controller/action" />
      #   auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
      #     <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/feed" />
      #   auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
      #     <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.curenthost.com/controller/feed" />
      def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
        tag(
          "link", 
          "rel"   => tag_options[:rel] || "alternate",
          "type"  => tag_options[:type] || "application/#{type}+xml",
          "title" => tag_options[:title] || type.to_s.upcase,
          "href"  => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
        )
      end

      # Returns path to a javascript asset. Example:
      #
      #   javascript_path "xmlhr" # => /javascripts/xmlhr.js
      def javascript_path(source)
        compute_public_path(source, 'javascripts', 'js')        
      end

      JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
      @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup

      # Returns a script include tag per source given as argument. Examples:
      #
      #   javascript_include_tag "xmlhr" # =>
      #     <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
      #
      #   javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
      #     <script type="text/javascript" src="/javascripts/common.javascript"></script>
      #     <script type="text/javascript" src="/elsewhere/cools.js"></script>
      #
      #   javascript_include_tag :defaults # =>
      #     <script type="text/javascript" src="/javascripts/prototype.js"></script>
      #     <script type="text/javascript" src="/javascripts/effects.js"></script>
      #     ...
      #     <script type="text/javascript" src="/javascripts/application.js"></script> *see below
      #   
      # If there's an <tt>application.js</tt> file in your <tt>public/javascripts</tt> directory,
      # <tt>javascript_include_tag :defaults</tt> will automatically include it. This file
      # facilitates the inclusion of small snippets of JavaScript code, along the lines of
      # <tt>controllers/application.rb</tt> and <tt>helpers/application_helper.rb</tt>.
      def javascript_include_tag(*sources)
        options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }

        if 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 defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") 
        end

        sources.collect { |source|
          source = javascript_path(source)        
          content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options))
        }.join("\n")
      end
      
      # Register one or more additional JavaScript files to be included when
      #   
      #   javascript_include_tag :defaults
      #
      # is called. This method is intended to be called only from plugin initialization
      # to register extra .js files the plugin installed in <tt>public/javascripts</tt>.
      def self.register_javascript_include_default(*sources)
        @@javascript_default_sources.concat(sources)
      end
      
      def self.reset_javascript_include_default #:nodoc:
        @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
      end

      # Returns path to a stylesheet asset. Example:
      #
      #   stylesheet_path "style" # => /stylesheets/style.css
      def stylesheet_path(source)
        compute_public_path(source, 'stylesheets', 'css')
      end

      # Returns a css link tag per source given as argument. Examples:
      #
      #   stylesheet_link_tag "style" # =>
      #     <link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />
      #
      #   stylesheet_link_tag "style", :media => "all" # =>
      #     <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
      #
      #   stylesheet_link_tag "random.styles", "/css/stylish" # =>
      #     <link href="/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />
      #     <link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />
      def stylesheet_link_tag(*sources)
        options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
        sources.collect { |source|
          source = stylesheet_path(source)
          tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
        }.join("\n")
      end

      # Returns path to an image asset. Example:
      #
      # The +src+ can be supplied as a...
      # * full path, like "/my_images/image.gif"
      # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
      # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
      def image_path(source)
        compute_public_path(source, 'images', 'png')
      end

      # Returns an image tag converting the +options+ into html options on the tag, but with these special cases:
      #
      # * <tt>:alt</tt>  - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
      # * <tt>:size</tt> - Supplied as "XxY", so "30x45" becomes width="30" and height="45"
      #
      # The +src+ can be supplied as a...
      # * full path, like "/my_images/image.gif"
      # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
      # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
      def image_tag(source, options = {})
        options.symbolize_keys!
                
        options[:src] = image_path(source)
        options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
        
        if options[:size]
          options[:width], options[:height] = options[:size].split("x")
          options.delete :size
        end

        tag("img", options)
      end
      
      private
        def compute_public_path(source, dir, ext)
          source  = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":")
          source << ".#{ext}" unless source.split("/").last.include?(".")
          source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source
          source  = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source
          source = ActionController::Base.asset_host + source unless source.include?(":")
          source
        end
        
        def rails_asset_id(source)
          ENV["RAILS_ASSET_ID"] || 
            File.mtime("#{RAILS_ROOT}/public/#{source}").to_i.to_s rescue ""
        end
    end
  end
end
require 'benchmark'

module ActionView
  module Helpers
    module BenchmarkHelper
      # Measures the execution time of a block in a template and reports the result to the log. Example:
      #
      #  <% benchmark "Notes section" do %>
      #    <%= expensive_notes_operation %>
      #  <% end %>
      #
      # Will add something like "Notes section (0.34523)" to the log.
      #
      # You may give an optional logger level as the second argument
      # (:debug, :info, :warn, :error).  The default is :info.
      def benchmark(message = "Benchmarking", level = :info)
        if @logger
          real = Benchmark.realtime { yield }
          @logger.send level, "#{message} (#{'%.5f' % real})"
        end
      end
    end
  end
end
module ActionView
  module Helpers
    # See ActionController::Caching::Fragments for usage instructions.
    module CacheHelper
      def cache(name = {}, &block)
        @controller.cache_erb_fragment(block, name)
      end
    end
  end
end
module ActionView
  module Helpers
    # Capture lets you extract parts of code which
    # can be used in other points of the template or even layout file.
    #
    # == Capturing a block into an instance variable
    #
    #   <% @script = capture do %>
    #     [some html...]
    #   <% end %>
    #
    # == Add javascript to header using content_for
    #
    # content_for("name") is a wrapper for capture which will 
    # make the fragment available by name to a yielding layout or template.
    #
    # layout.rhtml:
    #
    #   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    #   <head>
    #	    <title>layout with js</title>
    #	    <script type="text/javascript">
    #	      <%= yield :script %>
    #     </script>
    #   </head>
    #   <body>
    #     <%= yield %>
    #   </body>
    #   </html>
    #
    # view.rhtml
    #   
    #   This page shows an alert box!
    #
    #   <% content_for("script") do %>
    #     alert('hello world')
    #   <% end %>
    #
    #   Normal view text
    module CaptureHelper
      # Capture allows you to extract a part of the template into an 
      # instance variable. You can use this instance variable anywhere
      # in your templates and even in your layout. 
      # 
      # Example of capture being used in a .rhtml page:
      # 
      #   <% @greeting = capture do %>
      #     Welcome To my shiny new web page!
      #   <% end %>
      #
      # Example of capture being used in a .rxml page:
      # 
      #   @greeting = capture do
      #     'Welcome To my shiny new web page!'
      #   end
      def capture(*args, &block)
        # execute the block
        begin
          buffer = eval("_erbout", block.binding)
        rescue
          buffer = nil
        end
        
        if buffer.nil?
          capture_block(*args, &block)
        else
          capture_erb_with_buffer(buffer, *args, &block)
        end
      end
      
      # Calling content_for stores the block of markup for later use.
      # Subsequently, you can make calls to it by name with <tt>yield</tt>
      # in another template or in the layout. 
      # 
      # Example:
      # 
      #   <% content_for("header") do %>
      #     alert('hello world')
      #   <% end %>
      #
      # You can use yield :header anywhere in your templates.
      #
      #   <%= yield :header %>
      #
      # NOTE: Beware that content_for is ignored in caches. So you shouldn't use it
      # for elements that are going to be fragment cached.
      #
      # The deprecated way of accessing a content_for block was to use a instance variable
      # named @@content_for_#{name_of_the_content_block}@. So <tt><%= content_for('footer') %></tt>
      # would be avaiable as <tt><%= @content_for_footer %></tt>. The preferred notation now is
      # <tt><%= yield :footer %></tt>.
      def content_for(name, &block)
        eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
      end

      private
        def capture_block(*args, &block)
          block.call(*args)
        end
      
        def capture_erb(*args, &block)
          buffer = eval("_erbout", block.binding)
          capture_erb_with_buffer(buffer, *args, &block)
        end
      
        def capture_erb_with_buffer(buffer, *args, &block)
          pos = buffer.length
          block.call(*args)
        
          # extract the block 
          data = buffer[pos..-1]
        
          # replace it in the original with empty string
          buffer[pos..-1] = ''
        
          data
        end
      
        def erb_content_for(name, &block)
          eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
        end
      
        def block_content_for(name, &block)
          eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
        end
    end
  end
end
require "date"

module ActionView
  module Helpers
    # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
    # share a number of common options that are as follows:
    #
    # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
    #   birthday[month] instead of date[month] if passed to the select_month method.
    # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
    # * <tt>:discard_type</tt> - 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 <tt>:prefix</tt>) instead of "date[month]".
    module DateHelper
      DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')

      # Reports the approximate distance in time between two Time objects or integers. 
      # For example, if the distance is 47 minutes, it'll return
      # "about 1 hour". See the source for the complete wording list.
      #
      # Integers are interpreted as seconds. So,
      # <tt>distance_of_time_in_words(50)</tt> returns "less than a minute".
      #
      # Set <tt>include_seconds</tt> to true if you want more detailed approximations if distance < 1 minute
      def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
        from_time = from_time.to_time if from_time.respond_to?(:to_time)
        to_time = to_time.to_time if to_time.respond_to?(:to_time)
        distance_in_minutes = (((to_time - from_time).abs)/60).round
        distance_in_seconds = ((to_time - from_time).abs).round

        case distance_in_minutes
          when 0..1
            return (distance_in_minutes==0) ? 'less than a minute' : '1 minute' unless include_seconds
            case distance_in_seconds
              when 0..5   then 'less than 5 seconds'
              when 6..10  then 'less than 10 seconds'
              when 11..20 then 'less than 20 seconds'
              when 21..40 then 'half a minute'
              when 41..59 then 'less than a minute'
              else             '1 minute'
            end
                                
          when 2..45      then "#{distance_in_minutes} minutes"
          when 46..90     then 'about 1 hour'
          when 90..1440   then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
          when 1441..2880 then '1 day'
          else                 "#{(distance_in_minutes / 1440).round} days"
        end
      end
      
      # Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
      def time_ago_in_words(from_time, include_seconds = false)
        distance_of_time_in_words(from_time, Time.now, include_seconds)
      end
      
      alias_method :distance_of_time_in_words_to_now, :time_ago_in_words

      # 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
      # discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. 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 <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in
      # the desired order. Symbols may be omitted and the respective select is not included.
      #
      # Passing :disabled => true as part of the +options+ will make elements inaccessible for change.
      #
      # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
      #
      # Examples:
      #
      #   date_select("post", "written_on")
      #   date_select("post", "written_on", :start_year => 1995)
      #   date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
      #                                     :discard_day => true, :include_blank => true)
      #   date_select("post", "written_on", :order => [:day, :month, :year])
      #   date_select("user", "birthday",   :order => [:month, :day])
      #
      # The selects are prepared for multi-parameter assignment to an Active Record object.
      def date_select(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(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:
      #
      #   datetime_select("post", "written_on")
      #   datetime_select("post", "written_on", :start_year => 1995)
      #
      # 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)
      end

      # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
      def select_date(date = Date.today, options = {})
        select_year(date, options) + select_month(date, options) + select_day(date, options)
      end

      # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
      def select_datetime(datetime = Time.now, options = {})
        select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
        select_hour(datetime, options) + select_minute(datetime, options)
      end

      # Returns a set of html select-tags (one for hour and minute)
      def select_time(datetime = Time.now, options = {})
        h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '')
      end

      # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
      # The <tt>second</tt> can also be substituted for a second number.
      # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
      def select_second(datetime, options = {})
        second_options = []

        0.upto(59) do |second|
          second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ?
            %(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
            %(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
          )
        end

        select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
      end

      # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
      # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected
      # The <tt>minute</tt> can also be substituted for a minute number.
      # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
      def select_minute(datetime, options = {})
        minute_options = []

        0.step(59, options[:minute_step] || 1) do |minute|
          minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ?
            %(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
            %(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
          )
        end

        select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
      end

      # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
      # The <tt>hour</tt> can also be substituted for a hour number.
      # Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
      def select_hour(datetime, options = {})
        hour_options = []

        0.upto(23) do |hour|
          hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ?
            %(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
            %(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
          )
        end

        select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
      end

      # Returns a select tag with options for each of the days 1 through 31 with the current day selected.
      # The <tt>date</tt> can also be substituted for a hour number.
      # Override the field name using the <tt>:field_name</tt> option, 'day' by default.
      def select_day(date, options = {})
        day_options = []

        1.upto(31) do |day|
          day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ?
            %(<option value="#{day}" selected="selected">#{day}</option>\n) :
            %(<option value="#{day}">#{day}</option>\n)
          )
        end

        select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
      end

      # Returns a select tag with options for each of the months January through December with the current month selected.
      # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
      # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
      # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
      # set the <tt>:add_month_numbers</tt> key in +options+ to true. Examples:
      #
      #   select_month(Date.today)                             # Will use keys like "January", "March"
      #   select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
      #   select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
      #
      # Override the field name using the <tt>:field_name</tt> option, 'month' by default.
      #
      # If you would prefer to show month names as abbreviations, set the
      # <tt>:use_short_month</tt> key in +options+ to true.
      def select_month(date, options = {})
        month_options = []
        month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES

        1.upto(12) do |month_number|
          month_name = if options[:use_month_numbers]
            month_number
          elsif options[:add_month_numbers]
            month_number.to_s + ' - ' + month_names[month_number]
          else
            month_names[month_number]
          end

          month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ?
            %(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
            %(<option value="#{month_number}">#{month_name}</option>\n)
          )
        end

        select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
      end

      # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
      # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year
      # lists are supported by making <tt>:start_year</tt> less than or greater than <tt>:end_year</tt>. The <tt>date</tt> can also be
      # substituted for a year given as a number. Example:
      #
      #   select_year(Date.today, :start_year => 1992, :end_year => 2007)  # ascending year values
      #   select_year(Date.today, :start_year => 2005, :end_year => 1900)  # descending year values
      #
      # Override the field name using the <tt>:field_name</tt> option, 'year' by default.
      def select_year(date, options = {})
        year_options = []
        y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year

        start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
        step_val = start_year < end_year ? 1 : -1

        start_year.step(end_year, step_val) do |year|
          year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ?
            %(<option value="#{year}" selected="selected">#{year}</option>\n) :
            %(<option value="#{year}">#{year}</option>\n)
          )
        end

        select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
      end

      private
        def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false)
          select_html  = %(<select name="#{prefix || DEFAULT_PREFIX})
          select_html << "[#{type}]" unless discard_type
          select_html << %(")
          select_html << %( disabled="disabled") if disabled
          select_html << %(>\n)
          select_html << %(<option value=""></option>\n) if include_blank
          select_html << options.to_s
          select_html << "</select>\n"
        end

        def leading_zero_on_single_digits(number)
          number > 9 ? number : "0#{number}"
        end
    end

    class InstanceTag #:nodoc:
      include DateHelper

      def to_date_select_tag(options = {})
        defaults = { :discard_type => true }
        options  = defaults.merge(options)
        options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
        date     = options[:include_blank] ? (value || 0) : (value || Date.today)

        date_select = ''
        options[:order]   = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility
        options[:order] ||= [:year, :month, :day]

        position = {:year => 1, :month => 2, :day => 3}

        discard = {}
        discard[:year]  = true if options[:discard_year]
        discard[:month] = true if options[:discard_month]
        discard[:day]   = true if options[:discard_day] or options[:discard_month]

        options[:order].each do |param|
          date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param]
        end

        date_select
      end

      def to_datetime_select_tag(options = {})
        defaults = { :discard_type => true }
        options  = defaults.merge(options)
        options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
        datetime = options[:include_blank] ? (value || nil) : (value || Time.now)

        datetime_select  = select_year(datetime, options_with_prefix.call(1))
        datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
        datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
        datetime_select << ' &mdash; ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
        datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]

        datetime_select
      end
    end

    class FormBuilder
      def date_select(method, options = {})
        @template.date_select(@object_name, method, options.merge(:object => @object))
      end

      def datetime_select(method, options = {})
        @template.datetime_select(@object_name, method, options.merge(:object => @object))
      end
    end
  end
end
module ActionView
  module Helpers
    # Provides a set of methods for making it easier to locate problems.
    module DebugHelper
      # Returns a <pre>-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
      def debug(object)
        begin
          Marshal::dump(object)
          "<pre class='debug_dump'>#{h(object.to_yaml).gsub("  ", "&nbsp; ")}</pre>"
        rescue Object => e
          # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
          "<code class='debug_dump'>#{h(object.inspect)}</code>"
        end
      end
    end
  end
endrequire 'cgi'
require File.dirname(__FILE__) + '/date_helper'
require File.dirname(__FILE__) + '/tag_helper'

module ActionView
  module Helpers
    # Provides a set of methods for working with forms and especially forms related to objects assigned to the template.
    # The following is an example of a complete form for a person object that works for both creates and updates built
    # with all the form helpers. The <tt>@person</tt> object was assigned by an action on the controller:
    #   <form action="save_person" method="post">
    #     Name:
    #     <%= text_field "person", "name", "size" => 20 %>
    #
    #     Password:
    #     <%= password_field "person", "password", "maxsize" => 20 %>
    #
    #     Single?:
    #     <%= check_box "person", "single" %>
    #
    #     Description:
    #     <%= text_area "person", "description", "cols" => 20 %>
    #
    #     <input type="submit" value="Save">
    #   </form>
    #
    # ...is compiled to:
    #
    #   <form action="save_person" method="post">
    #     Name:
    #     <input type="text" id="person_name" name="person[name]"
    #       size="20" value="<%= @person.name %>" />
    #
    #     Password:
    #     <input type="password" id="person_password" name="person[password]"
    #       size="20" maxsize="20" value="<%= @person.password %>" />
    #
    #     Single?:
    #     <input type="checkbox" id="person_single" name="person[single]" value="1" />
    #
    #     Description:
    #     <textarea cols="20" rows="40" id="person_description" name="person[description]">
    #       <%= @person.description %>
    #     </textarea>
    #
    #     <input type="submit" value="Save">
    #   </form>
    #
    # If the object name contains square brackets the id for the object will be inserted. Example:
    #
    #   <%= text_field "person[]", "name" %> 
    # 
    # ...becomes:
    #
    #   <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
    #
    # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
    # used by render_collection_of_partials, the "index" option may come in handy. Example:
    #
    #   <%= text_field "person", "name", "index" => 1 %>
    #
    # becomes
    #
    #   <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
    #
    # There's 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, which is then used as a base for questioning about
      # values for the fields. Examples:
      #
      #   <% 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 %>
      #   <% end %>
      #
      # Worth noting is that the form_for tag is called in a ERb evaluation block, not a ERb output block. So that's <tt><% %></tt>, 
      # not <tt><%= %></tt>. Also worth noting is that the 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 <tt>text_field :person, :name</tt>,
      # you get away with <tt>f.text_field :name</tt>. 
      #
      # That in itself is a modest increase in comfort. The big news is that form_for allows us to more easily escape the instance
      # variable convention, so while the stand-alone approach would require <tt>text_field :person, :name, :object => person</tt> 
      # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with 
      # <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
      #
      # 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. Example:
      #
      #   <% form_for :person, @person, :url => { :action => "update" } do |f| %>
      #     First name: <%= f.text_field :first_name %>
      #     Last name : <%= f.text_field :last_name %>
      #     Biography : <%= text_area :person, :biography %>
      #     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 collection_select and datetime_select.
      #
      # Html attributes for the form tag can be given as :html => {...}. Example:
      #     
      #   <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
      #     ...
      #   <% end %>
      #
      # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
      # then use your custom builder like so:
      #   
      #   <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
      #     <%= f.text_field :first_name %>
      #     <%= f.text_field :last_name %>
      #     <%= text_area :person, :biography %>
      #     <%= check_box_tag "person[admin]", @person.company.admin? %>
      #   <% end %>
      # 
      # In many cases you will want to wrap the above in another helper, such as:
      #
      #   def labelled_form_for(name, object, options, &proc)
      #     form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc)
      #   end
      #
      def form_for(object_name, *args, &proc)
        raise ArgumentError, "Missing block" unless block_given?
        options = args.last.is_a?(Hash) ? args.pop : {}
        concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
        fields_for(object_name, *(args << options), &proc)
        concat('</form>', proc.binding)
      end

      # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
      # fields_for suitable for specifying additional model objects in the same form. Example:
      #
      #   <% form_for :person, @person, :url => { :action => "update" } do |person_form| %>
      #     First name: <%= person_form.text_field :first_name %>
      #     Last name : <%= person_form.text_field :last_name %>
      #     
      #     <% fields_for :permission, @person.permission do |permission_fields| %>
      #       Admin?  : <%= permission_fields.check_box :admin %>
      #     <% end %>
      #   <% end %>
      #
      # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
      # Like collection_select and datetime_select.
      def fields_for(object_name, *args, &proc)
        raise ArgumentError, "Missing block" unless block_given?
        options = args.last.is_a?(Hash) ? args.pop : {}
        object  = args.first
        yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc))
      end

      # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
      # hash with +options+.
      #
      # Examples (call, result):
      #   text_field("post", "title", "size" => 20)
      #     <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
      def text_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
      end

      # Works just like text_field, but returns an input tag of the "password" type instead.
      def password_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
      end

      # Works just like text_field, but returns an input tag of the "hidden" type instead.
      def hidden_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
      end

      # Works just like text_field, but returns an input tag of the "file" type instead, which won't have a default value.
      def file_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
      end

      # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
      # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
      # hash with +options+.
      #
      # Example (call, result):
      #   text_area("post", "body", "cols" => 20, "rows" => 40)
      #     <textarea cols="20" rows="40" id="post_body" name="post[body]">
      #       #{@post.body}
      #     </textarea>
      def text_area(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
      end

      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
      # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
      # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
      # is set to 0 which is convenient for boolean values. Usually unchecked checkboxes don't post anything.
      # We work around this problem by adding a hidden value with the same name as the checkbox.
      #
      # Example (call, result). Imagine that @post.validated? returns 1:
      #   check_box("post", "validated")
      #     <input type="checkbox" id="post_validate" name="post[validated]" value="1" checked="checked" />
      #     <input name="post[validated]" type="hidden" value="0" />
      #
      # Example (call, result). Imagine that @puppy.gooddog returns no:
      #   check_box("puppy", "gooddog", {}, "yes", "no")
      #     <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
      #     <input name="puppy[gooddog]" type="hidden" value="no" />
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
      end

      # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
      # radio button will be checked. Additional options on the input tag can be passed as a
      # hash with +options+.
      # Example (call, result). Imagine that @post.category returns "rails":
      #   radio_button("post", "category", "rails")
      #   radio_button("post", "category", "java")
      #     <input type="radio" id="post_category" name="post[category]" value="rails" checked="checked" />
      #     <input type="radio" id="post_category" name="post[category]" value="java" />
      #
      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

      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
        @template_object, @local_binding = template_object, local_binding
        @object = object
        if @object_name.sub!(/\[\]$/,"")
          @auto_index = @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}").id_before_type_cast
        end
      end

      def to_input_field_tag(field_type, options = {})
        options = options.stringify_keys
        options["size"] ||= options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"]
        options = DEFAULT_FIELD_OPTIONS.merge(options)
        if field_type == "hidden"
          options.delete("size")
        end
        options["type"] = field_type
        options["value"] ||= value_before_type_cast unless field_type == "file"
        add_default_name_and_id(options)
        tag("input", options)
      end

      def to_radio_button_tag(tag_value, options = {})
        options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
        options["type"]     = "radio"
        options["value"]    = tag_value
        options["checked"]  = "checked" if value.to_s == tag_value.to_s
        pretty_tag_value    = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
        options["id"]       = @auto_index ?             
          "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
          "#{@object_name}_#{@method_name}_#{pretty_tag_value}"
        add_default_name_and_id(options)
        tag("input", options)
      end

      def to_text_area_tag(options = {})
        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
        add_default_name_and_id(options)
        content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast), options)
      end

      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
        options = options.stringify_keys
        options["type"]     = "checkbox"
        options["value"]    = checked_value
        checked = case value
          when TrueClass, FalseClass
            value
          when NilClass
            false
          when Integer
            value != 0
          when String
            value == checked_value
          else
            value.to_i != 0
          end
        if checked || options["checked"] == "checked"
          options["checked"] = "checked"
        else
          options.delete("checked")
        end
        add_default_name_and_id(options)
        tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
      end

      def to_date_tag()
        defaults = DEFAULT_DATE_OPTIONS.dup
        date     = value || 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)
        tag_text = "<select"
        tag_text << tag_options(options)
        tag_text << "><option value=\"false\""
        tag_text << " selected" if value == false
        tag_text << ">False</option><option value=\"true\""
        tag_text << " selected" if value
        tag_text << ">True</option></select>"
      end
      
      def to_content_tag(tag_name, options = {})
        content_tag(tag_name, value, options)
      end
      
      def object
        @object || @template_object.instance_variable_get("@#{@object_name}")
      end

      def value
        unless object.nil?
          object.send(@method_name)
        end
      end

      def value_before_type_cast
        unless object.nil?
          object.respond_to?(@method_name + "_before_type_cast") ?
            object.send(@method_name + "_before_type_cast") :
            object.send(@method_name)
        end
      end

      private
        def add_default_name_and_id(options)
          if options.has_key?("index")
            options["name"] ||= tag_name_with_index(options["index"])
            options["id"]   ||= tag_id_with_index(options["index"])
            options.delete("index")
          elsif @auto_index
            options["name"] ||= tag_name_with_index(@auto_index)
            options["id"]   ||= tag_id_with_index(@auto_index)
          else
            options["name"] ||= tag_name
            options["id"]   ||= tag_id
          end
        end

        def tag_name
          "#{@object_name}[#{@method_name}]"
        end

        def tag_name_with_index(index)
          "#{@object_name}[#{index}][#{@method_name}]"
        end

        def tag_id
          "#{@object_name}_#{@method_name}"
        end

        def tag_id_with_index(index)
          "#{@object_name}_#{index}_#{@method_name}"
        end
    end

    class FormBuilder #:nodoc:
      # The methods which wrap a form helper call.
      class_inheritable_accessor :field_helpers
      self.field_helpers = (FormHelper.instance_methods - ['form_for'])

      attr_accessor :object_name, :object

      def initialize(object_name, object, template, options, proc)
        @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc        
      end
      
      (field_helpers - %w(check_box radio_button)).each do |selector|
        src = <<-end_src
          def #{selector}(method, options = {})
            @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object))
          end
        end_src
        class_eval src, __FILE__, __LINE__
      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)
      end
      
      def radio_button(method, tag_value, options = {})
        @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object))
      end
    end
  end
end
require 'cgi'
require 'erb'
require File.dirname(__FILE__) + '/form_helper'

module ActionView
  module Helpers
    # Provides a number of methods for turning different kinds of containers into a set of option tags.
    # == Options
    # The <tt>collection_select</tt>, <tt>country_select</tt>, <tt>select</tt>,
    # and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter,
    # a hash.
    #
    # * <tt>:include_blank</tt> - set to true if the first option element of the select element is a blank. Useful if there is not a default value required for the select element. For example,
    #
    #   select("post", "category", Post::CATEGORIES, {:include_blank => true})
    #
    # could become:
    #
    #   <select name="post[category]">
    #     <option></option>
    #     <option>joke</option>
    #     <option>poem</option>
    #   </select>
    #
    # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
    #
    # Another common case is a select tag for an <tt>belongs_to</tt>-associated object. For example,
    #
    #   select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] })
    #
    # could become:
    #
    #   <select name="post[person_id]">
    #     <option value="1">David</option>
    #     <option value="2">Sam</option>
    #     <option value="3">Tobias</option>
    #   </select>
    module FormOptionsHelper
      include ERB::Util

      # Create a select tag and a series of contained option tags for the provided object and method.
      # The option currently held by the object will be selected, provided that the object is available.
      # See options_for_select for the required format of the choices parameter.
      #
      # Example with @post.person_id => 1:
      #   select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
      #
      # could become:
      #
      #   <select name="post[person_id]">
      #     <option></option>
      #     <option value="1" selected="selected">David</option>
      #     <option value="2">Sam</option>
      #     <option value="3">Tobias</option>
      #   </select>
      #
      # This can be used to provide a default set of options in the standard way: before rendering the create form, a
      # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
      # to the database. Instead, a second model object is created when the create request is received.
      # 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.
      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

      # Return select and option tags for the given object and method using options_from_collection_for_select to generate the list of option tags.
      def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
        InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
      end

      # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
      def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
        InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
      end

      # Return select and option tags for the given object and method, using
      # #time_zone_options_for_select to generate the list of option tags.
      #
      # In addition to the <tt>:include_blank</tt> option documented above,
      # this method also supports a <tt>:model</tt> 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
      # information.)
      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

      # 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+
      # may also be an array of values to be selected when using a multiple select.
      #
      # Examples (call, result):
      #   options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
      #     <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
      #
      #   options_for_select([ "VISA", "MasterCard" ], "MasterCard")
      #     <option>VISA</option>\n<option selected="selected">MasterCard</option>
      #
      #   options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
      #     <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
      #
      #   options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
      #     <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
      #
      # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
      def options_for_select(container, selected = nil)
        container = container.to_a if Hash === container

        options_for_select = container.inject([]) do |options, element|
          if !element.is_a?(String) and element.respond_to?(:first) and element.respond_to?(:last)
            is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element.last) : element.last == selected) )
            is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element.last) : element.last == selected) )
            if is_selected
              options << "<option value=\"#{html_escape(element.last.to_s)}\" selected=\"selected\">#{html_escape(element.first.to_s)}</option>"
            else
              options << "<option value=\"#{html_escape(element.last.to_s)}\">#{html_escape(element.first.to_s)}</option>"
            end
          else
            is_selected = ( (selected.respond_to?(:include?) ? selected.include?(element) : element == selected) )
            is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element) : element == selected) )
            options << ((is_selected) ? "<option value=\"#{html_escape(element.to_s)}\" selected=\"selected\">#{html_escape(element.to_s)}</option>" : "<option value=\"#{html_escape(element.to_s)}\">#{html_escape(element.to_s)}</option>")
          end
        end

        options_for_select.join("\n")
      end

      # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
      # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
      # If +selected_value+ is specified, the element returning a match on +value_method+ will get the selected option tag.
      #
      # Example (call, result). Imagine a loop iterating over each +person+ in <tt>@project.people</tt> to generate an input tag:
      #   options_from_collection_for_select(@project.people, "id", "name")
      #     <option value="#{person.id}">#{person.name}</option>
      #
      # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
      def options_from_collection_for_select(collection, value_method, text_method, selected_value = nil)
        options_for_select(
          collection.inject([]) { |options, object| options << [ object.send(text_method), object.send(value_method) ] },
          selected_value
        )
      end

      # Returns a string of option tags, like options_from_collection_for_select, but surrounds them with <optgroup> tags.
      #
      # An array of group objects are passed. Each group should return an array of options when calling group_method
      # Each group should return its name when calling group_label_method.
      #
      # html_option_groups_from_collection(@continents, "countries", "continent_name", "country_id", "country_name", @selected_country.id)
      #
      # Could become:
      #  <optgroup label="Africa">
      #   <select>Egypt</select>
      #   <select>Rwanda</select>
      #   ...
      #  </optgroup>
      #  <optgroup label="Asia">
      #   <select>China</select>
      #   <select>India</select>
      #   <select>Japan</select>
      #   ...
      #  </optgroup>
      #
      # with objects of the following classes:
      # class Continent
      #   def initialize(p_name, p_countries) @continent_name = p_name; @countries = p_countries; end
      #   def continent_name() @continent_name; end
      #   def countries() @countries; end
      # end
      # class Country
      #   def initialize(id, name) @id = id; @name = name end
      #   def country_id() @id; end
      #   def country_name() @name; end
      # end
      #
      # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
      def option_groups_from_collection_for_select(collection, group_method, group_label_method,
            option_key_method, option_value_method, selected_key = nil)
        collection.inject("") do |options_for_select, group|
          group_label_string = eval("group.#{group_label_method}")
          options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
          options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
          options_for_select += '</optgroup>'
        end
      end

      # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to
      # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so
      # that they will be listed above the rest of the (long) list.
      #
      # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
      def country_options_for_select(selected = nil, priority_countries = nil)
        country_options = ""

        if priority_countries
          country_options += options_for_select(priority_countries, selected)
          country_options += "<option value=\"\">-------------</option>\n"
        end

        if priority_countries && priority_countries.include?(selected)
          country_options += options_for_select(COUNTRIES - priority_countries, selected)
        else
          country_options += options_for_select(COUNTRIES, selected)
        end

        return country_options
      end

      # Returns a string of option tags for pretty much any time zone in the
      # world. Supply a TimeZone name as +selected+ to have it marked as the
      # selected option tag. 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.)
      #
      # The +selected+ parameter must be either +nil+, or a string that names
      # 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
      # an array of objects that represent time zones.
      #
      # NOTE: Only the option tags are returned, you have to wrap this call in
      # a regular HTML select tag.
      def time_zone_options_for_select(selected = nil, priority_zones = nil, model = TimeZone)
        zone_options = ""

        zones = model.all
        convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }

        if priority_zones
          zone_options += options_for_select(convert_zones[priority_zones], selected)
          zone_options += "<option value=\"\">-------------</option>\n"

          zones = zones.reject { |z| priority_zones.include?( z ) }
        end

        zone_options += options_for_select(convert_zones[zones], selected)
        zone_options
      end

      private
        # All the countries included in the country_options output.
        COUNTRIES = [ "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", 
            "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", 
            "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", 
            "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", 
            "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", 
            "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burma", "Burundi", "Cambodia", 
            "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", 
            "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", 
            "Comoros", "Congo", "Congo, the Democratic Republic of the", "Cook Islands", 
            "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", 
            "Djibouti", "Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt", 
            "El Salvador", "England", "Equatorial Guinea", "Eritrea", "Espana", "Estonia", 
            "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France", 
            "French Guiana", "French Polynesia", "French Southern Territories", "Gabon", "Gambia", 
            "Georgia", "Germany", "Ghana", "Gibraltar", "Great Britain", "Greece", "Greenland", 
            "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", 
            "Haiti", "Heard and Mc Donald Islands", "Honduras", "Hong Kong", "Hungary", "Iceland", 
            "India", "Indonesia", "Ireland", "Israel", "Italy", "Iran", "Iraq", "Jamaica", "Japan", "Jordan", 
            "Kazakhstan", "Kenya", "Kiribati", "Korea, Republic of", "Korea (South)", "Kuwait", 
            "Kyrgyzstan", "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", 
            "Liberia", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", 
            "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", 
            "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", 
            "Micronesia, Federated States of", "Moldova, Republic of", "Monaco", "Mongolia", 
            "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", 
            "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", 
            "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Ireland", 
            "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Panama", 
            "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", 
            "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda", 
            "Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines", 
            "Samoa (Independent)", "San Marino", "Sao Tome and Principe", "Saudi Arabia", 
            "Scotland", "Senegal", "Serbia and Montenegro", "Seychelles", "Sierra Leone", "Singapore", 
            "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", 
            "South Georgia and the South Sandwich Islands", "South Korea", "Spain", "Sri Lanka", 
            "St. Helena", "St. Pierre and Miquelon", "Suriname", "Svalbard and Jan Mayen Islands", 
            "Swaziland", "Sweden", "Switzerland", "Taiwan", "Tajikistan", "Tanzania", "Thailand", 
            "Togo", "Tokelau", "Tonga", "Trinidad", "Trinidad and Tobago", "Tunisia", "Turkey", 
            "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", 
            "United Arab Emirates", "United Kingdom", "United States", 
            "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", 
            "Vatican City State (Holy See)", "Venezuela", "Viet Nam", "Virgin Islands (British)", 
            "Virgin Islands (U.S.)", "Wales", "Wallis and Futuna Islands", "Western Sahara", 
            "Yemen", "Zambia", "Zimbabwe" ] unless const_defined?("COUNTRIES")
    end

    class InstanceTag #:nodoc:
      include FormOptionsHelper

      def to_select_tag(choices, options, html_options)
        html_options = html_options.stringify_keys
        add_default_name_and_id(html_options)
        selected_value = options.has_key?(:selected) ? options[:selected] : value
        content_tag("select", add_options(options_for_select(choices, selected_value), options, value), html_options)
      end

      def to_collection_select_tag(collection, value_method, text_method, options, html_options)
        html_options = html_options.stringify_keys
        add_default_name_and_id(html_options)
        content_tag(
          "select", add_options(options_from_collection_for_select(collection, value_method, text_method, value), options, value), html_options
        )
      end

      def to_country_select_tag(priority_countries, options, html_options)
        html_options = html_options.stringify_keys
        add_default_name_and_id(html_options)
        content_tag("select", add_options(country_options_for_select(value, priority_countries), options, value), html_options)
      end

      def to_time_zone_select_tag(priority_zones, options, html_options)
        html_options = html_options.stringify_keys
        add_default_name_and_id(html_options)
        content_tag("select",
          add_options(
            time_zone_options_for_select(value, priority_zones, options[:model] || TimeZone),
            options, value
          ), html_options
        )
      end

      private
        def add_options(option_tags, options, value = nil)
          option_tags = "<option value=\"\"></option>\n" + option_tags if options[:include_blank]

          if value.blank? && options[:prompt]
            ("<option value=\"\">#{options[:prompt].kind_of?(String) ? options[:prompt] : 'Please select'}</option>\n") + option_tags
          else
            option_tags
          end
        end
    end

    class FormBuilder
      def select(method, choices, options = {}, html_options = {})
        @template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
      end

      def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
        @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
      end

      def country_select(method, priority_countries = nil, options = {}, html_options = {})
        @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
      end

      def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
        @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
      end
    end
  end
end
require 'cgi'
require File.dirname(__FILE__) + '/tag_helper'

module ActionView
  module Helpers
    # Provides a number of methods for creating form tags that doesn't rely on conventions with an object assigned to the template like
    # FormHelper does. With the FormTagHelper, you provide the names and values yourself.
    #
    # NOTE: The html options disabled, readonly, and multiple can all be treated as booleans. So specifying <tt>:disabled => true</tt>
    # will give <tt>disabled="disabled"</tt>.
    module FormTagHelper
      # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
      # ActionController::Base#url_for. The method for the form defaults to POST.
      #
      # Options:
      # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
      # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
      def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc)
        html_options = { "method" => "post" }.merge(options.stringify_keys)
        html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
        html_options["action"] = url_for(url_for_options, *parameters_for_url)
        tag :form, html_options, true
      end

      alias_method :start_form_tag, :form_tag

      # Outputs "</form>"
      def end_form_tag
        "</form>"
      end

      # Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
      # choice selection box.
      #
      # Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
      # associated records.
      #
      # <tt>option_tags</tt> is a string containing the option tags for the select box:
      #   # Outputs <select id="people" name="people"><option>David</option></select>
      #   select_tag "people", "<option>David</option>"
      #
      # Options:
      # * <tt>:multiple</tt> - If set to true the selection will allow multiple choices.
      def select_tag(name, option_tags = nil, options = {})
        content_tag :select, option_tags, { "name" => name, "id" => name }.update(options.stringify_keys)
      end

      # Creates a standard text field.
      #
      # Options:
      # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
      # * <tt>:size</tt> - The number of visible characters that will fit in the input.
      # * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
      # 
      # A hash of standard HTML options for the tag.
      def text_field_tag(name, value = nil, options = {})
        tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
      end

      # Creates a hidden field.
      #
      # Takes the same options as text_field_tag
      def hidden_field_tag(name, value = nil, options = {})
        text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
      end

      # Creates a file upload field.
      #
      # If you are using file uploads then you will also need to set the multipart option for the form:
      #   <%= form_tag { :action => "post" }, { :multipart => true } %>
      #     <label for="file">File to Upload</label> <%= file_field_tag "file" %>
      #     <%= submit_tag %>
      #   <%= end_form_tag %>
      #
      # The specified URL will then be passed a File object containing the selected file, or if the field 
      # was left blank, a StringIO object.
      def file_field_tag(name, options = {})
        text_field_tag(name, nil, options.update("type" => "file"))
      end

      # Creates a password field.
      #
      # Takes the same options as text_field_tag
      def password_field_tag(name = "password", value = nil, options = {})
        text_field_tag(name, value, options.update("type" => "password"))
      end

      # Creates a text input area.
      #
      # Options:
      # * <tt>:size</tt> - A string specifying the dimensions of the textarea.
      #     # Outputs <textarea name="body" id="body" cols="25" rows="10"></textarea>
      #     <%= text_area_tag "body", nil, :size => "25x10" %>
      def text_area_tag(name, content = nil, options = {})
        options.stringify_keys!

        if size = options.delete("size")
          options["cols"], options["rows"] = size.split("x")
        end

        content_tag :textarea, content, { "name" => name, "id" => name }.update(options.stringify_keys)
      end

      # Creates a check box.
      def check_box_tag(name, value = "1", checked = false, options = {})
        html_options = { "type" => "checkbox", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
        html_options["checked"] = "checked" if checked
        tag :input, html_options
      end

      # Creates a radio button.
      def radio_button_tag(name, value, checked = false, options = {})
        html_options = { "type" => "radio", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys)
        html_options["checked"] = "checked" if checked
        tag :input, html_options
      end

      # Creates a submit button with the text <tt>value</tt> as the caption. If options contains a pair with the key of "disable_with",
      # then the value will be used to rename a disabled version of the submit button.
      def submit_tag(value = "Save changes", options = {})
        options.stringify_keys!
        
        if disable_with = options.delete("disable_with")
          options["onclick"] = "this.disabled=true;this.value='#{disable_with}';this.form.submit();#{options["onclick"]}"
        end
          
        tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
      end
      
      # Displays an image which when clicked will submit the form.
      #
      # <tt>source</tt> is passed to AssetTagHelper#image_path
      def image_submit_tag(source, options = {})
        tag :input, { "type" => "image", "src" => image_path(source) }.update(options.stringify_keys)
      end
    end
  end
end
require File.dirname(__FILE__) + '/tag_helper'

module ActionView
  module Helpers
    # Provides a set of helpers for creating JavaScript macros that rely on and often bundle methods from JavaScriptHelper into
    # larger units. These macros also rely on counterparts in the controller that provide them with their backing. The in-place
    # editing relies on ActionController::Base.in_place_edit_for and the autocompletion relies on 
    # ActionController::Base.auto_complete_for.
    module JavaScriptMacrosHelper
      # Makes an HTML element specified by the DOM ID +field_id+ become an in-place
      # editor of a property.
      #
      # A form is automatically created and displayed when the user clicks the element,
      # something like this:
      #   <form id="myElement-in-place-edit-form" target="specified url">
      #     <input name="value" text="The content of myElement"/>
      #     <input type="submit" value="ok"/>
      #     <a onclick="javascript to cancel the editing">cancel</a>
      #   </form>
      # 
      # The form is serialized and sent to the server using an AJAX call, the action on
      # the server should process the value and return the updated value in the body of
      # the reponse. The element will automatically be updated with the changed value
      # (as returned from the server).
      # 
      # Required +options+ are:
      # <tt>:url</tt>::       Specifies the url where the updated value should
      #                       be sent after the user presses "ok".
      # 
      #
      # Addtional +options+ are:
      # <tt>:rows</tt>::              Number of rows (more than 1 will use a TEXTAREA)
      # <tt>:cols</tt>::              Number of characters the text input should span (works for both INPUT and TEXTAREA)
      # <tt>:size</tt>::              Synonym for :cols when using a single line text input.
      # <tt>:cancel_text</tt>::       The text on the cancel link. (default: "cancel")
      # <tt>:save_text</tt>::         The text on the save link. (default: "ok")
      # <tt>:loading_text</tt>::      The text to display when submitting to the server (default: "Saving...")
      # <tt>:external_control</tt>::  The id of an external control used to enter edit mode.
      # <tt>:load_text_url</tt>::     URL where initial value of editor (content) is retrieved.
      # <tt>:options</tt>::           Pass through options to the AJAX call (see prototype's Ajax.Updater)
      # <tt>:with</tt>::              JavaScript snippet that should return what is to be sent
      #                               in the AJAX call, +form+ is an implicit parameter
      # <tt>:script</tt>::            Instructs the in-place editor to evaluate the remote JavaScript response (default: false)
      def in_place_editor(field_id, options = {})
        function =  "new Ajax.InPlaceEditor("
        function << "'#{field_id}', "
        function << "'#{url_for(options[:url])}'"

        js_options = {}
        js_options['cancelText'] = %('#{options[:cancel_text]}') if options[:cancel_text]
        js_options['okText'] = %('#{options[:save_text]}') if options[:save_text]
        js_options['loadingText'] = %('#{options[:loading_text]}') if options[:loading_text]
        js_options['rows'] = options[:rows] if options[:rows]
        js_options['cols'] = options[:cols] if options[:cols]
        js_options['size'] = options[:size] if options[:size]
        js_options['externalControl'] = "'#{options[:external_control]}'" if options[:external_control]
        js_options['loadTextURL'] = "'#{url_for(options[:load_text_url])}'" if options[:load_text_url]        
        js_options['ajaxOptions'] = options[:options] if options[:options]
        js_options['evalScripts'] = options[:script] if options[:script]
        js_options['callback']   = "function(form) { return #{options[:with]} }" if options[:with]
        function << (', ' + options_for_javascript(js_options)) unless js_options.empty?
        
        function << ')'

        javascript_tag(function)
      end
      
      # Renders the value of the specified object and method with in-place editing capabilities.
      #
      # See the RDoc on ActionController::InPlaceEditing to learn more about this.
      def in_place_editor_field(object, method, tag_options = {}, in_place_editor_options = {})
        tag = ::ActionView::Helpers::InstanceTag.new(object, method, self)
        tag_options = {:tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field"}.merge!(tag_options)
        in_place_editor_options[:url] = in_place_editor_options[:url] || url_for({ :action => "set_#{object}_#{method}", :id => tag.object.id })
        tag.to_content_tag(tag_options.delete(:tag), tag_options) +
        in_place_editor(tag_options[:id], in_place_editor_options)
      end
      
      # Adds AJAX autocomplete functionality to the text input field with the 
      # DOM ID specified by +field_id+.
      #
      # This function expects that the called action returns a HTML <ul> list,
      # or nothing if no entries should be displayed for autocompletion.
      #
      # You'll probably want to turn the browser's built-in autocompletion off,
      # so be sure to include a autocomplete="off" attribute with your text
      # input field.
      #
      # The autocompleter object is assigned to a Javascript variable named <tt>field_id</tt>_auto_completer.
      # This object is useful if you for example want to trigger the auto-complete suggestions through
      # other means than user input (for that specific case, call the <tt>activate</tt> method on that object). 
      # 
      # Required +options+ are:
      # <tt>:url</tt>::       URL to call for autocompletion results
      #                       in url_for format.
      # 
      # Addtional +options+ are:
      # <tt>:update</tt>::    Specifies the DOM ID of the element whose 
      #                       innerHTML should be updated with the autocomplete
      #                       entries returned by the AJAX request. 
      #                       Defaults to field_id + '_auto_complete'
      # <tt>:with</tt>::      A JavaScript expression specifying the
      #                       parameters for the XMLHttpRequest. This defaults
      #                       to 'fieldname=value'.
      # <tt>:frequency</tt>:: Determines the time to wait after the last keystroke
      #                       for the AJAX request to be initiated.
      # <tt>:indicator</tt>:: Specifies the DOM ID of an element which will be
      #                       displayed while autocomplete is running.
      # <tt>:tokens</tt>::    A string or an array of strings containing
      #                       separator tokens for tokenized incremental 
      #                       autocompletion. Example: <tt>:tokens => ','</tt> would
      #                       allow multiple autocompletion entries, separated
      #                       by commas.
      # <tt>:min_chars</tt>:: The minimum number of characters that should be
      #                       in the input field before an Ajax call is made
      #                       to the server.
      # <tt>:on_hide</tt>::   A Javascript expression that is called when the
      #                       autocompletion div is hidden. The expression
      #                       should take two variables: element and update.
      #                       Element is a DOM element for the field, update
      #                       is a DOM element for the div from which the
      #                       innerHTML is replaced.
      # <tt>:on_show</tt>::   Like on_hide, only now the expression is called
      #                       then the div is shown.
      # <tt>:after_update_element</tt>::   A Javascript expression that is called when the
      #                                    user has selected one of the proposed values. 
      #                                    The expression should take two variables: element and value.
      #                                    Element is a DOM element for the field, value
      #                                    is the value selected by the user.
      # <tt>:select</tt>::    Pick the class of the element from which the value for 
      #                       insertion should be extracted. If this is not specified,
      #                       the entire element is used.
      def auto_complete_field(field_id, options = {})
        function =  "var #{field_id}_auto_completer = new Ajax.Autocompleter("
        function << "'#{field_id}', "
        function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', "
        function << "'#{url_for(options[:url])}'"
        
        js_options = {}
        js_options[:tokens] = array_or_string_for_javascript(options[:tokens]) if options[:tokens]
        js_options[:callback]   = "function(element, value) { return #{options[:with]} }" if options[:with]
        js_options[:indicator]  = "'#{options[:indicator]}'" if options[:indicator]
        js_options[:select]     = "'#{options[:select]}'" if options[:select]
        js_options[:frequency]  = "#{options[:frequency]}" if options[:frequency]

        { :after_update_element => :afterUpdateElement, 
          :on_show => :onShow, :on_hide => :onHide, :min_chars => :minChars }.each do |k,v|
          js_options[v] = options[k] if options[k]
        end

        function << (', ' + options_for_javascript(js_options) + ')')

        javascript_tag(function)
      end
      
      # Use this method in your view to generate a return for the AJAX autocomplete requests.
      #
      # Example action:
      #
      #   def auto_complete_for_item_title
      #     @items = Item.find(:all, 
      #       :conditions => [ 'LOWER(description) LIKE ?', 
      #       '%' + request.raw_post.downcase + '%' ])
      #     render :inline => '<%= auto_complete_result(@items, 'description') %>'
      #   end
      #
      # The auto_complete_result can of course also be called from a view belonging to the 
      # auto_complete action if you need to decorate it further.
      def auto_complete_result(entries, field, phrase = nil)
        return unless entries
        items = entries.map { |entry| content_tag("li", phrase ? highlight(entry[field], phrase) : h(entry[field])) }
        content_tag("ul", items.uniq)
      end
      
      # Wrapper for text_field with added AJAX autocompletion functionality.
      #
      # In your controller, you'll need to define an action called
      # auto_complete_for_object_method to respond the AJAX calls,
      # 
      # See the RDoc on ActionController::AutoComplete to learn more about this.
      def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {})
        (completion_options[:skip_style] ? "" : auto_complete_stylesheet) +
        text_field(object, method, tag_options) +
        content_tag("div", "", :id => "#{object}_#{method}_auto_complete", :class => "auto_complete") +
        auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options))
      end
      
      private
        def auto_complete_stylesheet
          content_tag("style", <<-EOT
            div.auto_complete {
              width: 350px;
              background: #fff;
            }
            div.auto_complete ul {
              border:1px solid #888;
              margin:0;
              padding:0;
              width:100%;
              list-style-type:none;
            }
            div.auto_complete ul li {
              margin:0;
              padding:3px;
            }
            div.auto_complete ul li.selected { 
              background-color: #ffb; 
            }
            div.auto_complete ul strong.highlight { 
              color: #800; 
              margin:0;
              padding:0;
            }
          EOT
          )
        end
      
    end
  end
end
require File.dirname(__FILE__) + '/tag_helper'

module ActionView
  module Helpers
    # Provides functionality for working with JavaScript in your views.
    # 
    # == Ajax, controls and visual effects
    # 
    # * For information on using Ajax, see 
    #   ActionView::Helpers::PrototypeHelper.
    # * For information on using controls and visual effects, see
    #   ActionView::Helpers::ScriptaculousHelper.
    #
    # == Including the JavaScript libraries into your pages
    #
    # Rails includes the Prototype JavaScript framework and the Scriptaculous
    # JavaScript controls and visual effects library.  If you wish to use
    # these libraries and their helpers (ActionView::Helpers::PrototypeHelper
    # and ActionView::Helpers::ScriptaculousHelper), you must do one of the
    # following:
    #
    # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD 
    #   section of your page (recommended): This function will return 
    #   references to the JavaScript files created by the +rails+ command in
    #   your <tt>public/javascripts</tt> directory. Using it is recommended as
    #   the browser can then cache the libraries instead of fetching all the 
    #   functions anew on every request.
    # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but 
    #   will only include the Prototype core library, which means you are able
    #   to use all basic AJAX functionality. For the Scriptaculous-based 
    #   JavaScript helpers, like visual effects, autocompletion, drag and drop 
    #   and so on, you should use the method described above.
    # * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
    #   JavaScript support functions within a single script block. Not
    #   recommended.
    #
    # For documentation on +javascript_include_tag+ see 
    # ActionView::Helpers::AssetTagHelper.
    module JavaScriptHelper
      unless const_defined? :JAVASCRIPT_PATH
        JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts')
      end
      
      # Returns a link that'll trigger a JavaScript +function+ using the 
      # onclick handler and return false after the fact.
      #
      # Examples:
      #   link_to_function "Greeting", "alert('Hello world!')"
      #   link_to_function(image_tag("delete"), "if confirm('Really?'){ do_delete(); }")
      def link_to_function(name, function, html_options = {})
        html_options.symbolize_keys!
        content_tag(
          "a", name, 
          html_options.merge({ 
            :href => html_options[:href] || "#", 
            :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;" 
          })
        )
      end
      
      # Returns a link that'll trigger a JavaScript +function+ using the 
      # onclick handler.
      #
      # Examples:
      #   button_to_function "Greeting", "alert('Hello world!')"
      #   button_to_function "Delete", "if confirm('Really?'){ do_delete(); }")
      def button_to_function(name, function, html_options = {})
        html_options.symbolize_keys!
        tag(:input, html_options.merge({ 
          :type => "button", :value => name, 
          :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};" 
        }))
      end

      # Includes the Action Pack JavaScript libraries inside a single <script> 
      # tag. The function first includes prototype.js and then its core extensions,
      # (determined by filenames starting with "prototype").
      # Afterwards, any additional scripts will be included in undefined order.
      #
      # Note: The recommended approach is to copy the contents of
      # lib/action_view/helpers/javascripts/ into your application's
      # public/javascripts/ directory, and use +javascript_include_tag+ to 
      # create remote <script> links.
      def define_javascript_functions
        javascript = '<script type="text/javascript">'
        
        # load prototype.js and its extensions first 
        prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
        prototype_libs.each do |filename| 
          javascript << "\n" << IO.read(filename)
        end
        
        # load other librairies
        (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename| 
          javascript << "\n" << IO.read(filename)
        end
        javascript << '</script>'
      end

      # Escape carrier returns and single and double quotes for JavaScript segments.
      def escape_javascript(javascript)
        (javascript || '').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
      end

      # Returns a JavaScript tag with the +content+ inside. Example:
      #   javascript_tag "alert('All is good')" # => <script type="text/javascript">alert('All is good')</script>
      def javascript_tag(content)
        content_tag("script", javascript_cdata_section(content), :type => "text/javascript")
      end

      def javascript_cdata_section(content) #:nodoc:
        "\n//#{cdata_section("\n#{content}\n//")}\n"
      end
      
    protected
      def options_for_javascript(options)
        '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
      end
      
      def array_or_string_for_javascript(option)
        js_option = if option.kind_of?(Array)
          "['#{option.join('\',\'')}']"
        elsif !option.nil?
          "'#{option}'"
        end
        js_option
      end
    end
    
    JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
  end
end
module ActionView
  module Helpers
    # Provides methods for converting a number into a formatted string that currently represents
    # one of the following forms: phone number, percentage, money, or precision level.
    module NumberHelper

      # Formats a +number+ into a US phone number string. The +options+ can be a hash used to customize the format of the output.
      # The area code can be surrounded by parentheses by setting +:area_code+ to true; default is false
      # The delimiter can be set using +:delimiter+; default is "-"
      # Examples:
      #   number_to_phone(1235551234)   => 123-555-1234
      #   number_to_phone(1235551234, {:area_code => true})   => (123) 555-1234
      #   number_to_phone(1235551234, {:delimiter => " "})    => 123 555 1234
      #   number_to_phone(1235551234, {:area_code => true, :extension => 555})  => (123) 555-1234 x 555
      def number_to_phone(number, options = {})
        options   = options.stringify_keys
        area_code = options.delete("area_code") { false }
        delimiter = options.delete("delimiter") { "-" }
        extension = options.delete("extension") { "" }
        begin
          str = area_code == true ? number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"(\\1) \\2#{delimiter}\\3") : number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"\\1#{delimiter}\\2#{delimiter}\\3")
          extension.to_s.strip.empty? ? str : "#{str} x #{extension.to_s.strip}"
        rescue
          number
        end
      end

      # Formats a +number+ into a currency string. The +options+ hash can be used to customize the format of the output.
      # The +number+ can contain a level of precision using the +precision+ key; default is 2
      # The currency type can be set using the +unit+ key; default is "$"
      # The unit separator can be set using the +separator+ key; default is "."
      # The delimiter can be set using the +delimiter+ key; default is ","
      # Examples:
      #    number_to_currency(1234567890.50)     => $1,234,567,890.50
      #    number_to_currency(1234567890.506)    => $1,234,567,890.51
      #    number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}) => &pound;1234567890,50
      def number_to_currency(number, options = {})
        options = options.stringify_keys
        precision, unit, separator, delimiter = options.delete("precision") { 2 }, options.delete("unit") { "$" }, options.delete("separator") { "." }, options.delete("delimiter") { "," }
        separator = "" unless precision > 0
        begin
          parts = number_with_precision(number, precision).split('.')
          unit + number_with_delimiter(parts[0], delimiter) + separator + parts[1].to_s
        rescue
          number
        end
      end

      # Formats a +number+ as into a percentage string. The +options+ hash can be used to customize the format of the output.
      # The +number+ can contain a level of precision using the +precision+ key; default is 3
      # The unit separator can be set using the +separator+ key; default is "."
      # Examples:
      #   number_to_percentage(100)    => 100.000%
      #   number_to_percentage(100, {:precision => 0}) => 100%
      #   number_to_percentage(302.0574, {:precision => 2})  => 302.06%
      def number_to_percentage(number, options = {})
        options = options.stringify_keys
        precision, separator = options.delete("precision") { 3 }, options.delete("separator") { "." }
        begin
          number = number_with_precision(number, precision)
          parts = number.split('.')
          if parts.at(1).nil?
            parts[0] + "%"
          else
            parts[0] + separator + parts[1].to_s + "%"
          end
        rescue
          number
        end
      end

      # Formats a +number+ with a +delimiter+.
      # Example:
      #    number_with_delimiter(12345678) => 12,345,678
      def number_with_delimiter(number, delimiter=",")
        number.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
      end

      # Returns a formatted-for-humans file size.
      # 
      # Examples:
      #   human_size(123)          => 123 Bytes
      #   human_size(1234)         => 1.2 KB
      #   human_size(12345)        => 12.1 KB
      #   human_size(1234567)      => 1.2 MB
      #   human_size(1234567890)   => 1.1 GB
      def number_to_human_size(size)
        case 
          when size < 1.kilobyte: '%d Bytes' % size
          when size < 1.megabyte: '%.1f KB'  % (size / 1.0.kilobyte)
          when size < 1.gigabyte: '%.1f MB'  % (size / 1.0.megabyte)
          when size < 1.terabyte: '%.1f GB'  % (size / 1.0.gigabyte)
          else                    '%.1f TB'  % (size / 1.0.terabyte)
        end.sub('.0', '')
      rescue
        nil
      end
      
      alias_method :human_size, :number_to_human_size # deprecated alias

      # Formats a +number+ with a level of +precision+.
      # Example:
      #    number_with_precision(111.2345) => 111.235
      def number_with_precision(number, precision=3)
        sprintf("%01.#{precision}f", number)
      end
    end
  end
end
module ActionView
  module Helpers
    # Provides methods for linking to ActionController::Pagination objects.
    #
    # You can also build your links manually, like in this example:
    #
    # <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %>
    #
    # <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %>
    module PaginationHelper
      unless const_defined?(:DEFAULT_OPTIONS)
        DEFAULT_OPTIONS = {
          :name => :page,
          :window_size => 2,
          :always_show_anchors => true,
          :link_to_current_page => false,
          :params => {}
        }
      end

      # Creates a basic HTML link bar for the given +paginator+.
      # +html_options+ are passed to +link_to+.
      #
      # +options+ are:
      # <tt>:name</tt>::                 the routing name for this paginator
      #                                  (defaults to +page+)
      # <tt>:window_size</tt>::          the number of pages to show around 
      #                                  the current page (defaults to +2+)
      # <tt>:always_show_anchors</tt>::  whether or not the first and last
      #                                  pages should always be shown
      #                                  (defaults to +true+)
      # <tt>:link_to_current_page</tt>:: whether or not the current page
      #                                  should be linked to (defaults to
      #                                  +false+)
      # <tt>:params</tt>::               any additional routing parameters
      #                                  for page URLs
      def pagination_links(paginator, options={}, html_options={})
        name = options[:name] || DEFAULT_OPTIONS[:name]
        params = (options[:params] || DEFAULT_OPTIONS[:params]).clone
        
        pagination_links_each(paginator, options) do |n|
          params[name] = n
          link_to(n.to_s, params, html_options)
        end
      end

      # Iterate through the pages of a given +paginator+, invoking a
      # block for each page number that needs to be rendered as a link.
      def pagination_links_each(paginator, options)
        options = DEFAULT_OPTIONS.merge(options)
        link_to_current_page = options[:link_to_current_page]
        always_show_anchors = options[:always_show_anchors]

        current_page = paginator.current_page
        window_pages = current_page.window(options[:window_size]).pages
        return if window_pages.length <= 1 unless link_to_current_page
        
        first, last = paginator.first, paginator.last
        
        html = ''
        if always_show_anchors and not (wp_first = window_pages[0]).first?
          html << yield(first.number)
          html << ' ... ' if wp_first.number - first.number > 1
          html << ' '
        end
          
        window_pages.each do |page|
          if current_page == page && !link_to_current_page
            html << page.number.to_s
          else
            html << yield(page.number)
          end
          html << ' '
        end
        
        if always_show_anchors and not (wp_last = window_pages[-1]).last? 
          html << ' ... ' if last.number - wp_last.number > 1
          html << yield(last.number)
        end
        
        html
      end
      
    end # PaginationHelper
  end # Helpers
end # ActionView
require File.dirname(__FILE__) + '/javascript_helper'
require 'set'

module ActionView
  module Helpers
    # Provides a set of helpers for calling Prototype JavaScript functions, 
    # including functionality to call remote methods using 
    # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]. 
    # This means that you can call actions in your controllers without 
    # reloading the page, but still update certain parts of it using 
    # injections into the DOM. The common use case is having a form that adds
    # a new element to a list without reloading the page.
    #
    # To be able to use these helpers, you must include the Prototype 
    # JavaScript framework in your pages. See the documentation for 
    # ActionView::Helpers::JavaScriptHelper for more information on including 
    # the necessary JavaScript.
    #
    # See link_to_remote for documentation of options common to all Ajax
    # helpers.
    #
    # See also ActionView::Helpers::ScriptaculousHelper for helpers which work
    # with the Scriptaculous controls and visual effects library.
    #
    # See JavaScriptGenerator for information on updating multiple elements
    # on the page in an Ajax response. 
    module PrototypeHelper
      unless const_defined? :CALLBACKS
        CALLBACKS    = Set.new([ :uninitialized, :loading, :loaded,
                         :interactive, :complete, :failure, :success ] +
                         (100..599).to_a)
        AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
                         :asynchronous, :method, :insertion, :position,
                         :form, :with, :update, :script ]).merge(CALLBACKS)
      end

      # Returns a link to a remote action defined by <tt>options[:url]</tt> 
      # (using the url_for format) that's called in the background using 
      # XMLHttpRequest. The result of that request can then be inserted into a
      # DOM object whose id can be specified with <tt>options[:update]</tt>. 
      # Usually, the result would be a partial prepared by the controller with
      # either render_partial or render_partial_collection. 
      #
      # Examples:
      #   link_to_remote "Delete this post", :update => "posts", 
      #     :url => { :action => "destroy", :id => post.id }
      #   link_to_remote(image_tag("refresh"), :update => "emails", 
      #     :url => { :action => "list_emails" })
      #
      # You can also specify a hash for <tt>options[:update]</tt> to allow for
      # easy redirection of output to an other DOM element if a server-side 
      # error occurs:
      #
      # Example:
      #   link_to_remote "Delete this post",
      #     :url => { :action => "destroy", :id => post.id },
      #     :update => { :success => "posts", :failure => "error" }
      #
      # Optionally, you can use the <tt>options[:position]</tt> parameter to 
      # influence how the target DOM element is updated. It must be one of 
      # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
      #
      # By default, these remote requests are processed asynchronous during 
      # which various JavaScript callbacks can be triggered (for progress 
      # indicators and the likes). All callbacks get access to the 
      # <tt>request</tt> object, which holds the underlying XMLHttpRequest. 
      #
      # To access the server response, use <tt>request.responseText</tt>, to
      # find out the HTTP status, use <tt>request.status</tt>.
      #
      # Example:
      #   link_to_remote word,
      #     :url => { :action => "undo", :n => word_counter },
      #     :complete => "undoRequestCompleted(request)"
      #
      # The callbacks that may be specified are (in order):
      #
      # <tt>:loading</tt>::       Called when the remote document is being 
      #                           loaded with data by the browser.
      # <tt>:loaded</tt>::        Called when the browser has finished loading
      #                           the remote document.
      # <tt>:interactive</tt>::   Called when the user can interact with the 
      #                           remote document, even though it has not 
      #                           finished loading.
      # <tt>:success</tt>::       Called when the XMLHttpRequest is completed,
      #                           and the HTTP status code is in the 2XX range.
      # <tt>:failure</tt>::       Called when the XMLHttpRequest is completed,
      #                           and the HTTP status code is not in the 2XX
      #                           range.
      # <tt>:complete</tt>::      Called when the XMLHttpRequest is complete 
      #                           (fires after success/failure if they are 
      #                           present).
      #                     
      # You can further refine <tt>:success</tt> and <tt>:failure</tt> by 
      # adding additional callbacks for specific status codes.
      #
      # Example:
      #   link_to_remote word,
      #     :url => { :action => "action" },
      #     404 => "alert('Not found...? Wrong URL...?')",
      #     :failure => "alert('HTTP Error ' + request.status + '!')"
      #
      # A status code callback overrides the success/failure handlers if 
      # present.
      #
      # If you for some reason or another need synchronous processing (that'll
      # block the browser while the request is happening), you can specify 
      # <tt>options[:type] = :synchronous</tt>.
      #
      # You can customize further browser side call logic by passing in
      # JavaScript code snippets via some optional parameters. In their order 
      # of use these are:
      #
      # <tt>:confirm</tt>::      Adds confirmation dialog.
      # <tt>:condition</tt>::    Perform remote request conditionally
      #                          by this expression. Use this to
      #                          describe browser-side conditions when
      #                          request should not be initiated.
      # <tt>:before</tt>::       Called before request is initiated.
      # <tt>:after</tt>::        Called immediately after request was
      #                          initiated and before <tt>:loading</tt>.
      # <tt>:submit</tt>::       Specifies the DOM element ID that's used
      #                          as the parent of the form elements. By 
      #                          default this is the current form, but
      #                          it could just as well be the ID of a
      #                          table row or any other DOM element.
      def link_to_remote(name, options = {}, html_options = {})  
        link_to_function(name, remote_function(options), html_options)
      end

      # Periodically calls the specified url (<tt>options[:url]</tt>) every 
      # <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
      # update a specified div (<tt>options[:update]</tt>) 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.
      def periodically_call_remote(options = {})
         frequency = options[:frequency] || 10 # every ten seconds by default
         code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
         javascript_tag(code)
      end

      # Returns a form tag that will submit using XMLHttpRequest in the 
      # background instead of the regular reloading POST arrangement. Even 
      # 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 <tt>params</tt>). The options for 
      # 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.
      #
      # Example:
      #   form_remote_tag :html => { :action => 
      #     url_for(:controller => "some", :action => "place") }
      #
      # 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).
      def form_remote_tag(options = {})
        options[:form] = true

        options[:html] ||= {}
        options[:html][:onsubmit] = "#{remote_function(options)}; return false;"
        options[:html][:action] = options[:html][:action] || url_for(options[:url])
        options[:html][:method] = options[:html][:method] || "post"

        tag("form", options[:html], true)
      end

      # Works like form_remote_tag, but uses form_for semantics.
      def remote_form_for(object_name, *args, &proc)
        options = args.last.is_a?(Hash) ? args.pop : {}
        concat(form_remote_tag(options), proc.binding)
        fields_for(object_name, *(args << options), &proc)
        concat('</form>', proc.binding)
      end
      alias_method :form_remote_for, :remote_form_for
      
      # Returns a button input tag that will submit form using XMLHttpRequest 
      # in the background instead of regular reloading POST arrangement. 
      # <tt>options</tt> argument is the same as in <tt>form_remote_tag</tt>.
      def submit_to_remote(name, value, options = {})
        options[:with] ||= 'Form.serialize(this.form)'

        options[:html] ||= {}
        options[:html][:type] = 'button'
        options[:html][:onclick] = "#{remote_function(options)}; return false;"
        options[:html][:name] = name
        options[:html][:value] = value

        tag("input", options[:html], false)
      end
      
      # Returns a JavaScript function (or expression) that'll update a DOM 
      # element according to the options passed.
      #
      # * <tt>:content</tt>: The content to use for updating. Can be left out
      #   if using block, see example.
      # * <tt>:action</tt>: Valid options are :update (assumed by default), 
      #   :empty, :remove
      # * <tt>:position</tt> If the :action is :update, you can optionally 
      #   specify one of the following positions: :before, :top, :bottom, 
      #   :after.
      #
      # Examples:
      #   <%= javascript_tag(update_element_function("products", 
      #     :position => :bottom, :content => "<p>New product!</p>")) %>
      #
      #   <% replacement_function = update_element_function("products") do %>
      #     <p>Product 1</p>
      #     <p>Product 2</p>
      #   <% end %>
      #   <%= javascript_tag(replacement_function) %>
      #
      # This method can also be used in combination with remote method call 
      # where the result is evaluated afterwards to cause multiple updates on
      # a page. Example:
      #
      #   # Calling view
      #   <%= form_remote_tag :url => { :action => "buy" }, 
      #     :complete => evaluate_remote_response %>
      #   all the inputs here...
      #
      #   # Controller action
      #   def buy
      #     @product = Product.find(1)
      #   end
      #
      #   # Returning view
      #   <%= update_element_function(
      #         "cart", :action => :update, :position => :bottom, 
      #         :content => "<p>New Product: #{@product.name}</p>")) %>
      #   <% update_element_function("status", :binding => binding) do %>
      #     You've bought a new product!
      #   <% end %>
      #
      # Notice how the second call doesn't need to be in an ERb output block
      # since it uses a block and passes in the binding to render directly. 
      # This trick will however only work in ERb (not Builder or other 
      # template forms).
      #
      # See also JavaScriptGenerator and update_page.
      def update_element_function(element_id, options = {}, &block)
        content = escape_javascript(options[:content] || '')
        content = escape_javascript(capture(&block)) if block
        
        javascript_function = case (options[:action] || :update)
          when :update
            if options[:position]
              "new Insertion.#{options[:position].to_s.camelize}('#{element_id}','#{content}')"
            else
              "$('#{element_id}').innerHTML = '#{content}'"
            end
          
          when :empty
            "$('#{element_id}').innerHTML = ''"
          
          when :remove
            "Element.remove('#{element_id}')"
          
          else
            raise ArgumentError, "Invalid action, choose one of :update, :remove, :empty"
        end
        
        javascript_function << ";\n"
        options[:binding] ? concat(javascript_function, options[:binding]) : javascript_function
      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.
      def evaluate_remote_response
        "eval(request.responseText)"
      end

      # Returns the JavaScript needed for a remote function.
      # Takes the same arguments as link_to_remote.
      # 
      # Example:
      #   <select id="options" onchange="<%= remote_function(:update => "options", 
      #       :url => { :action => :update_options }) %>">
      #     <option value="0">Hello</option>
      #     <option value="1">World</option>
      #   </select>
      def remote_function(options)
        javascript_options = options_for_ajax(options)

        update = ''
        if options[:update] and options[:update].is_a?Hash
          update  = []
          update << "success:'#{options[:update][:success]}'" if options[:update][:success]
          update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
          update  = '{' + update.join(',') + '}'
        elsif options[:update]
          update << "'#{options[:update]}'"
        end

        function = update.empty? ? 
          "new Ajax.Request(" :
          "new Ajax.Updater(#{update}, "

        url_options = options[:url]
        url_options = url_options.merge(:escape => false) if url_options.is_a? Hash
        function << "'#{url_for(url_options)}'"
        function << ", #{javascript_options})"

        function = "#{options[:before]}; #{function}" if options[:before]
        function = "#{function}; #{options[:after]}"  if options[:after]
        function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
        function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]

        return function
      end

      # Observes the field with the DOM ID specified by +field_id+ and makes
      # an Ajax call when its contents have changed.
      # 
      # Required +options+ are either of:
      # <tt>:url</tt>::       +url_for+-style options for the action to call
      #                       when the field has changed.
      # <tt>:function</tt>::  Instead of making a remote call to a URL, you
      #                       can specify a function to be called instead.
      # 
      # Additional options are:
      # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
      #                       this field will be detected. Not setting this
      #                       option at all or to a value equal to or less than
      #                       zero will use event based observation instead of
      #                       time based observation.
      # <tt>:update</tt>::    Specifies the DOM ID of the element whose 
      #                       innerHTML should be updated with the
      #                       XMLHttpRequest response text.
      # <tt>:with</tt>::      A JavaScript expression specifying the
      #                       parameters for the XMLHttpRequest. This defaults
      #                       to 'value', which in the evaluated context 
      #                       refers to the new field value. If you specify a
      #                       string without a "=", it'll be extended to mean
      #                       the form key that the value should be assigned to.
      #                       So :with => "term" gives "'term'=value". If a "=" is
      #                       present, no extension will happen.
      # <tt>:on</tt>::        Specifies which event handler to observe. By default,
      #                       it's set to "changed" for text fields and areas and
      #                       "click" for radio buttons and checkboxes. With this,
      #                       you can specify it instead to be "blur" or "focus" or
      #                       any other event.
      #
      # Additionally, you may specify any of the options documented in
      # link_to_remote.
      def observe_field(field_id, options = {})
        if options[:frequency] && options[:frequency] > 0
          build_observer('Form.Element.Observer', field_id, options)
        else
          build_observer('Form.Element.EventObserver', field_id, options)
        end
      end
      
      # Like +observe_field+, but operates on an entire form identified by the
      # DOM ID +form_id+. +options+ are the same as +observe_field+, except 
      # the default value of the <tt>:with</tt> option evaluates to the
      # serialized (request string) value of the form.
      def observe_form(form_id, options = {})
        if options[:frequency]
          build_observer('Form.Observer', form_id, options)
        else
          build_observer('Form.EventObserver', form_id, options)
        end
      end
      
      # All the methods were moved to GeneratorMethods so that 
      # #include_helpers_from_context has nothing to overwrite.
      class JavaScriptGenerator #:nodoc:
        def initialize(context, &block) #:nodoc:
          @context, @lines = context, []
          include_helpers_from_context
          @context.instance_exec(self, &block)
        end
      
        private
          def include_helpers_from_context
            @context.extended_by.each do |mod|
              extend mod unless mod.name =~ /^ActionView::Helpers/
            end
            extend GeneratorMethods
          end
      
        # JavaScriptGenerator generates blocks of JavaScript code that allow you 
        # to change the content and presentation of multiple DOM elements.  Use 
        # this in your Ajax response bodies, either in a <script> tag or as plain
        # 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 
        # methods on the yielded generator in any order you like to modify the 
        # content and appearance of the current page. 
        #
        # Example:
        #
        #   update_page do |page|
        #     page.insert_html :bottom, 'list', "<li>#{@item.name}</li>"
        #     page.visual_effect :highlight, 'list'
        #     page.hide 'status-indicator', 'cancel-link'
        #   end
        # 
        # generates the following JavaScript:
        #
        #   new Insertion.Bottom("list", "<li>Some item</li>");
        #   new Effect.Highlight("list");
        #   ["status-indicator", "cancel-link"].each(Element.hide);
        #
        # Helper methods can be used in conjunction with JavaScriptGenerator.
        # When a helper method is called inside an update block on the +page+ 
        # object, that method will also have access to a +page+ object.
        # 
        # Example:
        #
        #   module ApplicationHelper
        #     def update_time
        #       page.replace_html 'time', Time.now.to_s(:db)
        #       page.visual_effect :highlight, 'time'
        #     end
        #   end
        #
        #   # Controller action
        #   def poll
        #     render(:update) { |page| page.update_time }
        #   end
        #
        # You can also use PrototypeHelper#update_page_tag instead of 
        # PrototypeHelper#update_page to wrap the generated JavaScript in a
        # <script> tag.
        module GeneratorMethods
          def to_s #:nodoc:
            returning javascript = @lines * $/ do
              if ActionView::Base.debug_rjs
                source = javascript.dup
                javascript.replace "try {\n#{source}\n} catch (e) "
                javascript << "{ alert('RJS error:\\n\\n' + e.toString()); alert('#{source.gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }}'); throw e }"
              end
            end
          end
          
          # Returns a element reference by finding it through +id+ in the DOM. This element can then be
          # used for further method calls. Examples:
          #
          #   page['blank_slate']                  # => $('blank_slate');
          #   page['blank_slate'].show             # => $('blank_slate').show();
          #   page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
          def [](id)
            JavaScriptElementProxy.new(self, id)
          end
          
          # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
          # used for further method calls. Examples:
          #
          #   page.select('p')                      # => $$('p');
          #   page.select('p.welcome b').first      # => $$('p.welcome b').first();
          #   page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
          # 
          # You can also use prototype enumerations with the collection.  Observe:
          # 
          #   page.select('#items li').each do |value|
          #     value.hide
          #   end 
          #   # => $$('#items li').each(function(value) { value.hide(); });
          #
          # Though you can call the block param anything you want, they are always rendered in the 
          # javascript as 'value, index.'  Other enumerations, like collect() return the last statement:
          # 
          #   page.select('#items li').collect('hidden') do |item|
          #     item.hide
          #   end
          #   # => var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
          def select(pattern)
            JavaScriptElementCollectionProxy.new(self, pattern)
          end
          
          # Inserts HTML at the specified +position+ relative to the DOM element
          # identified by the given +id+.
          # 
          # +position+ may be one of:
          # 
          # <tt>:top</tt>::    HTML is inserted inside the element, before the 
          #                    element's existing content.
          # <tt>:bottom</tt>:: HTML is inserted inside the element, after the
          #                    element's existing content.
          # <tt>:before</tt>:: HTML is inserted immediately preceeding the element.
          # <tt>:after</tt>::  HTML is inserted immediately following the element.
          #
          # +options_for_render+ may be either a string of HTML to insert, or a hash
          # of options to be passed to ActionView::Base#render.  For example:
          #
          #   # Insert the rendered 'navigation' partial just before the DOM
          #   # element with ID 'content'.
          #   insert_html :before, 'content', :partial => 'navigation'
          #
          #   # Add a list item to the bottom of the <ul> with ID 'list'.
          #   insert_html :bottom, 'list', '<li>Last item</li>'
          #
          def insert_html(position, id, *options_for_render)
            insertion = position.to_s.camelize
            call "new Insertion.#{insertion}", id, render(*options_for_render)
          end
          
          # Replaces the inner HTML of the DOM element with the given +id+.
          #
          # +options_for_render+ may be either a string of HTML to insert, or a hash
          # of options to be passed to ActionView::Base#render.  For example:
          #
          #   # Replace the HTML of the DOM element having ID 'person-45' with the
          #   # 'person' partial for the appropriate object.
          #   replace_html 'person-45', :partial => 'person', :object => @person
          #
          def replace_html(id, *options_for_render)
            call 'Element.update', id, render(*options_for_render)
          end
          
          # Replaces the "outer HTML" (i.e., the entire element, not just its
          # contents) of the DOM element with the given +id+.
          #
          # +options_for_render+ may be either a string of HTML to insert, or a hash
          # of options to be passed to ActionView::Base#render.  For example:
          #
          #   # Replace the DOM element having ID 'person-45' with the
          #   # 'person' partial for the appropriate object.
          #   replace_html 'person-45', :partial => 'person', :object => @person
          #
          # This allows the same partial that is used for the +insert_html+ to
          # be also used for the input to +replace+ without resorting to
          # the use of wrapper elements.
          #
          # Examples:
          #
          #   <div id="people">
          #     <%= render :partial => 'person', :collection => @people %>
          #   </div>
          #
          #   # Insert a new person
          #   page.insert_html :bottom, :partial => 'person', :object => @person
          #
          #   # Replace an existing person
          #   page.replace 'person_45', :partial => 'person', :object => @person
          #
          def replace(id, *options_for_render)
            call 'Element.replace', id, render(*options_for_render)
          end
          
          # Removes the DOM elements with the given +ids+ from the page.
          def remove(*ids)
            record "#{javascript_object_for(ids)}.each(Element.remove)"
          end
          
          # Shows hidden DOM elements with the given +ids+.
          def show(*ids)
            call 'Element.show', *ids
          end
          
          # Hides the visible DOM elements with the given +ids+.
          def hide(*ids)
            call 'Element.hide', *ids
          end
          
          # Toggles the visibility of the DOM elements with the given +ids+.
          def toggle(*ids)
            call 'Element.toggle', *ids
          end
          
          # Displays an alert dialog with the given +message+.
          def alert(message)
            call 'alert', message
          end
          
          # Redirects the browser to the given +location+, in the same form as
          # +url_for+.
          def redirect_to(location)
            assign 'window.location.href', @context.url_for(location)
          end
          
          # Calls the JavaScript +function+, optionally with the given 
          # +arguments+.
          def call(function, *arguments)
            record "#{function}(#{arguments_for_call(arguments)})"
          end
          
          # Assigns the JavaScript +variable+ the given +value+.
          def assign(variable, value)
            record "#{variable} = #{javascript_object_for(value)}"
          end
          
          # Writes raw JavaScript to the page.
          def <<(javascript)
            @lines << javascript
          end
          
          # Executes the content of the block after a delay of +seconds+. Example:
          #
          #   page.delay(20) do
          #     page.visual_effect :fade, 'notice'
          #   end
          def delay(seconds = 1)
            record "setTimeout(function() {\n\n"
            yield
            record "}, #{(seconds * 1000).to_i})"
          end
          
          # Starts a script.aculo.us visual effect. See 
          # ActionView::Helpers::ScriptaculousHelper for more information.
          def visual_effect(name, id = nil, options = {})
            record @context.send(:visual_effect, name, id, options)
          end
          
          # Creates a script.aculo.us sortable element. Useful
          # to recreate sortable elements after items get added
          # or deleted.
          # See ActionView::Helpers::ScriptaculousHelper for more information.
          def sortable(id, options = {})
            record @context.send(:sortable_element_js, id, options)
          end
          
          # Creates a script.aculo.us draggable element.
          # See ActionView::Helpers::ScriptaculousHelper for more information.
          def draggable(id, options = {})
            record @context.send(:draggable_element_js, id, options)
          end
          
          # Creates a script.aculo.us drop receiving element.
          # See ActionView::Helpers::ScriptaculousHelper for more information.
          def drop_receiving(id, options = {})
            record @context.send(:drop_receiving_element_js, id, options)
          end
          
          private
            def page
              self
            end
          
            def record(line)
              returning line = "#{line.to_s.chomp.gsub /\;$/, ''};" do
                self << line
              end
            end
          
            def render(*options_for_render)
              Hash === options_for_render.first ? 
                @context.render(*options_for_render) : 
                  options_for_render.first.to_s
            end
          
            def javascript_object_for(object)
              object.respond_to?(:to_json) ? object.to_json : object.inspect
            end
          
            def arguments_for_call(arguments)
              arguments.map { |argument| javascript_object_for(argument) }.join ', '
            end
            
            def method_missing(method, *arguments)
              JavaScriptProxy.new(self, method.to_s.camelize)
            end
        end
      end
      
      # Yields a JavaScriptGenerator and returns the generated JavaScript code.
      # Use this to update multiple elements on a page in an Ajax response.
      # See JavaScriptGenerator for more information.
      def update_page(&block)
        JavaScriptGenerator.new(@template, &block).to_s
      end
      
      # Works like update_page but wraps the generated JavaScript in a <script>
      # tag. Use this to include generated JavaScript in an ERb template.
      # See JavaScriptGenerator for more information.
      def update_page_tag(&block)
        javascript_tag update_page(&block)
      end

    protected
      def options_for_ajax(options)
        js_options = build_callbacks(options)
      
        js_options['asynchronous'] = options[:type] != :synchronous
        js_options['method']       = method_option_to_s(options[:method]) if options[:method]
        js_options['insertion']    = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
        js_options['evalScripts']  = options[:script].nil? || options[:script]

        if options[:form]
          js_options['parameters'] = 'Form.serialize(this)'
        elsif options[:submit]
          js_options['parameters'] = "Form.serialize('#{options[:submit]}')"
        elsif options[:with]
          js_options['parameters'] = options[:with]
        end
      
        options_for_javascript(js_options)
      end

      def method_option_to_s(method) 
        (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
      end
    
      def build_observer(klass, name, options = {})
        if options[:with] && !options[:with].include?("=")
          options[:with] = "'#{options[:with]}=' + value"
        else
          options[:with] ||= 'value' if options[:update]
        end

        callback = options[:function] || remote_function(options)
        javascript  = "new #{klass}('#{name}', "
        javascript << "#{options[:frequency]}, " if options[:frequency]
        javascript << "function(element, value) {"
        javascript << "#{callback}}"
        javascript << ", '#{options[:on]}'" if options[:on]
        javascript << ")"
        javascript_tag(javascript)
      end
          
      def build_callbacks(options)
        callbacks = {}
        options.each do |callback, code|
          if CALLBACKS.include?(callback)
            name = 'on' + callback.to_s.capitalize
            callbacks[name] = "function(request){#{code}}"
          end
        end
        callbacks
      end
    end

    # Converts chained method calls on DOM proxy elements into JavaScript chains 
    class JavaScriptProxy < Builder::BlankSlate #:nodoc:
      def initialize(generator, root = nil)
        @generator = generator
        @generator << root if root
      end

      private
        def method_missing(method, *arguments)
          if method.to_s =~ /(.*)=$/
            assign($1, arguments.first)
          else
            call("#{method.to_s.camelize(:lower)}", *arguments)
          end
        end
      
        def call(function, *arguments)
          append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments)})")
          self
        end

        def assign(variable, value)
          append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
        end
        
        def function_chain
          @function_chain ||= @generator.instance_variable_get("@lines")
        end
        
        def append_to_function_chain!(call)
          function_chain[-1].chomp!(';')
          function_chain[-1] += ".#{call};"
        end
    end
    
    class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
      def initialize(generator, id)
        @id = id
        super(generator, "$(#{id.to_json})")
      end
      
      def replace_html(*options_for_render)
        call 'update', @generator.send(:render, *options_for_render)
      end

      def replace(*options_for_render)
        call 'replace', @generator.send(:render, *options_for_render)
      end
      
      def reload
        replace :partial => @id.to_s
      end
      
    end

    class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
      def initialize(generator, variable)
        @variable = variable
        @empty    = true # only record lines if we have to.  gets rid of unnecessary linebreaks
        super(generator)
      end

      # The JSON Encoder calls this to check for the #to_json method
      # Since it's a blank slate object, I suppose it responds to anything.
      def respond_to?(method)
        true
      end

      def to_json
        @variable
      end
      
      private
        def append_to_function_chain!(call)
          @generator << @variable if @empty
          @empty = false
          super
        end
    end

    class JavaScriptCollectionProxy < JavaScriptProxy #:nodoc:
      ENUMERABLE_METHODS_WITH_RETURN = [:all, :any, :collect, :map, :detect, :find, :find_all, :select, :max, :min, :partition, :reject, :sort_by]
      ENUMERABLE_METHODS = ENUMERABLE_METHODS_WITH_RETURN + [:each]
      attr_reader :generator
      delegate :arguments_for_call, :to => :generator

      def initialize(generator, pattern)
        super(generator, @pattern = pattern)
      end

      def grep(variable, pattern, &block)
        enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
      end

      def inject(variable, memo, &block)
        enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
      end

      def pluck(variable, property)
        add_variable_assignment!(variable)
        append_enumerable_function!("pluck(#{property.to_json});")
      end

      def zip(variable, *arguments, &block)
        add_variable_assignment!(variable)
        append_enumerable_function!("zip(#{arguments_for_call arguments}")
        if block
          function_chain[-1] += ", function(array) {"
          yield ActiveSupport::JSON::Variable.new('array')
          add_return_statement!
          @generator << '});'
        else
          function_chain[-1] += ');'
        end
      end

      private
        def method_missing(method, *arguments, &block)
          if ENUMERABLE_METHODS.include?(method)
            returnable = ENUMERABLE_METHODS_WITH_RETURN.include?(method)
            variable   = arguments.first if returnable
            enumerate(method, {:variable => (arguments.first if returnable), :return => returnable, :yield_args => %w(value index)}, &block)
          else
            super
          end
        end

        # Options
        #   * variable - name of the variable to set the result of the enumeration to
        #   * method_args - array of the javascript enumeration method args that occur before the function
        #   * yield_args - array of the javascript yield args
        #   * return - true if the enumeration should return the last statement
        def enumerate(enumerable, options = {}, &block)
          options[:method_args] ||= []
          options[:yield_args]  ||= []
          yield_args  = options[:yield_args] * ', '
          method_args = arguments_for_call options[:method_args] # foo, bar, function
          method_args << ', ' unless method_args.blank?
          add_variable_assignment!(options[:variable]) if options[:variable]
          append_enumerable_function!("#{enumerable.to_s.camelize(:lower)}(#{method_args}function(#{yield_args}) {")
          # only yield as many params as were passed in the block
          yield *options[:yield_args].collect { |p| JavaScriptVariableProxy.new(@generator, p) }[0..block.arity-1]
          add_return_statement! if options[:return]
          @generator << '});'
        end

        def add_variable_assignment!(variable)
          function_chain.push("var #{variable} = #{function_chain.pop}")
        end

        def add_return_statement!
          unless function_chain.last =~ /return/
            function_chain.push("return #{function_chain.pop.chomp(';')};")
          end
        end
        
        def append_enumerable_function!(call)
          function_chain[-1].chomp!(';')
          function_chain[-1] += ".#{call}"
        end
    end
    
    class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
      def initialize(generator, pattern)
        super(generator, "$$(#{pattern.to_json})")
      end
    end
  end
end
require File.dirname(__FILE__) + '/javascript_helper'

module ActionView
  module Helpers
    # Provides a set of helpers for calling Scriptaculous JavaScript 
    # functions, including those which create Ajax controls and visual effects.
    #
    # To be able to use these helpers, you must include the Prototype 
    # JavaScript framework and the Scriptaculous JavaScript library in your 
    # pages. See the documentation for ActionView::Helpers::JavaScriptHelper
    # for more information on including the necessary JavaScript.
    #
    # The Scriptaculous helpers' behavior can be tweaked with various options.
    # See the documentation at http://script.aculo.us for more information on
    # using these helpers in your application.
    module ScriptaculousHelper
      unless const_defined? :TOGGLE_EFFECTS
        TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
      end
      
      # Returns a JavaScript snippet to be used on the Ajax callbacks for
      # starting visual effects.
      #
      # Example:
      #   <%= link_to_remote "Reload", :update => "posts", 
      #         :url => { :action => "reload" }, 
      #         :complete => visual_effect(:highlight, "posts", :duration => 0.5)
      #
      # If no element_id is given, it assumes "element" which should be a local
      # variable in the generated JavaScript execution context. This can be 
      # used for example with drop_receiving_element:
      #
      #   <%= drop_receving_element (...), :loading => visual_effect(:fade) %>
      #
      # This would fade the element that was dropped on the drop receiving 
      # element.
      #
      # For toggling visual effects, you can use :toggle_appear, :toggle_slide, and
      # :toggle_blind which will alternate between appear/fade, slidedown/slideup, and
      # blinddown/blindup respectively.
      #
      # You can change the behaviour with various options, see
      # http://script.aculo.us for more documentation.
      def visual_effect(name, element_id = false, js_options = {})
        element = element_id ? element_id.to_json : "element"
        
        js_options[:queue] = if js_options[:queue].is_a?(Hash)
          '{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
        elsif js_options[:queue]
          "'#{js_options[:queue]}'"
        end if js_options[:queue]
        
        if TOGGLE_EFFECTS.include? name.to_sym
          "Effect.toggle(#{element},'#{name.to_s.gsub(/^toggle_/,'')}',#{options_for_javascript(js_options)});"
        else
          "new Effect.#{name.to_s.camelize}(#{element},#{options_for_javascript(js_options)});"
        end
      end
      
      # Makes the element with the DOM ID specified by +element_id+ sortable
      # by drag-and-drop and make an Ajax call whenever the sort order has
      # changed. By default, the action called gets the serialized sortable
      # element as parameters.
      #
      # Example:
      #   <%= sortable_element("my_list", :url => { :action => "order" }) %>
      #
      # In the example, the action gets a "my_list" array parameter 
      # containing the values of the ids of elements the sortable consists 
      # of, in the current order.
      #
      # You can change the behaviour with various options, see
      # http://script.aculo.us for more documentation.
      def sortable_element(element_id, options = {})
        javascript_tag(sortable_element_js(element_id, options).chop!)
      end
      
      def sortable_element_js(element_id, options = {}) #:nodoc:
        options[:with]     ||= "Sortable.serialize(#{element_id.to_json})"
        options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
        options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
  
        [:tag, :overlap, :constraint, :handle].each do |option|
          options[option] = "'#{options[option]}'" if options[option]
        end
  
        options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
        options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
  
        %(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
      end

      # Makes the element with the DOM ID specified by +element_id+ draggable.
      #
      # Example:
      #   <%= draggable_element("my_image", :revert => true)
      # 
      # You can change the behaviour with various options, see
      # http://script.aculo.us for more documentation.
      def draggable_element(element_id, options = {})
        javascript_tag(draggable_element_js(element_id, options).chop!)
      end
      
      def draggable_element_js(element_id, options = {}) #:nodoc:
        %(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
      end

      # Makes the element with the DOM ID specified by +element_id+ receive
      # dropped draggable elements (created by draggable_element).
      # and make an AJAX call  By default, the action called gets the DOM ID 
      # of the element as parameter.
      #
      # Example:
      #   <%= drop_receiving_element("my_cart", :url => 
      #     { :controller => "cart", :action => "add" }) %>
      #
      # You can change the behaviour with various options, see
      # http://script.aculo.us for more documentation.
      def drop_receiving_element(element_id, options = {})
        javascript_tag(drop_receiving_element_js(element_id, options).chop!)
      end
      
      def drop_receiving_element_js(element_id, options = {}) #:nodoc:
        options[:with]     ||= "'id=' + encodeURIComponent(element.id)"
        options[:onDrop]   ||= "function(element){" + remote_function(options) + "}"
        options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }

        options[:accept] = array_or_string_for_javascript(options[:accept]) if options[:accept]    
        options[:hoverclass] = "'#{options[:hoverclass]}'" if options[:hoverclass]
        
        %(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)
      end
    end
  end
end
require 'cgi'
require 'erb'

module ActionView
  module Helpers
    # This is poor man's Builder for the rare cases where you need to programmatically make tags but can't use Builder.
    module TagHelper
      include ERB::Util

      # Examples:
      # * <tt>tag("br") => <br /></tt>
      # * <tt>tag("input", { "type" => "text"}) => <input type="text" /></tt>
      def tag(name, options = nil, open = false)
        "<#{name}#{tag_options(options.stringify_keys) if options}" + (open ? ">" : " />")
      end

      # Examples:
      # * <tt>content_tag("p", "Hello world!") => <p>Hello world!</p></tt>
      # * <tt>content_tag("div", content_tag("p", "Hello world!"), "class" => "strong") => </tt>
      #   <tt><div class="strong"><p>Hello world!</p></div></tt>
      def content_tag(name, content, options = nil)
        "<#{name}#{tag_options(options.stringify_keys) if options}>#{content}</#{name}>"
      end

      # Returns a CDATA section for the given +content+.  CDATA sections
      # are used to escape blocks of text containing characters which would
      # otherwise be recognized as markup. CDATA sections begin with the string
      # <tt>&lt;![CDATA[</tt> and end with (and may not contain) the string 
      # <tt>]]></tt>. 
      def cdata_section(content)
        "<![CDATA[#{content}]]>"
      end

      private
        def tag_options(options)
          cleaned_options = convert_booleans(options.stringify_keys.reject {|key, value| value.nil?})
          ' ' + cleaned_options.map {|key, value| %(#{key}="#{html_escape(value.to_s)}")}.sort * ' ' unless cleaned_options.empty?
        end

        def convert_booleans(options)
          %w( disabled readonly multiple ).each { |a| boolean_attribute(options, a) }
          options
        end

        def boolean_attribute(options, attribute)
          options[attribute] ? options[attribute] = attribute : options.delete(attribute)
        end
    end
  end
end
require File.dirname(__FILE__) + '/tag_helper'

module ActionView
  module Helpers #:nodoc:
    # Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the
    # templates. In the example below we iterate over a collection of posts provided to the template and print each title
    # after making sure it doesn't run longer than 20 characters:
    #   <% for post in @posts %>
    #     Title: <%= truncate(post.title, 20) %>
    #   <% end %>
    module TextHelper      
      # The regular puts and print are outlawed in eRuby. It's recommended to use the <%= "hello" %> form instead of print "hello".
      # If you absolutely must use a method-based output, you can use concat. It's used like this: <% concat "hello", binding %>. Notice that
      # it doesn't have an equal sign in front. Using <%= concat "hello" %> would result in a double hello.
      def concat(string, binding)
        eval("_erbout", binding).concat(string)
      end

      # Truncates +text+ to the length of +length+ and replaces the last three characters with the +truncate_string+
      # if the +text+ is longer than +length+.
      def truncate(text, length = 30, truncate_string = "...")
        if text.nil? then return end
        l = length - truncate_string.length
        if $KCODE == "NONE"
          text.length > length ? text[0...l] + truncate_string : text
        else
          chars = text.split(//)
          chars.length > length ? chars[0...l].join + truncate_string : text
        end
      end

      # Highlights the +phrase+ where it is found in the +text+ by surrounding it like
      # <strong class="highlight">I'm a highlight phrase</strong>. The highlighter can be specialized by
      # passing +highlighter+ as single-quoted string with \1 where the phrase is supposed to be inserted.
      # N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use.
      def highlight(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
        if phrase.blank? then return text end
        text.gsub(/(#{Regexp.escape(phrase)})/i, highlighter) unless text.nil?
      end

      # Extracts an excerpt from the +text+ surrounding the +phrase+ with a number of characters on each side determined
      # by +radius+. If the phrase isn't found, nil is returned. Ex:
      #   excerpt("hello my world", "my", 3) => "...lo my wo..."
      def excerpt(text, phrase, radius = 100, excerpt_string = "...")
        if text.nil? || phrase.nil? then return end
        phrase = Regexp.escape(phrase)

        if found_pos = text =~ /(#{phrase})/i
          start_pos = [ found_pos - radius, 0 ].max
          end_pos   = [ found_pos + phrase.length + radius, text.length ].min

          prefix  = start_pos > 0 ? excerpt_string : ""
          postfix = end_pos < text.length ? excerpt_string : ""

          prefix + text[start_pos..end_pos].strip + postfix
        else
          nil
        end
      end

      # Attempts to pluralize the +singular+ word unless +count+ is 1. See source for pluralization rules.
      def pluralize(count, singular, plural = nil)
         "#{count} " + if count == 1
          singular
        elsif plural
          plural
        elsif Object.const_defined?("Inflector")
          Inflector.pluralize(singular)
        else
          singular + "s"
        end
      end

      # Word wrap long lines to line_width.
      def word_wrap(text, line_width = 80)
        text.gsub(/\n/, "\n\n").gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip
      end

      begin
        require_library_or_gem "redcloth" unless Object.const_defined?(:RedCloth)

        # Returns the text with all the Textile codes turned into HTML-tags.
        # <i>This method is only available if RedCloth can be required</i>.
        def textilize(text)
          if text.blank?
            ""
          else
            textilized = RedCloth.new(text, [ :hard_breaks ])
            textilized.hard_breaks = true if textilized.respond_to?("hard_breaks=")
            textilized.to_html
          end
        end

        # Returns the text with all the Textile codes turned into HTML-tags, but without the regular bounding <p> tag.
        # <i>This method is only available if RedCloth can be required</i>.
        def textilize_without_paragraph(text)
          textiled = textilize(text)
          if textiled[0..2] == "<p>" then textiled = textiled[3..-1] end
          if textiled[-4..-1] == "</p>" then textiled = textiled[0..-5] end
          return textiled
        end
      rescue LoadError
        # We can't really help what's not there
      end

      begin
        require_library_or_gem "bluecloth" unless Object.const_defined?(:BlueCloth)

        # Returns the text with all the Markdown codes turned into HTML-tags.
        # <i>This method is only available if BlueCloth can be required</i>.
        def markdown(text)
          text.blank? ? "" : BlueCloth.new(text).to_html
        end
      rescue LoadError
        # We can't really help what's not there
      end
      
      # Returns +text+ transformed into HTML using very simple formatting rules
      # Surrounds paragraphs with <tt><p></tt> tags, and converts line breaks into <tt><br/></tt>
      # Two consecutive newlines(<tt>\n\n</tt>) are considered as a paragraph, one newline (<tt>\n</tt>) is
      # considered a linebreak, three or more consecutive newlines are turned into two newlines 
      def simple_format(text)
        text.gsub!(/(\r\n|\n|\r)/, "\n") # lets make them newlines crossplatform
        text.gsub!(/\n\n+/, "\n\n") # zap dupes
        text.gsub!(/\n\n/, '</p>\0<p>') # turn two newlines into paragraph
        text.gsub!(/([^\n])(\n)([^\n])/, '\1\2<br />\3') # turn single newline into <br />
        
        content_tag("p", text)
      end

      # Turns all urls and email addresses into clickable links. The +link+ parameter can limit what should be linked.
      # Options are <tt>:all</tt> (default), <tt>:email_addresses</tt>, and <tt>:urls</tt>.
      #
      # Example:
      #   auto_link("Go to http://www.rubyonrails.com and say hello to david@loudthinking.com") =>
      #     Go to <a href="http://www.rubyonrails.com">http://www.rubyonrails.com</a> and
      #     say hello to <a href="mailto:david@loudthinking.com">david@loudthinking.com</a>
      #
      # If a block is given, each url and email address is yielded and the
      # result is used as the link text.  Example:
      #   auto_link(post.body, :all, :target => '_blank') do |text|
      #     truncate(text, 15)
      #   end
      def auto_link(text, link = :all, href_options = {}, &block)
        return '' if text.blank?
        case link
          when :all             then auto_link_urls(auto_link_email_addresses(text, &block), href_options, &block)
          when :email_addresses then auto_link_email_addresses(text, &block)
          when :urls            then auto_link_urls(text, href_options, &block)
        end
      end

      # Turns all links into words, like "<a href="something">else</a>" to "else".
      def strip_links(text)
        text.gsub(/<a.*>(.*)<\/a>/m, '\1')
      end

      # Try to require the html-scanner library
      begin
        require 'html/tokenizer'
        require 'html/node'
      rescue LoadError
        # if there isn't a copy installed, use the vendor version in
        # action controller
        $:.unshift File.join(File.dirname(__FILE__), "..", "..",
                      "action_controller", "vendor", "html-scanner")
        require 'html/tokenizer'
        require 'html/node'
      end

      VERBOTEN_TAGS = %w(form script) unless defined?(VERBOTEN_TAGS)
      VERBOTEN_ATTRS = /^on/i unless defined?(VERBOTEN_ATTRS)

      # Sanitizes the given HTML by making form and script tags into regular
      # text, and removing all "onxxx" attributes (so that arbitrary Javascript
      # cannot be executed). Also removes href attributes that start with
      # "javascript:".
      #
      # Returns the sanitized text.
      def sanitize(html)
        # only do this if absolutely necessary
        if html.index("<")
          tokenizer = HTML::Tokenizer.new(html)
          new_text = ""

          while token = tokenizer.next
            node = HTML::Node.parse(nil, 0, 0, token, false)
            new_text << case node
              when HTML::Tag
                if VERBOTEN_TAGS.include?(node.name)
                  node.to_s.gsub(/</, "&lt;")
                else
                  if node.closing != :close
                    node.attributes.delete_if { |attr,v| attr =~ VERBOTEN_ATTRS }
                    if node.attributes["href"] =~ /^javascript:/i
                      node.attributes.delete "href"
                    end
                  end
                  node.to_s
                end
              else
                node.to_s.gsub(/</, "&lt;")
            end
          end

          html = new_text
        end

        html
      end
      
      # Strips all HTML tags from the input, including comments.  This uses the html-scanner
      # tokenizer and so it's HTML parsing ability is limited by that of html-scanner.
      #
      # Returns the tag free text.
      def strip_tags(html)
        if html.index("<")
          text = ""
          tokenizer = HTML::Tokenizer.new(html)

          while token = tokenizer.next
            node = HTML::Node.parse(nil, 0, 0, token, false)
            # result is only the content of any Text nodes
            text << node.to_s if node.class == HTML::Text  
          end
          # strip any comments, and if they have a newline at the end (ie. line with
          # only a comment) strip that too
          text.gsub(/<!--(.*?)-->[\n]?/m, "") 
        else
          html # already plain text
        end 
      end
      
      # Returns a Cycle object whose to_s value cycles through items of an
      # array every time it is called. This can be used to alternate classes
      # for table rows:
      #
      #   <%- for item in @items do -%>
      #     <tr class="<%= cycle("even", "odd") %>">
      #       ... use item ...
      #     </tr>
      #   <%- end -%>
      #
      # You can use named cycles to prevent clashes in nested loops.  You'll
      # have to reset the inner cycle, manually:
      #
      #   <%- for item in @items do -%>
      #     <tr class="<%= cycle("even", "odd", :name => "row_class")
      #       <td>
      #         <%- for value in item.values do -%>
      #           <span style="color:'<%= cycle("red", "green", "blue"
      #                                         :name => "colors") %>'">
      #             item
      #           </span>
      #         <%- end -%>
      #         <%- reset_cycle("colors") -%>
      #       </td>
      #    </tr>
      #  <%- end -%>
      def cycle(first_value, *values)
        if (values.last.instance_of? Hash)
          params = values.pop
          name = params[:name]
        else
          name = "default"
        end
        values.unshift(first_value)

        cycle = get_cycle(name)
        if (cycle.nil? || cycle.values != values)
          cycle = set_cycle(name, Cycle.new(*values))
        end
        return cycle.to_s
      end
      
      # Resets a cycle so that it starts from the first element in the array
      # the next time it is used.
      def reset_cycle(name = "default")
        cycle = get_cycle(name)
        return if cycle.nil?
        cycle.reset
      end

      class Cycle #:nodoc:
        attr_reader :values
        
        def initialize(first_value, *values)
          @values = values.unshift(first_value)
          reset
        end
        
        def reset
          @index = 0
        end

        def to_s
          value = @values[@index].to_s
          @index = (@index + 1) % @values.size
          return value
        end
      end
      
      private
        # The cycle helpers need to store the cycles in a place that is
        # guaranteed to be reset every time a page is rendered, so it
        # uses an instance variable of ActionView::Base.
        def get_cycle(name)
          @_cycles = Hash.new if @_cycles.nil?
          return @_cycles[name]
        end
        
        def set_cycle(name, cycle_object)
          @_cycles = Hash.new if @_cycles.nil?
          @_cycles[name] = cycle_object
        end
      
        AUTO_LINK_RE = /
                        (                       # leading text
                          <\w+.*?>|             #   leading HTML tag, or
                          [^=!:'"\/]|           #   leading punctuation, or 
                          ^                     #   beginning of line
                        )
                        (
                          (?:http[s]?:\/\/)|    # protocol spec, or
                          (?:www\.)             # www.*
                        ) 
                        (
                          ([\w]+:?[=?&\/.-]?)*    # url segment
                          \w+[\/]?              # url tail
                          (?:\#\w*)?            # trailing anchor
                        )
                        ([[:punct:]]|\s|<|$)    # trailing text
                       /x unless const_defined?(:AUTO_LINK_RE)

        # Turns all urls into clickable links.  If a block is given, each url
        # is yielded and the result is used as the link text.  Example:
        #   auto_link_urls(post.body, :all, :target => '_blank') do |text|
        #     truncate(text, 15)
        #   end
        def auto_link_urls(text, href_options = {})
          extra_options = tag_options(href_options.stringify_keys) || ""
          text.gsub(AUTO_LINK_RE) do
            all, a, b, c, d = $&, $1, $2, $3, $5
            if a =~ /<a\s/i # don't replace URL's that are already linked
              all
            else
              text = b + c
              text = yield(text) if block_given?
              %(#{a}<a href="#{b=="www."?"http://www.":b}#{c}"#{extra_options}>#{text}</a>#{d})
              })
            end
          end
        end

        # Turns all email addresses into clickable links.  If a block is given,
        # each email is yielded and the result is used as the link text.
        # Example:
        #   auto_link_email_addresses(post.body) do |text|
        #     truncate(text, 15)
        #   end
        def auto_link_email_addresses(text)
          text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
            text = $1
            text = yield(text) if block_given?
            %{<a href="mailto:#{$1}">#{text}</a>}
          end
        end
    end
  end
end
require File.dirname(__FILE__) + '/javascript_helper'

module ActionView
  module Helpers
    # Provides a set of methods for making easy links and getting urls that depend on the controller and action. This means that
    # you can use the same format for links in the views that you do in the controller. The different methods are even named
    # synchronously, so link_to uses that same url as is generated by url_for, which again is the same url used for
    # redirection in redirect_to.
    module UrlHelper
      include JavaScriptHelper
      
      # Returns the URL for the set of +options+ provided. This takes the same options 
      # as url_for. For a list, see the documentation for ActionController::Base#url_for.
      # Note that it'll set :only_path => true so you'll get /controller/action instead of the 
      # http://example.com/controller/action part (makes it harder to parse httpd log files)
      # 
      # When called from a view, url_for returns an HTML escaped url. If you need an unescaped
      # url, pass :escape => false to url_for.
      # 
      def url_for(options = {}, *parameters_for_method_reference)
        if options.kind_of? Hash
          options = { :only_path => true }.update(options.symbolize_keys)
          escape = options.key?(:escape) ? options.delete(:escape) : true
        else
          escape = true
        end
        url = @controller.send(:url_for, options, *parameters_for_method_reference)
        escape ? html_escape(url) : url
      end

      # Creates a link tag of the given +name+ using an URL created by the set of +options+. See the valid options in
      # the documentation for ActionController::Base#url_for. It's also possible to pass a string instead of an options hash to
      # get a link tag that just points without consideration. If nil is passed as a name, the link itself will become the name.
      #
      # The html_options has three special features. One for creating javascript confirm alerts where if you pass :confirm => 'Are you sure?',
      # the link will be guarded with a JS popup asking that question. If the user accepts, the link is processed, otherwise not.
      #
      # Another for creating a popup window, which is done by either passing :popup with true or the options of the window in 
      # Javascript form.
      #
      # And a third for making the link do a POST request (instead of the regular GET) through a dynamically added form element that
      # is instantly submitted. Note that if the user has turned off Javascript, the request will fall back on the GET. So its
      # your responsibility to determine what the action should be once it arrives at the controller. The POST form is turned on by
      # passing :post as true. Note, it's not possible to use POST requests and popup targets at the same time (an exception will be thrown).
      #
      # Examples:
      #   link_to "Delete this page", { :action => "destroy", :id => @page.id }, :confirm => "Are you sure?"
      #   link_to "Help", { :action => "help" }, :popup => true
      #   link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
      #   link_to "Destroy account", { :action => "destroy" }, :confirm => "Are you sure?", :post => true
      def link_to(name, options = {}, html_options = nil, *parameters_for_method_reference)
        if html_options
          html_options = html_options.stringify_keys
          convert_options_to_javascript!(html_options)
          tag_options = tag_options(html_options)
        else
          tag_options = nil
        end
        url = options.is_a?(String) ? options : self.url_for(options, *parameters_for_method_reference)
        "<a href=\"#{url}\"#{tag_options}>#{name || url}</a>"
      end

      # Generates a form containing a sole button that submits to the
      # URL given by _options_.  Use this method instead of +link_to+
      # for actions that do not have the safe HTTP GET semantics
      # implied by using a hypertext link.
      #
      # The parameters are the same as for +link_to+.  Any _html_options_
      # that you pass will be applied to the inner +input+ element.
      # In particular, pass
      # 
      #   :disabled => true/false
      #
      # as part of _html_options_ to control whether the button is
      # disabled.  The generated form element is given the class
      # 'button-to', to which you can attach CSS styles for display
      # purposes.
      #
      # Example 1:
      #
      #   # inside of controller for "feeds"
      #   button_to "Edit", :action => 'edit', :id => 3
      #
      # Generates the following HTML (sans formatting):
      #
      #   <form method="post" action="/feeds/edit/3" class="button-to">
      #     <div><input value="Edit" type="submit" /></div>
      #   </form>
      #
      # Example 2:
      #
      #   button_to "Destroy", { :action => 'destroy', :id => 3 },
      #             :confirm => "Are you sure?"
      #
      # Generates the following HTML (sans formatting):
      #
      #   <form method="post" action="/feeds/destroy/3" class="button-to">
      #     <div><input onclick="return confirm('Are you sure?');"
      #                 value="Destroy" type="submit" />
      #     </div>
      #   </form>
      #
      # *NOTE*: This method generates HTML code that represents a form.
      # Forms are "block" content, which means that you should not try to
      # insert them into your HTML where only inline content is expected.
      # For example, you can legally insert a form inside of a +div+ or
      # +td+ element or in between +p+ elements, but not in the middle of
      # a run of text, nor can you place a form within another form.
      # (Bottom line: Always validate your HTML before going public.)
      def button_to(name, options = {}, html_options = nil)
        html_options = (html_options || {}).stringify_keys
        convert_boolean_attributes!(html_options, %w( disabled ))
        
        if confirm = html_options.delete("confirm")
          html_options["onclick"] = "return #{confirm_javascript_function(confirm)};"
        end
        
        url = options.is_a?(String) ? options : url_for(options)
        name ||= url
        
        html_options.merge!("type" => "submit", "value" => name)

        "<form method=\"post\" action=\"#{h url}\" class=\"button-to\"><div>" +
          tag("input", html_options) + "</div></form>"
      end


      # This tag is deprecated. Combine the link_to and AssetTagHelper::image_tag yourself instead, like:
      #   link_to(image_tag("rss", :size => "30x45", :border => 0), "http://www.example.com")
      def link_image_to(src, options = {}, html_options = {}, *parameters_for_method_reference)
        image_options = { "src" => src.include?("/") ? src : "/images/#{src}" }
        image_options["src"] += ".png" unless image_options["src"].include?(".")

        html_options = html_options.stringify_keys
        if html_options["alt"]
          image_options["alt"] = html_options["alt"]
          html_options.delete "alt"
        else
          image_options["alt"] = src.split("/").last.split(".").first.capitalize
        end

        if html_options["size"]
          image_options["width"], image_options["height"] = html_options["size"].split("x")
          html_options.delete "size"
        end

        if html_options["border"]
          image_options["border"] = html_options["border"]
          html_options.delete "border"
        end

        if html_options["align"]
          image_options["align"] = html_options["align"]
          html_options.delete "align"
        end

        link_to(tag("img", image_options), options, html_options, *parameters_for_method_reference)
      end

      alias_method :link_to_image, :link_image_to # deprecated name

      # Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
      # request uri is the same as the link's, in which case only the name is returned (or the
      # given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
      # to the page currently being viewed.
      def link_to_unless_current(name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
        link_to_unless current_page?(options), name, options, html_options, *parameters_for_method_reference, &block
      end

      # Create a link tag of the given +name+ using an URL created by the set of +options+, unless +condition+
      # is true, in which case only the name is returned (or the given block is yielded, if one exists). 
      def link_to_unless(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
        if condition
          if block_given?
            block.arity <= 1 ? yield(name) : yield(name, options, html_options, *parameters_for_method_reference)
          else
            name
          end
        else
          link_to(name, options, html_options, *parameters_for_method_reference)
        end  
      end
      
      # Create a link tag of the given +name+ using an URL created by the set of +options+, if +condition+
      # is true, in which case only the name is returned (or the given block is yielded, if one exists). 
      def link_to_if(condition, name, options = {}, html_options = {}, *parameters_for_method_reference, &block)
        link_to_unless !condition, name, options, html_options, *parameters_for_method_reference, &block
      end

      # Creates a link tag for starting an email to the specified <tt>email_address</tt>, which is also used as the name of the
      # link unless +name+ is specified. Additional HTML options, such as class or id, can be passed in the <tt>html_options</tt> hash.
      #
      # You can also make it difficult for spiders to harvest email address by obfuscating them.
      # Examples:
      #   mail_to "me@domain.com", "My email", :encode => "javascript"  # =>
      #     <script type="text/javascript" language="javascript">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>
      #
      #   mail_to "me@domain.com", "My email", :encode => "hex"  # =>
      #     <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
      #
      # You can also specify the cc address, bcc address, subject, and body parts of the message header to create a complex e-mail using the
      # corresponding +cc+, +bcc+, +subject+, and +body+ <tt>html_options</tt> keys. Each of these options are URI escaped and then appended to
      # the <tt>email_address</tt> before being output. <b>Be aware that javascript keywords will not be escaped and may break this feature
      # when encoding with javascript.</b>
      # Examples:
      #   mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", :bcc => "bccaddress@domain.com", :subject => "This is an example email", :body => "This is the body of the message."   # =>
      #     <a href="mailto:me@domain.com?cc="ccaddress@domain.com"&bcc="bccaddress@domain.com"&body="This%20is%20the%20body%20of%20the%20message."&subject="This%20is%20an%20example%20email">My email</a>
      def mail_to(email_address, name = nil, html_options = {})
        html_options = html_options.stringify_keys
        encode = html_options.delete("encode")
        cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")

        string = ''
        extras = ''
        extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
        extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
        extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
        extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
        extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?

        email_address_obfuscated = email_address.dup
        email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
        email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")

        if encode == 'javascript'
          tmp = "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address.to_s+extras }))}');"
          for i in 0...tmp.length
            string << sprintf("%%%x",tmp[i])
          end
          "<script type=\"text/javascript\">eval(unescape('#{string}'))</script>"
        elsif encode == 'hex'
          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
          end
          content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{string}#{extras}" })
        else
          content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
        end
      end

      # Returns true if the current page uri is generated by the options passed (in url_for format).
      def current_page?(options)
        CGI.escapeHTML(url_for(options)) == @controller.request.request_uri
      end

      private
        def convert_options_to_javascript!(html_options)
          confirm, popup, post = html_options.delete("confirm"), html_options.delete("popup"), html_options.delete("post")
        
          html_options["onclick"] = case
            when popup && post
              raise ActionView::ActionViewError, "You can't use :popup and :post in the same link"
            when confirm && popup
              "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;"
            when confirm && post
              "if (#{confirm_javascript_function(confirm)}) { #{post_javascript_function} };return false;"
            when confirm
              "return #{confirm_javascript_function(confirm)};"
            when post
              "#{post_javascript_function}return false;"
            when popup
              popup_javascript_function(popup) + 'return false;'
            else
              html_options["onclick"]
          end
        end
        
        def confirm_javascript_function(confirm)
          "confirm('#{escape_javascript(confirm)}')"
        end
        
        def popup_javascript_function(popup)
          popup.is_a?(Array) ? "window.open(this.href,'#{popup.first}','#{popup.last}');" : "window.open(this.href);"
        end
        
        def post_javascript_function
          "var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();"
        end

        # Processes the _html_options_ hash, converting the boolean
        # attributes from true/false form into the form required by
        # HTML/XHTML.  (An attribute is considered to be boolean if
        # its name is listed in the given _bool_attrs_ array.)
        #
        # More specifically, for each boolean attribute in _html_options_
        # given as:
        #
        #     "attr" => bool_value
        #
        # if the associated _bool_value_ evaluates to true, it is
        # replaced with the attribute's name; otherwise the attribute is
        # removed from the _html_options_ hash.  (See the XHTML 1.0 spec,
        # section 4.5 "Attribute Minimization" for more:
        # http://www.w3.org/TR/xhtml1/#h-4.5)
        #
        # Returns the updated _html_options_ hash, which is also modified
        # in place.
        #
        # Example:
        #
        #   convert_boolean_attributes!( html_options,
        #                                %w( checked disabled readonly ) )
        def convert_boolean_attributes!(html_options, bool_attrs)
          bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
          html_options
        end
    end
  end
end
module ActionView
  # There's also a convenience method for rendering sub templates within the current controller that depends on a single object 
  # (we call this kind of sub templates for partials). It relies on the fact that partials should follow the naming convention of being 
  # prefixed with an underscore -- as to separate them from regular templates that could be rendered on their own. 
  #
  # In a template for Advertiser#account:
  #
  #  <%= render :partial => "account" %>
  #
  # This would render "advertiser/_account.rhtml" and pass the instance variable @account in as a local variable +account+ to 
  # the template for display.
  #
  # In another template for Advertiser#buy, we could have:
  #
  #   <%= render :partial => "account", :locals => { :account => @buyer } %>
  #
  #   <% for ad in @advertisements %>
  #     <%= render :partial => "ad", :locals => { :ad => ad } %>
  #   <% end %>
  #
  # This would first render "advertiser/_account.rhtml" with @buyer passed in as the local variable +account+, then render 
  # "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display.
  #
  # == Rendering a collection of partials
  #
  # The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub
  # template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders
  # a partial by the same name as the elements contained within. So the three-lined example in "Using partials" can be rewritten
  # with a single line:
  #
  #   <%= render :partial => "ad", :collection => @advertisements %>
  #
  # This will render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. An iteration counter
  # will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the 
  # example above, the template would be fed +ad_counter+.
  #
  # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also just keep domain objects,
  # like Active Records, in there.
  # 
  # == Rendering shared partials
  #
  # Two controllers can share a set of partials and render them like this:
  #
  #   <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %>
  #
  # This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from.
  module Partials
    # Deprecated, use render :partial
    def render_partial(partial_path, local_assigns = nil, deprecated_local_assigns = nil) #:nodoc:
      path, partial_name = partial_pieces(partial_path)
      object = extracting_object(partial_name, local_assigns, deprecated_local_assigns)
      local_assigns = extract_local_assigns(local_assigns, deprecated_local_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
        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
    end

    # Deprecated, use render :partial, :collection
    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)
      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

      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}"))
      else
        collection_of_partials.join
      end
    end
    
    alias_method :render_collection_of_partials, :render_partial_collection
    
    private
      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
        end
      end

      def partial_counter_name(partial_name)
        "#{partial_name.split('/').last}_counter".intern
      end
      
      def extracting_object(partial_name, local_assigns, deprecated_local_assigns)
        if local_assigns.is_a?(Hash) || local_assigns.nil?
          controller.instance_variable_get("@#{partial_name}")
        else
          # deprecated form where object could be passed in as second parameter
          local_assigns
        end
      end
      
      def extract_local_assigns(local_assigns, deprecated_local_assigns)
        local_assigns.is_a?(Hash) ? local_assigns : deprecated_local_assigns
      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)
        local_assigns[partial_name.intern] ||=
          if object.is_a?(ActionView::Base::ObjectWrapper)
            object.value
          else
            object
          end || controller.instance_variable_get("@#{partial_name}")
      end
  end
end
module ActionView
  # The TemplateError exception is raised when the compilation of the template fails. This exception then gathers a
  # bunch of intimate details and uses it to report a very precise exception message.
  class TemplateError < ActionViewError #:nodoc:
    SOURCE_CODE_RADIUS = 3

    attr_reader :original_exception

    def initialize(base_path, file_name, assigns, source, original_exception)
      @base_path, @assigns, @source, @original_exception = 
        base_path, assigns, source, original_exception
      @file_name = file_name
    end
  
    def message
      original_exception.message
    end
  
    def sub_template_message
      if @sub_templates
        "Trace of template inclusion: " +
        @sub_templates.collect { |template| strip_base_path(template) }.join(", ")
      else
        ""
      end
    end
  
    def source_extract(indention = 0)
      source_code = IO.readlines(@file_name)
      
      start_on_line = [ line_number - SOURCE_CODE_RADIUS - 1, 0 ].max
      end_on_line   = [ line_number + SOURCE_CODE_RADIUS - 1, source_code.length].min

      line_counter = start_on_line
      extract = source_code[start_on_line..end_on_line].collect do |line| 
        line_counter += 1
        "#{' ' * indention}#{line_counter}: " + line
      end

      extract.join
    end

    def sub_template_of(file_name)
      @sub_templates ||= []
      @sub_templates << file_name
    end
  
    def line_number
      if file_name
        regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
        [@original_exception.message, @original_exception.clean_backtrace].flatten.each do |line|
          return $1.to_i if regexp =~ line
        end
      end
      0
    end
  
    def file_name
      stripped = strip_base_path(@file_name)
      stripped[0] == ?/ ? stripped[1..-1] : stripped
    end
    
    def to_s
      "\n\n#{self.class} (#{message}) on line ##{line_number} of #{file_name}:\n" + 
      source_extract + "\n    " +
      original_exception.clean_backtrace.join("\n    ") +
      "\n\n"
    end

    def backtrace
      [ 
        "On line ##{line_number} of #{file_name}\n\n#{source_extract(4)}\n    " + 
        original_exception.clean_backtrace.join("\n    ")
      ]
    end

    private
      def strip_base_path(file_name)
        file_name = File.expand_path(file_name).gsub(/^#{Regexp.escape File.expand_path(RAILS_ROOT)}/, '')
        file_name.gsub(@base_path, "")
      end
  end
end

Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, ''] if defined?(Exception::TraceSubstitutions)
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}}, '#{RAILS_ROOT}'] if defined?(RAILS_ROOT)
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# 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.
#++

$:.unshift(File.dirname(__FILE__) + "/action_view/vendor")
require 'action_view/base'
require 'action_view/partials'

ActionView::Base.class_eval do
  include ActionView::Partials
end

ActionView::Base.load_helpers(File.dirname(__FILE__) + "/action_view/helpers/")
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib/active_support')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')

require 'yaml'
require 'test/unit'
require 'action_controller'
require 'breakpoint'

require 'action_controller/test_process'

ActionController::Base.logger = nil
ActionController::Base.ignore_missing_templates = false
ActionController::Routing::Routes.reload rescue nilrequire  File.dirname(__FILE__) + '/abstract_unit'

# Define the essentials
class ActiveRecordTestConnector
  cattr_accessor :able_to_connect
  cattr_accessor :connected
  
  # Set our defaults
  self.connected = false
  self.able_to_connect = true
end

# Try to grab AR
begin
  PATH_TO_AR = File.dirname(__FILE__) + '/../../activerecord'
  require "#{PATH_TO_AR}/lib/active_record" unless Object.const_defined?(:ActiveRecord)
  require "#{PATH_TO_AR}/lib/active_record/fixtures" unless Object.const_defined?(:Fixtures)
rescue Object => e
  $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
  ActiveRecordTestConnector.able_to_connect = false
end

# Define the rest of the connector
class ActiveRecordTestConnector  
  def self.setup
    unless self.connected || !self.able_to_connect
      setup_connection
      load_schema
      self.connected = true
    end
  rescue Object => e
    $stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
    #$stderr.puts "  #{e.backtrace.join("\n  ")}\n"
    self.able_to_connect = false
  end
  
  private
  
  def self.setup_connection
    if Object.const_defined?(:ActiveRecord)
          
      begin
        ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
        ActiveRecord::Base.connection
      rescue Object
        $stderr.puts 'SQLite 3 unavailable; falling to SQLite 2.'
        ActiveRecord::Base.establish_connection(:adapter => 'sqlite', :dbfile => ':memory:')
        ActiveRecord::Base.connection
      end
    
      Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
    else
      raise "Couldn't locate ActiveRecord."
    end
  end
  
  # Load actionpack sqlite tables
  def self.load_schema
    File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(';').each do |sql|
      ActiveRecord::Base.connection.execute(sql) unless sql.blank?
    end
  end
end
  
# Test case for inheiritance  
class ActiveRecordTestCase < Test::Unit::TestCase
  # Set our fixture path
  self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"
  
  def setup
    abort_tests unless ActiveRecordTestConnector.connected = true
  end
  
  # Default so Test::Unit::TestCase doesn't complain
  def test_truth
  end
  
  private
  
  # If things go wrong, we don't want to run our test cases. We'll just define them to test nothing.
  def abort_tests
    self.class.public_instance_methods.grep(/^test./).each do |method|
      self.class.class_eval { define_method(method.to_sym){} }
    end
  end
end

ActiveRecordTestConnector.setuprequire "#{File.dirname(__FILE__)}/../active_record_unit"
require 'fixtures/company'

class ActiveRecordAssertionsController < ActionController::Base
  self.template_root = "#{File.dirname(__FILE__)}/../fixtures/"

  # fail with 1 bad column
  def nasty_columns_1
    @company = Company.new
    @company.name = "B"
    @company.rating = 2
    render :inline => "snicker...."
  end

  # fail with 2 bad columns
  def nasty_columns_2
    @company = Company.new
    @company.name = ""
    @company.rating = 2
    render :inline => "double snicker...."
  end

  # this will pass validation
  def good_company
    @company = Company.new
    @company.name = "A"
    @company.rating = 69
    render :inline => "Goodness Gracious!"
  end

  # this will fail validation
  def bad_company
    @company = Company.new 
    render :inline => "Who's Bad?"
  end

  # the safety dance......
  def rescue_action(e) raise; end
end
                  
class ActiveRecordAssertionsControllerTest < ActiveRecordTestCase
  fixtures :companies
  
  def setup
    @request = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
    @controller = ActiveRecordAssertionsController.new
    super
  end

  # test for 1 bad apple column
  def test_some_invalid_columns
    process :nasty_columns_1
    assert_success
    assert_invalid_record 'company'
    assert_invalid_column_on_record 'company', 'rating'
    assert_valid_column_on_record 'company', 'name'
    assert_valid_column_on_record 'company', %w(name id)
  end

  # test for 2 bad apples columns
  def test_all_invalid_columns
    process :nasty_columns_2
    assert_success
    assert_invalid_record 'company'
    assert_invalid_column_on_record 'company', 'rating'
    assert_invalid_column_on_record 'company', 'name'
    assert_invalid_column_on_record 'company', %w(name rating)
  end

  # ensure we have no problems with an ActiveRecord
  def test_valid_record
    process :good_company
    assert_success
    assert_valid_record 'company'
  end

  # ensure we have problems with an ActiveRecord
  def test_invalid_record
    process :bad_company
    assert_success
    assert_invalid_record 'company'
  end
end# Unfurl the safety net.
path_to_ar = File.dirname(__FILE__) + '/../../../activerecord'
if Object.const_defined?(:ActiveRecord) or File.exist?(path_to_ar)
  begin

# These tests exercise CGI::Session::ActiveRecordStore, so you're going to
# need AR in a sibling directory to AP and have SQLite installed.

unless Object.const_defined?(:ActiveRecord)
  require File.join(path_to_ar, 'lib', 'active_record')
end

require File.dirname(__FILE__) + '/../abstract_unit'
require 'action_controller/session/active_record_store'

#ActiveRecord::Base.logger = Logger.new($stdout)
begin
  CGI::Session::ActiveRecordStore::Session.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
  CGI::Session::ActiveRecordStore::Session.connection
rescue Object
  $stderr.puts 'SQLite 3 unavailable; falling back to SQLite 2.'
  begin
    CGI::Session::ActiveRecordStore::Session.establish_connection(:adapter => 'sqlite', :database => ':memory:')
    CGI::Session::ActiveRecordStore::Session.connection
  rescue Object
    $stderr.puts 'SQLite 2 unavailable; skipping ActiveRecordStore test suite.'
    raise SystemExit
  end
end


module CommonActiveRecordStoreTests
  def test_basics
    s = session_class.new(:session_id => '1234', :data => { 'foo' => 'bar' })
    assert_equal 'bar', s.data['foo']
    assert s.save
    assert_equal 'bar', s.data['foo']

    assert_not_nil t = session_class.find_by_session_id('1234')
    assert_not_nil t.data
    assert_equal 'bar', t.data['foo']
  end

  def test_reload_same_session
    @new_session.update
    reloaded = CGI::Session.new(CGI.new, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore)
    assert_equal 'bar', reloaded['foo']
  end

  def test_tolerates_close_close
    assert_nothing_raised do
      @new_session.close
      @new_session.close
    end
  end
end

class ActiveRecordStoreTest < Test::Unit::TestCase
  include CommonActiveRecordStoreTests

  def session_class
    CGI::Session::ActiveRecordStore::Session
  end

  def session_id_column
    "session_id"
  end

  def setup
    session_class.create_table!

    ENV['REQUEST_METHOD'] = 'GET'
    CGI::Session::ActiveRecordStore.session_class = session_class

    @cgi = CGI.new
    @new_session = CGI::Session.new(@cgi, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true)
    @new_session['foo'] = 'bar'
  end

# this test only applies for eager sesssion saving
#  def test_another_instance
#    @another = CGI::Session.new(@cgi, 'session_id' => @new_session.session_id, 'database_manager' => CGI::Session::ActiveRecordStore)
#    assert_equal @new_session.session_id, @another.session_id
#  end

  def test_model_attribute
    assert_kind_of CGI::Session::ActiveRecordStore::Session, @new_session.model
    assert_equal({ 'foo' => 'bar' }, @new_session.model.data)
  end

  def test_save_unloaded_session
    c = session_class.connection
    bogus_class = c.quote(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')
    assert_not_nil sess
    assert !sess.loaded?

    # because the session is not loaded, the save should be a no-op. If it
    # isn't, this'll try and unmarshall the bogus class, and should get an error.
    assert_nothing_raised { sess.save }
  end

  def teardown
    session_class.drop_table!
  end
end

class ColumnLimitTest < Test::Unit::TestCase
  def setup
    @session_class = CGI::Session::ActiveRecordStore::Session
    @session_class.create_table!
  end

  def teardown
    @session_class.drop_table!
  end

  def test_protection_from_data_larger_than_column
    # Can't test this unless there is a limit
    return unless limit = @session_class.data_column_size_limit
    too_big = ':(' * limit
    s = @session_class.new(:session_id => '666', :data => {'foo' => too_big})
    s.data
    assert_raise(ActionController::SessionOverflowError) { s.save }
  end
end

class DeprecatedActiveRecordStoreTest < ActiveRecordStoreTest
  def session_id_column
    "sessid"
  end

  def setup
    session_class.connection.execute 'create table old_sessions (id integer primary key, sessid text unique, data text)'
    session_class.table_name = 'old_sessions'
    session_class.send :setup_sessid_compatibility!

    ENV['REQUEST_METHOD'] = 'GET'
    CGI::Session::ActiveRecordStore.session_class = session_class

    @new_session = CGI::Session.new(CGI.new, 'database_manager' => CGI::Session::ActiveRecordStore, 'new_session' => true)
    @new_session['foo'] = 'bar'
  end

  def teardown
    session_class.connection.execute 'drop table old_sessions'
    session_class.table_name = 'sessions'
  end
end

class SqlBypassActiveRecordStoreTest < ActiveRecordStoreTest
  def session_class
    unless @session_class
      @session_class = CGI::Session::ActiveRecordStore::SqlBypass
      @session_class.connection = CGI::Session::ActiveRecordStore::Session.connection
    end
    @session_class
  end

  def test_model_attribute
    assert_kind_of CGI::Session::ActiveRecordStore::SqlBypass, @new_session.model
    assert_equal({ 'foo' => 'bar' }, @new_session.model.data)
  end
end


# End of safety net.
  rescue Object => e
    $stderr.puts "Skipping CGI::Session::ActiveRecordStore tests: #{e}"
    #$stderr.puts "  #{e.backtrace.join("\n  ")}"
  end
end
require File.dirname(__FILE__) + '/../active_record_unit'

require 'fixtures/topic'
require 'fixtures/reply'
require 'fixtures/developer'
require 'fixtures/project'

class PaginationTest < ActiveRecordTestCase
  fixtures :topics, :replies, :developers, :projects, :developers_projects

  class PaginationController < ActionController::Base
    self.template_root = "#{File.dirname(__FILE__)}/../fixtures/"
    
    def simple_paginate
      @topic_pages, @topics = paginate(:topics)
      render :nothing => true
    end
    
    def paginate_with_per_page
      @topic_pages, @topics = paginate(:topics, :per_page => 1)
      render :nothing => true
    end
    
    def paginate_with_order
      @topic_pages, @topics = paginate(:topics, :order => 'created_at asc')
      render :nothing => true
    end
    
    def paginate_with_order_by
      @topic_pages, @topics = paginate(:topics, :order_by => 'created_at asc')
      render :nothing => true
    end
    
    def paginate_with_include_and_order
      @topic_pages, @topics = paginate(:topics, :include => :replies, :order => 'replies.created_at asc, topics.created_at asc')
      render :nothing => true
    end
    
    def paginate_with_conditions
      @topic_pages, @topics = paginate(:topics, :conditions => ["created_at > ?", 30.minutes.ago])
      render :nothing => true
    end
    
    def paginate_with_class_name
      @developer_pages, @developers = paginate(:developers, :class_name => "DeVeLoPeR")
      render :nothing => true
    end
    
    def paginate_with_singular_name
      @developer_pages, @developers = paginate()
      render :nothing => true
    end
    
    def paginate_with_joins
      @developer_pages, @developers = paginate(:developers, 
                                             :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
                                             :conditions => 'project_id=1')        
      render :nothing => true
    end
    
    def paginate_with_join
      @developer_pages, @developers = paginate(:developers, 
                                             :join => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
                                             :conditions => 'project_id=1')        
      render :nothing => true
    end
     
    def paginate_with_join_and_count
      @developer_pages, @developers = paginate(:developers, 
                                             :join => 'd LEFT JOIN developers_projects ON d.id = developers_projects.developer_id',
                                             :conditions => 'project_id=1',
                                             :count => "d.id")        
      render :nothing => true
    end
    
    def rescue_errors(e) raise e end

    def rescue_action(e) raise end
    
  end
  
  def setup
    @controller = PaginationController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    super
  end

  # Single Action Pagination Tests

  def test_simple_paginate
    get :simple_paginate
    assert_equal 1, assigns(:topic_pages).page_count
    assert_equal 3, assigns(:topics).size
  end
  
  def test_paginate_with_per_page
    get :paginate_with_per_page
    assert_equal 1, assigns(:topics).size
    assert_equal 3, assigns(:topic_pages).page_count
  end
  
  def test_paginate_with_order
    get :paginate_with_order
    expected = [topics(:futurama),
               topics(:harvey_birdman),
               topics(:rails)]
    assert_equal expected, assigns(:topics)
    assert_equal 1, assigns(:topic_pages).page_count
  end
  
  def test_paginate_with_order_by
    get :paginate_with_order
    expected = assigns(:topics)
    get :paginate_with_order_by
    assert_equal expected, assigns(:topics)  
    assert_equal 1, assigns(:topic_pages).page_count    
  end
  
  def test_paginate_with_conditions
    get :paginate_with_conditions
    expected = [topics(:rails)]
    assert_equal expected, assigns(:topics)
    assert_equal 1, assigns(:topic_pages).page_count
  end
  
  def test_paginate_with_class_name
    get :paginate_with_class_name
    
    assert assigns(:developers).size > 0
    assert_equal DeVeLoPeR, assigns(:developers).first.class
  end
      
  def test_paginate_with_joins
    get :paginate_with_joins
    assert_equal 2, assigns(:developers).size
    developer_names = assigns(:developers).map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
  end
  
  def test_paginate_with_join_and_conditions
    get :paginate_with_joins
    expected = assigns(:developers)
    get :paginate_with_join
    assert_equal expected, assigns(:developers)
  end
  
  def test_paginate_with_join_and_count
    get :paginate_with_joins
    expected = assigns(:developers)
    get :paginate_with_join_and_count
    assert_equal expected, assigns(:developers)
  end
  
  def test_paginate_with_include_and_order
    get :paginate_with_include_and_order
    expected = Topic.find(:all, :include => 'replies', :order => 'replies.created_at asc, topics.created_at asc', :limit => 10)
    assert_equal expected, assigns(:topics)
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

# a controller class to facilitate the tests
class ActionPackAssertionsController < ActionController::Base

  # this does absolutely nothing
  def nothing() render_text ""; end

  # a standard template
  def hello_world() render "test/hello_world"; end

  # a standard template
  def hello_xml_world() render "test/hello_xml_world"; end
 
  # a redirect to an internal location
  def redirect_internal() redirect_to "/nothing"; end

  def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end

  def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end

  def redirect_to_path() redirect_to '/some/path' end

  def redirect_to_named_route() redirect_to route_one_url end
  
  # a redirect to an external location
  def redirect_external() redirect_to_url "http://www.rubyonrails.org"; end
  
  # a 404
  def response404() render_text "", "404 AWOL"; end

  # a 500
  def response500() render_text "", "500 Sorry"; end

  # a fictional 599
  def response599() render_text "", "599 Whoah!"; end

  # putting stuff in the flash
  def flash_me
    flash['hello'] = 'my name is inigo montoya...'
    render_text "Inconceivable!"
  end

  # we have a flash, but nothing is in it
  def flash_me_naked
    flash.clear
    render_text "wow!"
  end

  # assign some template instance variables
  def assign_this
    @howdy = "ho"
    render :inline => "Mr. Henke"
  end

  def render_based_on_parameters
    render_text "Mr. #{@params["name"]}"
  end

  def render_url
    render_text "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>"
  end

  # puts something in the session
  def session_stuffing
    session['xmas'] = 'turkey'
    render_text "ho ho ho"
  end
  
  # raises exception on get requests
  def raise_on_get
    raise "get" if @request.get?
    render_text "request method: #{@request.env['REQUEST_METHOD']}"
  end

  # raises exception on post requests
  def raise_on_post
    raise "post" if @request.post?
    render_text "request method: #{@request.env['REQUEST_METHOD']}"
  end       
  
  def get_valid_record
    @record = Class.new do       
      def valid?
        true
      end

      def errors
        Class.new do 
           def full_messages; []; end          
        end.new
      end    
    
    end.new
        
    render :nothing => true    
  end


  def get_invalid_record
    @record = Class.new do 
      
      def valid?
        false
      end
      
      def errors
        Class.new do 
           def full_messages; ['...stuff...']; end          
        end.new
      end
    end.new                
    
    render :nothing => true    
  end

  # 911
  def rescue_action(e) raise; end
end

module Admin
  class InnerModuleController < ActionController::Base
    def redirect_to_absolute_controller
      redirect_to :controller => '/content'
    end
    def redirect_to_fellow_controller
      redirect_to :controller => 'user'
    end
  end
end

# ---------------------------------------------------------------------------


# tell the controller where to find its templates but start from parent 
# directory of test_request_response to simulate the behaviour of a 
# production environment
ActionPackAssertionsController.template_root = File.dirname(__FILE__) + "/../fixtures/"


# a test case to exercise the new capabilities TestRequest & TestResponse
class ActionPackAssertionsControllerTest < Test::Unit::TestCase
  # let's get this party started  
  def setup
    @controller = ActionPackAssertionsController.new
    @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
  end
 
  # -- assertion-based testing ------------------------------------------------

  def test_assert_tag_and_url_for
    get :render_url
    assert_tag :content => "/action_pack_assertions/flash_me"
  end

  # test the session assertion to make sure something is there.
  def test_assert_session_has
    process :session_stuffing
    assert_session_has 'xmas'
    assert_session_has_no 'halloween'
  end
  
  # test the get method, make sure the request really was a get
  def test_get
    assert_raise(RuntimeError) { get :raise_on_get }
    get :raise_on_post
    assert_equal @response.body, 'request method: GET'
  end

  # test the get method, make sure the request really was a get
  def test_post
    assert_raise(RuntimeError) { post :raise_on_post }
    post :raise_on_get
    assert_equal @response.body, 'request method: POST'
  end
  
#   the following test fails because the request_method is now cached on the request instance
#   test the get/post switch within one test action
#   def test_get_post_switch
#     post :raise_on_get
#     assert_equal @response.body, 'request method: POST'
#     get :raise_on_post
#     assert_equal @response.body, 'request method: GET'
#     post :raise_on_get
#     assert_equal @response.body, 'request method: POST'
#     get :raise_on_post
#     assert_equal @response.body, 'request method: GET'
#   end

  # test the assertion of goodies in the template
  def test_assert_template_has
    process :assign_this
    assert_template_has 'howdy'
  end

  # test the assertion for goodies that shouldn't exist in the template
  def test_assert_template_has_no
    process :nothing
    assert_template_has_no 'maple syrup'
    assert_template_has_no 'howdy'
  end
  
  # test the redirection assertions
  def test_assert_redirect
    process :redirect_internal
    assert_redirect
  end

  # test the redirect url string
  def test_assert_redirect_url
    process :redirect_external
    assert_redirect_url 'http://www.rubyonrails.org'
  end

  # test the redirection pattern matching on a string
  def test_assert_redirect_url_match_string
    process :redirect_external
    assert_redirect_url_match 'rails.org'
  end
  
  # test the redirection pattern matching on a pattern
  def test_assert_redirect_url_match_pattern
    process :redirect_external
    assert_redirect_url_match /ruby/
  end

  # test the redirection to a named route
  def test_assert_redirect_to_named_route
    process :redirect_to_named_route
    assert_raise(Test::Unit::AssertionFailedError) do
      assert_redirected_to 'http://test.host/route_two'
    end
  end
  
  # test the flash-based assertions with something is in the flash
  def test_flash_assertions_full
    process :flash_me
    assert @response.has_flash_with_contents?
    assert_flash_exists
    assert_flash_not_empty
    assert_flash_has 'hello'
    assert_flash_has_no 'stds'
  end

  # test the flash-based assertions with no flash at all
  def test_flash_assertions_negative
    process :nothing
    assert_flash_empty
    assert_flash_has_no 'hello'
    assert_flash_has_no 'qwerty'
  end
  
  # test the assert_rendered_file 
  def test_assert_rendered_file
    process :hello_world
    assert_rendered_file 'test/hello_world'
    assert_rendered_file 'hello_world'
  end
  
  # test the assert_success assertion
  def test_assert_success
    process :nothing
    assert_success
    assert_rendered_file
  end
  
  # -- standard request/response object testing --------------------------------
 
  # ensure our session is working properly
  def test_session_objects
    process :session_stuffing
    assert @response.has_session_object?('xmas')
    assert_session_equal 'turkey', 'xmas'
    assert !@response.has_session_object?('easter')
  end
  
  # make sure that the template objects exist
  def test_template_objects_alive
    process :assign_this
    assert !@response.has_template_object?('hi')
    assert @response.has_template_object?('howdy')
  end
  
  # make sure we don't have template objects when we shouldn't
  def test_template_object_missing
    process :nothing
    assert_nil @response.template_objects['howdy']
  end
  
  def test_assigned_equal
    process :assign_this
    assert_assigned_equal "ho", :howdy
  end

  # check the empty flashing
  def test_flash_me_naked
    process :flash_me_naked
    assert !@response.has_flash?
    assert !@response.has_flash_with_contents?
  end

  # check if we have flash objects
  def test_flash_haves
    process :flash_me
    assert @response.has_flash?
    assert @response.has_flash_with_contents?
    assert @response.has_flash_object?('hello')
  end

  # ensure we don't have flash objects
  def test_flash_have_nots
    process :nothing
    assert !@response.has_flash?
    assert !@response.has_flash_with_contents?
    assert_nil @response.flash['hello']
  end
  
  # examine that the flash objects are what we expect
  def test_flash_equals
    process :flash_me
    assert_flash_equal 'my name is inigo montoya...', 'hello'
  end
  
  
  # check if we were rendered by a file-based template? 
  def test_rendered_action
    process :nothing
    assert !@response.rendered_with_file?

    process :hello_world
    assert @response.rendered_with_file?
    assert 'hello_world', @response.rendered_file
  end
  
  # check the redirection location
  def test_redirection_location
    process :redirect_internal
    assert_equal 'http://test.host/nothing', @response.redirect_url

    process :redirect_external
    assert_equal 'http://www.rubyonrails.org', @response.redirect_url

    process :nothing
    assert_nil @response.redirect_url
  end
  
 
  # check server errors 
  def test_server_error_response_code
    process :response500
    assert @response.server_error?
    
    process :response599
    assert @response.server_error?
    
    process :response404
    assert !@response.server_error?
  end
  
  # check a 404 response code
  def test_missing_response_code
    process :response404
    assert @response.missing?
  end

  # check to see if our redirection matches a pattern
  def test_redirect_url_match
    process :redirect_external
    assert @response.redirect?
    assert @response.redirect_url_match?("rubyonrails")
    assert @response.redirect_url_match?(/rubyonrails/)
    assert !@response.redirect_url_match?("phpoffrails")
    assert !@response.redirect_url_match?(/perloffrails/)
  end
  
  # check for a redirection
  def test_redirection
    process :redirect_internal
    assert @response.redirect?

    process :redirect_external
    assert @response.redirect?

    process :nothing
    assert !@response.redirect?
  end
  
  # check a successful response code
  def test_successful_response_code
    process :nothing
    assert @response.success?
  end 
  
  # a basic check to make sure we have a TestResponse object
  def test_has_response
    process :nothing
    assert_kind_of ActionController::TestResponse, @response
  end
  
  def test_render_based_on_parameters
    process :render_based_on_parameters, "name" => "David"
    assert_equal "Mr. David", @response.body
  end

  def test_assert_template_xpath_match_no_matches
    process :hello_xml_world
    assert_raises Test::Unit::AssertionFailedError do
      assert_template_xpath_match('/no/such/node/in/document')
    end
  end

  def test_simple_one_element_xpath_match
    process :hello_xml_world
    assert_template_xpath_match('//title', "Hello World")
  end

  def test_array_of_elements_in_xpath_match
    process :hello_xml_world
    assert_template_xpath_match('//p', %w( abes monks wiseguys ))
  end

  def test_follow_redirect
    process :redirect_to_action
    assert_redirected_to :action => "flash_me"
    
    follow_redirect
    assert_equal 1, @request.parameters["id"].to_i

    assert "Inconceivable!", @response.body
  end
  
  def test_follow_redirect_outside_current_action
    process :redirect_to_controller
    assert_redirected_to :controller => "elsewhere", :action => "flash_me"

    assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") { follow_redirect }
  end

  def test_redirected_to_url_leadling_slash
    process :redirect_to_path
    assert_redirected_to '/some/path'
  end
  def test_redirected_to_url_no_leadling_slash
    process :redirect_to_path
    assert_redirected_to 'some/path'
  end
  def test_redirected_to_url_full_url
    process :redirect_to_path
    assert_redirected_to 'http://test.host/some/path'
  end

  def test_redirected_to_with_nested_controller
    @controller = Admin::InnerModuleController.new
    get :redirect_to_absolute_controller
    assert_redirected_to :controller => 'content'
    
    get :redirect_to_fellow_controller
    assert_redirected_to :controller => 'admin/user'
  end                 
  
  def test_assert_valid
    get :get_valid_record
    assert_valid assigns('record')    
  end                
  
  def test_assert_valid_failing
    get :get_invalid_record
    
    begin
      assert_valid assigns('record')    
      assert false
    rescue Test::Unit::AssertionFailedError => e             
    end
  end
end

class ActionPackHeaderTest < Test::Unit::TestCase  
  def setup
    @controller = ActionPackAssertionsController.new
    @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
  end

  def test_rendering_xml_sets_content_type
    process :hello_xml_world
    assert_equal('application/xml', @controller.headers['Content-Type'])
  end

  def test_rendering_xml_respects_content_type
    @response.headers['Content-Type'] = 'application/pdf'
    process :hello_xml_world
    assert_equal('application/pdf', @controller.headers['Content-Type'])
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class Address

  def Address.count(conditions = nil, join = nil)
    nil
  end
  
  def Address.find_all(arg1, arg2, arg3, arg4)
    []
  end
  
  def self.find(*args)
    []
  end
end

class AddressesTestController < ActionController::Base

  scaffold :address

  def self.controller_name; "addresses"; end
  def self.controller_path; "addresses"; end

end

AddressesTestController.template_root = File.dirname(__FILE__) + "/../fixtures/"

class AddressesTest < Test::Unit::TestCase
  def setup
    @controller = AddressesTestController.new

    # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
    # a more accurate simulation of what happens in "real life".
    @controller.logger = Logger.new(nil)

    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @request.host = "www.nextangle.com"
  end

  def test_list
    get :list
    assert_equal "We only need to get this far!", @response.body.chomp
  end


end
require File.dirname(__FILE__) + '/../abstract_unit'
require 'test/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.
module Submodule
  class ContainedEmptyController < ActionController::Base
  end
  class ContainedNonEmptyController < ActionController::Base
    def public_action
    end
    
    hide_action :hidden_action
    def hidden_action
    end
    
    def another_hidden_action
    end
    hide_action :another_hidden_action
  end
  class SubclassedController < ContainedNonEmptyController
    hide_action :public_action # Hiding it here should not affect the superclass.
  end
end
class EmptyController < ActionController::Base
  include ActionController::Caching
end
class NonEmptyController < ActionController::Base
  def public_action
  end
  
  hide_action :hidden_action
  def hidden_action
  end
end

class ControllerClassTests < Test::Unit::TestCase
  def test_controller_path
    assert_equal 'empty', EmptyController.controller_path
    assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
  end
  def test_controller_name
    assert_equal 'empty', EmptyController.controller_name
    assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
 end
end

class ControllerInstanceTests < Test::Unit::TestCase
  def setup
    @empty = EmptyController.new
    @contained = Submodule::ContainedEmptyController.new
    @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
    
    @non_empty_controllers = [NonEmptyController.new,
                              Submodule::ContainedNonEmptyController.new]
  end

  def test_action_methods
    @empty_controllers.each do |c|
      assert_equal Set.new, c.send(:action_methods), "#{c.class.controller_path} should be empty!"
    end
    @non_empty_controllers.each do |c|
      assert_equal Set.new('public_action'), c.send(:action_methods), "#{c.class.controller_path} should not be empty!"
    end
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'
require 'test/unit'

# Provide some static controllers.
class BenchmarkedController < ActionController::Base
  def public_action
    render :nothing => true
  end

  def rescue_action(e)
    raise e
  end
end

class BenchmarkTest < Test::Unit::TestCase
  class MockLogger
    def method_missing(*args)
    end
  end

  def setup
    @controller = BenchmarkedController.new
    # benchmark doesn't do anything unless a logger is set
    @controller.logger = MockLogger.new
    @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
    @request.host = "test.actioncontroller.i"
  end

  def test_with_http_1_0_request
    @request.host = nil
    assert_nothing_raised { get :public_action }
  end
end
require 'fileutils'
require File.dirname(__FILE__) + '/../abstract_unit'

class TestLogDevice < Logger::LogDevice
  attr :last_message, true
  
  def initialize
    @last_message=String.new
  end
  
  def write(message)
    @last_message << message
  end

  def clear
    @last_message = String.new
  end
end

#setup our really sophisticated logger
TestLog = TestLogDevice.new
RAILS_DEFAULT_LOGGER = Logger.new(TestLog)
ActionController::Base.logger = RAILS_DEFAULT_LOGGER

def use_store
  #generate a random key to ensure the cache is always in a different location
  RANDOM_KEY = rand(99999999).to_s
  FILE_STORE_PATH = File.dirname(__FILE__) + '/../temp/' + RANDOM_KEY
  ActionController::Base.perform_caching = true
  ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH
end

class TestController < ActionController::Base
  caches_action :render_to_cache, :index
 
  def render_to_cache
    render_text "Render Cached"
  end
  alias :index :render_to_cache
end

class FileStoreTest < Test::Unit::TestCase
  def setup
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @controller = TestController.new
    @request.host = "hostname.com"
  end
  
  def teardown
    FileUtils.rm_rf(FILE_STORE_PATH)
  end

  def test_render_cached
    assert_fragment_cached { get :render_to_cache }
    assert_fragment_hit { get :render_to_cache }
  end


  private
    def assert_fragment_cached
      yield
      assert(TestLog.last_message.include?("Cached fragment:"), "--ERROR-- FileStore write failed ----")
      assert(!TestLog.last_message.include?("Couldn't create cache directory:"), "--ERROR-- FileStore create directory failed ----")
      TestLog.clear
    end
    
    def assert_fragment_hit
      yield
      assert(TestLog.last_message.include?("Fragment read:"), "--ERROR-- Fragment not found in FileStore ----")
      assert(!TestLog.last_message.include?("Cached fragment:"), "--ERROR-- Did cache ----")
      TestLog.clear
    end
endrequire File.dirname(__FILE__) + '/../abstract_unit'

class CaptureController < ActionController::Base
  def self.controller_name; "test"; end
  def self.controller_path; "test"; end

  def content_for
    render :layout => "talk_from_action"
  end
  
  def erb_content_for
    render :layout => "talk_from_action"
  end
  
  def block_content_for
    render :layout => "talk_from_action"
  end
  
  def non_erb_block_content_for
    render :layout => "talk_from_action"
  end

  def rescue_action(e) raise end
end

CaptureController.template_root = File.dirname(__FILE__) + "/../fixtures/"

class CaptureTest < Test::Unit::TestCase
  def setup
    @controller = CaptureController.new

    # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
    # a more accurate simulation of what happens in "real life".
    @controller.logger = Logger.new(nil)

    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @request.host = "www.nextangle.com"
  end

  def test_simple_capture
    get :capturing
    assert_equal "Dreamy days", @response.body.strip
  end
  
  def test_content_for
    get :content_for
    assert_equal expected_content_for_output, @response.body
  end
  
  def test_erb_content_for
    get :content_for
    assert_equal expected_content_for_output, @response.body
  end
  
  def test_block_content_for
    get :block_content_for
    assert_equal expected_content_for_output, @response.body
  end
  
  def test_non_erb_block_content_for
    get :non_erb_block_content_for
    assert_equal expected_content_for_output, @response.body
  end

  def test_update_element_with_capture
    get :update_element_with_capture
    assert_equal(
      "<script type=\"text/javascript\">\n//<![CDATA[\n$('products').innerHTML = '\\n  <p>Product 1</p>\\n  <p>Product 2</p>\\n';\n\n//]]>\n</script>" +
        "\n\n$('status').innerHTML = '\\n  <b>You bought something!</b>\\n';", 
      @response.body.strip
    )
  end
  
  private
  def expected_content_for_output
    "<title>Putting stuff in the title!</title>\n\nGreat stuff!"
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'
require 'action_controller/cgi_process'
require 'action_controller/cgi_ext/cgi_ext'


require 'stringio'

class CGITest < Test::Unit::TestCase
  def setup
    @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
    @query_string_with_nil = "action=create_customer&full_name="
    @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
    @query_string_with_amps  = "action=create_customer&name=Don%27t+%26+Does"
    @query_string_with_multiple_of_same_name = 
      "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
    @query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi"
    @query_string_without_equal = "action"
    @query_string_with_many_ampersands =
      "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
  end

  def test_query_string
    assert_equal(
      { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
      CGIMethods.parse_query_parameters(@query_string)
    )
  end
  
  def test_deep_query_string
    assert_equal({'x' => {'y' => {'z' => '10'}}}, CGIMethods.parse_query_parameters('x[y][z]=10'))
  end
  
  def test_deep_query_string_with_array
    assert_equal({'x' => {'y' => {'z' => ['10']}}}, CGIMethods.parse_query_parameters('x[y][z][]=10'))
    assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, CGIMethods.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
  end
  
  def test_query_string_with_nil
    assert_equal(
      { "action" => "create_customer", "full_name" => nil},
      CGIMethods.parse_query_parameters(@query_string_with_nil)
    )
  end

  def test_query_string_with_array
    assert_equal(
      { "action" => "create_customer", "selected" => ["1", "2", "3"]},
      CGIMethods.parse_query_parameters(@query_string_with_array)
    )
  end

  def test_query_string_with_amps
    assert_equal(
      { "action" => "create_customer", "name" => "Don't & Does"},
      CGIMethods.parse_query_parameters(@query_string_with_amps)
    )    
  end
  
  def test_query_string_with_many_equal
    assert_equal(
      { "action" => "create_customer", "full_name" => "abc=def=ghi"},
      CGIMethods.parse_query_parameters(@query_string_with_many_equal)
    )    
  end
  
  def test_query_string_without_equal
    assert_equal(
      { "action" => nil },
      CGIMethods.parse_query_parameters(@query_string_without_equal)
    )    
  end

  def test_query_string_with_many_ampersands
    assert_equal(
      { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
      CGIMethods.parse_query_parameters(@query_string_with_many_ampersands)
    )
  end

  def test_parse_params
    input = {
      "customers[boston][first][name]" => [ "David" ],
      "customers[boston][first][url]" => [ "http://David" ],
      "customers[boston][second][name]" => [ "Allan" ],
      "customers[boston][second][url]" => [ "http://Allan" ],
      "something_else" => [ "blah" ],
      "something_nil" => [ nil ],
      "something_empty" => [ "" ],
      "products[first]" => [ "Apple Computer" ],
      "products[second]" => [ "Pc" ]
    }
    
    expected_output =  {
      "customers" => {
        "boston" => {
          "first" => {
            "name" => "David",
            "url" => "http://David"
          },
          "second" => {
            "name" => "Allan",
            "url" => "http://Allan"
          }
        }
      },
      "something_else" => "blah",
      "something_empty" => "",
      "something_nil" => "",
      "products" => {
        "first" => "Apple Computer",
        "second" => "Pc"
      }
    }

    assert_equal expected_output, CGIMethods.parse_request_parameters(input)
  end
  
  def test_parse_params_from_multipart_upload
    mockup = Struct.new(:content_type, :original_filename)
    file = mockup.new('img/jpeg', 'foo.jpg')
    ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg')
  
    input = {
      "something" => [ StringIO.new("") ],
      "array_of_stringios" => [[ StringIO.new("One"), StringIO.new("Two") ]],
      "mixed_types_array" => [[ StringIO.new("Three"), "NotStringIO" ]],
      "mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", StringIO.new("StringIO")]],
      "ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", StringIO.new("StringIO")]],
      "products[string]" => [ StringIO.new("Apple Computer") ],
      "products[file]" => [ file ],
      "ie_products[string]" => [ StringIO.new("Microsoft") ],
      "ie_products[file]" => [ ie_file ]
    }
    
    expected_output =  {
      "something" => "",
      "array_of_stringios" => ["One", "Two"],
      "mixed_types_array" => [ "Three", "NotStringIO" ],
      "mixed_types_as_checkboxes" => {
         "strings" => {
            "nested" => [ file, "String", "StringIO" ]
         },
      },
      "ie_mixed_types_as_checkboxes" => {
         "strings" => {
            "nested" => [ ie_file, "String", "StringIO" ]
         },
      },
      "products" => {
        "string" => "Apple Computer",
        "file" => file
      },
      "ie_products" => {
        "string" => "Microsoft",
        "file" => ie_file
      }
    }

    params = CGIMethods.parse_request_parameters(input)
    assert_equal expected_output, params

    # Lone filenames are preserved.
    assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
    assert_equal 'foo.jpg', params['products']['file'].original_filename

    # But full Windows paths are reduced to their basename.
    assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
    assert_equal 'bar.jpg', params['ie_products']['file'].original_filename
  end
  
  def test_parse_params_with_file
    input = {
      "customers[boston][first][name]" => [ "David" ],
      "something_else" => [ "blah" ],
      "logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ]
    }
    
    expected_output = {
      "customers" => {
        "boston" => {
          "first" => {
            "name" => "David"
          }
        }
      },
      "something_else" => "blah",
      "logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path,
    }

    assert_equal expected_output, CGIMethods.parse_request_parameters(input)
  end

  def test_parse_params_with_array
    input = { "selected[]" =>  [ "1", "2", "3" ] }

    expected_output = { "selected" => [ "1", "2", "3" ] }

    assert_equal expected_output, CGIMethods.parse_request_parameters(input)
  end

  def test_parse_params_with_non_alphanumeric_name
    input     = { "a/b[c]" =>  %w(d) }
    expected  = { "a/b" => { "c" => "d" }}
    assert_equal expected, CGIMethods.parse_request_parameters(input)
  end

  def test_parse_params_with_single_brackets_in_middle
    input     = { "a/b[c]d" =>  %w(e) }
    expected  = { "a/b[c]d" => "e" }
    assert_equal expected, CGIMethods.parse_request_parameters(input)
  end

  def test_parse_params_with_separated_brackets
    input     = { "a/b@[c]d[e]" =>  %w(f) }
    expected  = { "a/b@" => { "c]d[e" => "f" }}
    assert_equal expected, CGIMethods.parse_request_parameters(input)
  end

  def test_parse_params_with_separated_brackets_and_array
    input     = { "a/b@[c]d[e][]" =>  %w(f) }
    expected  = { "a/b@" => { "c]d[e" => ["f"] }}
    assert_equal expected , CGIMethods.parse_request_parameters(input)
  end

  def test_parse_params_with_unmatched_brackets_and_array
    input     = { "a/b@[c][d[e][]" =>  %w(f) }
    expected  = { "a/b@" => { "c" => { "d[e" => ["f"] }}}
    assert_equal expected, CGIMethods.parse_request_parameters(input)
  end
end

class MultipartCGITest < Test::Unit::TestCase
  FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart'

  def setup
    ENV['REQUEST_METHOD'] = 'POST'
    ENV['CONTENT_LENGTH'] = '0'
    ENV['CONTENT_TYPE']   = 'multipart/form-data, boundary=AaB03x'
  end

  def test_single_parameter
    params = process('single_parameter')
    assert_equal({ 'foo' => 'bar' }, params)
  end

  def test_text_file
    params = process('text_file')
    assert_equal %w(file foo), params.keys.sort
    assert_equal 'bar', params['foo']

    file = params['file']
    assert_kind_of StringIO, file
    assert_equal 'file.txt', file.original_filename
    assert_equal "text/plain\r", file.content_type
    assert_equal 'contents', file.read
  end

  def test_large_text_file
    params = process('large_text_file')
    assert_equal %w(file foo), params.keys.sort
    assert_equal 'bar', params['foo']

    file = params['file']
    assert_kind_of Tempfile, file
    assert_equal 'file.txt', file.original_filename
    assert_equal "text/plain\r", file.content_type
    assert ('a' * 20480) == file.read
  end

  def test_binary_file
    params = process('binary_file')
    assert_equal %w(file flowers foo), params.keys.sort
    assert_equal 'bar', params['foo']

    file = params['file']
    assert_kind_of StringIO, file
    assert_equal 'file.txt', file.original_filename
    assert_equal "text/plain\r", file.content_type
    assert_equal 'contents', file.read

    file = params['flowers']
    assert_kind_of StringIO, file
    assert_equal 'flowers.jpg', file.original_filename
    assert_equal "image/jpeg\r", file.content_type
    assert_equal 19512, file.size
    #assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read
  end

  def test_mixed_files
    params = process('mixed_files')
    assert_equal %w(files foo), params.keys.sort
    assert_equal 'bar', params['foo']

    # Ruby CGI doesn't handle multipart/mixed for us.
    assert_kind_of StringIO, params['files']
    assert_equal 19756, params['files'].size
  end

  private
    def process(name)
      old_stdin = $stdin
      File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
        ENV['CONTENT_LENGTH'] = file.stat.size.to_s
        $stdin = file
        CGIMethods.parse_request_parameters CGI.new.params
      end
    ensure
      $stdin = old_stdin
    end
end


class CGIRequestTest < 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"}
    @fake_cgi = Struct.new(:env_table).new(@request_hash)
    @request = ActionController::CgiRequest.new(@fake_cgi)
  end
  
  def test_proxy_request
    assert_equal 'glu.ttono.us', @request.host_with_port
  end
  
  def test_http_host
    @request_hash.delete "HTTP_X_FORWARDED_HOST"
    @request_hash['HTTP_HOST'] = "rubyonrails.org:8080"
    assert_equal "rubyonrails.org:8080", @request.host_with_port
    
    @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
    assert_equal "www.secondhost.org", @request.host
  end
  
  def test_http_host_with_default_port_overrides_server_port
    @request_hash.delete "HTTP_X_FORWARDED_HOST"
    @request_hash['HTTP_HOST'] = "rubyonrails.org"
    assert_equal "rubyonrails.org", @request.host_with_port
  end

  def test_host_with_port_defaults_to_server_name_if_no_host_headers
    @request_hash.delete "HTTP_X_FORWARDED_HOST"
    @request_hash.delete "HTTP_HOST"
    assert_equal "glu.ttono.us:8007", @request.host_with_port
  end

  def test_host_with_port_falls_back_to_server_addr_if_necessary
    @request_hash.delete "HTTP_X_FORWARDED_HOST"
    @request_hash.delete "HTTP_HOST"
    @request_hash.delete "SERVER_NAME"
    assert_equal "207.7.108.53:8007", @request.host_with_port
  end

  def test_cookie_syntax_resilience
    cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
    assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"]
    assert_equal ["yes"], cookies["is_admin"]
    
    alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
    assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"]
    assert_equal ["yes"], alt_cookies["is_admin"]
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class CallerController < ActionController::Base
  def calling_from_controller
    render_component(:controller => "callee", :action => "being_called")
  end

  def calling_from_controller_with_params
    render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" })
  end

  def calling_from_controller_with_different_status_code
    render_component(:controller => "callee", :action => "blowing_up")
  end

  def calling_from_template
    render_template "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>"
  end

  def internal_caller
    render_template "Are you there? <%= render_component(:action => 'internal_callee') %>"
  end
  
  def internal_callee
    render_text "Yes, ma'am"
  end

  def set_flash
    render_component(:controller => "callee", :action => "set_flash")
  end

  def use_flash
    render_component(:controller => "callee", :action => "use_flash")
  end
  
  def calling_redirected
    render_component(:controller => "callee", :action => "redirected")
  end
  
  def calling_redirected_as_string
    render_template "<%= render_component(:controller => 'callee', :action => 'redirected') %>"
  end

  def rescue_action(e) raise end
end

class CalleeController < ActionController::Base
  def being_called
    render_text "#{@params["name"] || "Lady"} of the House, speaking"
  end
  
  def blowing_up
    render_text "It's game over, man, just game over, man!", "500 Internal Server Error"
  end
  
  def set_flash
    flash[:notice] = 'My stoney baby'
    render :text => 'flash is set'
  end
  
  def use_flash
    render :text => flash[:notice] || 'no flash'
  end
  
  def redirected
    redirect_to :controller => "callee", :action => "being_called"
  end

  def rescue_action(e) raise end
end

class ComponentsTest < Test::Unit::TestCase
  def setup
    @controller = CallerController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  def test_calling_from_controller
    get :calling_from_controller
    assert_equal "Lady of the House, speaking", @response.body
  end

  def test_calling_from_controller_with_params
    get :calling_from_controller_with_params
    assert_equal "David of the House, speaking", @response.body
  end
  
  def test_calling_from_controller_with_different_status_code
    get :calling_from_controller_with_different_status_code
    assert_equal 500, @response.response_code
  end

  def test_calling_from_template
    get :calling_from_template
    assert_equal "Ring, ring: Lady of the House, speaking", @response.body
  end
  
  def test_internal_calling
    get :internal_caller
    assert_equal "Are you there? Yes, ma'am", @response.body
  end
  
  def test_flash
    get :set_flash
    assert_equal 'My stoney baby', flash[:notice]
    get :use_flash
    assert_equal 'My stoney baby', @response.body
    get :use_flash
    assert_equal 'no flash', @response.body
  end
  
  def test_component_redirect_redirects
    get :calling_redirected
    
    assert_redirected_to :action => "being_called"
  end
  
  def test_component_multiple_redirect_redirects
    test_component_redirect_redirects
    test_internal_calling
  end
  
  def test_component_as_string_redirect_renders_redirecte_action
    get :calling_redirected_as_string
    
    assert_equal "Lady of the House, speaking", @response.body
  end
endrequire File.dirname(__FILE__) + '/../abstract_unit'

class CookieTest < Test::Unit::TestCase
  class TestController < ActionController::Base
    def authenticate_with_deprecated_writer
      cookie "name" => "user_name", "value" => "david"
      render_text "hello world"
    end

    def authenticate
      cookies["user_name"] = "david"
      render_text "hello world"
    end

    def authenticate_for_fourten_days
      cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
      render_text "hello world"
    end

    def authenticate_for_fourten_days_with_symbols
      cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) }
      render_text "hello world"
    end

    def set_multiple_cookies
      cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
      cookies["login"]     = "XJ-122"
      render_text "hello world"
    end

    def access_frozen_cookies
      @cookies["will"] = "work"
      render_text "hello world"
    end

    def rescue_action(e) raise end
  end

  def setup
    @request  = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new

    @request.host = "www.nextangle.com"
  end

  def test_setting_cookie_with_deprecated_writer
    @request.action = "authenticate_with_deprecated_writer"
    assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"]
  end

  def test_setting_cookie
    @request.action = "authenticate"
    assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], process_request.headers["cookie"]
  end

  def test_setting_cookie_for_fourteen_days
    @request.action = "authenticate_for_fourten_days"
    assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"]
  end

  def test_setting_cookie_for_fourteen_days_with_symbols
    @request.action = "authenticate_for_fourten_days"
    assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], process_request.headers["cookie"]
  end

  def test_multiple_cookies
    @request.action = "set_multiple_cookies"
    assert_equal 2, process_request.headers["cookie"].size
  end

  def test_setting_test_cookie
    @request.action = "access_frozen_cookies"
    assert_nothing_raised { process_request }
  end

  private
    def process_request
      TestController.process(@request, @response)
    end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class CustomHandler
  def initialize( view )
    @view = view
  end

  def render( template, local_assigns )
    [ template,
      local_assigns,
      @view ]
  end
end

class CustomHandlerTest < Test::Unit::TestCase
  def setup
    ActionView::Base.register_template_handler "foo", CustomHandler
    ActionView::Base.register_template_handler :foo2, CustomHandler
    @view = ActionView::Base.new
  end

  def test_custom_render
    result = @view.render_template( "foo", "hello <%= one %>", nil, :one => "two" )
    assert_equal(
      [ "hello <%= one %>", { :one => "two" }, @view ],
      result )
  end

  def test_custom_render2
    result = @view.render_template( "foo2", "hello <%= one %>", nil, :one => "two" )
    assert_equal(
      [ "hello <%= one %>", { :one => "two" }, @view ],
      result )
  end

  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" )
    assert_equal "hello two", result
  end
end
class << Object; alias_method :const_available?, :const_defined?; end
  
class ContentController < Class.new(ActionController::Base)
end
class NotAController
end
module Admin
  class << self; alias_method :const_available?, :const_defined?; end
  SomeConstant = 10
  class UserController < Class.new(ActionController::Base); end
  class NewsFeedController < Class.new(ActionController::Base); end
end

ActionController::Routing::Routes.draw do |map|
  map.route_one 'route_one', :controller => 'elsewhere', :action => 'flash_me'
  map.connect ':controller/:action/:id'
end
require File.dirname(__FILE__) + '/../abstract_unit'

class FilterParamController < ActionController::Base
end

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'=>'bar'},{'foo'=>'bar'},[]],
    [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
    [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
    [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
    [{'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)
      
      filter_words.push('blah')
      FilterParamController.filter_parameter_logging(*filter_words) do |key, value|
        value.reverse! if key =~ /bargain/
      end

      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)
    end
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class FilterTest < Test::Unit::TestCase
  class TestController < ActionController::Base
    before_filter :ensure_login
    after_filter  :clean_up

    def show
      render :inline => "ran action"
    end

    private
      def ensure_login
        @ran_filter ||= []
        @ran_filter << "ensure_login"
      end
      
      def clean_up
        @ran_after_filter ||= []
        @ran_after_filter << "clean_up"
      end
  end

  class RenderingController < ActionController::Base
    before_filter :render_something_else

    def show
      @ran_action = true
      render :inline => "ran action"
    end

    private
      def render_something_else
        render :inline => "something else"
      end
  end
  
  class ConditionalFilterController < ActionController::Base
    def show
      render :inline => "ran action"
    end

    def another_action
      render :inline => "ran action"
    end

    def show_without_filter
      render :inline => "ran action without filter"
    end

    private
      def ensure_login
        @ran_filter ||= []
        @ran_filter << "ensure_login"
      end

      def clean_up_tmp
        @ran_filter ||= []
        @ran_filter << "clean_up_tmp"
      end
      
      def rescue_action(e) raise(e) end
  end

  class ConditionalCollectionFilterController < ConditionalFilterController
    before_filter :ensure_login, :except => [ :show_without_filter, :another_action ]
  end

  class OnlyConditionSymController < ConditionalFilterController 
    before_filter :ensure_login, :only => :show
  end

  class ExceptConditionSymController < ConditionalFilterController
    before_filter :ensure_login, :except => :show_without_filter
  end

  class BeforeAndAfterConditionController < ConditionalFilterController
    before_filter :ensure_login, :only => :show
    after_filter  :clean_up_tmp, :only => :show 
  end
  
  class OnlyConditionProcController < ConditionalFilterController 
    before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true }
  end

  class ExceptConditionProcController < ConditionalFilterController
    before_filter(:except => :show_without_filter) {|c| c.assigns["ran_proc_filter"] = true }
  end

  class ConditionalClassFilter
    def self.filter(controller) controller.assigns["ran_class_filter"] = true end
  end

  class OnlyConditionClassController < ConditionalFilterController
    before_filter ConditionalClassFilter, :only => :show
  end

  class ExceptConditionClassController < ConditionalFilterController
    before_filter ConditionalClassFilter, :except => :show_without_filter
  end

  class AnomolousYetValidConditionController < ConditionalFilterController
    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 PrependingController < TestController
    prepend_before_filter :wonderful_life
    # skip_before_filter :fire_flash

    private
      def wonderful_life
        @ran_filter ||= []
        @ran_filter << "wonderful_life"
      end
  end

  class ConditionalSkippingController < TestController
    skip_before_filter :ensure_login, :only => [ :login ]
    skip_after_filter  :clean_up,     :only => [ :login ]
    
    before_filter :find_user, :only => [ :change_password ]

    def login
      render :inline => "ran action"
    end

    def change_password
      render :inline => "ran action"
    end
    
    protected
      def find_user
        @ran_filter ||= []
        @ran_filter << "find_user"
      end
  end
  
  class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
    before_filter :conditional_in_parent, :only => [:show, :another_action]
    after_filter  :conditional_in_parent, :only => [:show, :another_action]
    
    private
      
      def conditional_in_parent
        @ran_filter ||= []
        @ran_filter << 'conditional_in_parent'
      end
  end
  
  class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
    skip_before_filter :conditional_in_parent, :only => :another_action
    skip_after_filter  :conditional_in_parent, :only => :another_action
  end

  class ProcController < PrependingController
    before_filter(proc { |c| c.assigns["ran_proc_filter"] = true })
  end

  class ImplicitProcController < PrependingController
    before_filter { |c| c.assigns["ran_proc_filter"] = true }
  end

  class AuditFilter
    def self.filter(controller)
      controller.assigns["was_audited"] = true
    end
  end
  
  class AroundFilter
    def before(controller)
      @execution_log = "before"
      controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log
      controller.assigns["before_ran"] = true
    end

    def after(controller)
      controller.assigns["execution_log"] = @execution_log + " and after"
      controller.assigns["after_ran"] = true
      controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
    end    
  end

  class AppendedAroundFilter
    def before(controller)
      controller.class.execution_log << " before appended aroundfilter "
    end

    def after(controller)
      controller.class.execution_log << " after appended aroundfilter "
    end    
  end  
  
  class AuditController < ActionController::Base
    before_filter(AuditFilter)
    
    def show
      render_text "hello"
    end
  end

  class BadFilterController < ActionController::Base
    before_filter 2
    
    def show() "show" end
    
    protected
      def rescue_action(e) raise(e) end
  end

  class AroundFilterController < PrependingController
    around_filter AroundFilter.new
  end

  class MixedFilterController < PrependingController
    cattr_accessor :execution_log

    def initialize
      @@execution_log = ""
    end

    before_filter { |c| c.class.execution_log << " before procfilter "  }
    prepend_around_filter AroundFilter.new

    after_filter  { |c| c.class.execution_log << " after procfilter " }
    append_around_filter AppendedAroundFilter.new
  end
  
  class MixedSpecializationController < ActionController::Base
    class OutOfOrder < StandardError; end

    before_filter :first
    before_filter :second, :only => :foo

    def foo
      render_text 'foo'
    end

    def bar
      render_text 'bar'
    end

    protected
      def first
        @first = true
      end

      def second
        raise OutOfOrder unless @first
      end
  end

  class DynamicDispatchController < ActionController::Base
    before_filter :choose

    %w(foo bar baz).each do |action|
      define_method(action) { render :text => action }
    end

    private
      def choose
        self.action_name = params[:choose]
      end
  end

  def test_added_filter_to_inheritance_graph
    assert_equal [ :ensure_login ], TestController.before_filters
  end

  def test_base_class_in_isolation
    assert_equal [ ], ActionController::Base.before_filters
  end
  
  def test_prepending_filter
    assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters
  end
  
  def test_running_filters
    assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"]
  end

  def test_running_filters_with_proc
    assert test_process(ProcController).template.assigns["ran_proc_filter"]
  end
  
  def test_running_filters_with_implicit_proc
    assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"]
  end
  
  def test_running_filters_with_class
    assert test_process(AuditController).template.assigns["was_audited"]
  end

  def test_running_anomolous_yet_valid_condition_filters
    response = test_process(AnomolousYetValidConditionController)
    assert_equal %w( ensure_login ), response.template.assigns["ran_filter"]
    assert response.template.assigns["ran_class_filter"]
    assert response.template.assigns["ran_proc_filter1"]
    assert response.template.assigns["ran_proc_filter2"]
    
    response = test_process(AnomolousYetValidConditionController, "show_without_filter")
    assert_equal nil, response.template.assigns["ran_filter"]
    assert !response.template.assigns["ran_class_filter"]
    assert !response.template.assigns["ran_proc_filter1"]
    assert !response.template.assigns["ran_proc_filter2"]
  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"]
    assert_equal nil, test_process(ConditionalCollectionFilterController, "another_action").template.assigns["ran_filter"]
  end

  def test_running_only_condition_filters
    assert_equal %w( ensure_login ), test_process(OnlyConditionSymController).template.assigns["ran_filter"]
    assert_equal nil, test_process(OnlyConditionSymController, "show_without_filter").template.assigns["ran_filter"]

    assert test_process(OnlyConditionProcController).template.assigns["ran_proc_filter"]
    assert !test_process(OnlyConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]

    assert test_process(OnlyConditionClassController).template.assigns["ran_class_filter"]
    assert !test_process(OnlyConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
  end

  def test_running_except_condition_filters
    assert_equal %w( ensure_login ), test_process(ExceptConditionSymController).template.assigns["ran_filter"]
    assert_equal nil, test_process(ExceptConditionSymController, "show_without_filter").template.assigns["ran_filter"]

    assert test_process(ExceptConditionProcController).template.assigns["ran_proc_filter"]
    assert !test_process(ExceptConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]

    assert test_process(ExceptConditionClassController).template.assigns["ran_class_filter"]
    assert !test_process(ExceptConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
  end

  def test_running_before_and_after_condition_filters
    assert_equal %w( ensure_login clean_up_tmp), test_process(BeforeAndAfterConditionController).template.assigns["ran_filter"]
    assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"]
  end
  
  def test_bad_filter
    assert_raises(ActionController::ActionControllerError) { 
      test_process(BadFilterController)
    }
  end
  
  def test_around_filter
    controller = test_process(AroundFilterController)
    assert controller.template.assigns["before_ran"]
    assert controller.template.assigns["after_ran"]
  end
 
  def test_having_properties_in_around_filter
    controller = test_process(AroundFilterController)
    assert_equal "before and after", controller.template.assigns["execution_log"]
  end

  def test_prepending_and_appending_around_filter
    controller = test_process(MixedFilterController)
    assert_equal " before aroundfilter  before procfilter  before appended aroundfilter " +
                 " after appended aroundfilter  after aroundfilter  after procfilter ", 
                 MixedFilterController.execution_log
  end
  
  def test_rendering_breaks_filtering_chain
    response = test_process(RenderingController)
    assert_equal "something else", response.body
    assert !response.template.assigns["ran_action"]
  end

  def test_filters_with_mixed_specialization_run_in_order
    assert_nothing_raised do
      response = test_process(MixedSpecializationController, 'bar')
      assert_equal 'bar', response.body
    end

    assert_nothing_raised do
      response = test_process(MixedSpecializationController, 'foo')
      assert_equal 'foo', response.body
    end
  end

  def test_dynamic_dispatch
    %w(foo bar baz).each do |action|
      request = ActionController::TestRequest.new
      request.query_parameters[:choose] = action
      response = DynamicDispatchController.process(request, ActionController::TestResponse.new)
      assert_equal action, response.body
    end
  end

  def test_conditional_skipping_of_filters
    assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"]
    assert_equal %w( ensure_login find_user ), test_process(ConditionalSkippingController, "change_password").template.assigns["ran_filter"]

    assert_nil test_process(ConditionalSkippingController, "login").template.controller.instance_variable_get("@ran_after_filter")
    assert_equal %w( clean_up ), test_process(ConditionalSkippingController, "change_password").template.controller.instance_variable_get("@ran_after_filter")
  end

  def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional
    assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter']
    assert_nil test_process(ChildOfConditionalParentController, 'another_action').template.assigns['ran_filter']
  end

  private
    def test_process(controller, action = "show")
      request = ActionController::TestRequest.new
      request.action = action
      controller.process(request, ActionController::TestResponse.new)
    end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class FlashTest < Test::Unit::TestCase
  class TestController < ActionController::Base
    def set_flash
      flash["that"] = "hello"
      render :inline => "hello"
    end

    def set_flash_now
      flash.now["that"] = "hello"
      flash.now["foo"] ||= "bar"
      flash.now["foo"] ||= "err"
      @flashy = flash.now["that"]
      @flash_copy = {}.update flash
      render :inline => "hello"
    end

    def attempt_to_use_flash_now
      @flash_copy = {}.update flash
      @flashy = flash["that"]
      render :inline => "hello"
    end

    def use_flash
      @flash_copy = {}.update flash
      @flashy = flash["that"]
      render :inline => "hello"
    end

    def use_flash_and_keep_it
      @flash_copy = {}.update flash
      @flashy = flash["that"]
      silence_warnings { keep_flash }
      render :inline => "hello"
    end

    def rescue_action(e)
      raise unless ActionController::MissingTemplate === e
    end
  end

  def setup
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @controller = TestController.new
  end

  def test_flash
    get :set_flash

    get :use_flash
    assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
    assert_equal "hello", @response.template.assigns["flashy"]

    get :use_flash
    assert_nil @response.template.assigns["flash_copy"]["that"], "On second flash"
  end

  def test_keep_flash
    get :set_flash
    
    get :use_flash_and_keep_it
    assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
    assert_equal "hello", @response.template.assigns["flashy"]

    get :use_flash
    assert_equal "hello", @response.template.assigns["flash_copy"]["that"], "On second flash"

    get :use_flash
    assert_nil @response.template.assigns["flash_copy"]["that"], "On third flash"
  end
  
  def test_flash_now
    get :set_flash_now
    assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
    assert_equal "bar"  , @response.template.assigns["flash_copy"]["foo"]
    assert_equal "hello", @response.template.assigns["flashy"]

    get :attempt_to_use_flash_now
    assert_nil @response.template.assigns["flash_copy"]["that"]
    assert_nil @response.template.assigns["flash_copy"]["foo"]
    assert_nil @response.template.assigns["flashy"]
  end 
endrequire 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
  
  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

  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
require File.dirname(__FILE__) + '/../abstract_unit'

class TestController < ActionController::Base
  attr_accessor :delegate_attr
  def delegate_method() end
  def rescue_action(e) raise end
end

module Fun
  class GamesController < ActionController::Base
    def render_hello_world
      render :inline => "hello: <%= stratego %>"
    end

    def rescue_action(e) raise end
  end

  class PDFController < ActionController::Base
    def test
      render :inline => "test: <%= foobar %>"
    end

    def rescue_action(e) raise end
  end
end

module LocalAbcHelper
  def a() end
  def b() end
  def c() end
end

class HelperTest < Test::Unit::TestCase
  def setup
    # Increment symbol counter.
    @symbol = (@@counter ||= 'A0').succ!.dup

    # Generate new controller class.
    controller_class_name = "Helper#{@symbol}Controller"
    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 }
    assert_equal [], missing_methods
  end

  def test_declare_helper
    require 'abc_helper'
    self.test_helper = AbcHelper
    assert_equal expected_helper_methods, missing_methods
    assert_nothing_raised { @controller_class.helper :abc }
    assert_equal [], missing_methods
  end

  def test_declare_missing_helper
    assert_equal expected_helper_methods, missing_methods
    assert_raise(MissingSourceFile) { @controller_class.helper :missing }
  end

  def test_declare_missing_file_from_helper
    require 'broken_helper'
    rescue LoadError => e
      assert_nil /\bbroken_helper\b/.match(e.to_s)[1]
  end

  def test_helper_block
    assert_nothing_raised {
      @controller_class.helper { def block_helper_method; end }
    }
    assert master_helper_methods.include?('block_helper_method')
  end

  def test_helper_block_include
    assert_equal expected_helper_methods, missing_methods
    assert_nothing_raised {
      @controller_class.helper { include TestHelper }
    }
    assert [], missing_methods
  end

  def test_helper_method
    assert_nothing_raised { @controller_class.helper_method :delegate_method }
    assert master_helper_methods.include?('delegate_method')
  end

  def test_helper_attr
    assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
    assert master_helper_methods.include?('delegate_attr')
    assert master_helper_methods.include?('delegate_attr=')
  end

  def test_helper_for_nested_controller
    request  = ActionController::TestRequest.new
    response = ActionController::TestResponse.new
    request.action = 'render_hello_world'

    assert_equal 'hello: Iz guuut!', Fun::GamesController.process(request, response).body
  end

  def test_helper_for_acronym_controller
    request  = ActionController::TestRequest.new
    response = ActionController::TestResponse.new
    request.action = 'test'

    assert_equal 'test: baz', Fun::PDFController.process(request, response).body
  end

  private
    def expected_helper_methods
      TestHelper.instance_methods
    end

    def master_helper_methods
      @controller_class.master_helper_module.instance_methods
    end

    def missing_methods
      expected_helper_methods - master_helper_methods
    end

    def test_helper=(helper_module)
      silence_warnings { self.class.const_set('TestHelper', helper_module) }
    end
end


class IsolatedHelpersTest < Test::Unit::TestCase
  class A < ActionController::Base
    def index
      render :inline => '<%= shout %>'
    end

    def rescue_action(e) raise end
  end

  class B < A
    helper { def shout; 'B' end }

    def index
      render :inline => '<%= shout %>'
    end
  end

  class C < A
    helper { def shout; 'C' end }

    def index
      render :inline => '<%= shout %>'
    end
  end

  def setup
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @request.action = 'index'
  end

  def test_helper_in_a
    assert_raise(NameError) { A.process(@request, @response) }
  end

  def test_helper_in_b
    assert_equal 'B', B.process(@request, @response).body
  end

  def test_helper_in_c
    assert_equal 'C', C.process(@request, @response).body
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

# The template_root must be set on Base and not LayoutTest so that LayoutTest's inherited method has access to
# the template_root when looking for a layout
ActionController::Base.template_root = File.dirname(__FILE__) + '/../fixtures/layout_tests/'

class LayoutTest < ActionController::Base
  def self.controller_path; 'views' end
end

# Restore template root to be unset
ActionController::Base.template_root = nil

class ProductController < LayoutTest
end

class ItemController < LayoutTest
end

class ThirdPartyTemplateLibraryController < LayoutTest
end

module ControllerNameSpace
end

class ControllerNameSpace::NestedController < LayoutTest
end

class MabView
  def initialize(view)
  end
  
  def render(text, locals = {})
    text
  end
end

ActionView::Base::register_template_handler :mab, MabView

class LayoutAutoDiscoveryTest < Test::Unit::TestCase
  def setup
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @request.host = "www.nextangle.com"
  end
  
  def test_application_layout_is_default_when_no_controller_match
    @controller = ProductController.new
    get :hello
    assert_equal 'layout_test.rhtml hello.rhtml', @response.body
  end
  
  def test_controller_name_layout_name_match
    @controller = ItemController.new
    get :hello
    assert_equal 'item.rhtml hello.rhtml', @response.body
  end
  
  def test_third_party_template_library_auto_discovers_layout
    @controller = ThirdPartyTemplateLibraryController.new
    get :hello
    assert_equal 'layouts/third_party_template_library', @controller.active_layout
    assert_equal 'Mab', @response.body
  end
  
  def test_namespaced_controllers_auto_detect_layouts
    @controller = ControllerNameSpace::NestedController.new
    get :hello
    assert_equal 'layouts/controller_name_space/nested', @controller.active_layout
    assert_equal 'controller_name_space/nested.rhtml hello.rhtml', @response.body
  end
endrequire File.dirname(__FILE__) + '/../abstract_unit'

class RespondToController < ActionController::Base
  layout :set_layout

  def html_xml_or_rss
    respond_to do |type|
      type.html { render :text => "HTML"    }
      type.xml  { render :text => "XML"     }
      type.rss  { render :text => "RSS"     }
      type.all  { render :text => "Nothing" }
    end
  end
  
  def js_or_html
    respond_to do |type|
      type.html { render :text => "HTML"    }
      type.js   { render :text => "JS"      }
      type.all  { render :text => "Nothing" }
    end
  end

  def html_or_xml
    respond_to do |type|
      type.html { render :text => "HTML"    }
      type.xml  { render :text => "XML"     }
      type.all  { render :text => "Nothing" }
    end
  end
  
  def just_xml
    respond_to do |type|
      type.xml  { render :text => "XML" }
    end
  end
  
  def using_defaults
    respond_to do |type|
      type.html
      type.js
      type.xml
    end
  end
  
  def using_defaults_with_type_list
    respond_to(:html, :js, :xml)
  end
  
  def made_for_content_type
    respond_to do |type|
      type.rss  { render :text => "RSS"  }
      type.atom { render :text => "ATOM" }
      type.all  { render :text => "Nothing" }
    end
  end

  def custom_type_handling
    respond_to do |type|
      type.html { render :text => "HTML"    }
      type.custom("application/crazy-xml")  { render :text => "Crazy XML"  }
      type.all  { render :text => "Nothing" }
    end
  end

  def handle_any
    respond_to do |type|
      type.html { render :text => "HTML" }
      type.any(:js, :xml) { render :text => "Either JS or XML" }
    end
  end

  def all_types_with_layout
    respond_to do |type|
      type.html
      type.js
    end
  end

  def rescue_action(e)
    raise
  end
  
  protected
    def set_layout
      if action_name == "all_types_with_layout"
        "standard"
      end
    end
end

RespondToController.template_root = File.dirname(__FILE__) + "/../fixtures/"

class MimeControllerTest < Test::Unit::TestCase
  def setup
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @controller = RespondToController.new
    @request.host = "www.example.com"
  end
  
  def test_html
    @request.env["HTTP_ACCEPT"] = "text/html"
    get :js_or_html
    assert_equal 'HTML', @response.body
    
    get :html_or_xml
    assert_equal 'HTML', @response.body

    get :just_xml
    assert_response 406
  end

  def test_all
    @request.env["HTTP_ACCEPT"] = "*/*"
    get :js_or_html
    assert_equal 'HTML', @response.body # js is not part of all

    get :html_or_xml
    assert_equal 'HTML', @response.body

    get :just_xml
    assert_equal 'XML', @response.body
  end

  def test_xml
    @request.env["HTTP_ACCEPT"] = "application/xml"
    get :html_xml_or_rss
    assert_equal 'XML', @response.body
  end

  def test_js_or_html
    @request.env["HTTP_ACCEPT"] = "text/javascript, text/html"
    get :js_or_html
    assert_equal 'JS', @response.body

    get :html_or_xml
    assert_equal 'HTML', @response.body

    get :just_xml
    assert_response 406
  end

  def test_js_or_anything
    @request.env["HTTP_ACCEPT"] = "text/javascript, */*"
    get :js_or_html
    assert_equal 'JS', @response.body

    get :html_or_xml
    assert_equal 'HTML', @response.body

    get :just_xml
    assert_equal 'XML', @response.body
  end
  
  def test_using_defaults
    @request.env["HTTP_ACCEPT"] = "*/*"
    get :using_defaults
    assert_equal 'Hello world!', @response.body

    @request.env["HTTP_ACCEPT"] = "text/javascript"
    get :using_defaults
    assert_equal '$("body").visualEffect("highlight");', @response.body

    @request.env["HTTP_ACCEPT"] = "application/xml"
    get :using_defaults
    assert_equal "<p>Hello world!</p>\n", @response.body
  end
  
  def test_using_defaults_with_type_list
    @request.env["HTTP_ACCEPT"] = "*/*"
    get :using_defaults_with_type_list
    assert_equal 'Hello world!', @response.body

    @request.env["HTTP_ACCEPT"] = "text/javascript"
    get :using_defaults_with_type_list
    assert_equal '$("body").visualEffect("highlight");', @response.body

    @request.env["HTTP_ACCEPT"] = "application/xml"
    get :using_defaults_with_type_list
    assert_equal "<p>Hello world!</p>\n", @response.body
  end
  
  def test_with_content_type
    @request.env["CONTENT_TYPE"] = "application/atom+xml"
    get :made_for_content_type
    assert_equal "ATOM", @response.body

    @request.env["CONTENT_TYPE"] = "application/rss+xml"
    get :made_for_content_type
    assert_equal "RSS", @response.body
  end
  
  def test_synonyms
    @request.env["HTTP_ACCEPT"] = "application/javascript"
    get :js_or_html
    assert_equal 'JS', @response.body

    @request.env["HTTP_ACCEPT"] = "application/x-xml"
    get :html_xml_or_rss
    assert_equal "XML", @response.body
  end
  
  def test_custom_types
    @request.env["HTTP_ACCEPT"] = "application/crazy-xml"
    get :custom_type_handling
    assert_equal 'Crazy XML', @response.body

    @request.env["HTTP_ACCEPT"] = "text/html"
    get :custom_type_handling
    assert_equal 'HTML', @response.body
  end

  def test_xhtml_alias
    @request.env["HTTP_ACCEPT"] = "application/xhtml+xml,application/xml"
    get :html_or_xml
    assert_equal 'HTML', @response.body
  end
  
  def test_firefox_simulation
    @request.env["HTTP_ACCEPT"] = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
    get :html_or_xml
    assert_equal 'HTML', @response.body
  end

  def test_handle_any
    @request.env["HTTP_ACCEPT"] = "*/*"
    get :handle_any
    assert_equal 'HTML', @response.body

    @request.env["HTTP_ACCEPT"] = "text/javascript"
    get :handle_any
    assert_equal 'Either JS or XML', @response.body

    @request.env["HTTP_ACCEPT"] = "text/xml"
    get :handle_any
    assert_equal 'Either JS or XML', @response.body
  end
  
  def test_all_types_with_layout
    @request.env["HTTP_ACCEPT"] = "text/javascript"
    get :all_types_with_layout
    assert_equal 'RJS for all_types_with_layout', @response.body

    @request.env["HTTP_ACCEPT"] = "text/html"
    get :all_types_with_layout
    assert_equal '<html>HTML for all_types_with_layout</html>', @response.body
  end

  def test_xhr
    xhr :get, :js_or_html
    assert_equal 'JS', @response.body

    xhr :get, :using_defaults
    assert_equal '$("body").visualEffect("highlight");', @response.body
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class MimeTypeTest < Test::Unit::TestCase
  Mime::PNG   = Mime::Type.new("image/png")
  Mime::PLAIN = Mime::Type.new("text/plain")

  def test_parse_single
    Mime::LOOKUP.keys.each do |mime_type|
      assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
    end
  end

  def test_parse_without_q
    accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,*/*"
    expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::PLAIN, Mime::ALL]
    assert_equal expect, Mime::Type.parse(accept)
  end

  def test_parse_with_q
    accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,*/*; q=0.2"
    expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PLAIN, Mime::YAML, Mime::ALL]
    assert_equal expect, Mime::Type.parse(accept)
  end
endrequire File.dirname(__FILE__) + '/../abstract_unit'

silence_warnings { Customer = Struct.new("Customer", :name) }

module Fun
  class GamesController < ActionController::Base
    def hello_world
    end
  end
end

module NewRenderTestHelper
  def rjs_helper_method_from_module
    page.visual_effect :highlight
  end
end

class NewRenderTestController < ActionController::Base
  layout :determine_layout

  def self.controller_name; "test"; end
  def self.controller_path; "test"; end

  def hello_world
  end

  def render_hello_world
    render :template => "test/hello_world"
  end

  def render_hello_world_from_variable
    @person = "david"
    render :text => "hello #{@person}"
  end

  def render_action_hello_world
    render :action => "hello_world"
  end

  def render_action_hello_world_as_symbol
    render :action => :hello_world
  end
  
  def render_text_hello_world
    render :text => "hello world"
  end
 
  def render_text_hello_world_with_layout
    @variable_for_layout = ", I'm here!"
    render :text => "hello world", :layout => true
  end

  def hello_world_with_layout_false
    render :layout => false
  end

  def render_custom_code
    render :text => "hello world", :status => "404 Moved"
  end

  def render_file_with_instance_variables
    @secret = 'in the sauce'
    path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.rhtml')
    render :file => path
  end

  def render_file_with_locals
    path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.rhtml')
    render :file => path, :locals => {:secret => 'in the sauce'} 
  end

  def render_file_not_using_full_path
    @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'
    render :file => 'test/dot.directory/render_file_with_ivar', :use_full_path => true
  end

  def render_xml_hello
    @name = "David"
    render :template => "test/hello"
  end

  def greeting
    # let's just rely on the template
  end

  def layout_test
    render :action => "hello_world"
  end

  def layout_test_with_different_layout
    render :action => "hello_world", :layout => "standard"
  end
  
  def rendering_without_layout
    render :action => "hello_world", :layout => false
  end

  def layout_overriding_layout
    render :action => "hello_world", :layout => "standard"
  end
  
  def rendering_nothing_on_layout
    render :nothing => true
  end
  
  def builder_layout_test
    render :action => "hello"
  end

  def partials_list
    @test_unchanged = 'hello'
    @customers = [ Customer.new("david"), Customer.new("mary") ]
    render :action => "list"
  end

  def partial_only
    render :partial => true
  end

  def partial_only_with_layout
    render :partial => "partial_only", :layout => true
  end
  
  def partial_with_locals
    render :partial => "customer", :locals => { :customer => Customer.new("david") } 
  end
  
  def partial_collection
    render :partial => "customer", :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 empty_partial_collection
    render :partial => "customer", :collection => []
  end
  
  def partial_with_hash_object
    render :partial => "hash_object", :object => {:first_name => "Sam"}
  end

  def partial_with_implicit_local_assignment
    @customer = Customer.new("Marcel")
    render :partial => "customer"
  end
  
  def hello_in_a_string
    @customers = [ Customer.new("david"), Customer.new("mary") ]
    render :text =>  "How's there? #{render_to_string("test/list")}"
  end
  
  def accessing_params_in_template
    render :inline =>  "Hello: <%= params[:name] %>"
  end

  def accessing_params_in_template_with_layout
    render :layout => nil, :inline =>  "Hello: <%= params[:name] %>"
  end

  def render_with_explicit_template
    render "test/hello_world"
  end

  def double_render
    render :text => "hello"
    render :text => "world"
  end

  def double_redirect
    redirect_to :action => "double_render"
    redirect_to :action => "double_render"
  end

  def render_and_redirect
    render :text => "hello"
    redirect_to :action => "double_render"
  end

  def rendering_with_conflicting_local_vars
    @name = "David"
    def @template.name() nil end
    render :action => "potential_conflicts"
  end

  def hello_world_from_rxml_using_action
    render :action => "hello_world.rxml"
  end

  def hello_world_from_rxml_using_template
    render :template => "test/hello_world.rxml"
  end

  helper NewRenderTestHelper
  helper do 
    def rjs_helper_method(value)
      page.visual_effect :highlight, value
    end
  end

  def enum_rjs_test
    render :update do |page|
      page.select('.product').each do |value|
        page.rjs_helper_method_from_module
        page.rjs_helper_method(value)
        page.sortable(value, :url => { :action => "order" })
        page.draggable(value)
      end
    end
  end

  def delete_with_js
    @project_id = 4
  end

  def render_js_with_explicit_template
    @project_id = 4
    render :template => 'test/delete_with_js'
  end

  def render_js_with_explicit_action_template
    @project_id = 4
    render :action => 'delete_with_js'
  end

  def update_page
    render :update do |page|
      page.replace_html 'balance', '$37,000,000.00'
      page.visual_effect :highlight, 'balance'
    end
  end
  
  def update_page_with_instance_variables
    @money = '$37,000,000.00'
    @div_id = 'balance'
    render :update do |page|
      page.replace_html @div_id, @money
      page.visual_effect :highlight, @div_id
    end
  end

  def action_talk_to_layout
    # Action template sets variable that's picked up by layout
  end

  def render_text_with_assigns
    @hello = "world"
    render :text => "foo"
  end

  def yield_content_for
    render :action => "content_for", :layout => "yield"
  end

  def rescue_action(e) raise end
    
  private
    def determine_layout
      case action_name 
        when "hello_world", "layout_test", "rendering_without_layout",
             "rendering_nothing_on_layout", "render_text_hello_world",
             "render_text_hello_world_with_layout",
             "hello_world_with_layout_false",
             "partial_only", "partial_only_with_layout",
             "accessing_params_in_template",
             "accessing_params_in_template_with_layout",
             "render_with_explicit_template",
             "render_js_with_explicit_template",
             "render_js_with_explicit_action_template",
             "delete_with_js", "update_page", "update_page_with_instance_variables"
    
          "layouts/standard"
        when "builder_layout_test"
          "layouts/builder"
        when "action_talk_to_layout", "layout_overriding_layout"
          "layouts/talk_from_action"
      end
    end
end

NewRenderTestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/"

class NewRenderTest < Test::Unit::TestCase
  def setup
    @controller = NewRenderTestController.new

    # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
    # a more accurate simulation of what happens in "real life".
    @controller.logger = Logger.new(nil)

    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new

    @request.host = "www.nextangle.com"
  end

  def test_simple_show
    get :hello_world
    assert_response :success
    assert_template "test/hello_world"
    assert_equal "<html>Hello world!</html>", @response.body
  end

  def test_do_with_render
    get :render_hello_world
    assert_template "test/hello_world"
  end

  def test_do_with_render_from_variable
    get :render_hello_world_from_variable
    assert_equal "hello david", @response.body
  end

  def test_do_with_render_action
    get :render_action_hello_world
    assert_template "test/hello_world"
  end

  def test_do_with_render_action_as_symbol
    get :render_action_hello_world_as_symbol
    assert_template "test/hello_world"
  end

  def test_do_with_render_text
    get :render_text_hello_world
    assert_equal "hello world", @response.body
  end

  def test_do_with_render_text_and_layout
    get :render_text_hello_world_with_layout
    assert_equal "<html>hello world, I'm here!</html>", @response.body
  end

  def test_do_with_render_action_and_layout_false
    get :hello_world_with_layout_false
    assert_equal 'Hello world!', @response.body
  end

  def test_do_with_render_custom_code
    get :render_custom_code
    assert_response :missing
  end

  def test_render_file_with_instance_variables
    get :render_file_with_instance_variables
    assert_equal "The secret is in the sauce\n", @response.body
  end

  def test_render_file_not_using_full_path
    get :render_file_not_using_full_path 
    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
  end

  def test_render_file_with_locals
    get :render_file_with_locals
    assert_equal "The secret is in the sauce\n", @response.body
  end

  def test_attempt_to_access_object_method
    assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
  end

  def test_private_methods
    assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
  end

  def test_access_to_request_in_view
    view_internals_old_value = ActionController::Base.view_controller_internals

    ActionController::Base.view_controller_internals = false
    ActionController::Base.protected_variables_cache = nil

    get :hello_world
    assert_nil(assigns["request"])

    ActionController::Base.view_controller_internals = true
    ActionController::Base.protected_variables_cache = nil

    get :hello_world
    assert_kind_of ActionController::AbstractRequest, assigns["request"]

    ActionController::Base.view_controller_internals = view_internals_old_value
    ActionController::Base.protected_variables_cache = nil
  end
  
  def test_render_xml
    get :render_xml_hello
    assert_equal "<html>\n  <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
  end

  def test_enum_rjs_test
    get :enum_rjs_test
    assert_equal <<-EOS.strip, @response.body
$$(".product").each(function(value, index) {
new Effect.Highlight(element,{});
new Effect.Highlight(value,{});
Sortable.create(value, {onUpdate:function(){new Ajax.Request('/test/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(value)})}});
new Draggable(value, {});
});
EOS
  end

  def test_render_xml_with_default
    get :greeting
    assert_equal "<p>This is grand!</p>\n", @response.body
  end

  def test_render_rjs_with_default
    get :delete_with_js
    assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body
  end

  def test_render_rjs_template_explicitly
    get :render_js_with_explicit_template
    assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body
  end

  def test_rendering_rjs_action_explicitly
    get :render_js_with_explicit_action_template
    assert_equal %!["person"].each(Element.remove);\nnew Effect.Highlight(\"project-4\",{});!, @response.body
  end

  def test_layout_rendering
    get :layout_test
    assert_equal "<html>Hello world!</html>", @response.body
  end

  def test_layout_test_with_different_layout
    get :layout_test_with_different_layout
    assert_equal "<html>Hello world!</html>", @response.body
  end

  def test_rendering_without_layout
    get :rendering_without_layout
    assert_equal "Hello world!", @response.body
  end

  def test_layout_overriding_layout
    get :layout_overriding_layout
    assert_no_match %r{<title>}, @response.body
  end

  def test_rendering_nothing_on_layout
    get :rendering_nothing_on_layout
    assert_equal " ", @response.body
  end

  def test_render_xml_with_layouts
    get :builder_layout_test
    assert_equal "<wrapper>\n<html>\n  <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
  end

  def test_partial_only
    get :partial_only
    assert_equal "only partial", @response.body
  end

  def test_partial_only_with_layout
    get :partial_only_with_layout
    assert_equal "<html>only partial</html>", @response.body
  end

  def test_render_to_string
    get :hello_in_a_string
    assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
  end

  def test_nested_rendering
    get :hello_world
    assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body
  end

  def test_accessing_params_in_template
    get :accessing_params_in_template, :name => "David"
    assert_equal "Hello: David", @response.body
  end

  def test_accessing_params_in_template_with_layout
    get :accessing_params_in_template_with_layout, :name => "David"
    assert_equal "<html>Hello: David</html>", @response.body
  end

  def test_render_with_explicit_template
    get :render_with_explicit_template
    assert_response :success
  end

  def test_double_render
    assert_raises(ActionController::DoubleRenderError) { get :double_render }
  end

  def test_double_redirect
    assert_raises(ActionController::DoubleRenderError) { get :double_redirect }
  end

  def test_render_and_redirect
    assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
  end

  def test_rendering_with_conflicting_local_vars
    get :rendering_with_conflicting_local_vars
    assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body)
  end

  def test_action_talk_to_layout
    get :action_talk_to_layout
    assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body
  end

  def test_partials_list
    get :partials_list
    assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
  end
  
  def test_partial_with_locals
    get :partial_with_locals
    assert_equal "Hello: david", @response.body
  end

  def test_partial_collection
    get :partial_collection
    assert_equal "Hello: davidHello: mary", @response.body
  end

  def test_partial_collection_with_locals
    get :partial_collection_with_locals
    assert_equal "Bonjour: davidBonjour: mary", @response.body
  end

  def test_empty_partial_collection
    get :empty_partial_collection
    assert_equal " ", @response.body
  end

  def test_partial_with_hash_object
    get :partial_with_hash_object
    assert_equal "Sam", @response.body
  end

  def test_partial_with_implicit_local_assignment
    get :partial_with_implicit_local_assignment
    assert_equal "Hello: Marcel", @response.body
  end
  
  def test_render_text_with_assigns
    get :render_text_with_assigns
    assert_equal "world", assigns["hello"]
  end
  
  def test_update_page
    get :update_page
    assert_template nil
    assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type']
    assert_equal 2, @response.body.split($/).length
  end
  
  def test_update_page_with_instance_variables
    get :update_page_with_instance_variables
    assert_template nil
    assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type']
    assert_match /balance/, @response.body
    assert_match /\$37/, @response.body
  end
  
  def test_yield_content_for
    get :yield_content_for
    assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
  end


  def test_overwritting_rendering_relative_file_with_extension
    get :hello_world_from_rxml_using_template
    assert_equal "<html>\n  <p>Hello</p>\n</html>\n", @response.body

    get :hello_world_from_rxml_using_action
    assert_equal "<html>\n  <p>Hello</p>\n</html>\n", @response.body
  end
end
require 'test/unit'
require 'cgi'
require 'stringio'
require File.dirname(__FILE__) + '/../../lib/action_controller/cgi_ext/raw_post_data_fix'

class RawPostDataTest < Test::Unit::TestCase
  def setup
    ENV['REQUEST_METHOD'] = 'POST'
    ENV['CONTENT_TYPE'] = ''
    ENV['CONTENT_LENGTH'] = '0'
  end

  def test_raw_post_data
    process_raw "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
  end

  private
    def process_raw(query_string)
      old_stdin = $stdin
      begin
        $stdin = StringIO.new(query_string.dup)
        ENV['CONTENT_LENGTH'] = $stdin.size.to_s
        CGI.new
        assert_not_nil ENV['RAW_POST_DATA']
        assert ENV['RAW_POST_DATA'].frozen?
        assert_equal query_string, ENV['RAW_POST_DATA']
      ensure
        $stdin = old_stdin
      end
    end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class RedirectController < ActionController::Base
  def simple_redirect
    redirect_to :action => "hello_world"
  end
  
  def method_redirect
    redirect_to :dashbord_url, 1, "hello"
  end
  
  def host_redirect
    redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host'
  end

  def module_redirect
    redirect_to :controller => 'module_test/module_redirect', :action => "hello_world"
  end

  def redirect_with_assigns
    @hello = "world"
    redirect_to :action => "hello_world"
  end

  def redirect_to_back
    redirect_to :back
  end

  def rescue_errors(e) raise e end
    
  def rescue_action(e) raise end
  
  protected
    def dashbord_url(id, message)
      url_for :action => "dashboard", :params => { "id" => id, "message" => message }
    end
end

class RedirectTest < Test::Unit::TestCase
  def setup
    @controller = RedirectController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  def test_simple_redirect
    get :simple_redirect
    assert_redirect_url "http://test.host/redirect/hello_world"
  end

  def test_redirect_with_method_reference_and_parameters
    get :method_redirect
    assert_redirect_url "http://test.host/redirect/dashboard/1?message=hello"
  end

  def test_simple_redirect_using_options
    get :host_redirect
    assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
  end

  def test_redirect_error_with_pretty_diff
    get :host_redirect
    begin
      assert_redirected_to :action => "other_host", :only_path => true
    rescue Test::Unit::AssertionFailedError => err
      redirection_msg, diff_msg = err.message.scan(/<\{[^\}]+\}>/).collect { |s| s[2..-3] }
      assert_match %r(:only_path=>false),        redirection_msg
      assert_match %r(:host=>"other.test.host"), redirection_msg
      assert_match %r(:action=>"other_host"),    redirection_msg
      assert_match %r(:only_path=>true),         diff_msg
      assert_match %r(:host=>"other.test.host"), diff_msg
    end
  end

  def test_module_redirect
    get :module_redirect
    assert_redirect_url "http://test.host/module_test/module_redirect/hello_world"
  end

  def test_module_redirect_using_options
    get :module_redirect
    assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world'
  end

  def test_redirect_with_assigns
    get :redirect_with_assigns
    assert_equal "world", assigns["hello"]
  end

  def test_redirect_to_back
    @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
    get :redirect_to_back
    assert_redirect_url "http://www.example.com/coming/from"
  end
  
  def test_redirect_to_back_with_no_referer
    assert_raises(ActionController::RedirectBackError) {
      @request.env["HTTP_REFERER"] = nil
      get :redirect_to_back
    }
  end
end

module ModuleTest
  class ModuleRedirectController < ::RedirectController
    def module_redirect
      redirect_to :controller => '/redirect', :action => "hello_world"
    end
  end

  class ModuleRedirectTest < Test::Unit::TestCase
    def setup
      @controller = ModuleRedirectController.new
      @request    = ActionController::TestRequest.new
      @response   = ActionController::TestResponse.new
    end
  
    def test_simple_redirect
      get :simple_redirect
      assert_redirect_url "http://test.host/module_test/module_redirect/hello_world"
    end
  
    def test_redirect_with_method_reference_and_parameters
      get :method_redirect
      assert_redirect_url "http://test.host/module_test/module_redirect/dashboard/1?message=hello"
    end
    
    def test_simple_redirect_using_options
      get :host_redirect
      assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
    end

    def test_module_redirect
      get :module_redirect
      assert_redirect_url "http://test.host/redirect/hello_world"
    end

    def test_module_redirect_using_options
      get :module_redirect
      assert_redirected_to :controller => 'redirect', :action => "hello_world"
    end
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

unless defined?(Customer)
  Customer = Struct.new("Customer", :name)
end

module Fun
  class GamesController < ActionController::Base
    def hello_world
    end
  end
end


class TestController < ActionController::Base
  layout :determine_layout

  def hello_world
  end

  def render_hello_world
    render "test/hello_world"
  end

  def render_hello_world_from_variable
    @person = "david"
    render_text "hello #{@person}"
  end

  def render_action_hello_world
    render_action "hello_world"
  end

  def render_action_hello_world_with_symbol
    render_action :hello_world
  end
 
  def render_text_hello_world
    render_text "hello world"
  end

  def render_custom_code
    render_text "hello world", "404 Moved"
  end
  
  def render_xml_hello
    @name = "David"
    render "test/hello"
  end

  def greeting
    # let's just rely on the template
  end

  def layout_test
    render_action "hello_world"
  end
  
  def builder_layout_test
    render_action "hello"
  end

  def partials_list
    @test_unchanged = 'hello'
    @customers = [ Customer.new("david"), Customer.new("mary") ]
    render_action "list"
  end

  def partial_only
    render_partial
  end

  def hello_in_a_string
    @customers = [ Customer.new("david"), Customer.new("mary") ]
    render_text "How's there? #{render_to_string("test/list")}"
  end
  
  def accessing_params_in_template
    render_template "Hello: <%= params[:name] %>"
  end

  def accessing_local_assigns_in_inline_template
    name = params[:local_name]
    render :inline => "<%= 'Goodbye, ' + local_name %>",
           :locals => { :local_name => name }
  end
  
  def accessing_local_assigns_in_inline_template_with_string_keys
    name = params[:local_name]
    ActionView::Base.local_assigns_support_string_keys = true
    render :inline => "<%= 'Goodbye, ' + local_name %>",
           :locals => { "local_name" => name }
    ActionView::Base.local_assigns_support_string_keys = false
  end

  def render_to_string_test
    @foo = render_to_string :inline => "this is a test"
  end

  def rescue_action(e) raise end
    
  private
    def determine_layout
      case action_name 
        when "layout_test":         "layouts/standard"
        when "builder_layout_test": "layouts/builder"
      end
    end
end

TestController.template_root = File.dirname(__FILE__) + "/../fixtures/"
Fun::GamesController.template_root = File.dirname(__FILE__) + "/../fixtures/"

class RenderTest < Test::Unit::TestCase
  def setup
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @controller = TestController.new

    @request.host = "www.nextangle.com"
  end

  def test_simple_show
    get :hello_world
    assert_response 200
    assert_template "test/hello_world"
  end

  def test_do_with_render
    get :render_hello_world
    assert_template "test/hello_world"
  end

  def test_do_with_render_from_variable
    get :render_hello_world_from_variable
    assert_equal "hello david", @response.body
  end

  def test_do_with_render_action
    get :render_action_hello_world
    assert_template "test/hello_world"
  end

  def test_do_with_render_action_with_symbol
    get :render_action_hello_world_with_symbol
    assert_template "test/hello_world"
  end

  def test_do_with_render_text
    get :render_text_hello_world
    assert_equal "hello world", @response.body
  end

  def test_do_with_render_custom_code
    get :render_custom_code
    assert_response 404
  end

  def test_attempt_to_access_object_method
    assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
  end

  def test_private_methods
    assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
  end

  def test_access_to_request_in_view
    view_internals_old_value = ActionController::Base.view_controller_internals

    ActionController::Base.view_controller_internals = false
    ActionController::Base.protected_variables_cache = nil

    get :hello_world
    assert_nil assigns["request"]

    ActionController::Base.view_controller_internals = true
    ActionController::Base.protected_variables_cache = nil

    get :hello_world
    assert_kind_of ActionController::AbstractRequest,  assigns["request"]

    ActionController::Base.view_controller_internals = view_internals_old_value
    ActionController::Base.protected_variables_cache = nil
  end
  
  def test_render_xml
    get :render_xml_hello
    assert_equal "<html>\n  <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
  end

  def test_render_xml_with_default
    get :greeting
    assert_equal "<p>This is grand!</p>\n", @response.body
  end

  def test_layout_rendering
    get :layout_test
    assert_equal "<html>Hello world!</html>", @response.body
  end

  def test_render_xml_with_layouts
    get :builder_layout_test
    assert_equal "<wrapper>\n<html>\n  <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body
  end

  # def test_partials_list
  #   get :partials_list
  #   assert_equal "goodbyeHello: davidHello: marygoodbye\n", process_request.body
  # end

  def test_partial_only
    get :partial_only
    assert_equal "only partial", @response.body
  end

  def test_render_to_string
    get :hello_in_a_string
    assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
  end

  def test_render_to_string_resets_assigns
    get :render_to_string_test
    assert_equal "The value of foo is: ::this is a test::\n", @response.body
  end

  def test_nested_rendering
    @controller = Fun::GamesController.new
    get :hello_world
    assert_equal "Living in a nested world", @response.body
  end

  def test_accessing_params_in_template
    get :accessing_params_in_template, :name => "David"
    assert_equal "Hello: David", @response.body
  end

  def test_accessing_local_assigns_in_inline_template
    get :accessing_local_assigns_in_inline_template, :local_name => "Local David"
    assert_equal "Goodbye, Local David", @response.body
  end
  
  def test_accessing_local_assigns_in_inline_template_with_string_keys
    get :accessing_local_assigns_in_inline_template_with_string_keys, :local_name => "Local David"
    assert_equal "Goodbye, Local David", @response.body
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class RequestTest < Test::Unit::TestCase
  def setup
    @request = ActionController::TestRequest.new
  end

  def test_remote_ip
    assert_equal '0.0.0.0', @request.remote_ip

    @request.remote_addr = '1.2.3.4'
    assert_equal '1.2.3.4', @request.remote_ip

    @request.env['HTTP_CLIENT_IP'] = '2.3.4.5'
    assert_equal '2.3.4.5', @request.remote_ip
    @request.env.delete 'HTTP_CLIENT_IP'

    @request.env['HTTP_X_FORWARDED_FOR'] = '3.4.5.6'
    assert_equal '3.4.5.6', @request.remote_ip

    @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,3.4.5.6'
    assert_equal '3.4.5.6', @request.remote_ip

    @request.env['HTTP_X_FORWARDED_FOR'] = '172.16.0.1,3.4.5.6'
    assert_equal '3.4.5.6', @request.remote_ip

    @request.env['HTTP_X_FORWARDED_FOR'] = '192.168.0.1,3.4.5.6'
    assert_equal '3.4.5.6', @request.remote_ip

    @request.env['HTTP_X_FORWARDED_FOR'] = '10.0.0.1,3.4.5.6'
    assert_equal '3.4.5.6', @request.remote_ip

    @request.env['HTTP_X_FORWARDED_FOR'] = '127.0.0.1,3.4.5.6'
    assert_equal '127.0.0.1', @request.remote_ip

    @request.env['HTTP_X_FORWARDED_FOR'] = 'unknown,192.168.0.1'
    assert_equal '1.2.3.4', @request.remote_ip
    @request.env.delete 'HTTP_X_FORWARDED_FOR'
  end

  def test_domains
    @request.host = "www.rubyonrails.org"
    assert_equal "rubyonrails.org", @request.domain

    @request.host = "www.rubyonrails.co.uk"
    assert_equal "rubyonrails.co.uk", @request.domain(2)
    
    @request.host = "192.168.1.200"
    assert_nil @request.domain

    @request.host = nil
    assert_nil @request.domain
  end

  def test_subdomains
    @request.host = "www.rubyonrails.org"
    assert_equal %w( www ), @request.subdomains

    @request.host = "www.rubyonrails.co.uk"
    assert_equal %w( www ), @request.subdomains(2)

    @request.host = "dev.www.rubyonrails.co.uk"
    assert_equal %w( dev www ), @request.subdomains(2)

    @request.host = "foobar.foobar.com"
    assert_equal %w( foobar ), @request.subdomains

    @request.host = nil
    assert_equal [], @request.subdomains
  end
  
  def test_port_string
    @request.port = 80
    assert_equal "", @request.port_string

    @request.port = 8080
    assert_equal ":8080", @request.port_string
  end
  
  def test_relative_url_root
    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
    @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
    assert_equal '', @request.relative_url_root, "relative_url_root should be disabled on lighttpd"

    @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
      
    @request.env['SCRIPT_NAME'] = nil
    assert_equal "", @request.relative_url_root

    @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
    assert_equal "", @request.relative_url_root

    @request.env['SCRIPT_NAME'] = "/myapp.rb"
    assert_equal "", @request.relative_url_root

    @request.relative_url_root = nil
    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
    assert_equal "/hieraki", @request.relative_url_root

    @request.relative_url_root = nil
    @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
    assert_equal "/collaboration/hieraki", @request.relative_url_root    
    
    # apache/scgi case
    @request.relative_url_root = nil
    @request.env['SCRIPT_NAME'] = "/collaboration/hieraki"
    assert_equal "/collaboration/hieraki", @request.relative_url_root    
    
    @request.relative_url_root = nil
    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
    @request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
    @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki"
    assert_equal "/hieraki", @request.relative_url_root
    
    # @env overrides path guess
    @request.relative_url_root = nil
    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
    @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
    @request.env['RAILS_RELATIVE_URL_ROOT'] = "/real_url"
    assert_equal "/real_url", @request.relative_url_root
  end
  
  def test_request_uri
    @request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432'
  
    @request.relative_url_root = nil
    @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1"
    assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
    assert_equal "/path/of/some/uri", @request.path
    
    @request.relative_url_root = nil
    @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri"
    assert_equal "/path/of/some/uri", @request.request_uri
    assert_equal "/path/of/some/uri", @request.path

    @request.relative_url_root = nil
    @request.set_REQUEST_URI "/path/of/some/uri"
    assert_equal "/path/of/some/uri", @request.request_uri
    assert_equal "/path/of/some/uri", @request.path

    @request.relative_url_root = nil
    @request.set_REQUEST_URI "/"
    assert_equal "/", @request.request_uri
    assert_equal "/", @request.path

    @request.relative_url_root = nil
    @request.set_REQUEST_URI "/?m=b"
    assert_equal "/?m=b", @request.request_uri
    assert_equal "/", @request.path
    
    @request.relative_url_root = nil
    @request.set_REQUEST_URI "/"
    @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
    assert_equal "/", @request.request_uri
    assert_equal "/", @request.path    

    @request.relative_url_root = nil
    @request.set_REQUEST_URI "/hieraki/"
    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
    assert_equal "/hieraki/", @request.request_uri
    assert_equal "/", @request.path    

    @request.relative_url_root = nil
    @request.set_REQUEST_URI "/collaboration/hieraki/books/edit/2"
    @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
    assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri
    assert_equal "/books/edit/2", @request.path
  
    # The following tests are for when REQUEST_URI is not supplied (as in IIS)
    @request.relative_url_root = nil
    @request.set_REQUEST_URI nil
    @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
    @request.env['SCRIPT_NAME'] = nil #"/path/dispatch.rb"
    assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
    assert_equal "/path/of/some/uri", @request.path

    @request.relative_url_root = nil
    @request.env['PATH_INFO'] = "/path/of/some/uri?mapped=1"
    @request.env['SCRIPT_NAME'] = "/path/dispatch.rb"
    assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
    assert_equal "/of/some/uri", @request.path

    @request.relative_url_root = nil
    @request.env['PATH_INFO'] = "/path/of/some/uri"
    @request.env['SCRIPT_NAME'] = nil
    assert_equal "/path/of/some/uri", @request.request_uri
    assert_equal "/path/of/some/uri", @request.path

    @request.relative_url_root = nil
    @request.env['PATH_INFO'] = "/"
    assert_equal "/", @request.request_uri
    assert_equal "/", @request.path

    @request.relative_url_root = nil
    @request.env['PATH_INFO'] = "/?m=b"
    assert_equal "/?m=b", @request.request_uri
    assert_equal "/", @request.path
    
    @request.relative_url_root = nil
    @request.env['PATH_INFO'] = "/"
    @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
    assert_equal "/", @request.request_uri
    assert_equal "/", @request.path    

    @request.relative_url_root = nil
    @request.env['PATH_INFO'] = "/hieraki/"
    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
    assert_equal "/hieraki/", @request.request_uri
    assert_equal "/", @request.path    

    # This test ensures that Rails uses REQUEST_URI over PATH_INFO
    @request.relative_url_root = nil
    @request.env['REQUEST_URI'] = "/some/path"
    @request.env['PATH_INFO'] = "/another/path"
    @request.env['SCRIPT_NAME'] = "/dispatch.cgi"
    assert_equal "/some/path", @request.request_uri
    assert_equal "/some/path", @request.path
  end
  

  def test_host_with_port
    @request.host = "rubyonrails.org"
    @request.port = 80
    assert_equal "rubyonrails.org", @request.host_with_port
    
    @request.host = "rubyonrails.org"
    @request.port = 81
    assert_equal "rubyonrails.org:81", @request.host_with_port
  end
  
  def test_server_software
    assert_equal nil, @request.server_software
  
    @request.env['SERVER_SOFTWARE'] = 'Apache3.422'
    assert_equal 'apache', @request.server_software
    
    @request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)'
    assert_equal 'lighttpd', @request.server_software
  end
  
  def test_xml_http_request
    assert !@request.xml_http_request?
    assert !@request.xhr?
    
    @request.env['HTTP_X_REQUESTED_WITH'] = "DefinitelyNotAjax1.0"
    assert !@request.xml_http_request?
    assert !@request.xhr?
    
    @request.env['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"
    assert @request.xml_http_request?
    assert @request.xhr?
  end

  def test_reports_ssl
    assert !@request.ssl?
    @request.env['HTTPS'] = 'on'
    assert @request.ssl?
  end

  def test_reports_ssl_when_proxied_via_lighttpd
    assert !@request.ssl?
    @request.env['HTTP_X_FORWARDED_PROTO'] = 'https'
    assert @request.ssl?
  end
  
end
require File.dirname(__FILE__) + '/../abstract_unit'
require 'test/unit'
require File.dirname(__FILE__) + '/fake_controllers'
require 'stringio'

RunTimeTests = ARGV.include? 'time'

module ActionController::CodeGeneration

class SourceTests < Test::Unit::TestCase
  attr_accessor :source
  def setup
    @source = Source.new
  end
    
  def test_initial_state
    assert_equal [], source.lines
    assert_equal 0, source.indentation_level
  end
  
  def test_trivial_operations
    source << "puts 'Hello World'"
    assert_equal ["puts 'Hello World'"], source.lines
    assert_equal "puts 'Hello World'", source.to_s
    
    source.line "puts 'Goodbye World'"
    assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], source.lines
    assert_equal "puts 'Hello World'\nputs 'Goodbye World'", source.to_s
  end

  def test_indentation
    source << "x = gets.to_i"
    source << 'if x.odd?'
    source.indent { source << "puts 'x is odd!'" }
    source << 'else'
    source.indent { source << "puts 'x is even!'" }
    source << 'end'
    
    assert_equal ["x = gets.to_i", "if x.odd?", "  puts 'x is odd!'", 'else', "  puts 'x is even!'", 'end'], source.lines
    
    text = "x = gets.to_i
if x.odd?
  puts 'x is odd!'
else
  puts 'x is even!'
end"

    assert_equal text, source.to_s
  end 
end

class CodeGeneratorTests < Test::Unit::TestCase
  attr_accessor :generator
  def setup
    @generator = CodeGenerator.new
  end
  
  def test_initial_state
    assert_equal [], generator.source.lines
    assert_equal [], generator.locals
  end
    
  def test_trivial_operations
    ["puts 'Hello World'", "puts 'Goodbye World'"].each {|l| generator << l} 
    assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], generator.source.lines
    assert_equal "puts 'Hello World'\nputs 'Goodbye World'", generator.to_s
  end
  
  def test_if
    generator << "x = gets.to_i"
    generator.if("x.odd?") { generator << "puts 'x is odd!'" }
    
    assert_equal "x = gets.to_i\nif x.odd?\n  puts 'x is odd!'\nend", generator.to_s
  end
  
  def test_else
    test_if
    generator.else { generator << "puts 'x is even!'" }
    
    assert_equal "x = gets.to_i\nif x.odd?\n  puts 'x is odd!'\nelse \n  puts 'x is even!'\nend", generator.to_s
  end

  def test_dup
    generator << 'x = 2'
    generator.locals << :x
    
    g = generator.dup
    assert_equal generator.source, g.source
    assert_equal generator.locals, g.locals
    
    g << 'y = 3'
    g.locals << :y
    assert_equal [:x, :y], g.locals # Make sure they don't share the same array.
    assert_equal [:x], generator.locals
  end
end 

class RecognitionTests < Test::Unit::TestCase
  attr_accessor :generator
  alias :g :generator
  def setup
    @generator = RecognitionGenerator.new
  end

  def go(components)
    g.current = components.first
    g.after = components[1..-1] || []
    g.go
  end
  
  def execute(path, show = false)
    path = path.split('/') if path.is_a? String
    source = "index, path = 0, #{path.inspect}\n#{g.to_s}"
    puts source if show
    r = eval source
    r ? r.symbolize_keys : nil
  end
  
  Static = ::ActionController::Routing::StaticComponent
  Dynamic = ::ActionController::Routing::DynamicComponent
  Path = ::ActionController::Routing::PathComponent
  Controller = ::ActionController::Routing::ControllerComponent
  
  def test_all_static
    c = %w(hello world how are you).collect {|str| Static.new(str)}
    
    g.result :controller, "::ContentController", true
    g.constant_result :action, 'index' 
    
    go c
    
    assert_nil execute('x')
    assert_nil execute('hello/world/how')
    assert_nil execute('hello/world/how/are')
    assert_nil execute('hello/world/how/are/you/today')
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hello/world/how/are/you'))
  end

  def test_basic_dynamic
    c = [Static.new("hi"), Dynamic.new(:action)]
    g.result :controller, "::ContentController", true
    go c
    
    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi')
    assert_nil execute('hi/dude/what')
    assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
  end 

  def test_basic_dynamic_backwards
    c = [Dynamic.new(:action), Static.new("hi")]
    go c

    assert_nil execute('')
    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi')
    assert_equal({:action => 'index'}, execute('index/hi'))
    assert_equal({:action => 'show'}, execute('show/hi'))
    assert_nil execute('hi/dude')
  end

  def test_dynamic_with_default
    c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')]
    g.result :controller, "::ContentController", true
    go c
    
    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi/dude/what')
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi'))
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
    assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
  end 

  def test_dynamic_with_string_condition
    c = [Static.new("hi"), Dynamic.new(:action, :condition => 'index')]
    g.result :controller, "::ContentController", true
    go c

    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi')
    assert_nil execute('hi/dude/what')
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
    assert_nil execute('hi/dude')
  end

  def test_dynamic_with_string_condition_backwards
    c = [Dynamic.new(:action, :condition => 'index'), Static.new("hi")]
    g.result :controller, "::ContentController", true
    go c

    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi')
    assert_nil execute('dude/what/hi')
    assert_nil execute('index/what')
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('index/hi'))
    assert_nil execute('dude/hi')
  end

  def test_dynamic_with_regexp_condition
    c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)]
    g.result :controller, "::ContentController", true
    go c
    
    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi')
    assert_nil execute('hi/FOXY')
    assert_nil execute('hi/138708jkhdf')
    assert_nil execute('hi/dkjfl8792343dfsf')
    assert_nil execute('hi/dude/what')
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
    assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
  end 
  
  def test_dynamic_with_regexp_and_default
    c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/, :default => 'index')]
    g.result :controller, "::ContentController", true
    go c
    
    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi/FOXY')
    assert_nil execute('hi/138708jkhdf')
    assert_nil execute('hi/dkjfl8792343dfsf')
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi'))
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
    assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
    assert_nil execute('hi/dude/what')
  end

  def test_path
    c = [Static.new("hi"), Path.new(:file)]
    g.result :controller, "::ContentController", true
    g.constant_result :action, "download"
    
    go c
    
    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('hi'))
    assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)},
                 execute('hi/books/agile_rails_dev.pdf'))
    assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('hi/dude'))
    assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s
  end

  def test_path_with_dynamic
    c = [Dynamic.new(:action), Path.new(:file)]
    g.result :controller, "::ContentController", true

    go c

    assert_nil execute('')
    assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('download'))
    assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)},
                 execute('download/books/agile_rails_dev.pdf'))
    assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('download/dude'))
    assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s
  end

  def test_path_with_dynamic_and_default
    c = [Dynamic.new(:action, :default => 'index'), Path.new(:file)]

    go c

    assert_equal({:action => 'index', :file => []}, execute(''))
    assert_equal({:action => 'index', :file => []}, execute('index'))
    assert_equal({:action => 'blarg', :file => []}, execute('blarg'))
    assert_equal({:action => 'index', :file => ['content']}, execute('index/content'))
    assert_equal({:action => 'show', :file => ['rails_dev.pdf']}, execute('show/rails_dev.pdf'))
  end
  
  def test_controller
    c = [Static.new("hi"), Controller.new(:controller)]
    g.constant_result :action, "hi"
    
    go c
    
    assert_nil execute('boo')
    assert_nil execute('boo/blah')
    assert_nil execute('hi/x')
    assert_nil execute('hi/13870948')
    assert_nil execute('hi/content/dog')
    assert_nil execute('hi/admin/user/foo')
    assert_equal({:controller => ::ContentController, :action => 'hi'}, execute('hi/content'))
    assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user'))
  end
  
  def test_controller_with_regexp
    c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)]
    g.constant_result :action, "hi"
    
    go c
    
    assert_nil execute('hi')
    assert_nil execute('hi/x')
    assert_nil execute('hi/content')
    assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user'))
    assert_equal({:controller => ::Admin::NewsFeedController, :action => 'hi'}, execute('hi/admin/news_feed'))
    assert_nil execute('hi/admin/user/foo')
  end
  
  def test_standard_route(time = ::RunTimeTests)
    c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)]
    go c
    
    # Make sure we get the right answers
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute('content'))
    assert_equal({:controller => ::ContentController, :action => 'list'}, execute('content/list'))
    assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, execute('content/show/10'))
    
    assert_equal({:controller => ::Admin::UserController, :action => 'index'}, execute('admin/user'))
    assert_equal({:controller => ::Admin::UserController, :action => 'list'}, execute('admin/user/list'))
    assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => 'nseckar'}, execute('admin/user/show/nseckar'))
    
    assert_nil execute('content/show/10/20')
    assert_nil execute('food')

    if time
      source = "def self.execute(path)
        path = path.split('/') if path.is_a? String
        index = 0
        r = #{g.to_s}
      end"
      eval(source)

      GC.start
      n = 1000
      time = Benchmark.realtime do n.times {
        execute('content')
        execute('content/list')
        execute('content/show/10')
        
        execute('admin/user')
        execute('admin/user/list')
        execute('admin/user/show/nseckar')
        
        execute('admin/user/show/nseckar/dude')
        execute('admin/why/show/nseckar')
        execute('content/show/10/20')
        execute('food')
      } end
      time -= Benchmark.realtime do n.times { } end
    
      
      puts "\n\nRecognition:"
      per_url = time / (n * 10)
    
      puts "#{per_url * 1000} ms/url"
      puts "#{1 / per_url} urls/s\n\n"
    end
  end

  def test_default_route
    g.result :controller, "::ContentController", true
    g.constant_result :action, 'index' 
    
    go []
    
    assert_nil execute('x')
    assert_nil execute('hello/world/how')
    assert_nil execute('hello/world/how/are')
    assert_nil execute('hello/world/how/are/you/today')
    assert_equal({:controller => ::ContentController, :action => 'index'}, execute([]))
  end
end

class GenerationTests < Test::Unit::TestCase
  attr_accessor :generator
  alias :g :generator
  def setup
    @generator = GenerationGenerator.new # ha!
  end
  
  def go(components)
    g.current = components.first
    g.after = components[1..-1] || []
    g.go
  end
  
  def execute(options, recall, show = false)
    source = "\n
expire_on = ::ActionController::Routing.expiry_hash(options, recall)
hash = merged = recall.merge(options)
not_expired = true

#{g.to_s}\n\n"
    puts source if show
    eval(source)
  end
  
  Static = ::ActionController::Routing::StaticComponent
  Dynamic = ::ActionController::Routing::DynamicComponent
  Path = ::ActionController::Routing::PathComponent
  Controller = ::ActionController::Routing::ControllerComponent
  
  def test_all_static_no_requirements
    c = [Static.new("hello"), Static.new("world")]
    go c
    
    assert_equal "/hello/world", execute({}, {})
  end
  
  def test_basic_dynamic
    c = [Static.new("hi"), Dynamic.new(:action)]
    go c
    
    assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'})
    assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'})
    assert_equal '/hi/list+people', execute({}, {:action => 'list people'})
    assert_nil execute({},{})
  end
  
  def test_dynamic_with_default
    c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')]
    go c
    
    assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'})
    assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'})
    assert_equal '/hi/list+people', execute({}, {:action => 'list people'})
    assert_equal '/hi', execute({}, {})
  end
  
  def test_dynamic_with_regexp_condition
    c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)]
    go c
    
    assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'})
    assert_nil execute({:action => 'fox5'}, {:action => 'index'})
    assert_nil execute({:action => 'something_is_up'}, {:action => 'index'})
    assert_nil execute({}, {:action => 'list people'})
    assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {})
    assert_nil execute({}, {})
  end
  
  def test_dynamic_with_default_and_regexp_condition
    c = [Static.new("hi"), Dynamic.new(:action, :default => 'index', :condition => /^[a-z]+$/)]
    go c
    
    assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'})
    assert_nil execute({:action => 'fox5'}, {:action => 'index'})
    assert_nil execute({:action => 'something_is_up'}, {:action => 'index'})
    assert_nil execute({}, {:action => 'list people'})
    assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {})
    assert_equal '/hi', execute({}, {})
  end

  def test_path
    c = [Static.new("hi"), Path.new(:file)]
    go c
    
    assert_equal '/hi', execute({:file => []}, {})
    assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => %w(books agile_rails_dev.pdf)}, {})
    assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => %w(books development&whatever agile_rails_dev.pdf)}, {})
    
    assert_equal '/hi', execute({:file => ''}, {})
    assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => 'books/agile_rails_dev.pdf'}, {})
    assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => 'books/development&whatever/agile_rails_dev.pdf'}, {})
  end
  
  def test_controller
    c = [Static.new("hi"), Controller.new(:controller)]
    go c
    
    assert_nil execute({}, {})
    assert_equal '/hi/content', execute({:controller => 'content'}, {})
    assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {})
    assert_equal '/hi/content', execute({}, {:controller => 'content'}) 
    assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'})
  end
  
  def test_controller_with_regexp
    c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)]
    go c
    
    assert_nil execute({}, {})
    assert_nil execute({:controller => 'content'}, {})
    assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {})
    assert_nil execute({}, {:controller => 'content'}) 
    assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'})
  end
  
  def test_standard_route(time = ::RunTimeTests)
    c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)]
    go c
    
    # Make sure we get the right answers
    assert_equal('/content', execute({:action => 'index'}, {:controller => 'content', :action => 'list'}))
    assert_equal('/content/list', execute({:action => 'list'}, {:controller => 'content', :action => 'index'}))
    assert_equal('/content/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}))

    assert_equal('/admin/user', execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'}))
    assert_equal('/admin/user/list', execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'}))
    assert_equal('/admin/user/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}))

    if time
      GC.start
      n = 1000
      time = Benchmark.realtime do n.times {
        execute({:action => 'index'}, {:controller => 'content', :action => 'list'})
        execute({:action => 'list'}, {:controller => 'content', :action => 'index'})
        execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'})

        execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'})
        execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'})
        execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'})
      } end
      time -= Benchmark.realtime do n.times { } end
    
      puts "\n\nGeneration:"
      per_url = time / (n * 6)
    
      puts "#{per_url * 1000} ms/url"
      puts "#{1 / per_url} urls/s\n\n"
    end
  end

  def test_default_route
    g.if(g.check_conditions(:controller => 'content', :action => 'welcome')) { go [] }
    
    assert_nil execute({:controller => 'foo', :action => 'welcome'}, {})
    assert_nil execute({:controller => 'content', :action => 'elcome'}, {})
    assert_nil execute({:action => 'elcome'}, {:controller => 'content'})

    assert_equal '/', execute({:controller => 'content', :action => 'welcome'}, {})
    assert_equal '/', execute({:action => 'welcome'}, {:controller => 'content'})
    assert_equal '/', execute({:action => 'welcome', :id => '10'}, {:controller => 'content'})
  end
end

class RouteTests < Test::Unit::TestCase
  
  def route(*args)
    @route = ::ActionController::Routing::Route.new(*args) unless args.empty?
    return @route
  end
  
  def rec(path, show = false)
    path = path.split('/') if path.is_a? String
    index = 0
    source = route.write_recognition.to_s
    puts "\n\n#{source}\n\n" if show
    r = eval(source)
    r ? r.symbolize_keys : r
  end
  def gen(options, recall = nil, show = false)
    recall ||= options.dup
    
    expire_on = ::ActionController::Routing.expiry_hash(options, recall)
    hash = merged = recall.merge(options)
    not_expired = true
    
    source = route.write_generation.to_s
    puts "\n\n#{source}\n\n" if show
    eval(source)
    
  end
  
  def test_static
    route 'hello/world', :known => 'known_value', :controller => 'content', :action => 'index'
    
    assert_nil rec('hello/turn')
    assert_nil rec('turn/world')
    assert_equal(
      {:known => 'known_value', :controller => ::ContentController, :action => 'index'},
      rec('hello/world')
    )
    
    assert_nil gen(:known => 'foo')
    assert_nil gen({})
    assert_equal '/hello/world', gen(:known => 'known_value', :controller => 'content', :action => 'index')
    assert_equal '/hello/world', gen(:known => 'known_value', :extra => 'hi', :controller => 'content', :action => 'index')
    assert_equal [:extra], route.extra_keys(:known => 'known_value', :extra => 'hi')
  end
  
  def test_dynamic
    route 'hello/:name', :controller => 'content', :action => 'show_person'
    
    assert_nil rec('hello')
    assert_nil rec('foo/bar')
    assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'rails'}, rec('hello/rails'))
    assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'Nicholas Seckar'}, rec('hello/Nicholas+Seckar'))
    
    assert_nil gen(:controller => 'content', :action => 'show_dude', :name => 'rails')
    assert_nil gen(:controller => 'content', :action => 'show_person')
    assert_nil gen(:controller => 'admin/user', :action => 'show_person', :name => 'rails')
    assert_equal '/hello/rails', gen(:controller => 'content', :action => 'show_person', :name => 'rails')
    assert_equal '/hello/Nicholas+Seckar', gen(:controller => 'content', :action => 'show_person', :name => 'Nicholas Seckar')
  end
  
  def test_typical
    route ':controller/:action/:id', :action => 'index', :id => nil
    assert_nil rec('hello')
    assert_nil rec('foo bar')
    assert_equal({:controller => ::ContentController, :action => 'index'}, rec('content'))
    assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user'))
    
    assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user/index'))
    assert_equal({:controller => ::Admin::UserController, :action => 'list'}, rec('admin/user/list'))
    assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}, rec('admin/user/show/10'))
    
    assert_equal({:controller => ::ContentController, :action => 'list'}, rec('content/list'))
    assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, rec('content/show/10'))
    
    
    assert_equal '/content', gen(:controller => 'content', :action => 'index')
    assert_equal '/content/list', gen(:controller => 'content', :action => 'list')
    assert_equal '/content/show/10', gen(:controller => 'content', :action => 'show', :id => '10')
    
    assert_equal '/admin/user', gen(:controller => 'admin/user', :action => 'index')
    assert_equal '/admin/user', gen(:controller => 'admin/user')
    assert_equal '/admin/user', gen({:controller => 'admin/user'}, {:controller => 'content', :action => 'list', :id => '10'})
    assert_equal '/admin/user/show/10', gen(:controller => 'admin/user', :action => 'show', :id => '10')
  end
end

class RouteSetTests < Test::Unit::TestCase
  attr_reader :rs
  def setup
    @rs = ::ActionController::Routing::RouteSet.new
    @rs.draw {|m| m.connect ':controller/:action/:id' }
    ::ActionController::Routing::NamedRoutes.clear
  end
  
  def test_default_setup
    assert_equal({:controller => ::ContentController, :action => 'index'}.stringify_keys, rs.recognize_path(%w(content)))
    assert_equal({:controller => ::ContentController, :action => 'list'}.stringify_keys, rs.recognize_path(%w(content list)))
    assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(content show 10)))
    
    assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(admin user show 10)))
    
    assert_equal ['/admin/user/show/10', []], rs.generate({:controller => 'admin/user', :action => 'show', :id => 10})
    
    assert_equal ['/admin/user/show', []], rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
    assert_equal ['/admin/user/list/10', []], rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'})

    assert_equal ['/admin/stuff', []], rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
    assert_equal ['/stuff', []], rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
  end
  
  def test_ignores_leading_slash
    @rs.draw {|m| m.connect '/:controller/:action/:id'}
    test_default_setup
  end
  
  def test_time_recognition
    n = 10000
    if RunTimeTests
      GC.start
      rectime = Benchmark.realtime do
        n.times do
          rs.recognize_path(%w(content))
          rs.recognize_path(%w(content list))
          rs.recognize_path(%w(content show 10))
          rs.recognize_path(%w(admin user))
          rs.recognize_path(%w(admin user list))
          rs.recognize_path(%w(admin user show 10))
        end
      end
      puts "\n\nRecognition (RouteSet):"
      per_url = rectime / (n * 6)
      puts "#{per_url * 1000} ms/url"
      puts "#{1 / per_url} url/s\n\n"
    end
  end
  def test_time_generation
    n = 5000
    if RunTimeTests
      GC.start
      pairs = [
        [{:controller => 'content', :action => 'index'}, {:controller => 'content', :action => 'show'}],
        [{:controller => 'content'}, {:controller => 'content', :action => 'index'}],   
        [{:controller => 'content', :action => 'list'}, {:controller => 'content', :action => 'index'}],
        [{:controller => 'content', :action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}],
        [{:controller => 'admin/user', :action => 'index'}, {:controller => 'admin/user', :action => 'show'}],
        [{:controller => 'admin/user'}, {:controller => 'admin/user', :action => 'index'}],
        [{:controller => 'admin/user', :action => 'list'}, {:controller => 'admin/user', :action => 'index'}],
        [{:controller => 'admin/user', :action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}],
      ]
      p = nil
      gentime = Benchmark.realtime do
        n.times do
        pairs.each {|(a, b)| rs.generate(a, b)}
        end
      end
      
      puts "\n\nGeneration (RouteSet): (#{(n * 8)} urls)"
      per_url = gentime / (n * 8)
      puts "#{per_url * 1000} ms/url"
      puts "#{1 / per_url} url/s\n\n"
    end
  end

  def test_route_with_colon_first
    rs.draw do |map|
      map.connect '/:controller/:action/:id', :action => 'index', :id => nil
      map.connect ':url', :controller => 'tiny_url', :action => 'translate'
    end
  end

  def test_route_generating_string_literal_in_comparison_warning
    old_stderr = $stderr
    $stderr = StringIO.new
    rs.draw do |map|
      map.connect 'subscriptions/:action/:subscription_type', :controller => "subscriptions"
    end
    assert_equal "", $stderr.string
  ensure
    $stderr = old_stderr
  end

  def test_route_with_regexp_for_controller
    rs.draw do |map|
      map.connect ':controller/:admintoken/:action/:id', :controller => /admin\/.+/
      map.connect ':controller/:action/:id'
    end
    assert_equal({:controller => ::Admin::UserController, :admintoken => "foo", :action => "index"}.stringify_keys,
        rs.recognize_path(%w(admin user foo)))
    assert_equal({:controller => ::ContentController, :action => "foo"}.stringify_keys,
        rs.recognize_path(%w(content foo)))
    assert_equal ['/admin/user/foo', []], rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index")
    assert_equal ['/content/foo',[]], rs.generate(:controller => "content", :action => "foo")
  end
  
  def test_basic_named_route
    rs.home '', :controller => 'content', :action => 'list' 
    x = setup_for_named_route
    assert_equal({:controller => '/content', :action => 'list'},
                 x.new.send(:home_url))
  end

  def test_named_route_with_option
    rs.page 'page/:title', :controller => 'content', :action => 'show_page'
    x = setup_for_named_route
    assert_equal({:controller => '/content', :action => 'show_page', :title => 'new stuff'},
                 x.new.send(:page_url, :title => 'new stuff'))
  end

  def test_named_route_with_default
    rs.page 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
    x = setup_for_named_route
    assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutPage'},
                 x.new.send(:page_url))
    assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutRails'},
                 x.new.send(:page_url, :title => "AboutRails"))

  end

  def setup_for_named_route
    x = Class.new
    x.send(:define_method, :url_for) {|x| x}
    x.send :include, ::ActionController::Routing::NamedRoutes
    x
  end

  def test_named_route_without_hash
    rs.draw do |map|
      rs.normal ':controller/:action/:id'
    end
  end

  def test_named_route_with_regexps
    rs.draw do |map|
      rs.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show',
        :year => /^\d+$/, :month => /^\d+$/, :day => /^\d+$/
      rs.connect ':controller/:action/:id'
    end
    x = setup_for_named_route
    assert_equal(
      {:controller => '/page', :action => 'show', :title => 'hi'},
      x.new.send(:article_url, :title => 'hi')
    )
    assert_equal(
      {:controller => '/page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6},
      x.new.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
    )
  end

  def test_changing_controller
    assert_equal ['/admin/stuff/show/10', []], rs.generate(
      {:controller => 'stuff', :action => 'show', :id => 10},
      {:controller => 'admin/user', :action => 'index'}
    )
  end  

  def test_paths_escaped
    rs.draw do |map|
      rs.path 'file/*path', :controller => 'content', :action => 'show_file'
      rs.connect ':controller/:action/:id'
    end
    results = rs.recognize_path %w(file hello+world how+are+you%3F)
    assert results, "Recognition should have succeeded"
    assert_equal ['hello world', 'how are you?'], results['path']

    results = rs.recognize_path %w(file)
    assert results, "Recognition should have succeeded"
    assert_equal [], results['path']
  end
  
  def test_non_controllers_cannot_be_matched
    rs.draw do
      rs.connect ':controller/:action/:id'
    end
    assert_nil rs.recognize_path(%w(not_a show 10)), "Shouldn't recognize non-controllers as controllers!"
  end

  def test_paths_do_not_accept_defaults
    assert_raises(ActionController::RoutingError) do
      rs.draw do |map|
        rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default)
        rs.connect ':controller/:action/:id'
      end
    end
    
    rs.draw do |map|
      rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => []
      rs.connect ':controller/:action/:id'
    end
  end
  
  def test_backwards
    rs.draw do |map|
      rs.connect 'page/:id/:action', :controller => 'pages', :action => 'show'
      rs.connect ':controller/:action/:id'
    end

    assert_equal ['/page/20', []], rs.generate({:id => 20}, {:controller => 'pages'})
    assert_equal ['/page/20', []], rs.generate(:controller => 'pages', :id => 20, :action => 'show')
    assert_equal ['/pages/boo', []], rs.generate(:controller => 'pages', :action => 'boo')
  end

  def test_route_with_fixnum_default
    rs.draw do |map|
      rs.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1
      rs.connect ':controller/:action/:id'
    end

    assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page')
    assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 1)
    assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => '1')
    assert_equal ['/page/10', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 10)

    ctrl = ::ContentController

    assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => 1}, rs.recognize_path(%w(page)))
    assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '1'}, rs.recognize_path(%w(page 1)))
    assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '10'}, rs.recognize_path(%w(page 10)))
  end

  def test_action_expiry
    assert_equal ['/content', []], rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
  end

  def test_recognition_with_uppercase_controller_name
    assert_equal({'controller' => ::ContentController, 'action' => 'index'}, rs.recognize_path(%w(Content)))
    assert_equal({'controller' => ::ContentController, 'action' => 'list'}, rs.recognize_path(%w(Content list)))
    assert_equal({'controller' => ::ContentController, 'action' => 'show', 'id' => '10'}, rs.recognize_path(%w(Content show 10)))

    assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin NewsFeed)))
    assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin News_Feed)))
  end

  def test_both_requirement_and_optional
    rs.draw do
      rs.blog('test/:year', :controller => 'post', :action => 'show',
        :defaults => { :year => nil },
        :requirements => { :year => /\d{4}/ }
      )
      rs.connect ':controller/:action/:id'
    end

    assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show')
    assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show', :year => nil)
    
    x = setup_for_named_route
    assert_equal({:controller => '/post', :action => 'show'},
                 x.new.send(:blog_url))
  end
  
  def test_set_to_nil_forgets
    rs.draw do
      rs.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil
      rs.connect ':controller/:action/:id'
    end
    
    assert_equal ['/pages/2005', []],
      rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005)
    assert_equal ['/pages/2005/6', []],
      rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6)
    assert_equal ['/pages/2005/6/12', []],
      rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12)
    
    assert_equal ['/pages/2005/6/4', []],
      rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})

    assert_equal ['/pages/2005/6', []],
      rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})

    assert_equal ['/pages/2005', []],
      rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
  end
  
  def test_url_with_no_action_specified
    rs.draw do
      rs.connect '', :controller => 'content'
      rs.connect ':controller/:action/:id'
    end
    
    assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index')
    assert_equal ['/', []], rs.generate(:controller => 'content')
  end

  def test_named_url_with_no_action_specified
    rs.draw do
      rs.root '', :controller => 'content'
      rs.connect ':controller/:action/:id'
    end
    
    assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index')
    assert_equal ['/', []], rs.generate(:controller => 'content')
    
    x = setup_for_named_route
    assert_equal({:controller => '/content', :action => 'index'},
                 x.new.send(:root_url))
  end
  
  def test_url_generated_when_forgetting_action
    [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash| 
      rs.draw do
        rs.root '', hash
        rs.connect ':controller/:action/:id'
      end
      assert_equal ['/', []], rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'})
      assert_equal ['/', []], rs.generate({:controller => 'content'})
      assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'})
    end
  end
  
  def test_named_route_method
    rs.draw do
      assert_raises(ArgumentError) { rs.categories 'categories', :controller => 'content', :action => 'categories' }
      
      rs.named_route :categories, 'categories', :controller => 'content', :action => 'categories'
      rs.connect ':controller/:action/:id'
    end

    assert_equal ['/categories', []], rs.generate(:controller => 'content', :action => 'categories')
    assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'})
  end

  def test_named_route_helper_array
    test_named_route_method
    assert_equal [:categories_url, :hash_for_categories_url], ::ActionController::Routing::NamedRoutes::Helpers
  end

  def test_nil_defaults
    rs.draw do
      rs.connect 'journal',
        :controller => 'content',
        :action => 'list_journal',
        :date => nil, :user_id => nil
      rs.connect ':controller/:action/:id'
    end

    assert_equal ['/journal', []], rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil)
  end
end

class ControllerComponentTest < Test::Unit::TestCase
  
  def test_traverse_to_controller_should_not_load_arbitrary_files
    load_path = $:.dup
    base = File.dirname(File.dirname(File.expand_path(__FILE__)))
    $: << File.join(base, 'fixtures')
    Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root')
    assert_equal nil, ActionController::Routing::ControllerComponent.traverse_to_controller(%w(dont_load pretty please))
  ensure
    $:[0..-1] = load_path
    Object.send :remove_const, :RAILS_ROOT
  end
  
  def test_traverse_should_not_trip_on_non_module_constants
    assert_equal nil, ActionController::Routing::ControllerComponent.traverse_to_controller(%w(admin some_constant a))
  end
  
  # This is evil, but people do it.
  def test_traverse_to_controller_should_pass_thru_classes
    load_path = $:.dup
    base = File.dirname(File.dirname(File.expand_path(__FILE__)))
    $: << File.join(base, 'fixtures')
    $: << File.join(base, 'fixtures/application_root/app/controllers')
    $: << File.join(base, 'fixtures/application_root/app/models')
    Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root')
    pair = ActionController::Routing::ControllerComponent.traverse_to_controller(%w(a_class_that_contains_a_controller poorly_placed))
    
    # Make sure the container class was loaded properly
    assert defined?(AClassThatContainsAController)
    assert_kind_of Class, AClassThatContainsAController
    assert_equal :you_know_it, AClassThatContainsAController.is_special?
    
    # Make sure the controller was too
    assert_kind_of Array, pair
    assert_equal 2, pair[1]
    klass = pair.first
    assert_kind_of Class, klass
    assert_equal :decidedly_so, klass.is_evil?
    assert klass.ancestors.include?(ActionController::Base)
    assert defined?(AClassThatContainsAController::PoorlyPlacedController)
    assert_equal klass, AClassThatContainsAController::PoorlyPlacedController
  ensure
    $:[0..-1] = load_path
    Object.send :remove_const, :RAILS_ROOT
  end
  
  def test_traverse_to_nested_controller
    load_path = $:.dup
    base = File.dirname(File.dirname(File.expand_path(__FILE__)))
    $: << File.join(base, 'fixtures')
    $: << File.join(base, 'fixtures/application_root/app/controllers')
    Object.send :const_set, :RAILS_ROOT, File.join(base, 'fixtures/application_root')
    pair = ActionController::Routing::ControllerComponent.traverse_to_controller(%w(module_that_holds_controllers nested))
    
    assert_not_equal nil, pair
    
    # Make sure that we created a module for the dir
    assert defined?(ModuleThatHoldsControllers)
    assert_kind_of Module, ModuleThatHoldsControllers

    # Make sure the controller is ok
    assert_kind_of Array, pair
    assert_equal 2, pair[1]
    klass = pair.first
    assert_kind_of Class, klass
    assert klass.ancestors.include?(ActionController::Base)
    assert defined?(ModuleThatHoldsControllers::NestedController)
    assert_equal klass, ModuleThatHoldsControllers::NestedController
  ensure
    $:[0..-1] = load_path
    Object.send :remove_const, :RAILS_ROOT
  end
  
end

end
require File.join(File.dirname(__FILE__), '..', 'abstract_unit')


module TestFileUtils
  def file_name() File.basename(__FILE__) end
  def file_path() File.expand_path(__FILE__) end
  def file_data() File.open(file_path, 'rb') { |f| f.read } end
end


class SendFileController < ActionController::Base
  include TestFileUtils
  layout "layouts/standard" # to make sure layouts don't interfere

  attr_writer :options
  def options() @options ||= {} end

  def file() send_file(file_path, options) end
  def data() send_data(file_data, options) end

  def rescue_action(e) raise end
end

SendFileController.template_root = File.dirname(__FILE__) + "/../fixtures/"

class SendFileTest < Test::Unit::TestCase
  include TestFileUtils

  def setup
    @controller = SendFileController.new
    @request = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
  end

  def test_file_nostream
    @controller.options = { :stream => false }
    response = nil
    assert_nothing_raised { response = process('file') }
    assert_not_nil response
    assert_kind_of String, response.body
    assert_equal file_data, response.body
  end

  def test_file_stream
    response = nil
    assert_nothing_raised { response = process('file') }
    assert_not_nil response
    assert_kind_of Proc, response.body

    require 'stringio'
    output = StringIO.new
    output.binmode
    assert_nothing_raised { response.body.call(response, output) }
    assert_equal file_data, output.string
  end

  def test_data
    response = nil
    assert_nothing_raised { response = process('data') }
    assert_not_nil response

    assert_kind_of String, response.body
    assert_equal file_data, response.body
  end

  # Test that send_file_headers! is setting the correct HTTP headers.
  def test_send_file_headers!
    options = {
      :length => 1,
      :type => 'type',
      :disposition => 'disposition',
      :filename => 'filename'
    }

    # Do it a few times: the resulting headers should be identical
    # no matter how many times you send with the same options.
    # Test resolving Ticket #458.
    @controller.headers = {}
    @controller.send(:send_file_headers!, options)
    @controller.send(:send_file_headers!, options)
    @controller.send(:send_file_headers!, options)

    h = @controller.headers
    assert_equal 1, h['Content-Length']
    assert_equal 'type', h['Content-Type']
    assert_equal 'disposition; filename="filename"', h['Content-Disposition']
    assert_equal 'binary', h['Content-Transfer-Encoding']

    # test overriding Cache-Control: no-cache header to fix IE open/save dialog
    @controller.headers = { 'Cache-Control' => 'no-cache' }
    @controller.send(:send_file_headers!, options)
    h = @controller.headers
    assert_equal 'private', h['Cache-Control']
  end

  %w(file data).each do |method|
    define_method "test_send_#{method}_status" do
      @controller.options = { :stream => false, :status => 500 }
      assert_nothing_raised { assert_not_nil process(method) }
      assert_equal '500', @controller.headers['Status']
    end

    define_method "test_default_send_#{method}_status" do
      @controller.options = { :stream => false }
      assert_nothing_raised { assert_not_nil process(method) }
      assert_equal ActionController::Base::DEFAULT_RENDER_STATUS_CODE, @controller.headers['Status']
    end
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class SessionManagementTest < Test::Unit::TestCase
  class SessionOffController < ActionController::Base
    session :off

    def show
      render_text "done"
    end

    def tell
      render_text "done"
    end
  end

  class TestController < ActionController::Base
    session :off, :only => :show
    session :session_secure => true, :except => :show
    session :off, :only => :conditional,
            :if => Proc.new { |r| r.parameters[:ws] }

    def show
      render_text "done"
    end

    def tell
      render_text "done"
    end

    def conditional
      render_text ">>>#{params[:ws]}<<<"
    end
  end

  class SpecializedController < SessionOffController
    session :disabled => false, :only => :something

    def something
      render_text "done"
    end

    def another
      render_text "done"
    end
  end

  def setup
    @request, @response = ActionController::TestRequest.new,
      ActionController::TestResponse.new
  end

  def test_session_off_globally
    @controller = SessionOffController.new
    get :show
    assert_equal false, @request.session_options
    get :tell
    assert_equal false, @request.session_options
  end

  def test_session_off_conditionally
    @controller = TestController.new
    get :show
    assert_equal false, @request.session_options
    get :tell
    assert_instance_of Hash, @request.session_options
    assert @request.session_options[:session_secure]
  end

  def test_controller_specialization_overrides_settings
    @controller = SpecializedController.new
    get :something
    assert_instance_of Hash, @request.session_options
    get :another
    assert_equal false, @request.session_options
  end

  def test_session_off_with_if
    @controller = TestController.new
    get :conditional
    assert_instance_of Hash, @request.session_options
    get :conditional, :ws => "ws"
    assert_equal false, @request.session_options
  end
  
  def test_session_store_setting
    ActionController::Base.session_store = :drb_store
    assert_equal CGI::Session::DRbStore, ActionController::Base.session_store

    if Object.const_defined?(:ActiveRecord)
      ActionController::Base.session_store = :active_record_store
      assert_equal CGI::Session::ActiveRecordStore, ActionController::Base.session_store
    end
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'
require File.dirname(__FILE__) + '/fake_controllers'

class TestTest < Test::Unit::TestCase
  class TestController < ActionController::Base
    def set_flash
      flash["test"] = ">#{flash["test"]}<"
      render :text => 'ignore me'
    end

    def render_raw_post
      raise Test::Unit::AssertionFailedError, "#raw_post is blank" if request.raw_post.blank?
      render :text => request.raw_post
    end

    def test_params
      render :text => params.inspect
    end                         
    
    def test_uri
      render :text => request.request_uri
    end

    def test_html_output
      render :text => <<HTML
<html>
  <body>
    <a href="/"><img src="/images/button.png" /></a>
    <div id="foo">
      <ul>
        <li class="item">hello</li>
        <li class="item">goodbye</li>
      </ul>
    </div>
    <div id="bar">
      <form action="/somewhere">
        Name: <input type="text" name="person[name]" id="person_name" />
      </form>
    </div>
  </body>
</html>
HTML
    end
    
    def test_only_one_param
      render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
    end

    def test_remote_addr
      render :text => (request.remote_addr || "not specified")
    end
    
    def test_file_upload
      render :text => params[:file].size
    end

    def redirect_to_symbol
      redirect_to :generate_url, :id => 5
    end

    private
    
      def rescue_action(e)
        raise e
      end

      def generate_url(opts)
        url_for(opts.merge(:action => "test_uri"))
      end
  end

  def setup
    @controller = TestController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    ActionController::Routing::Routes.reload
  end

  def teardown
    ActionController::Routing::Routes.reload
  end

  def test_raw_post_handling
    params = {:page => {:name => 'page name'}, 'some key' => 123}
    get :render_raw_post, params.dup

    raw_post = params.map {|k,v| [CGI::escape(k.to_s), CGI::escape(v.to_s)].join('=')}.sort.join('&')
    assert_equal raw_post, @response.body
  end

  def test_process_without_flash
    process :set_flash
    assert_equal '><', flash['test']
  end          
  
  def test_process_with_flash
    process :set_flash, nil, nil, { "test" => "value" }
    assert_equal '>value<', flash['test']
  end

  def test_process_with_request_uri_with_no_params
    process :test_uri
    assert_equal "/test_test/test/test_uri", @response.body
  end

  def test_process_with_request_uri_with_params
    process :test_uri, :id => 7
    assert_equal "/test_test/test/test_uri/7", @response.body
  end

  def test_process_with_request_uri_with_params_with_explicit_uri
    @request.set_REQUEST_URI "/explicit/uri"
    process :test_uri, :id => 7
    assert_equal "/explicit/uri", @response.body
  end                     
  
  def test_multiple_calls         
    process :test_only_one_param, :left => true
    assert_equal "OK", @response.body
    process :test_only_one_param, :right => true  
    assert_equal "OK", @response.body
  end

  def test_assert_tag_tag
    process :test_html_output

    # there is a 'form' tag
    assert_tag :tag => 'form'
    # there is not an 'hr' tag
    assert_no_tag :tag => 'hr'
  end

  def test_assert_tag_attributes
    process :test_html_output

    # there is a tag with an 'id' of 'bar'
    assert_tag :attributes => { :id => "bar" }
    # there is no tag with a 'name' of 'baz'
    assert_no_tag :attributes => { :name => "baz" }
  end

  def test_assert_tag_parent
    process :test_html_output

    # there is a tag with a parent 'form' tag
    assert_tag :parent => { :tag => "form" }
    # there is no tag with a parent of 'input'
    assert_no_tag :parent => { :tag => "input" }
  end

  def test_assert_tag_child
    process :test_html_output

    # there is a tag with a child 'input' tag
    assert_tag :child => { :tag => "input" }
    # there is no tag with a child 'strong' tag
    assert_no_tag :child => { :tag => "strong" }
  end

  def test_assert_tag_ancestor
    process :test_html_output

    # there is a 'li' tag with an ancestor having an id of 'foo'
    assert_tag :ancestor => { :attributes => { :id => "foo" } }, :tag => "li"
    # there is no tag of any kind with an ancestor having an href matching 'foo'
    assert_no_tag :ancestor => { :attributes => { :href => /foo/ } }
  end

  def test_assert_tag_descendant
    process :test_html_output

    # there is a tag with a decendant 'li' tag
    assert_tag :descendant => { :tag => "li" }
    # there is no tag with a descendant 'html' tag
    assert_no_tag :descendant => { :tag => "html" }
  end

  def test_assert_tag_sibling
    process :test_html_output

    # there is a tag with a sibling of class 'item'
    assert_tag :sibling => { :attributes => { :class => "item" } }
    # there is no tag with a sibling 'ul' tag
    assert_no_tag :sibling => { :tag => "ul" }
  end

  def test_assert_tag_after
    process :test_html_output

    # there is a tag following a sibling 'div' tag
    assert_tag :after => { :tag => "div" }
    # there is no tag following a sibling tag with id 'bar'
    assert_no_tag :after => { :attributes => { :id => "bar" } }
  end

  def test_assert_tag_before
    process :test_html_output

    # there is a tag preceeding a tag with id 'bar'
    assert_tag :before => { :attributes => { :id => "bar" } }
    # there is no tag preceeding a 'form' tag
    assert_no_tag :before => { :tag => "form" }
  end

  def test_assert_tag_children_count
    process :test_html_output

    # there is a tag with 2 children
    assert_tag :children => { :count => 2 }
    # there is no tag with 4 children
    assert_no_tag :children => { :count => 4 }
  end

  def test_assert_tag_children_less_than
    process :test_html_output

    # there is a tag with less than 5 children
    assert_tag :children => { :less_than => 5 }
    # there is no 'ul' tag with less than 2 children
    assert_no_tag :children => { :less_than => 2 }, :tag => "ul"
  end

  def test_assert_tag_children_greater_than
    process :test_html_output

    # there is a 'body' tag with more than 1 children
    assert_tag :children => { :greater_than => 1 }, :tag => "body"
    # there is no tag with more than 10 children
    assert_no_tag :children => { :greater_than => 10 }
  end

  def test_assert_tag_children_only
    process :test_html_output

    # there is a tag containing only one child with an id of 'foo'
    assert_tag :children => { :count => 1, 
                              :only => { :attributes => { :id => "foo" } } }
    # there is no tag containing only one 'li' child
    assert_no_tag :children => { :count => 1, :only => { :tag => "li" } }
  end

  def test_assert_tag_content
    process :test_html_output

    # the output contains the string "Name"
    assert_tag :content => "Name"
    # the output does not contain the string "test"
    assert_no_tag :content => "test"
  end
  
  def test_assert_tag_multiple
    process :test_html_output

    # there is a 'div', id='bar', with an immediate child whose 'action'
    # attribute matches the regexp /somewhere/.
    assert_tag :tag => "div", :attributes => { :id => "bar" },
               :child => { :attributes => { :action => /somewhere/ } }

    # there is no 'div', id='foo', with a 'ul' child with more than
    # 2 "li" children.
    assert_no_tag :tag => "div", :attributes => { :id => "foo" },
                  :child => {
                    :tag => "ul",
                    :children => { :greater_than => 2,
                                   :only => { :tag => "li" } } }
  end

  def test_assert_tag_children_without_content
    process :test_html_output
    
    # there is a form tag with an 'input' child which is a self closing tag
    assert_tag :tag => "form",
      :children => { :count => 1,
        :only => { :tag => "input" } }

    # the body tag has an 'a' child which in turn has an 'img' child
    assert_tag :tag => "body",
      :children => { :count => 1,
        :only => { :tag => "a",
          :children => { :count => 1,
            :only => { :tag => "img" } } } }
  end

  def test_assert_generates
    assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5'
  end

  def test_assert_routing
    assert_routing 'content', :controller => 'content', :action => 'index'
  end

  def test_assert_routing_in_module
    assert_routing 'admin/user', :controller => 'admin/user', :action => 'index'
  end

  def test_params_passing
    get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
    parsed_params = eval(@response.body)
    assert_equal(
      {'controller' => 'test_test/test', 'action' => 'test_params',
       'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
      parsed_params
    )
  end

  def test_id_converted_to_string
    get :test_params, :id => 20, :foo => Object.new
    assert_kind_of String, @request.path_parameters['id']
  end

  def test_array_path_parameter_handled_properly
    with_routing do |set|
      set.draw do 
        set.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params'
        set.connect ':controller/:action/:id'
      end
      
      get :test_params, :path => ['hello', 'world']
      assert_equal ['hello', 'world'], @request.path_parameters['path']
      assert_equal 'hello/world', @request.path_parameters['path'].to_s
    end
  end

  def test_assert_realistic_path_parameters
    get :test_params, :id => 20, :foo => Object.new

    # All elements of path_parameters should use string keys
    @request.path_parameters.keys.each do |key|
      assert_kind_of String, key
    end
  end

  def test_with_routing_places_routes_back
    assert ActionController::Routing::Routes
    routes_id = ActionController::Routing::Routes.object_id
    
    begin
      with_routing { raise 'fail' }
      fail 'Should not be here.'
    rescue RuntimeError
    end
    
    assert ActionController::Routing::Routes
    assert_equal routes_id, ActionController::Routing::Routes.object_id
  end

  def test_remote_addr
    get :test_remote_addr
    assert_equal "0.0.0.0", @response.body

    @request.remote_addr = "192.0.0.1"
    get :test_remote_addr
    assert_equal "192.0.0.1", @response.body
  end

  def test_header_properly_reset_after_remote_http_request
    xhr :get, :test_params
    assert_nil @request.env['HTTP_X_REQUESTED_WITH']
  end

   def test_header_properly_reset_after_get_request
    get :test_params
    @request.recycle!
    assert_nil @request.instance_variable_get("@request_method")
  end

  %w(controller response request).each do |variable|
    %w(get post put delete head process).each do |method|
      define_method("test_#{variable}_missing_for_#{method}_raises_error") do
        remove_instance_variable "@#{variable}"
        begin
          send(method, :test_remote_addr)
          assert false, "expected RuntimeError, got nothing"
        rescue RuntimeError => error
          assert true
          assert_match %r{@#{variable} is nil}, error.message
        rescue => error
          assert false, "expected RuntimeError, got #{error.class}"
        end
      end
    end
  end
  
  FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart'
  
  def test_test_uploaded_file
    filename = 'mona_lisa.jpg'
    path = "#{FILES_DIR}/#{filename}"
    content_type = 'image/png'
    
    file = ActionController::TestUploadedFile.new(path, content_type)
    assert_equal filename, file.original_filename
    assert_equal content_type, file.content_type
    assert_equal file.path, file.local_path
    assert_equal File.read(path), file.read
  end
  
  def test_fixture_file_upload
    post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
    assert_equal 159528, @response.body
  end
  
  def test_test_uploaded_file_exception_when_file_doesnt_exist
    assert_raise(RuntimeError) { ActionController::TestUploadedFile.new('non_existent_file') }
  end

  def test_assert_redirected_to_symbol
    get :redirect_to_symbol
    assert_redirected_to :generate_url
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class UrlRewriterTests < Test::Unit::TestCase
  def setup
    @request = ActionController::TestRequest.new
    @params = {}
    @rewriter = ActionController::UrlRewriter.new(@request, @params)
  end 
  
  def test_simple_build_query_string
    assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => '1', :y => '2')
  end
  def test_convert_ints_build_query_string
    assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => 1, :y => 2)
  end
  def test_escape_spaces_build_query_string
    assert_query_equal '?x=hello+world&y=goodbye+world', @rewriter.send(:build_query_string, :x => 'hello world', :y => 'goodbye world')
  end
  def test_expand_array_build_query_string
    assert_query_equal '?x[]=1&x[]=2', @rewriter.send(:build_query_string, :x => [1, 2])
  end

  def test_escape_spaces_build_query_string_selected_keys
    assert_query_equal '?x=hello+world', @rewriter.send(:build_query_string, {:x => 'hello world', :y => 'goodbye world'}, [:x])
  end

  def test_overwrite_params
    @params[:controller] = 'hi'
    @params[:action] = 'bye'
    @params[:id] = '2'

    assert_equal '/hi/hi/2', @rewriter.rewrite(:only_path => true, :overwrite_params => {:action => 'hi'})
    u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'})
    assert_match %r(/hi/hi/2$), u
  end
  

  private
    def split_query_string(str)
      [str[0].chr] + str[1..-1].split(/&/).sort
    end
  
    def assert_query_equal(q1, q2)
      assert_equal(split_query_string(q1), split_query_string(q2))
    end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class VerificationTest < Test::Unit::TestCase
  class TestController < ActionController::Base
    verify :only => :guarded_one, :params => "one",
           :redirect_to => { :action => "unguarded" }

    verify :only => :guarded_two, :params => %w( one two ),
           :redirect_to => { :action => "unguarded" }

    verify :only => :guarded_with_flash, :params => "one",
           :add_flash => { "notice" => "prereqs failed" },
           :redirect_to => { :action => "unguarded" }

    verify :only => :guarded_in_session, :session => "one",
           :redirect_to => { :action => "unguarded" }

    verify :only => [:multi_one, :multi_two], :session => %w( one two ),
           :redirect_to => { :action => "unguarded" }

    verify :only => :guarded_by_method, :method => :post,
           :redirect_to => { :action => "unguarded" }
           
    verify :only => :guarded_by_xhr, :xhr => true,
           :redirect_to => { :action => "unguarded" }
           
    verify :only => :guarded_by_not_xhr, :xhr => false,
           :redirect_to => { :action => "unguarded" }

    before_filter :unconditional_redirect, :only => :two_redirects
    verify :only => :two_redirects, :method => :post,
           :redirect_to => { :action => "unguarded" }

    verify :only => :must_be_post, :method => :post, :render => { :status => 500, :text => "Must be post"}

    def guarded_one
      render :text => "#{@params["one"]}"
    end

    def guarded_with_flash
      render :text => "#{@params["one"]}"
    end

    def guarded_two
      render :text => "#{@params["one"]}:#{@params["two"]}"
    end

    def guarded_in_session
      render :text => "#{@session["one"]}"
    end

    def multi_one
      render :text => "#{@session["one"]}:#{@session["two"]}"
    end

    def multi_two
      render :text => "#{@session["two"]}:#{@session["one"]}"
    end

    def guarded_by_method
      render :text => "#{@request.method}"
    end
    
    def guarded_by_xhr
      render :text => "#{@request.xhr?}"
    end
    
    def guarded_by_not_xhr
      render :text => "#{@request.xhr?}"
    end

    def unguarded
      render :text => "#{@params["one"]}"
    end

    def two_redirects
      render :nothing => true
    end
    
    def must_be_post
      render :text => "Was a post!"
    end
    
    protected
      def rescue_action(e) raise end

      def unconditional_redirect
        redirect_to :action => "unguarded"
      end
  end

  def setup
    @controller = TestController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  def test_guarded_one_with_prereqs
    get :guarded_one, :one => "here"
    assert_equal "here", @response.body
  end

  def test_guarded_one_without_prereqs
    get :guarded_one
    assert_redirected_to :action => "unguarded"
  end

  def test_guarded_with_flash_with_prereqs
    get :guarded_with_flash, :one => "here"
    assert_equal "here", @response.body
    assert_flash_empty
  end

  def test_guarded_with_flash_without_prereqs
    get :guarded_with_flash
    assert_redirected_to :action => "unguarded"
    assert_flash_equal "prereqs failed", "notice"
  end

  def test_guarded_two_with_prereqs
    get :guarded_two, :one => "here", :two => "there"
    assert_equal "here:there", @response.body
  end

  def test_guarded_two_without_prereqs_one
    get :guarded_two, :two => "there"
    assert_redirected_to :action => "unguarded"
  end

  def test_guarded_two_without_prereqs_two
    get :guarded_two, :one => "here"
    assert_redirected_to :action => "unguarded"
  end

  def test_guarded_two_without_prereqs_both
    get :guarded_two
    assert_redirected_to :action => "unguarded"
  end

  def test_unguarded_with_params
    get :unguarded, :one => "here"
    assert_equal "here", @response.body
  end

  def test_unguarded_without_params
    get :unguarded
    assert_equal "", @response.body
  end

  def test_guarded_in_session_with_prereqs
    get :guarded_in_session, {}, "one" => "here"
    assert_equal "here", @response.body
  end

  def test_guarded_in_session_without_prereqs
    get :guarded_in_session
    assert_redirected_to :action => "unguarded"
  end

  def test_multi_one_with_prereqs
    get :multi_one, {}, "one" => "here", "two" => "there"
    assert_equal "here:there", @response.body
  end

  def test_multi_one_without_prereqs
    get :multi_one
    assert_redirected_to :action => "unguarded"
  end

  def test_multi_two_with_prereqs
    get :multi_two, {}, "one" => "here", "two" => "there"
    assert_equal "there:here", @response.body
  end

  def test_multi_two_without_prereqs
    get :multi_two
    assert_redirected_to :action => "unguarded"
  end

  def test_guarded_by_method_with_prereqs
    post :guarded_by_method
    assert_equal "post", @response.body
  end

  def test_guarded_by_method_without_prereqs
    get :guarded_by_method
    assert_redirected_to :action => "unguarded"
  end
  
  def test_guarded_by_xhr_with_prereqs
    xhr :post, :guarded_by_xhr
    assert_equal "true", @response.body
  end
    
  def test_guarded_by_xhr_without_prereqs
    get :guarded_by_xhr
    assert_redirected_to :action => "unguarded"
  end
  
  def test_guarded_by_not_xhr_with_prereqs
    get :guarded_by_not_xhr
    assert_equal "false", @response.body
  end
    
  def test_guarded_by_not_xhr_without_prereqs
    xhr :post, :guarded_by_not_xhr
    assert_redirected_to :action => "unguarded"
  end
  
  def test_guarded_post_and_calls_render_succeeds
    post :must_be_post
    assert_equal "Was a post!", @response.body
  end
    
  def test_guarded_post_and_calls_render_fails
    get :must_be_post
    assert_response 500
    assert_equal "Must be post", @response.body
  end
  

  def test_second_redirect
    assert_nothing_raised { get :two_redirects }
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'
require 'stringio'

class WebServiceTest < Test::Unit::TestCase

  class MockCGI < CGI #:nodoc:
    attr_accessor :stdinput, :stdoutput, :env_table

    def initialize(env, data = '')      
      self.env_table = env
      self.stdinput = StringIO.new(data)
      self.stdoutput = StringIO.new
      super()
    end
  end


  class TestController < ActionController::Base
    session :off

    def assign_parameters
      if params[:full]
        render :text => dump_params_keys
      else
        render :text => (params.keys - ['controller', 'action']).sort.join(", ")
      end
    end

    def dump_params_keys(hash=params)
      hash.keys.sort.inject("") do |s, k|
        value = hash[k]
        value = Hash === value ? "(#{dump_params_keys(value)})" : ""
        s << ", " unless s.empty?
        s << "#{k}#{value}"
      end
    end

    def rescue_action(e) raise end
  end
  
  def setup
    @controller = TestController.new
    ActionController::Base.param_parsers.clear
    ActionController::Base.param_parsers[Mime::XML] = :xml_node
  end
  
  def test_check_parameters
    process('GET')
    assert_equal '', @controller.response.body
  end

  def test_post_xml
    process('POST', 'application/xml', '<entry attributed="true"><summary>content...</summary></entry>')
    
    assert_equal 'entry', @controller.response.body
    assert @controller.params.has_key?(:entry)
    assert_equal 'content...', @controller.params["entry"].summary.node_value
    assert_equal 'true', @controller.params["entry"]['attributed']
  end
  
  def test_put_xml
    process('PUT', 'application/xml', '<entry attributed="true"><summary>content...</summary></entry>')
    
    assert_equal 'entry', @controller.response.body
    assert @controller.params.has_key?(:entry)
    assert_equal 'content...', @controller.params["entry"].summary.node_value
    assert_equal 'true', @controller.params["entry"]['attributed']
  end

  def test_register_and_use_yaml
    ActionController::Base.param_parsers[Mime::YAML] = Proc.new { |d| YAML.load(d) }
    process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml)
    assert_equal 'entry', @controller.response.body
    assert @controller.params.has_key?(:entry)
    assert_equal 'loaded from yaml', @controller.params["entry"]
  end
  
  def test_register_and_use_yaml_as_symbol
    ActionController::Base.param_parsers[Mime::YAML] = :yaml
    process('POST', 'application/x-yaml', {"entry" => "loaded from yaml"}.to_yaml)
    assert_equal 'entry', @controller.response.body
    assert @controller.params.has_key?(:entry)
    assert_equal 'loaded from yaml', @controller.params["entry"]
  end

  def test_register_and_use_xml_simple
    ActionController::Base.param_parsers[Mime::XML] = Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) }
    process('POST', 'application/xml', '<request><summary>content...</summary><title>SimpleXml</title></request>' )
    assert_equal 'summary, title', @controller.response.body
    assert @controller.params.has_key?(:summary)
    assert @controller.params.has_key?(:title)
    assert_equal 'content...', @controller.params["summary"]
    assert_equal 'SimpleXml', @controller.params["title"]
  end

  def test_use_xml_ximple_with_empty_request
    ActionController::Base.param_parsers[Mime::XML] = :xml_simple
    assert_nothing_raised { process('POST', 'application/xml', "") }
    assert_equal "", @controller.response.body
  end
  
  def test_deprecated_request_methods
    process('POST', 'application/x-yaml')
    assert_equal Mime::YAML, @controller.request.content_type
    assert_equal true, @controller.request.post?
    assert_equal :yaml, @controller.request.post_format
    assert_equal true, @controller.request.yaml_post?
    assert_equal false, @controller.request.xml_post?    
  end

  def test_dasherized_keys_as_xml
    ActionController::Base.param_parsers[Mime::XML] = :xml_simple
    process('POST', 'application/xml', "<first-key>\n<sub-key>...</sub-key>\n</first-key>", true)
    assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
    assert_equal "...", @controller.params[:first_key][:sub_key]
  end

  def test_typecast_as_xml
    ActionController::Base.param_parsers[Mime::XML] = :xml_simple
    process('POST', 'application/xml', <<-XML)
      <data>
        <a type="integer">15</a>
        <b type="boolean">false</b>
        <c type="boolean">true</c>
        <d type="date">2005-03-17</d>
        <e type="datetime">2005-03-17T21:41:07Z</e>
        <f>unparsed</f>
        <g type="integer">1</g>
        <g>hello</g>
        <g type="date">1974-07-25</g>
      </data>
    XML
    params = @controller.params
    assert_equal 15, params[:data][:a]
    assert_equal false, params[:data][:b]
    assert_equal true, params[:data][:c]
    assert_equal Date.new(2005,3,17), params[:data][:d]
    assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e]
    assert_equal "unparsed", params[:data][:f]
    assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g]
  end

  def test_entities_unescaped_as_xml_simple
    ActionController::Base.param_parsers[Mime::XML] = :xml_simple
    process('POST', 'application/xml', <<-XML)
      <data>&lt;foo &quot;bar&apos;s&quot; &amp; friends&gt;</data>
    XML
    assert_equal %(<foo "bar's" & friends>), @controller.params[:data]
  end

  def test_dasherized_keys_as_yaml
    ActionController::Base.param_parsers[Mime::YAML] = :yaml
    process('POST', 'application/x-yaml', "---\nfirst-key:\n  sub-key: ...\n", true)
    assert_equal 'action, controller, first_key(sub_key), full', @controller.response.body
    assert_equal "...", @controller.params[:first_key][:sub_key]
  end

  def test_typecast_as_yaml
    ActionController::Base.param_parsers[Mime::YAML] = :yaml
    process('POST', 'application/x-yaml', <<-YAML)
      ---
      data:
        a: 15
        b: false
        c: true
        d: 2005-03-17
        e: 2005-03-17T21:41:07Z
        f: unparsed
        g:
          - 1
          - hello
          - 1974-07-25
    YAML
    params = @controller.params
    assert_equal 15, params[:data][:a]
    assert_equal false, params[:data][:b]
    assert_equal true, params[:data][:c]
    assert_equal Date.new(2005,3,17), params[:data][:d]
    assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e]
    assert_equal "unparsed", params[:data][:f]
    assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g]
  end
  
  private  
  
  def process(verb, content_type = 'application/x-www-form-urlencoded', data = '', full=false)
    
    cgi = MockCGI.new({
      'REQUEST_METHOD' => verb,
      'CONTENT_TYPE'   => content_type,
      'QUERY_STRING'   => "action=assign_parameters&controller=webservicetest/test#{"&full=1" if full}",
      "REQUEST_URI"    => "/",
      "HTTP_HOST"      => 'testdomain.com',
      "CONTENT_LENGTH" => data.size,
      "SERVER_PORT"    => "80",
      "HTTPS"          => "off"}, data)
          
    @controller.send(:process, ActionController::CgiRequest.new(cgi, {}), ActionController::CgiResponse.new(cgi))
  end
    
end


class XmlNodeTest < Test::Unit::TestCase
  def test_all
    xn = XmlNode.from_xml(%{<?xml version="1.0" encoding="UTF-8"?>
      <response success='true'>
      <page title='Ajax Summit' id='1133' email_address='ry87ib@backpackit.com'>
        <description>With O'Reilly and Adaptive Path</description>
        <notes>
          <note title='Hotel' id='1020' created_at='2005-05-14 16:41:11'>
            Staying at the Savoy
          </note>
        </notes>
        <tags>
          <tag name='Technology' id='4' />
          <tag name='Travel' id='5' />
        </tags>
      </page>
      </response>
     }
    )     
    assert_equal 'UTF-8', xn.node.document.encoding
    assert_equal '1.0', xn.node.document.version
    assert_equal 'true', xn['success']
    assert_equal 'response', xn.node_name
    assert_equal 'Ajax Summit', xn.page['title']
    assert_equal '1133', xn.page['id']
    assert_equal "With O'Reilly and Adaptive Path", xn.page.description.node_value
    assert_equal nil, xn.nonexistent
    assert_equal "Staying at the Savoy", xn.page.notes.note.node_value.strip
    assert_equal 'Technology', xn.page.tags.tag[0]['name']
    assert_equal 'Travel', xn.page.tags.tag[1][:name]
    matches = xn.xpath('//@id').map{ |id| id.to_i }
    assert_equal [4, 5, 1020, 1133], matches.sort
    matches = xn.xpath('//tag').map{ |tag| tag['name'] }
    assert_equal ['Technology', 'Travel'], matches.sort
    assert_equal "Ajax Summit", xn.page['title']
    xn.page['title'] = 'Ajax Summit V2'
    assert_equal "Ajax Summit V2", xn.page['title']
    assert_equal "Staying at the Savoy", xn.page.notes.note.node_value.strip
    xn.page.notes.note.node_value = "Staying at the Ritz"
    assert_equal "Staying at the Ritz", xn.page.notes.note.node_value.strip
    assert_equal '5', xn.page.tags.tag[1][:id]
    xn.page.tags.tag[1]['id'] = '7'
    assert_equal '7', xn.page.tags.tag[1]['id']
  end
  

  def test_small_entry
    node = XmlNode.from_xml('<entry>hi</entry>')
    assert_equal 'hi', node.node_value
  end

end
class AClassThatContainsAController::PoorlyPlacedController < ActionController::Base
  
  def self.is_evil?
    :decidedly_so
  end
  
endclass ModuleThatHoldsControllers::NestedController < ActionController::Base
  
endclass AClassThatContainsAController #often < ActiveRecord::Base
  
  def self.is_special?
    :you_know_it
  end
  
endclass Company < ActiveRecord::Base
  attr_protected :rating
  set_sequence_name :companies_nonstd_seq

  validates_presence_of :name
  def validate
    errors.add('rating', 'rating should not be 2') if rating == 2
  end  
endclass Developer < ActiveRecord::Base
  has_and_belongs_to_many :projects
end

class DeVeLoPeR < ActiveRecord::Base
  set_table_name "developers"
end
# see routing/controller component tests

raise Exception, "I should never be loaded"module AbcHelper
  def bare_a() end
  def bare_b() end
  def bare_c() end
end
module Fun::GamesHelper
  def stratego() "Iz guuut!" end
endmodule Fun::PDFHelper
  def foobar() 'baz' end
end
class Project < ActiveRecord::Base
  has_and_belongs_to_many :developers, :uniq => true
end
class Reply < ActiveRecord::Base
  belongs_to :topic, :include => [:replies]
  
  validates_presence_of :content
end
class Topic < ActiveRecord::Base
  has_many :replies, :include => [:user], :dependent => true
endrequire 'test/unit'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/text_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/form_tag_helper'
# require File.dirname(__FILE__) + '/../../lib/action_view/helpers/active_record_helper'

class ActiveRecordHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::FormHelper
  include ActionView::Helpers::ActiveRecordHelper
  include ActionView::Helpers::TextHelper
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::FormTagHelper

  silence_warnings do
    Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on)
    Post.class_eval do
      alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
      alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
      alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
    end
    Column = Struct.new("Column", :type, :name, :human_name)
  end

  def setup
    @post = Post.new    
    def @post.errors
      Class.new {
        def on(field) field == "author_name" || field == "body" end
        def empty?() false end 
        def count() 1 end 
        def full_messages() [ "Author name can't be empty" ] end
      }.new
    end
    
    def @post.new_record?() true end
    def @post.to_param() nil end

    def @post.column_for_attribute(attr_name)
      Post.content_columns.select { |column| column.name == attr_name }.first
    end

    def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end

    @post.title       = "Hello World"
    @post.author_name = ""
    @post.body        = "Back to the hill and over it again!"
    @post.secret = 1
    @post.written_on  = Date.new(2004, 6, 15)

    @controller = Object.new
    def @controller.url_for(options, *parameters_for_method_reference)
      options = options.symbolize_keys

      [options[:action], options[:id].to_param].compact.join('/')
    end
  end

  def test_generic_input_tag
    assert_dom_equal(
      %(<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />), input("post", "title")
    )
  end
  
  def test_text_area_with_errors
    assert_dom_equal(
      %(<div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>),
      text_area("post", "body")
    )    
  end

  def test_text_field_with_errors
    assert_dom_equal(
      %(<div class="fieldWithErrors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /></div>),
      text_field("post", "author_name")
    )
  end
  
  def test_form_with_string
    assert_dom_equal(
      %(<form action="create" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
      form("post")
    )

    class << @post
      def new_record?() false end
      def to_param() id end
      def id() 1 end
    end
    assert_dom_equal(
      %(<form action="update/1" method="post"><input id="post_id" name="post[id]" type="hidden" value="1" /><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Update" /></form>),
      form("post")
    )
  end
  
  def test_form_with_date
    def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end

    assert_dom_equal(
      %(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
      form("post")
    )
  end
  
  def test_form_with_datetime
    def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
    @post.written_on  = Time.gm(2004, 6, 15, 16, 30)

    assert_dom_equal(
      %(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n &mdash; <select name="post[written_on(4i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select name="post[written_on(5i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
      form("post")
    )
  end
  
  def test_error_for_block
    assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post")
    assert_equal %(<div class="errorDeathByClass" id="errorDeathById"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1")
  end
  
  def test_error_messages_for_handles_nil
    assert_equal "", error_messages_for("notthere")
  end

  def test_form_with_string_multipart
    assert_dom_equal(
      %(<form action="create" enctype="multipart/form-data" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
      form("post", :multipart => true)
    )
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class AssetTagHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::AssetTagHelper

  def setup
    @controller = Class.new do

      attr_accessor :request
    
      def url_for(options, *parameters_for_method_reference)
        "http://www.example.com"
      end
      
    end.new
    
    @request = Class.new do 
      def relative_url_root
        ""
      end       
    end.new

    @controller.request = @request
    
    ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
  end

  def teardown
    Object.send(:remove_const, :RAILS_ROOT) if defined?(RAILS_ROOT)
    ENV["RAILS_ASSET_ID"] = nil
  end

  AutoDiscoveryToTag = {
    %(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
    %(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com" rel="alternate" title="ATOM" type="application/atom+xml" />),
    %(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />),
    %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(<link href="http://localhost/feed" rel="alternate" title="RSS" type="application/rss+xml" />),
    %(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
    %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="application/rss+xml" />),
    %(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="" type="text/html" />),
    %(auto_discovery_link_tag(nil, {}, {:title => "No stream.. really", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="No stream.. really" type="text/html" />),
    %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS", :type => "text/html"})) => %(<link href="http://www.example.com" rel="alternate" title="My RSS" type="text/html" />),
    %(auto_discovery_link_tag(:atom, {}, {:rel => "Not so alternate"})) => %(<link href="http://www.example.com" rel="Not so alternate" title="ATOM" type="application/atom+xml" />),
  }

  JavascriptPathToTag = {
    %(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js),
    %(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js)
  }

  JavascriptIncludeToTag = {
    %(javascript_include_tag("xmlhr")) => %(<script src="/javascripts/xmlhr.js" type="text/javascript"></script>),
    %(javascript_include_tag("xmlhr", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/xmlhr.js" type="text/javascript"></script>),
    %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
    %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>),
    %(javascript_include_tag(:defaults, "test")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/test.js" type="text/javascript"></script>),
    %(javascript_include_tag("test", :defaults)) => %(<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>)
  }

  StylePathToTag = {
    %(stylesheet_path("style")) => %(/stylesheets/style.css),
    %(stylesheet_path('dir/file')) => %(/stylesheets/dir/file.css),
    %(stylesheet_path('/dir/file')) => %(/dir/file.css)
  }

  StyleLinkToTag = {
    %(stylesheet_link_tag("style")) => %(<link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
    %(stylesheet_link_tag("/dir/file")) => %(<link href="/dir/file.css" media="screen" rel="Stylesheet" type="text/css" />),
    %(stylesheet_link_tag("dir/file")) => %(<link href="/stylesheets/dir/file.css" media="screen" rel="Stylesheet" type="text/css" />),
    %(stylesheet_link_tag("style", :media => "all")) => %(<link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />),
    %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />\n<link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />)
  }

  ImagePathToTag = {
    %(image_path("xml")) => %(/images/xml.png),
  }

  ImageLinkToTag = {
    %(image_tag("xml")) => %(<img alt="Xml" src="/images/xml.png" />),
    %(image_tag("rss", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.png" />),
    %(image_tag("gold", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />),
    %(image_tag("symbolize", "size" => "45x70")) => %(<img alt="Symbolize" height="70" src="/images/symbolize.png" width="45" />),
    %(image_tag("http://www.rubyonrails.com/images/rails")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />)
  }

  def test_auto_discovery
    AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_javascript_path
    JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_javascript_include
    JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end
  
  def test_register_javascript_include_default
    ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider'
    assert_dom_equal  %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
    ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2'
    assert_dom_equal  %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/slider.js" type="text/javascript"></script>\n<script src="/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/elsewhere/blub/lib2.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
  end
  
  def test_style_path
    StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_style_link
    StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_image_path
    ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_image_tag
    ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end
  
  def test_timebased_asset_id
    Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
    expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s
    assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
  end

  def test_skipping_asset_id_on_complete_url
    Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
    assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png")
  end
  
  def test_preset_asset_id
    Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + "/../fixtures/")
    ENV["RAILS_ASSET_ID"] = "4500"
    assert_equal %(<img alt="Rails" src="/images/rails.png?4500" />), image_tag("rails.png")
  end
end

class AssetTagHelperNonVhostTest < Test::Unit::TestCase
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::AssetTagHelper

  def setup
    @controller = Class.new do
    
      attr_accessor :request

      def url_for(options, *parameters_for_method_reference)
        "http://www.example.com/calloboration/hieraki"
      end
      
    end.new
    
    @request = Class.new do 
      def relative_url_root
        "/calloboration/hieraki"
      end
    end.new
    
    @controller.request = @request
    
    ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
  end

  AutoDiscoveryToTag = {
    %(auto_discovery_link_tag(:rss, :action => "feed")) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />),
    %(auto_discovery_link_tag(:atom)) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="ATOM" type="application/atom+xml" />),
    %(auto_discovery_link_tag) => %(<link href="http://www.example.com/calloboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />),
  }

  JavascriptPathToTag = {
    %(javascript_path("xmlhr")) => %(/calloboration/hieraki/javascripts/xmlhr.js),
  }

  JavascriptIncludeToTag = {
    %(javascript_include_tag("xmlhr")) => %(<script src="/calloboration/hieraki/javascripts/xmlhr.js" type="text/javascript"></script>),
    %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/calloboration/hieraki/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/calloboration/hieraki/elsewhere/cools.js" type="text/javascript"></script>),
    %(javascript_include_tag(:defaults)) => %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>)
  }

  StylePathToTag = {
    %(stylesheet_path("style")) => %(/calloboration/hieraki/stylesheets/style.css),
  }

  StyleLinkToTag = {
    %(stylesheet_link_tag("style")) => %(<link href="/calloboration/hieraki/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
    %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/calloboration/hieraki/stylesheets/random.styles" media="screen" rel="Stylesheet" type="text/css" />\n<link href="/calloboration/hieraki/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />)
  }

  ImagePathToTag = {
    %(image_path("xml")) => %(/calloboration/hieraki/images/xml.png),
  }
  
  ImageLinkToTag = {
    %(image_tag("xml")) => %(<img alt="Xml" src="/calloboration/hieraki/images/xml.png" />),
    %(image_tag("rss", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/calloboration/hieraki/images/rss.png" />),
    %(image_tag("gold", :size => "45x70")) => %(<img alt="Gold" height="70" src="/calloboration/hieraki/images/gold.png" width="45" />),
    %(image_tag("http://www.example.com/images/icon.gif")) => %(<img alt="Icon" src="http://www.example.com/images/icon.gif" />),
    %(image_tag("symbolize", "size" => "45x70")) => %(<img alt="Symbolize" height="70" src="/calloboration/hieraki/images/symbolize.png" width="45" />)
  }

  def test_auto_discovery
    AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_javascript_path
    JavascriptPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_javascript_include
    JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end
  
  def test_register_javascript_include_default
    ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'slider'
    assert_dom_equal  %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/slider.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
    ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2'
    assert_dom_equal  %(<script src="/calloboration/hieraki/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/effects.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/controls.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/slider.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/javascripts/lib1.js" type="text/javascript"></script>\n<script src="/calloboration/hieraki/elsewhere/blub/lib2.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
  end

  def test_style_path
    StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_style_link
    StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end

  def test_image_path
    ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
  end
  
  def test_image_tag
    ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
    # Assigning a default alt tag should not cause an exception to be raised
    assert_nothing_raised { image_tag('') }
  end
  
  def test_stylesheet_with_asset_host_already_encoded
    ActionController::Base.asset_host = "http://foo.example.com"
    result = stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")
    assert_dom_equal(
      %(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />),
      result)
  ensure
    ActionController::Base.asset_host = ""
  end
  
end
require 'test/unit'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/benchmark_helper'

class BenchmarkHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::BenchmarkHelper

  class MockLogger
    attr_reader :logged

    def initialize
      @logged = []
    end

    def method_missing(method, *args)
      @logged << [method, args]
    end
  end

  def setup
    @logger = MockLogger.new
  end

  def test_without_logger_or_block
    @logger = nil
    assert_nothing_raised { benchmark }
  end

  def test_without_block
    assert_raise(LocalJumpError) { benchmark }
    assert @logger.logged.empty?
  end

  def test_without_logger
    @logger = nil
    i_was_run = false
    benchmark { i_was_run = true }
    assert !i_was_run
  end

  def test_defaults
    i_was_run = false
    benchmark { i_was_run = true }
    assert i_was_run
    assert 1, @logger.logged.size
    assert_last_logged
  end

  def test_with_message
    i_was_run = false
    benchmark('test_run') { i_was_run = true }
    assert i_was_run
    assert 1, @logger.logged.size
    assert_last_logged 'test_run'
  end

  def test_with_message_and_level
    i_was_run = false
    benchmark('debug_run', :debug) { i_was_run = true }
    assert i_was_run
    assert 1, @logger.logged.size
    assert_last_logged 'debug_run', :debug
  end

  private
    def assert_last_logged(message = 'Benchmarking', level = :info)
      last = @logger.logged.last
      assert 2, last.size
      assert_equal level, last.first
      assert 1, last[1].size
      assert last[1][0] =~ /^#{message} \(.*\)$/
    end
end
require 'test/unit'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/date_helper'
require File.dirname(__FILE__) + "/../abstract_unit"

class CompiledTemplateTests < Test::Unit::TestCase

  def setup
    @ct = ActionView::CompiledTemplates.new
    @v = Class.new
    @v.send :include, @ct
    @a = './test_compile_template_a.rhtml'
    @b = './test_compile_template_b.rhtml'
    @s = './test_compile_template_link.rhtml'
  end
  def teardown
    [@a, @b, @s].each do |f|
      `rm #{f}` if File.exist?(f) || File.symlink?(f)
    end
  end
  attr_reader :ct, :v

  def test_name_allocation
    hi_world = ct.method_names['hi world']
    hi_sexy = ct.method_names['hi sexy']
    wish_upon_a_star = ct.method_names['I love seeing decent error messages']
    
    assert_equal hi_world, ct.method_names['hi world']
    assert_equal hi_sexy, ct.method_names['hi sexy']
    assert_equal wish_upon_a_star, ct.method_names['I love seeing decent error messages']
    assert_equal 3, [hi_world, hi_sexy, wish_upon_a_star].uniq.length
  end

  def test_wrap_source
    assert_equal(
      "def aliased_assignment(value)\nself.value = value\nend",
      @ct.wrap_source(:aliased_assignment, [:value], 'self.value = value')
    )

    assert_equal(
      "def simple()\nnil\nend",
      @ct.wrap_source(:simple, [], 'nil')
    )
  end

  def test_compile_source_single_method
    selector = ct.compile_source('doubling method', [:a], 'a + a')
    assert_equal 2, @v.new.send(selector, 1)
    assert_equal 4, @v.new.send(selector, 2)
    assert_equal -4, @v.new.send(selector, -2)
    assert_equal 0, @v.new.send(selector, 0)
    selector
  end

  def test_compile_source_two_method
    sel1 = test_compile_source_single_method # compile the method in the other test
    sel2 = ct.compile_source('doubling method', [:a, :b], 'a + b + a + b')
    assert_not_equal sel1, sel2

    assert_equal 2, @v.new.send(sel1, 1)
    assert_equal 4, @v.new.send(sel1, 2)

    assert_equal 6, @v.new.send(sel2, 1, 2)
    assert_equal 32, @v.new.send(sel2, 15, 1)
  end

  def test_mtime
    t1 = Time.now
    test_compile_source_single_method
    assert (t1..Time.now).include?(ct.mtime('doubling method', [:a]))
  end

  def test_compile_time
    `echo '#{@a}' > #{@a}; echo '#{@b}' > #{@b}; ln -s #{@a} #{@s}`

    v = ActionView::Base.new
    v.base_path = '.'
    v.cache_template_loading = false;

    sleep 1
    t = Time.now
    v.compile_and_render_template(:rhtml, '', @a)
    v.compile_and_render_template(:rhtml, '', @b)
    v.compile_and_render_template(:rhtml, '', @s)
    a_n = v.method_names[@a]
    b_n = v.method_names[@b]
    s_n = v.method_names[@s]
    # all of the files have changed since last compile
    assert v.compile_time[a_n] > t
    assert v.compile_time[b_n] > t
    assert v.compile_time[s_n] > t

    sleep 1
    t = Time.now
    v.compile_and_render_template(:rhtml, '', @a)
    v.compile_and_render_template(:rhtml, '', @b)
    v.compile_and_render_template(:rhtml, '', @s)
    # none of the files have changed since last compile
    assert v.compile_time[a_n] < t
    assert v.compile_time[b_n] < t
    assert v.compile_time[s_n] < t

    `rm #{@s}; ln -s #{@b} #{@s}`
    v.compile_and_render_template(:rhtml, '', @a)
    v.compile_and_render_template(:rhtml, '', @b)
    v.compile_and_render_template(:rhtml, '', @s)
    # the symlink has changed since last compile
    assert v.compile_time[a_n] < t
    assert v.compile_time[b_n] < t
    assert v.compile_time[s_n] > t

    sleep 1
    `touch #{@b}`
    t = Time.now
    v.compile_and_render_template(:rhtml, '', @a)
    v.compile_and_render_template(:rhtml, '', @b)
    v.compile_and_render_template(:rhtml, '', @s)
    # the file at the end of the symlink has changed since last compile
    # both the symlink and the file at the end of it should be recompiled
    assert v.compile_time[a_n] < t
    assert v.compile_time[b_n] > t
    assert v.compile_time[s_n] > t
  end
end

module ActionView
  class Base
    def compile_time
      @@compile_time
    end
    def method_names
      @@method_names
    end
  end
end
require 'test/unit'
require File.dirname(__FILE__) + "/../abstract_unit"

class DateHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::DateHelper
  include ActionView::Helpers::FormHelper

  silence_warnings do
    Post = Struct.new("Post", :written_on, :updated_at)
  end

  def test_distance_in_words
    from = Time.mktime(2004, 3, 6, 21, 41, 18)
    
    assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 25))
    assert_equal "5 minutes", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 46, 25))
    assert_equal "about 1 hour", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 22, 47, 25))
    assert_equal "about 3 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 0, 41))
    assert_equal "about 4 hours", distance_of_time_in_words(from, Time.mktime(2004, 3, 7, 1, 20))
    assert_equal "2 days", distance_of_time_in_words(from, Time.mktime(2004, 3, 9, 15, 40))

    # include seconds 
    assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 19), false)
    assert_equal "less than 5 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 19), true)
    assert_equal "less than 10 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 28), true)
    assert_equal "less than 20 seconds", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 38), true)
    assert_equal "half a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 41, 48), true)
    assert_equal "less than a minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 17), true)
    
    assert_equal "1 minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 18), true)
    assert_equal "1 minute", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 28), true)
    assert_equal "2 minutes", distance_of_time_in_words(from, Time.mktime(2004, 3, 6, 21, 42, 48), true)
    
    # test to < from
    assert_equal "about 4 hours", distance_of_time_in_words(Time.mktime(2004, 3, 7, 1, 20), from)
    assert_equal "less than 20 seconds", distance_of_time_in_words(Time.mktime(2004, 3, 6, 21, 41, 38), from, true)
    
    # test with integers
    assert_equal "less than a minute", distance_of_time_in_words(50)
    assert_equal "about 1 hour", distance_of_time_in_words(60*60)
    
    # more cumbersome test with integers
    assert_equal "less than a minute", distance_of_time_in_words(0, 50)
    assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0)
    
  end

  def test_distance_in_words_date
    start_date = Date.new 1975, 1, 31
    end_date = Date.new 1977, 4, 17
    assert_not_equal("13 minutes",
                     distance_of_time_in_words(start_date, end_date))
  end

  def test_select_day
    expected = %(<select name="date[day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_day(Time.mktime(2003, 8, 16))
    assert_equal expected, select_day(16)
  end

  def test_select_day_with_blank
    expected = %(<select name="date[day]">\n)
    expected <<
%(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_day(Time.mktime(2003, 8, 16), :include_blank => true)
    assert_equal expected, select_day(16, :include_blank => true)
  end
  
  def test_select_day_nil_with_blank
    expected = %(<select name="date[day]">\n)
    expected <<
%(<option value=""></option>\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_day(nil, :include_blank => true)
  end

  def test_select_month
    expected = %(<select name="date[month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16))
    assert_equal expected, select_month(8)
  end

  def test_select_month_with_disabled
    expected = %(<select name="date[month]" disabled="disabled">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16), :disabled => true)
    assert_equal expected, select_month(8, :disabled => true)
  end

  def test_select_month_with_field_name_override
    expected = %(<select name="date[mois]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16), :field_name => 'mois')
    assert_equal expected, select_month(8, :field_name => 'mois')
  end

  def test_select_month_with_blank
    expected = %(<select name="date[month]">\n)
    expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16), :include_blank => true)
    assert_equal expected, select_month(8, :include_blank => true)
  end

  def test_select_month_nil_with_blank
    expected = %(<select name="date[month]">\n)
    expected << %(<option value=""></option>\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(nil, :include_blank => true)
  end

  def test_select_month_with_numbers
    expected = %(<select name="date[month]">\n)
    expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8" selected="selected">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_month_numbers => true)
    assert_equal expected, select_month(8, :use_month_numbers => true)
  end

  def test_select_month_with_numbers_and_names
    expected = %(<select name="date[month]">\n)
    expected << %(<option value="1">1 - January</option>\n<option value="2">2 - February</option>\n<option value="3">3 - March</option>\n<option value="4">4 - April</option>\n<option value="5">5 - May</option>\n<option value="6">6 - June</option>\n<option value="7">7 - July</option>\n<option value="8" selected="selected">8 - August</option>\n<option value="9">9 - September</option>\n<option value="10">10 - October</option>\n<option value="11">11 - November</option>\n<option value="12">12 - December</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true)
    assert_equal expected, select_month(8, :add_month_numbers => true)
  end

  def test_select_month_with_numbers_and_names_with_abbv
    expected = %(<select name="date[month]">\n)
    expected << %(<option value="1">1 - Jan</option>\n<option value="2">2 - Feb</option>\n<option value="3">3 - Mar</option>\n<option value="4">4 - Apr</option>\n<option value="5">5 - May</option>\n<option value="6">6 - Jun</option>\n<option value="7">7 - Jul</option>\n<option value="8" selected="selected">8 - Aug</option>\n<option value="9">9 - Sep</option>\n<option value="10">10 - Oct</option>\n<option value="11">11 - Nov</option>\n<option value="12">12 - Dec</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16), :add_month_numbers => true, :use_short_month => true)
    assert_equal expected, select_month(8, :add_month_numbers => true, :use_short_month => true)
  end

  def test_select_month_with_abbv
    expected = %(<select name="date[month]">\n)
    expected << %(<option value="1">Jan</option>\n<option value="2">Feb</option>\n<option value="3">Mar</option>\n<option value="4">Apr</option>\n<option value="5">May</option>\n<option value="6">Jun</option>\n<option value="7">Jul</option>\n<option value="8" selected="selected">Aug</option>\n<option value="9">Sep</option>\n<option value="10">Oct</option>\n<option value="11">Nov</option>\n<option value="12">Dec</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_month(Time.mktime(2003, 8, 16), :use_short_month => true)
    assert_equal expected, select_month(8, :use_short_month => true)
  end

  def test_select_year
    expected = %(<select name="date[year]">\n)
    expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005)
    assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005)
  end

  def test_select_year_with_disabled
    expected = %(<select name="date[year]" disabled="disabled">\n)
    expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_year(Time.mktime(2003, 8, 16), :disabled => true, :start_year => 2003, :end_year => 2005)
    assert_equal expected, select_year(2003, :disabled => true, :start_year => 2003, :end_year => 2005)
  end

  def test_select_year_with_field_name_override
    expected = %(<select name="date[annee]">\n)
    expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_year(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :field_name => 'annee')
    assert_equal expected, select_year(2003, :start_year => 2003, :end_year => 2005, :field_name => 'annee')
  end
  
  def test_select_year_with_type_discarding
    expected = %(<select name="date_year">\n)
    expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_year(
      Time.mktime(2003, 8, 16), :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
    assert_equal expected, select_year(
      2003, :prefix => "date_year", :discard_type => true, :start_year => 2003, :end_year => 2005)
  end
  
  def test_select_year_descending
    expected = %(<select name="date[year]">\n)
    expected << %(<option value="2005" selected="selected">2005</option>\n<option value="2004">2004</option>\n<option value="2003">2003</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_year(Time.mktime(2005, 8, 16), :start_year => 2005, :end_year => 2003)
    assert_equal expected, select_year(2005, :start_year => 2005, :end_year => 2003)
  end

  def test_select_hour
    expected = %(<select name="date[hour]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18))
  end

  def test_select_hour_with_disabled
    expected = %(<select name="date[hour]" disabled="disabled">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
  end
  
  def test_select_hour_with_field_name_override
    expected = %(<select name="date[heure]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'heure')
  end

  def test_select_hour_with_blank
    expected = %(<select name="date[hour]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
  end

  def test_select_hour_nil_with_blank
    expected = %(<select name="date[hour]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_hour(nil, :include_blank => true)
  end

  def test_select_minute
    expected = %(<select name="date[minute]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18))
  end

  def test_select_minute_with_disabled
    expected = %(<select name="date[minute]" disabled="disabled">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
  end

  def test_select_minute_with_field_name_override
    expected = %(<select name="date[minuto]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'minuto')
  end

  def test_select_minute_with_blank
    expected = %(<select name="date[minute]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
  end

  def test_select_minute_with_blank_and_step
    expected = %(<select name="date[minute]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), { :include_blank => true , :minute_step => 15 })
  end

  def test_select_minute_nil_with_blank
    expected = %(<select name="date[minute]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_minute(nil, :include_blank => true)
  end

  def test_select_minute_nil_with_blank_and_step
    expected = %(<select name="date[minute]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_minute(nil, { :include_blank => true , :minute_step => 15 })
  end
  
  def test_select_second
    expected = %(<select name="date[second]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18))
  end
  
  def test_select_second_with_disabled
    expected = %(<select name="date[second]" disabled="disabled">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :disabled => true)
  end
  
  def test_select_second_with_field_name_override
    expected = %(<select name="date[segundo]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :field_name => 'segundo')
  end
  
  def test_select_second_with_blank
    expected = %(<select name="date[second]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :include_blank => true)
  end

  def test_select_second_nil_with_blank
    expected = %(<select name="date[second]">\n)
    expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_second(nil, :include_blank => true)
  end
 
  def test_select_date
    expected =  %(<select name="date[first][year]">\n)
    expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(
      Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]"
    )
  end

  def test_select_date_with_disabled
    expected =  %(<select name="date[first][year]" disabled="disabled">\n)
    expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][month]" disabled="disabled">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]" disabled="disabled">\n)
    expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :disabled => true)
  end

  
  def test_select_date_with_no_start_year
    expected =  %(<select name="date[first][year]">\n)
    (Date.today.year-5).upto(Date.today.year+1) do |y|
      if y == Date.today.year 
        expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
      else
        expected << %(<option value="#{y}">#{y}</option>\n)
      end
    end
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(
      Time.mktime(Date.today.year, 8, 16), :end_year => Date.today.year+1, :prefix => "date[first]"
    )
  end

  def test_select_date_with_no_end_year
    expected =  %(<select name="date[first][year]">\n)
    2003.upto(2008) do |y|
      if y == 2003
        expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
      else
        expected << %(<option value="#{y}">#{y}</option>\n)
      end
    end
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(
      Time.mktime(2003, 8, 16), :start_year => 2003, :prefix => "date[first]"
    )
  end

  def test_select_date_with_no_start_or_end_year
    expected =  %(<select name="date[first][year]">\n)
    (Date.today.year-5).upto(Date.today.year+5) do |y|
      if y == Date.today.year 
        expected << %(<option value="#{y}" selected="selected">#{y}</option>\n)
      else
        expected << %(<option value="#{y}">#{y}</option>\n)
      end
    end
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(
      Time.mktime(Date.today.year, 8, 16), :prefix => "date[first]"
    )
  end

  def test_select_time_with_seconds
    expected = %(<select name="date[hour]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[minute]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[second]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18" selected="selected">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true)
  end

  def test_select_time_without_seconds
    expected = %(<select name="date[hour]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08" selected="selected">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[minute]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18))
    assert_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false)
  end

  def test_date_select_with_zero_value
    expected =  %(<select name="date[first][year]">\n)
    expected << %(<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(0, :start_year => 2003, :end_year => 2005, :prefix => "date[first]")
  end

  def test_date_select_within_fields_for
    @post = Post.new
    @post.written_on = Date.new(2004, 6, 15)
    
    _erbout = ''
    
    fields_for :post, @post do |f|
      _erbout.concat f.date_select(:written_on)
    end
    
    expected = "<select name='post[written_on(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n" +
    "<select name='post[written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n" +
    "<select name='post[written_on(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"

    assert_dom_equal(expected, _erbout)    
  end

  def test_datetime_select_within_fields_for
    @post = Post.new
    @post.updated_at = Time.local(2004, 6, 15, 16, 35)
    
    _erbout = ''
    
    fields_for :post, @post do |f|
      _erbout.concat f.datetime_select(:updated_at)
    end
    
    expected = "<select name='post[updated_at(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n<select name='post[updated_at(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n<select name='post[updated_at(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n &mdash; <select name='post[updated_at(4i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option selected='selected' value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n</select>\n : <select name='post[updated_at(5i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n<option value='32'>32</option>\n<option value='33'>33</option>\n<option value='34'>34</option>\n<option selected='selected' value='35'>35</option>\n<option value='36'>36</option>\n<option value='37'>37</option>\n<option value='38'>38</option>\n<option value='39'>39</option>\n<option value='40'>40</option>\n<option value='41'>41</option>\n<option value='42'>42</option>\n<option value='43'>43</option>\n<option value='44'>44</option>\n<option value='45'>45</option>\n<option value='46'>46</option>\n<option value='47'>47</option>\n<option value='48'>48</option>\n<option value='49'>49</option>\n<option value='50'>50</option>\n<option value='51'>51</option>\n<option value='52'>52</option>\n<option value='53'>53</option>\n<option value='54'>54</option>\n<option value='55'>55</option>\n<option value='56'>56</option>\n<option value='57'>57</option>\n<option value='58'>58</option>\n<option value='59'>59</option>\n</select>\n"

    assert_dom_equal(expected, _erbout)    
  end

  def test_date_select_with_zero_value_and_no_start_year
    expected =  %(<select name="date[first][year]">\n)
    (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(0, :end_year => Date.today.year+1, :prefix => "date[first]")
  end

  def test_date_select_with_zero_value_and_no_end_year
    expected =  %(<select name="date[first][year]">\n)
    last_year = Time.now.year + 5
    2003.upto(last_year) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(0, :start_year => 2003, :prefix => "date[first]")
  end
  
  def test_date_select_with_zero_value_and_no_start_and_end_year
    expected =  %(<select name="date[first][year]">\n)
    (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(0, :prefix => "date[first]")
  end

  def test_date_select_with_nil_value_and_no_start_and_end_year
    expected =  %(<select name="date[first][year]">\n)
    (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    assert_equal expected, select_date(nil, :prefix => "date[first]")
  end

  def test_datetime_select_with_nil_value_and_no_start_and_end_year
    expected =  %(<select name="date[first][year]">\n)
    (Date.today.year-5).upto(Date.today.year+5) { |y| expected << %(<option value="#{y}">#{y}</option>\n) }
    expected << "</select>\n"

    expected << %(<select name="date[first][month]">\n)
    expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][day]">\n)
    expected <<
%(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
    expected << "</select>\n"
    
    expected << %(<select name="date[first][hour]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n)
    expected << "</select>\n"

    expected << %(<select name="date[first][minute]">\n)
    expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
    expected << "</select>\n"

    assert_equal expected, select_datetime(nil, :prefix => "date[first]")
  end

end
require File.dirname(__FILE__) + '/../abstract_unit'

class FormHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::FormHelper
  include ActionView::Helpers::FormTagHelper
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::TextHelper

  silence_warnings do
    Post = Struct.new("Post", :title, :author_name, :body, :secret, :written_on, :cost)
    Post.class_eval do
      alias_method :title_before_type_cast, :title unless respond_to?(:title_before_type_cast)
      alias_method :body_before_type_cast, :body unless respond_to?(:body_before_type_cast)
      alias_method :author_name_before_type_cast, :author_name unless respond_to?(:author_name_before_type_cast)
    end
  end

  def setup
    @post = Post.new
    def @post.errors() Class.new{ def on(field) field == "author_name" end }.new end

    def @post.id; 123; end
    def @post.id_before_type_cast; 123; end

    @post.title       = "Hello World"
    @post.author_name = ""
    @post.body        = "Back to the hill and over it again!"
    @post.secret      = 1
    @post.written_on  = Date.new(2004, 6, 15)

    @controller = Class.new do
      attr_reader :url_for_options
      def url_for(options, *parameters_for_method_reference)
        @url_for_options = options
        "http://www.example.com"
      end
    end
    @controller = @controller.new
  end

  def test_text_field
    assert_dom_equal(
      '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title")
    )
    assert_dom_equal(
      '<input id="post_title" name="post[title]" size="30" type="password" value="Hello World" />', password_field("post", "title")
    )
    assert_dom_equal(
      '<input id="person_name" name="person[name]" size="30" type="password" />', password_field("person", "name")
    )
  end

  def test_text_field_with_escapes
    @post.title = "<b>Hello World</b>"
    assert_dom_equal(
      '<input id="post_title" name="post[title]" size="30" type="text" value="&lt;b&gt;Hello World&lt;/b&gt;" />', text_field("post", "title")
    )
  end

  def test_text_field_with_options
    expected = '<input id="post_title" name="post[title]" size="35" type="text" value="Hello World" />'
    assert_dom_equal expected, text_field("post", "title", "size" => 35)
    assert_dom_equal expected, text_field("post", "title", :size => 35)
  end

  def test_text_field_assuming_size
    expected = '<input id="post_title" maxlength="35" name="post[title]" size="35" type="text" value="Hello World" />'
    assert_dom_equal expected, text_field("post", "title", "maxlength" => 35)
    assert_dom_equal expected, text_field("post", "title", :maxlength => 35)
  end

  def test_text_field_doesnt_change_param_values
    object_name = 'post[]'
    expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />'
    assert_equal expected, text_field(object_name, "title")
    assert_equal object_name, "post[]"
  end

  def test_check_box
    assert_dom_equal(
      '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
      check_box("post", "secret")
    )
    @post.secret = 0
    assert_dom_equal(
      '<input id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
      check_box("post", "secret")
    )
    assert_dom_equal(
      '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
      check_box("post", "secret" ,{"checked"=>"checked"})
    )
    @post.secret = true
    assert_dom_equal(
      '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
      check_box("post", "secret")
    )
  end

  def test_check_box_with_explicit_checked_and_unchecked_values
    @post.secret = "on"
    assert_dom_equal(
      '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="on" /><input name="post[secret]" type="hidden" value="off" />',
      check_box("post", "secret", {}, "on", "off")
    )
  end

  def test_radio_button
    assert_dom_equal('<input checked="checked" id="post_title_hello_world" name="post[title]" type="radio" value="Hello World" />',
      radio_button("post", "title", "Hello World")
    )
    assert_dom_equal('<input id="post_title_goodbye_world" name="post[title]" type="radio" value="Goodbye World" />',
      radio_button("post", "title", "Goodbye World")
    )
  end

  def test_radio_button_is_checked_with_integers
    assert_dom_equal('<input checked="checked" id="post_secret_1" name="post[secret]" type="radio" value="1" />',
      radio_button("post", "secret", "1")
   )
  end

  def test_text_area
    assert_dom_equal(
      '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
      text_area("post", "body")
    )
  end

  def test_text_area_with_escapes
    @post.body        = "Back to <i>the</i> hill and over it again!"
    assert_dom_equal(
      '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>',
      text_area("post", "body")
    )
  end
  
  def test_text_area_with_alternate_value
    assert_dom_equal(
      '<textarea cols="40" id="post_body" name="post[body]" rows="20">Testing alternate values.</textarea>',
      text_area("post", "body", :value => 'Testing alternate values.')
    )
  end
  
  def test_date_selects
    assert_dom_equal(
      '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
      text_area("post", "body")
    )
  end

  def test_explicit_name
    assert_dom_equal(
      '<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
    )
    assert_dom_equal(
      '<textarea cols="40" id="post_body" name="really!" rows="20">Back to the hill and over it again!</textarea>',
      text_area("post", "body", "name" => "really!")
    )
    assert_dom_equal(
      '<input checked="checked" id="post_secret" name="i mean it" type="checkbox" value="1" /><input name="i mean it" type="hidden" value="0" />',
      check_box("post", "secret", "name" => "i mean it")
    )
    assert_dom_equal text_field("post", "title", "name" => "dont guess"),
                 text_field("post", "title", :name => "dont guess")
    assert_dom_equal text_area("post", "body", "name" => "really!"),
                 text_area("post", "body", :name => "really!")
    assert_dom_equal check_box("post", "secret", "name" => "i mean it"),
                 check_box("post", "secret", :name => "i mean it")
  end

  def test_explicit_id
    assert_dom_equal(
      '<input id="dont guess" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess")
    )
    assert_dom_equal(
      '<textarea cols="40" id="really!" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
      text_area("post", "body", "id" => "really!")
    )
    assert_dom_equal(
      '<input checked="checked" id="i mean it" name="post[secret]" type="checkbox" value="1" /><input name="post[secret]" type="hidden" value="0" />',
      check_box("post", "secret", "id" => "i mean it")
    )
    assert_dom_equal text_field("post", "title", "id" => "dont guess"),
                 text_field("post", "title", :id => "dont guess")
    assert_dom_equal text_area("post", "body", "id" => "really!"),
                 text_area("post", "body", :id => "really!")
    assert_dom_equal check_box("post", "secret", "id" => "i mean it"),
                 check_box("post", "secret", :id => "i mean it")
  end

  def test_auto_index
    pid = @post.id
    assert_dom_equal(
      "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title")
    )
    assert_dom_equal(
      "<textarea cols=\"40\" id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
      text_area("post[]", "body")
    )
    assert_dom_equal(
      "<input checked=\"checked\" id=\"post_#{pid}_secret\" name=\"post[#{pid}][secret]\" type=\"checkbox\" value=\"1\" /><input name=\"post[#{pid}][secret]\" type=\"hidden\" value=\"0\" />",
      check_box("post[]", "secret")
    )
   assert_dom_equal(
"<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
      radio_button("post[]", "title", "Hello World")
    )
    assert_dom_equal("<input id=\"post_#{pid}_title_goodbye_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />",
      radio_button("post[]", "title", "Goodbye World")
    )
  end

  def test_form_for
    _erbout = ''

    form_for(:post, @post, :html => { :id => 'create-post' }) do |f|
      _erbout.concat f.text_field(:title)
      _erbout.concat f.text_area(:body)
      _erbout.concat f.check_box(:secret)
    end

    expected = 
      "<form action='http://www.example.com' id='create-post' method='post'>" +
      "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
      "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
      "<input name='post[secret]' type='hidden' value='0' />" +
      "</form>"

    assert_dom_equal expected, _erbout
  end

  def test_form_for_without_object
    _erbout = ''

    form_for(:post, :html => { :id => 'create-post' }) do |f|
      _erbout.concat f.text_field(:title)
      _erbout.concat f.text_area(:body)
      _erbout.concat f.check_box(:secret)
    end

    expected = 
      "<form action='http://www.example.com' id='create-post' method='post'>" +
      "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
      "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
      "<input name='post[secret]' type='hidden' value='0' />" +
      "</form>"

    assert_dom_equal expected, _erbout
  end

  def test_fields_for
    _erbout = ''

    fields_for(:post, @post) do |f|
      _erbout.concat f.text_field(:title)
      _erbout.concat f.text_area(:body)
      _erbout.concat f.check_box(:secret)
    end

    expected = 
      "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
      "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
      "<input name='post[secret]' type='hidden' value='0' />"

    assert_dom_equal expected, _erbout
  end

  def test_fields_for_without_object
    _erbout = ''
    fields_for(:post) do |f|
      _erbout.concat f.text_field(:title)
      _erbout.concat f.text_area(:body)
      _erbout.concat f.check_box(:secret)
    end

    expected = 
      "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
      "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
      "<input name='post[secret]' type='hidden' value='0' />"

    assert_dom_equal expected, _erbout
  end

  def test_form_builder_does_not_have_form_for_method
    assert ! ActionView::Helpers::FormBuilder.instance_methods.include?('form_for')
  end
  
  def test_form_for_and_fields_for
    _erbout = ''

    form_for(:post, @post, :html => { :id => 'create-post' }) do |post_form|
      _erbout.concat post_form.text_field(:title)
      _erbout.concat post_form.text_area(:body)

      fields_for(:parent_post, @post) do |parent_fields|
        _erbout.concat parent_fields.check_box(:secret)
      end
    end

    expected = 
      "<form action='http://www.example.com' id='create-post' method='post'>" +
      "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
      "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
      "<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />" +
      "<input name='parent_post[secret]' type='hidden' value='0' />" +
      "</form>"

    assert_dom_equal expected, _erbout
  end
  
  class LabelledFormBuilder < ActionView::Helpers::FormBuilder
    (field_helpers - %w(hidden_field)).each do |selector|
      src = <<-END_SRC
        def #{selector}(field, *args, &proc)
          "<label for='\#{field}'>\#{field.to_s.humanize}:</label> " + super + "<br/>"
        end
      END_SRC
      class_eval src, __FILE__, __LINE__
    end
  end
  
  def test_form_for_with_labelled_builder
    _erbout = ''

    form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
      _erbout.concat f.text_field(:title)
      _erbout.concat f.text_area(:body)
      _erbout.concat f.check_box(:secret)
    end

    expected = 
      "<form action='http://www.example.com' method='post'>" +
      "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
      "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
      "<input name='post[secret]' type='hidden' value='0' /><br/>" +
      "</form>"

    assert_dom_equal expected, _erbout
  end
  
  # Perhaps this test should be moved to prototype helper tests.
  def test_remote_form_for_with_labelled_builder
    self.extend ActionView::Helpers::PrototypeHelper
     _erbout = ''

     remote_form_for(:post, @post, :builder => LabelledFormBuilder) do |f|
       _erbout.concat f.text_field(:title)
       _erbout.concat f.text_area(:body)
       _erbout.concat f.check_box(:secret)
     end

     expected = 
       %(<form action="http://www.example.com" onsubmit="new Ajax.Request('http://www.example.com', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" method="post">) +
       "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
       "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
       "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
       "<input name='post[secret]' type='hidden' value='0' /><br/>" +
       "</form>"

     assert_dom_equal expected, _erbout
  end
   
  def test_fields_for_with_labelled_builder
    _erbout = ''
    
    fields_for(:post, @post, :builder => LabelledFormBuilder) do |f|
      _erbout.concat f.text_field(:title)
      _erbout.concat f.text_area(:body)
      _erbout.concat f.check_box(:secret)
    end
    
    expected = 
      "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
      "<label for='secret'>Secret:</label> <input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
      "<input name='post[secret]' type='hidden' value='0' /><br/>"
    
    assert_dom_equal expected, _erbout
  end
  
  def test_form_for_with_html_options_adds_options_to_form_tag
    _erbout = ''
    
    form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
    expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>"
    
    assert_dom_equal expected, _erbout
  end
  
  def test_form_for_with_string_url_option
    _erbout = ''

    form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end

    assert_equal 'http://www.otherdomain.com', @controller.url_for_options
  end

  def test_form_for_with_hash_url_option
    _erbout = ''

    form_for(:post, @post, :url => {:controller => 'controller', :action => 'action'}) do |f| end

    assert_equal 'controller', @controller.url_for_options[:controller]
    assert_equal 'action', @controller.url_for_options[:action]
  end
  
  def test_remote_form_for_with_html_options_adds_options_to_form_tag
    self.extend ActionView::Helpers::PrototypeHelper
    _erbout = ''
    
    remote_form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
    expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\" onsubmit=\"new Ajax.Request('http://www.example.com', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\"></form>"
    
    assert_dom_equal expected, _erbout
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class MockTimeZone
  attr_reader :name

  def initialize( name )
    @name = name
  end

  def self.all
    [ "A", "B", "C", "D", "E" ].map { |s| new s }
  end

  def ==( z )
    z && @name == z.name
  end

  def to_s
    @name
  end
end

ActionView::Helpers::FormOptionsHelper::TimeZone = MockTimeZone

class FormOptionsHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::FormHelper
  include ActionView::Helpers::FormOptionsHelper

  silence_warnings do
    Post      = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
    Continent = Struct.new('Continent', :continent_name, :countries)
    Country   = Struct.new('Country', :country_id, :country_name)
    Firm      = Struct.new('Firm', :time_zone)
  end

  def test_collection_options
    @posts = [
      Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
      Post.new("Babe went home", "Babe", "To a little house", "shh!"),
      Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
    ]

    assert_dom_equal(
      "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
      options_from_collection_for_select(@posts, "author_name", "title")
    )
  end


  def test_collection_options_with_preselected_value
    @posts = [
      Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
      Post.new("Babe went home", "Babe", "To a little house", "shh!"),
      Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
    ]

    assert_dom_equal(
      "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
      options_from_collection_for_select(@posts, "author_name", "title", "Babe")
    )
  end

  def test_collection_options_with_preselected_value_array
      @posts = [
        Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
        Post.new("Babe went home", "Babe", "To a little house", "shh!"),
        Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
      ]

      assert_dom_equal(
        "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>",
        options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ])
      )
  end

  def test_array_options_for_select
    assert_dom_equal(
      "<option value=\"&lt;Denmark&gt;\">&lt;Denmark&gt;</option>\n<option value=\"USA\">USA</option>\n<option value=\"Sweden\">Sweden</option>",
      options_for_select([ "<Denmark>", "USA", "Sweden" ])
    )
  end

  def test_array_options_for_select_with_selection
    assert_dom_equal(
      "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\">Sweden</option>",
      options_for_select([ "Denmark", "<USA>", "Sweden" ], "<USA>")
    )
  end

  def test_array_options_for_select_with_selection_array
      assert_dom_equal(
        "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>",
        options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ])
      )
  end

  def test_array_options_for_string_include_in_other_string_bug_fix
      assert_dom_equal(
        "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
        options_for_select([ "ruby", "rubyonrails" ], "rubyonrails")
      )
      assert_dom_equal(
        "<option value=\"ruby\" selected=\"selected\">ruby</option>\n<option value=\"rubyonrails\">rubyonrails</option>",
        options_for_select([ "ruby", "rubyonrails" ], "ruby")
      )
  end

  def test_hash_options_for_select
    assert_dom_equal(
      "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
      options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" })
    )
    assert_dom_equal(
      "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
      options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar")
    )
    assert_dom_equal(
      "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
      options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ])
    )
  end

  def test_ducktyped_options_for_select
    quack = Struct.new(:first, :last)
    assert_dom_equal(
      "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
      options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")])
    )
    assert_dom_equal(
      "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
      options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], "Dollar")
    )
    assert_dom_equal(
      "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
      options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], ["Dollar", "<Kroner>"])
    )
  end

  def test_html_option_groups_from_collection
    @continents = [
      Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
      Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
    ]

    assert_dom_equal(
      "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
      option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
    )
  end

  def test_time_zone_options_no_parms
    opts = time_zone_options_for_select
    assert_dom_equal "<option value=\"A\">A</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\">D</option>\n" +
                 "<option value=\"E\">E</option>",
                 opts
  end

  def test_time_zone_options_with_selected
    opts = time_zone_options_for_select( "D" )
    assert_dom_equal "<option value=\"A\">A</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\" selected=\"selected\">D</option>\n" +
                 "<option value=\"E\">E</option>",
                 opts
  end

  def test_time_zone_options_with_unknown_selected
    opts = time_zone_options_for_select( "K" )
    assert_dom_equal "<option value=\"A\">A</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\">D</option>\n" +
                 "<option value=\"E\">E</option>",
                 opts
  end

  def test_time_zone_options_with_priority_zones
    zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
    opts = time_zone_options_for_select( nil, zones )
    assert_dom_equal "<option value=\"B\">B</option>\n" +
                 "<option value=\"E\">E</option>" +
                 "<option value=\"\">-------------</option>\n" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\">D</option>",
                 opts
  end

  def test_time_zone_options_with_selected_priority_zones
    zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
    opts = time_zone_options_for_select( "E", zones )
    assert_dom_equal "<option value=\"B\">B</option>\n" +
                 "<option value=\"E\" selected=\"selected\">E</option>" +
                 "<option value=\"\">-------------</option>\n" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\">D</option>",
                 opts
  end

  def test_time_zone_options_with_unselected_priority_zones
    zones = [ TimeZone.new( "B" ), TimeZone.new( "E" ) ]
    opts = time_zone_options_for_select( "C", zones )
    assert_dom_equal "<option value=\"B\">B</option>\n" +
                 "<option value=\"E\">E</option>" +
                 "<option value=\"\">-------------</option>\n" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"C\" selected=\"selected\">C</option>\n" +
                 "<option value=\"D\">D</option>",
                 opts
  end

  def test_select
    @post = Post.new
    @post.category = "<mus>"
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest))
    )
  end

  def test_select_under_fields_for
    @post = Post.new
    @post.category = "<mus>"
    
    _erbout = ''
    
    fields_for :post, @post do |f|
      _erbout.concat f.select(:category, %w( abe <mus> hest))
    end
    
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      _erbout
    )
  end

  def test_select_with_blank
    @post = Post.new
    @post.category = "<mus>"
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest), :include_blank => true)
    )
  end

  def test_select_with_default_prompt
    @post = Post.new
    @post.category = ""
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest), :prompt => true)
    )
  end

  def test_select_no_prompt_when_select_has_value
    @post = Post.new
    @post.category = "<mus>"
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest), :prompt => true)
    )
  end

  def test_select_with_given_prompt
    @post = Post.new
    @post.category = ""
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">The prompt</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest), :prompt => 'The prompt')
    )
  end

  def test_select_with_prompt_and_blank
    @post = Post.new
    @post.category = ""
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest), :prompt => true, :include_blank => true)
    )
  end
  
  def test_select_with_selected_value
    @post = Post.new
    @post.category = "<mus>"
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\" selected=\"selected\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest ), :selected => 'abe')
    )
  end

  def test_select_with_selected_nil
    @post = Post.new
    @post.category = "<mus>"
    assert_dom_equal(
      "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
      select("post", "category", %w( abe <mus> hest ), :selected => nil)
    )
  end

  def test_collection_select
    @posts = [
      Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
      Post.new("Babe went home", "Babe", "To a little house", "shh!"),
      Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
    ]

    @post = Post.new
    @post.author_name = "Babe"

    assert_dom_equal(
      "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
      collection_select("post", "author_name", @posts, "author_name", "author_name")
    )
  end

  def test_collection_select_under_fields_for
    @posts = [
      Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
      Post.new("Babe went home", "Babe", "To a little house", "shh!"),
      Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
    ]

    @post = Post.new
    @post.author_name = "Babe"
    
    _erbout = ''
    
    fields_for :post, @post do |f|
      _erbout.concat f.collection_select(:author_name, @posts, :author_name, :author_name)
    end
    
    assert_dom_equal(
      "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
      _erbout
    )
  end

  def test_collection_select_with_blank_and_style
    @posts = [
      Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
      Post.new("Babe went home", "Babe", "To a little house", "shh!"),
      Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
    ]

    @post = Post.new
    @post.author_name = "Babe"

    assert_dom_equal(
      "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
      collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px")
    )
  end

  def test_country_select
    @post = Post.new
    @post.origin = "Denmark"
    assert_dom_equal(
      "<select id=\"post_origin\" name=\"post[origin]\"><option value=\"Afghanistan\">Afghanistan</option>\n<option value=\"Albania\">Albania</option>\n<option value=\"Algeria\">Algeria</option>\n<option value=\"American Samoa\">American Samoa</option>\n<option value=\"Andorra\">Andorra</option>\n<option value=\"Angola\">Angola</option>\n<option value=\"Anguilla\">Anguilla</option>\n<option value=\"Antarctica\">Antarctica</option>\n<option value=\"Antigua And Barbuda\">Antigua And Barbuda</option>\n<option value=\"Argentina\">Argentina</option>\n<option value=\"Armenia\">Armenia</option>\n<option value=\"Aruba\">Aruba</option>\n<option value=\"Australia\">Australia</option>\n<option value=\"Austria\">Austria</option>\n<option value=\"Azerbaijan\">Azerbaijan</option>\n<option value=\"Bahamas\">Bahamas</option>\n<option value=\"Bahrain\">Bahrain</option>\n<option value=\"Bangladesh\">Bangladesh</option>\n<option value=\"Barbados\">Barbados</option>\n<option value=\"Belarus\">Belarus</option>\n<option value=\"Belgium\">Belgium</option>\n<option value=\"Belize\">Belize</option>\n<option value=\"Benin\">Benin</option>\n<option value=\"Bermuda\">Bermuda</option>\n<option value=\"Bhutan\">Bhutan</option>\n<option value=\"Bolivia\">Bolivia</option>\n<option value=\"Bosnia and Herzegowina\">Bosnia and Herzegowina</option>\n<option value=\"Botswana\">Botswana</option>\n<option value=\"Bouvet Island\">Bouvet Island</option>\n<option value=\"Brazil\">Brazil</option>\n<option value=\"British Indian Ocean Territory\">British Indian Ocean Territory</option>\n<option value=\"Brunei Darussalam\">Brunei Darussalam</option>\n<option value=\"Bulgaria\">Bulgaria</option>\n<option value=\"Burkina Faso\">Burkina Faso</option>\n<option value=\"Burma\">Burma</option>\n<option value=\"Burundi\">Burundi</option>\n<option value=\"Cambodia\">Cambodia</option>\n<option value=\"Cameroon\">Cameroon</option>\n<option value=\"Canada\">Canada</option>\n<option value=\"Cape Verde\">Cape Verde</option>\n<option value=\"Cayman Islands\">Cayman Islands</option>\n<option value=\"Central African Republic\">Central African Republic</option>\n<option value=\"Chad\">Chad</option>\n<option value=\"Chile\">Chile</option>\n<option value=\"China\">China</option>\n<option value=\"Christmas Island\">Christmas Island</option>\n<option value=\"Cocos (Keeling) Islands\">Cocos (Keeling) Islands</option>\n<option value=\"Colombia\">Colombia</option>\n<option value=\"Comoros\">Comoros</option>\n<option value=\"Congo\">Congo</option>\n<option value=\"Congo, the Democratic Republic of the\">Congo, the Democratic Republic of the</option>\n<option value=\"Cook Islands\">Cook Islands</option>\n<option value=\"Costa Rica\">Costa Rica</option>\n<option value=\"Cote d'Ivoire\">Cote d'Ivoire</option>\n<option value=\"Croatia\">Croatia</option>\n<option value=\"Cuba\">Cuba</option>\n<option value=\"Cyprus\">Cyprus</option>\n<option value=\"Czech Republic\">Czech Republic</option>\n<option value=\"Denmark\" selected=\"selected\">Denmark</option>\n<option value=\"Djibouti\">Djibouti</option>\n<option value=\"Dominica\">Dominica</option>\n<option value=\"Dominican Republic\">Dominican Republic</option>\n<option value=\"East Timor\">East Timor</option>\n<option value=\"Ecuador\">Ecuador</option>\n<option value=\"Egypt\">Egypt</option>\n<option value=\"El Salvador\">El Salvador</option>\n<option value=\"England\">England" +
      "</option>\n<option value=\"Equatorial Guinea\">Equatorial Guinea</option>\n<option value=\"Eritrea\">Eritrea</option>\n<option value=\"Espana\">Espana</option>\n<option value=\"Estonia\">Estonia</option>\n<option value=\"Ethiopia\">Ethiopia</option>\n<option value=\"Falkland Islands\">Falkland Islands</option>\n<option value=\"Faroe Islands\">Faroe Islands</option>\n<option value=\"Fiji\">Fiji</option>\n<option value=\"Finland\">Finland</option>\n<option value=\"France\">France</option>\n<option value=\"French Guiana\">French Guiana</option>\n<option value=\"French Polynesia\">French Polynesia</option>\n<option value=\"French Southern Territories\">French Southern Territories</option>\n<option value=\"Gabon\">Gabon</option>\n<option value=\"Gambia\">Gambia</option>\n<option value=\"Georgia\">Georgia</option>\n<option value=\"Germany\">Germany</option>\n<option value=\"Ghana\">Ghana</option>\n<option value=\"Gibraltar\">Gibraltar</option>\n<option value=\"Great Britain\">Great Britain</option>\n<option value=\"Greece\">Greece</option>\n<option value=\"Greenland\">Greenland</option>\n<option value=\"Grenada\">Grenada</option>\n<option value=\"Guadeloupe\">Guadeloupe</option>\n<option value=\"Guam\">Guam</option>\n<option value=\"Guatemala\">Guatemala</option>\n<option value=\"Guinea\">Guinea</option>\n<option value=\"Guinea-Bissau\">Guinea-Bissau</option>\n<option value=\"Guyana\">Guyana</option>\n<option value=\"Haiti\">Haiti</option>\n<option value=\"Heard and Mc Donald Islands\">Heard and Mc Donald Islands</option>\n<option value=\"Honduras\">Honduras</option>\n<option value=\"Hong Kong\">Hong Kong</option>\n<option value=\"Hungary\">Hungary</option>\n<option value=\"Iceland\">Iceland</option>\n<option value=\"India\">India</option>\n<option value=\"Indonesia\">Indonesia</option>\n<option value=\"Ireland\">Ireland</option>\n<option value=\"Israel\">Israel</option>\n<option value=\"Italy\">Italy</option>\n<option value=\"Iran\">Iran</option>\n<option value=\"Iraq\">Iraq</option>\n<option value=\"Jamaica\">Jamaica</option>\n<option value=\"Japan\">Japan</option>\n<option value=\"Jordan\">Jordan</option>\n<option value=\"Kazakhstan\">Kazakhstan</option>\n<option value=\"Kenya\">Kenya</option>\n<option value=\"Kiribati\">Kiribati</option>\n<option value=\"Korea, Republic of\">Korea, Republic of</option>\n<option value=\"Korea (South)\">Korea (South)</option>\n<option value=\"Kuwait\">Kuwait</option>\n<option value=\"Kyrgyzstan\">Kyrgyzstan</option>\n<option value=\"Lao People's Democratic Republic\">Lao People's Democratic Republic</option>\n<option value=\"Latvia\">Latvia</option>\n<option value=\"Lebanon\">Lebanon</option>\n<option value=\"Lesotho\">Lesotho</option>\n<option value=\"Liberia\">Liberia</option>\n<option value=\"Liechtenstein\">Liechtenstein</option>\n<option value=\"Lithuania\">Lithuania</option>\n<option value=\"Luxembourg\">Luxembourg</option>\n<option value=\"Macau\">Macau</option>\n<option value=\"Macedonia\">Macedonia</option>\n<option value=\"Madagascar\">Madagascar</option>\n<option value=\"Malawi\">Malawi</option>\n<option value=\"Malaysia\">Malaysia</option>\n<option value=\"Maldives\">Maldives</option>\n<option value=\"Mali\">Mali</option>\n<option value=\"Malta\">Malta</option>\n<option value=\"Marshall Islands\">Marshall Islands</option>\n<option value=\"Martinique\">Martinique</option>\n<option value=\"Mauritania\">Mauritania</option>\n<option value=\"Mauritius\">Mauritius</option>\n<option value=\"Mayotte\">Mayotte</option>\n<option value=\"Mexico\">Mexico</option>\n<option value=\"Micronesia, Federated States of\">Micronesia, Federated States of</option>\n<option value=\"Moldova, Republic of\">Moldova, Republic of</option>\n<option value=\"Monaco\">Monaco</option>\n<option value=\"Mongolia\">Mongolia</option>\n<option value=\"Montserrat\">Montserrat</option>\n<option value=\"Morocco\">Morocco</option>\n<option value=\"Mozambique\">Mozambique</option>\n<option value=\"Myanmar\">Myanmar</option>\n<option value=\"Namibia\">Namibia</option>\n<option value=\"Nauru\">Nauru</option>\n<option value=\"Nepal\">Nepal</option>\n<option value=\"Netherlands\">Netherlands</option>\n<option value=\"Netherlands Antilles\">Netherlands Antilles</option>\n<option value=\"New Caledonia\">New Caledonia</option>" +
      "\n<option value=\"New Zealand\">New Zealand</option>\n<option value=\"Nicaragua\">Nicaragua</option>\n<option value=\"Niger\">Niger</option>\n<option value=\"Nigeria\">Nigeria</option>\n<option value=\"Niue\">Niue</option>\n<option value=\"Norfolk Island\">Norfolk Island</option>\n<option value=\"Northern Ireland\">Northern Ireland</option>\n<option value=\"Northern Mariana Islands\">Northern Mariana Islands</option>\n<option value=\"Norway\">Norway</option>\n<option value=\"Oman\">Oman</option>\n<option value=\"Pakistan\">Pakistan</option>\n<option value=\"Palau\">Palau</option>\n<option value=\"Panama\">Panama</option>\n<option value=\"Papua New Guinea\">Papua New Guinea</option>\n<option value=\"Paraguay\">Paraguay</option>\n<option value=\"Peru\">Peru</option>\n<option value=\"Philippines\">Philippines</option>\n<option value=\"Pitcairn\">Pitcairn</option>\n<option value=\"Poland\">Poland</option>\n<option value=\"Portugal\">Portugal</option>\n<option value=\"Puerto Rico\">Puerto Rico</option>\n<option value=\"Qatar\">Qatar</option>\n<option value=\"Reunion\">Reunion</option>\n<option value=\"Romania\">Romania</option>\n<option value=\"Russia\">Russia</option>\n<option value=\"Rwanda\">Rwanda</option>\n<option value=\"Saint Kitts and Nevis\">Saint Kitts and Nevis</option>\n<option value=\"Saint Lucia\">Saint Lucia</option>\n<option value=\"Saint Vincent and the Grenadines\">Saint Vincent and the Grenadines</option>\n<option value=\"Samoa (Independent)\">Samoa (Independent)</option>\n<option value=\"San Marino\">San Marino</option>\n<option value=\"Sao Tome and Principe\">Sao Tome and Principe</option>\n<option value=\"Saudi Arabia\">Saudi Arabia</option>\n<option value=\"Scotland\">Scotland</option>\n<option value=\"Senegal\">Senegal</option>\n<option value=\"Serbia and Montenegro\">Serbia and Montenegro</option>\n<option value=\"Seychelles\">Seychelles</option>\n<option value=\"Sierra Leone\">Sierra Leone</option>\n<option value=\"Singapore\">Singapore</option>\n<option value=\"Slovakia\">Slovakia</option>\n<option value=\"Slovenia\">Slovenia</option>\n<option value=\"Solomon Islands\">Solomon Islands</option>\n<option value=\"Somalia\">Somalia</option>\n<option value=\"South Africa\">South Africa</option>\n<option value=\"South Georgia and the South Sandwich Islands\">South Georgia and the South Sandwich Islands</option>\n<option value=\"South Korea\">South Korea</option>\n<option value=\"Spain\">Spain</option>\n<option value=\"Sri Lanka\">Sri Lanka</option>\n<option value=\"St. Helena\">St. Helena</option>\n<option value=\"St. Pierre and Miquelon\">St. Pierre and Miquelon</option>\n<option value=\"Suriname\">Suriname</option>\n<option value=\"Svalbard and Jan Mayen Islands\">Svalbard and Jan Mayen Islands</option>\n<option value=\"Swaziland\">Swaziland</option>\n<option value=\"Sweden\">Sweden</option>\n<option value=\"Switzerland\">Switzerland</option>\n<option value=\"Taiwan\">Taiwan</option>\n<option value=\"Tajikistan\">Tajikistan</option>\n<option value=\"Tanzania\">Tanzania</option>\n<option value=\"Thailand\">Thailand</option>\n<option value=\"Togo\">Togo</option>\n<option value=\"Tokelau\">Tokelau</option>\n<option value=\"Tonga\">Tonga</option>\n<option value=\"Trinidad\">Trinidad</option>\n<option value=\"Trinidad and Tobago\">Trinidad and Tobago</option>\n<option value=\"Tunisia\">Tunisia</option>\n<option value=\"Turkey\">Turkey</option>\n<option value=\"Turkmenistan\">" +
      "Turkmenistan</option>\n<option value=\"Turks and Caicos Islands\">Turks and Caicos Islands</option>\n<option value=\"Tuvalu\">Tuvalu</option>\n<option value=\"Uganda\">Uganda</option>\n<option value=\"Ukraine\">Ukraine</option>\n<option value=\"United Arab Emirates\">United Arab Emirates</option>\n<option value=\"United Kingdom\">United Kingdom</option>\n<option value=\"United States\">United States</option>\n<option value=\"United States Minor Outlying Islands\">United States Minor Outlying Islands</option>\n<option value=\"Uruguay\">Uruguay</option>\n<option value=\"Uzbekistan\">Uzbekistan</option>\n<option value=\"Vanuatu\">Vanuatu</option>\n<option value=\"Vatican City State (Holy See)\">Vatican City State (Holy See)</option>\n<option value=\"Venezuela\">Venezuela</option>\n<option value=\"Viet Nam\">Viet Nam</option>\n<option value=\"Virgin Islands (British)\">Virgin Islands (British)</option>\n<option value=\"Virgin Islands (U.S.)\">Virgin Islands (U.S.)</option>\n<option value=\"Wales\">Wales</option>\n<option value=\"Wallis and Futuna Islands\">Wallis and Futuna Islands</option>\n<option value=\"Western Sahara\">Western Sahara</option>\n<option value=\"Yemen\">Yemen</option>\n<option value=\"Zambia\">Zambia</option>\n<option value=\"Zimbabwe\">Zimbabwe</option></select>",
      country_select("post", "origin")
    )
  end

  def test_time_zone_select
    @firm = Firm.new("D")
    html = time_zone_select( "firm", "time_zone" )
    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\" selected=\"selected\">D</option>\n" +
                 "<option value=\"E\">E</option>" +
                 "</select>",
                 html
  end

  def test_time_zone_select_under_fields_for
    @firm = Firm.new("D")
    
    _erbout = ''
    
    fields_for :firm, @firm do |f|
      _erbout.concat f.time_zone_select(:time_zone)
    end
    
    assert_dom_equal(
      "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
      "<option value=\"A\">A</option>\n" +
      "<option value=\"B\">B</option>\n" +
      "<option value=\"C\">C</option>\n" +
      "<option value=\"D\" selected=\"selected\">D</option>\n" +
      "<option value=\"E\">E</option>" +
      "</select>",
      _erbout
    )
  end
  
  def test_time_zone_select_with_blank
    @firm = Firm.new("D")
    html = time_zone_select("firm", "time_zone", nil, :include_blank => true)
    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
                 "<option value=\"\"></option>\n" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\" selected=\"selected\">D</option>\n" +
                 "<option value=\"E\">E</option>" +
                 "</select>",
                 html
  end

  def test_time_zone_select_with_style
    @firm = Firm.new("D")
    html = time_zone_select("firm", "time_zone", nil, {},
      "style" => "color: red")
    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\" selected=\"selected\">D</option>\n" +
                 "<option value=\"E\">E</option>" +
                 "</select>",
                 html
    assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {},
      :style => "color: red")
  end

  def test_time_zone_select_with_blank_and_style
    @firm = Firm.new("D")
    html = time_zone_select("firm", "time_zone", nil,
      { :include_blank => true }, "style" => "color: red")
    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
                 "<option value=\"\"></option>\n" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"D\" selected=\"selected\">D</option>\n" +
                 "<option value=\"E\">E</option>" +
                 "</select>",
                 html
    assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
      { :include_blank => true }, :style => "color: red")
  end

  def test_time_zone_select_with_priority_zones
    @firm = Firm.new("D")
    zones = [ TimeZone.new("A"), TimeZone.new("D") ]
    html = time_zone_select("firm", "time_zone", zones )
    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
                 "<option value=\"A\">A</option>\n" +
                 "<option value=\"D\" selected=\"selected\">D</option>" +
                 "<option value=\"\">-------------</option>\n" +
                 "<option value=\"B\">B</option>\n" +
                 "<option value=\"C\">C</option>\n" +
                 "<option value=\"E\">E</option>" +
                 "</select>",
                 html
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

class FormTagHelperTest < Test::Unit::TestCase

  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::FormTagHelper

  def setup
    @controller = Class.new do
      def url_for(options, *parameters_for_method_reference)
        "http://www.example.com"
      end
    end
    @controller = @controller.new
  end

  def test_check_box_tag
    actual = check_box_tag "admin"
    expected = %(<input id="admin" name="admin" type="checkbox" value="1" />)
    assert_dom_equal expected, actual
  end

  def test_form_tag
    actual = form_tag
    expected = %(<form action="http://www.example.com" method="post">)
    assert_dom_equal expected, actual
  end

  def test_form_tag_multipart
    actual = form_tag({}, { 'multipart' => true })
    expected = %(<form action="http://www.example.com" enctype="multipart/form-data" method="post">)
    assert_dom_equal expected, actual
  end

  def test_hidden_field_tag
    actual = hidden_field_tag "id", 3
    expected = %(<input id="id" name="id" type="hidden" value="3" />)
    assert_dom_equal expected, actual
  end

  def test_password_field_tag
    actual = password_field_tag
    expected = %(<input id="password" name="password" type="password" />)
    assert_dom_equal expected, actual
  end

  def test_radio_button_tag
    actual = radio_button_tag "people", "david"
    expected = %(<input id="people" name="people" type="radio" value="david" />)
    assert_dom_equal expected, actual
  end

  def test_select_tag
    actual = select_tag "people", "<option>david</option>"
    expected = %(<select id="people" name="people"><option>david</option></select>)
    assert_dom_equal expected, actual
  end

  def test_text_area_tag_size_string
    actual = text_area_tag "body", "hello world", "size" => "20x40"
    expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
    assert_dom_equal expected, actual
  end

  def test_text_area_tag_size_symbol
    actual = text_area_tag "body", "hello world", :size => "20x40"
    expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
    assert_dom_equal expected, actual
  end

  def test_text_field_tag
    actual = text_field_tag "title", "Hello!"
    expected = %(<input id="title" name="title" type="text" value="Hello!" />)
    assert_dom_equal expected, actual
  end

  def test_text_field_tag_class_string
    actual = text_field_tag "title", "Hello!", "class" => "admin"
    expected = %(<input class="admin" id="title" name="title" type="text" value="Hello!" />)
    assert_dom_equal expected, actual
  end

  def test_boolean_optios
    assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes")
    assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil)
    assert_dom_equal %(<select id="people" multiple="multiple" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true)
    assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => nil)
  end

  def test_stringify_symbol_keys
    actual = text_field_tag "title", "Hello!", :id => "admin"
    expected = %(<input id="admin" name="title" type="text" value="Hello!" />)
    assert_dom_equal expected, actual
  end

  def test_submit_tag
    assert_dom_equal(
      %(<input name='commit' type='submit' value='Save' onclick="this.disabled=true;this.value='Saving...';this.form.submit();alert('hello!')" />),
      submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
    )
  end

  def test_pass
    assert_equal 1, 1
  end
end

require File.dirname(__FILE__) + '/../abstract_unit'

class JavaScriptMacrosHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::JavaScriptHelper
  include ActionView::Helpers::JavaScriptMacrosHelper
  
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::TextHelper
  include ActionView::Helpers::FormHelper
  include ActionView::Helpers::CaptureHelper
  
  def setup
    @controller = Class.new do
      def url_for(options, *parameters_for_method_reference)
        url =  "http://www.example.com/"
        url << options[:action].to_s if options and options[:action]
        url
      end
    end
    @controller = @controller.new
  end


  def test_auto_complete_field
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {})\n//]]>\n</script>),
      auto_complete_field("some_input", :url => { :action => "autocomplete" });
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:','})\n//]]>\n</script>),
      auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ',');
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:[',']})\n//]]>\n</script>),
      auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']);  
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {minChars:3})\n//]]>\n</script>),
      auto_complete_field("some_input", :url => { :action => "autocomplete" }, :min_chars => 3);
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {onHide:function(element, update){alert('me');}})\n//]]>\n</script>),
      auto_complete_field("some_input", :url => { :action => "autocomplete" }, :on_hide => "function(element, update){alert('me');}");
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {frequency:2})\n//]]>\n</script>),
      auto_complete_field("some_input", :url => { :action => "autocomplete" }, :frequency => 2);
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nvar some_input_auto_completer = new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {afterUpdateElement:function(element,value){alert('You have chosen: '+value)}})\n//]]>\n</script>),
      auto_complete_field("some_input", :url => { :action => "autocomplete" }, 
        :after_update_element => "function(element,value){alert('You have chosen: '+value)}");
  end
  
  def test_auto_complete_result
    result = [ { :title => 'test1'  }, { :title => 'test2'  } ]
    assert_equal %(<ul><li>test1</li><li>test2</li></ul>), 
      auto_complete_result(result, :title)
    assert_equal %(<ul><li>t<strong class=\"highlight\">est</strong>1</li><li>t<strong class=\"highlight\">est</strong>2</li></ul>), 
      auto_complete_result(result, :title, "est")
    
    resultuniq = [ { :title => 'test1'  }, { :title => 'test1'  } ]
    assert_equal %(<ul><li>t<strong class=\"highlight\">est</strong>1</li></ul>), 
      auto_complete_result(resultuniq, :title, "est")
  end
  
  def test_text_field_with_auto_complete
    assert_match "<style>",
      text_field_with_auto_complete(:message, :recipient)
    assert_dom_equal %(<input id=\"message_recipient\" name=\"message[recipient]\" size=\"30\" type=\"text\" /><div class=\"auto_complete\" id=\"message_recipient_auto_complete\"></div><script type=\"text/javascript\">\n//<![CDATA[\nvar message_recipient_auto_completer = new Ajax.Autocompleter('message_recipient', 'message_recipient_auto_complete', 'http://www.example.com/auto_complete_for_message_recipient', {})\n//]]>\n</script>),
      text_field_with_auto_complete(:message, :recipient, {}, :skip_style => true)
  end
  
  def test_in_place_editor_external_control
      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {externalControl:'blah'})\n//]]>\n</script>),
        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :external_control => 'blah'})
  end
  
  def test_in_place_editor_size
      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {size:4})\n//]]>\n</script>),
        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :size => 4})
  end
  
  def test_in_place_editor_cols_no_rows
      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {cols:4})\n//]]>\n</script>),
        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :cols => 4})
  end
  
  def test_in_place_editor_cols_with_rows
      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {cols:40, rows:5})\n//]]>\n</script>),
        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :rows => 5, :cols => 40})
  end

  def test_inplace_editor_loading_text
      assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Ajax.InPlaceEditor('some_input', 'http://www.example.com/inplace_edit', {loadingText:'Why are we waiting?'})\n//]]>\n</script>),
        in_place_editor('some_input', {:url => {:action => 'inplace_edit'}, :loading_text => 'Why are we waiting?'})
  end
  
  def test_in_place_editor_url
    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value')",
    in_place_editor( 'id-goes-here', :url => { :action => "action_to_set_value" })    
  end
  
  def test_in_place_editor_load_text_url
    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {loadTextURL:'http://www.example.com/action_to_get_value'})",
    in_place_editor( 'id-goes-here', 
      :url => { :action => "action_to_set_value" }, 
      :load_text_url => { :action => "action_to_get_value" })
  end
  
  def test_in_place_editor_eval_scripts
    assert_match "Ajax.InPlaceEditor('id-goes-here', 'http://www.example.com/action_to_set_value', {evalScripts:true})",
    in_place_editor( 'id-goes-here', 
      :url => { :action => "action_to_set_value" }, 
      :script => true )
  end
  
end
require File.dirname(__FILE__) + '/../abstract_unit'

class JavaScriptHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::JavaScriptHelper
  
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::TextHelper
  include ActionView::Helpers::FormHelper
  include ActionView::Helpers::CaptureHelper
  
  def test_define_javascript_functions
    # check if prototype.js is included first
    assert_not_nil define_javascript_functions.split("\n")[1].match(/Prototype JavaScript framework/)
    
    # check that scriptaculous.js is not in here, only needed if loaded remotely
    assert_nil define_javascript_functions.split("\n")[1].match(/var Scriptaculous = \{/)
  end

  def test_escape_javascript
    assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
  end
                                      
  def test_link_to_function
    assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>), 
      link_to_function("Greeting", "alert('Hello world!')")
  end
  
  def test_link_to_function_with_existing_onclick
    assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>), 
      link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
  end

  def test_button_to_function
    assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />), 
      button_to_function("Greeting", "alert('Hello world!')")
  end
end
require 'test/unit'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/number_helper'
require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/hash' # for stringify_keys
require File.dirname(__FILE__) + '/../../../activesupport/lib/active_support/core_ext/numeric'  # for human_size

class NumberHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::NumberHelper
  include ActiveSupport::CoreExtensions::Hash

  def test_number_to_phone
    assert_equal("123-555-1234", number_to_phone(1235551234))
    assert_equal("(123) 555-1234", number_to_phone(1235551234, {:area_code => true}))
    assert_equal("123 555 1234", number_to_phone(1235551234, {:delimiter => " "}))
    assert_equal("(123) 555-1234 x 555", number_to_phone(1235551234, {:area_code => true, :extension => 555}))
    assert_equal("123-555-1234", number_to_phone(1235551234, :extension => "   "))
  end

  def test_number_to_currency
    assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50))
    assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
    assert_equal("$1,234,567,890", number_to_currency(1234567890.50, {:precision => 0}))
    assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
    assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
  end

  def test_number_to_percentage
    assert_equal("100.000%", number_to_percentage(100))
    assert_equal("100%", number_to_percentage(100, {:precision => 0}))
    assert_equal("302.06%", number_to_percentage(302.0574, {:precision => 2}))
  end

  def test_number_with_delimiter
    assert_equal("12,345,678", number_with_delimiter(12345678))
  end

  def test_number_to_human_size
    assert_equal '0 Bytes',   human_size(0)
    assert_equal '3 Bytes',   human_size(3.14159265)
    assert_equal '123 Bytes', human_size(123.0)
    assert_equal '123 Bytes', human_size(123)
    assert_equal '1.2 KB',    human_size(1234)
    assert_equal '12.1 KB',   human_size(12345)
    assert_equal '1.2 MB',    human_size(1234567)
    assert_equal '1.1 GB',    human_size(1234567890)
    assert_equal '1.1 TB',    human_size(1234567890123)
    assert_equal '444 KB',    human_size(444.kilobytes)
    assert_equal '1023 MB',   human_size(1023.megabytes)
    assert_equal '3 TB',      human_size(3.terabytes)
    assert_nil human_size('x')
    assert_nil human_size(nil)
  end

  def test_number_with_precision
    assert_equal("111.235", number_with_precision(111.2346))
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'

module BaseTest
  include ActionView::Helpers::JavaScriptHelper
  include ActionView::Helpers::PrototypeHelper
  include ActionView::Helpers::ScriptaculousHelper
  
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::TextHelper
  include ActionView::Helpers::FormHelper
  include ActionView::Helpers::CaptureHelper
  
  def setup
    @controller = Class.new do
      def url_for(options, *parameters_for_method_reference)
        url =  "http://www.example.com/"
        url << options[:action].to_s if options and options[:action]
        url << "?a=#{options[:a]}" if options && options[:a]
        url << "&b=#{options[:b]}" if options && options[:a] && options[:b]
        url
      end
    end.new
  end

protected
  def create_generator
    block = Proc.new { |*args| yield *args if block_given? } 
    JavaScriptGenerator.new self, &block
  end
end

class PrototypeHelperTest < Test::Unit::TestCase
  include BaseTest
  
  def test_link_to_remote
    assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outpost</a>),
      link_to_remote("Remote outpost", { :url => { :action => "whatnot"  }}, { :class => "fine"  })
    assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
      link_to_remote("Remote outpost", :complete => "alert(request.reponseText)", :url => { :action => "whatnot"  })      
    assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
      link_to_remote("Remote outpost", :success => "alert(request.reponseText)", :url => { :action => "whatnot"  })
    assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
      link_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot"  })
    assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&amp;b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.reponseText)}}); return false;\">Remote outpost</a>),
      link_to_remote("Remote outpost", :failure => "alert(request.reponseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
  end
  
  def test_periodically_call_remote
    assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
      periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
  end
  
  def test_form_remote_tag
    assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
      form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast  })
    assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
      form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast  })
    assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
      form_remote_tag(:update => { :failure => "glass_of_water" }, :url => { :action => :fast  })
    assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
      form_remote_tag(:update => { :success => 'glass_of_beer', :failure => "glass_of_water" }, :url => { :action => :fast  })
  end
  
  def test_on_callbacks
    callbacks = [:uninitialized, :loading, :loaded, :interactive, :complete, :success, :failure]
    callbacks.each do |callback|
      assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
        form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast  }, callback=>"monkeys();")
      assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
        form_remote_tag(:update => { :success => "glass_of_beer" }, :url => { :action => :fast  }, callback=>"monkeys();")
      assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({failure:'glass_of_beer'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
        form_remote_tag(:update => { :failure => "glass_of_beer" }, :url => { :action => :fast  }, callback=>"monkeys();")
      assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
        form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast  }, callback=>"monkeys();")
    end
    
    #HTTP status codes 200 up to 599 have callbacks
    #these should work
    100.upto(599) do |callback|
      assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
        form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast  }, callback=>"monkeys();")
    end
    
    #test 200 and 404
    assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, parameters:Form.serialize(this)}); return false;">),
      form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast  }, 200=>"monkeys();", 404=>"bananas();")
    
    #these shouldn't
    1.upto(99) do |callback|
      assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
        form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast  }, callback=>"monkeys();")
    end
    600.upto(999) do |callback|
      assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
        form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast  }, callback=>"monkeys();")
    end
    
    #test ultimate combo
    assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, onComplete:function(request){c();}, onFailure:function(request){f();}, onLoading:function(request){c1()}, onSuccess:function(request){s()}, parameters:Form.serialize(this)}); return false;\">),
      form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast  }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
    
  end
  
  def test_submit_to_remote
    assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); return false;\" type=\"button\" value=\"1000000\" />),
      submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
  end
  
  def test_observe_field
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
      observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
  end
  
  def test_observe_field_using_function_for_callback
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {alert('Element changed')})\n//]]>\n</script>),
      observe_field("glass", :frequency => 5.minutes, :function => "alert('Element changed')")
  end
  
  def test_observe_form
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true})})\n//]]>\n</script>),
      observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
  end
  
  def test_observe_form_using_function_for_callback
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {alert('Form changed')})\n//]]>\n</script>),
      observe_form("cart", :frequency => 2, :function => "alert('Form changed')")
  end

  def test_update_element_function
    assert_equal %($('myelement').innerHTML = 'blub';\n),
      update_element_function('myelement', :content => 'blub')
    assert_equal %($('myelement').innerHTML = 'blub';\n),
      update_element_function('myelement', :action => :update, :content => 'blub')
    assert_equal %($('myelement').innerHTML = '';\n),
      update_element_function('myelement', :action => :empty)
    assert_equal %(Element.remove('myelement');\n),
      update_element_function('myelement', :action => :remove)
      
    assert_equal %(new Insertion.Bottom('myelement','blub');\n),
      update_element_function('myelement', :position => 'bottom', :content => 'blub')
    assert_equal %(new Insertion.Bottom('myelement','blub');\n),
      update_element_function('myelement', :action => :update, :position => :bottom, :content => 'blub')
      
    _erbout = ""
    assert_equal %($('myelement').innerHTML = 'test';\n),
      update_element_function('myelement') { _erbout << "test" }
      
    _erbout = ""
    assert_equal %($('myelement').innerHTML = 'blockstuff';\n),
      update_element_function('myelement', :content => 'paramstuff') { _erbout << "blockstuff" }
  end
  
  def test_update_page
    block = Proc.new { |page| page.replace_html('foo', 'bar') }
    assert_equal create_generator(&block).to_s, update_page(&block)
  end
  
  def test_update_page_tag
    block = Proc.new { |page| page.replace_html('foo', 'bar') }
    assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
  end
end

class JavaScriptGeneratorTest < Test::Unit::TestCase
  include BaseTest
  
  def setup
    super
    @generator = create_generator
  end
  
  def test_insert_html_with_string
    assert_equal 'new Insertion.Top("element", "<p>This is a test</p>");',
      @generator.insert_html(:top, 'element', '<p>This is a test</p>')
    assert_equal 'new Insertion.Bottom("element", "<p>This is a test</p>");',
      @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
    assert_equal 'new Insertion.Before("element", "<p>This is a test</p>");',
      @generator.insert_html(:before, 'element', '<p>This is a test</p>')
    assert_equal 'new Insertion.After("element", "<p>This is a test</p>");',
      @generator.insert_html(:after, 'element', '<p>This is a test</p>')
  end
  
  def test_replace_html_with_string
    assert_equal 'Element.update("element", "<p>This is a test</p>");',
      @generator.replace_html('element', '<p>This is a test</p>')
  end
  
  def test_replace_element_with_string
    assert_equal 'Element.replace("element", "<div id=\"element\"><p>This is a test</p></div>");',
      @generator.replace('element', '<div id="element"><p>This is a test</p></div>')
  end
  
  def test_remove
    assert_equal '["foo"].each(Element.remove);',
      @generator.remove('foo')
    assert_equal '["foo", "bar", "baz"].each(Element.remove);',
      @generator.remove('foo', 'bar', 'baz')
  end
  
  def test_show
    assert_equal 'Element.show("foo");',
      @generator.show('foo')
    assert_equal 'Element.show("foo", "bar", "baz");',
      @generator.show('foo', 'bar', 'baz')
  end
  
  def test_hide
    assert_equal 'Element.hide("foo");',
      @generator.hide('foo')
    assert_equal 'Element.hide("foo", "bar", "baz");',
      @generator.hide('foo', 'bar', 'baz')
  end
  
  def test_alert
    assert_equal 'alert("hello");', @generator.alert('hello')
  end
  
  def test_redirect_to
    assert_equal 'window.location.href = "http://www.example.com/welcome";',
      @generator.redirect_to(:action => 'welcome')
  end
  
  def test_delay
    @generator.delay(20) do
      @generator.hide('foo')
    end
    
    assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
  end
  
  def test_to_s
    @generator.insert_html(:top, 'element', '<p>This is a test</p>')
    @generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
    @generator.remove('foo', 'bar')
    @generator.replace_html('baz', '<p>This is a test</p>')
    
    assert_equal <<-EOS.chomp, @generator.to_s
new Insertion.Top("element", "<p>This is a test</p>");
new Insertion.Bottom("element", "<p>This is a test</p>");
["foo", "bar"].each(Element.remove);
Element.update("baz", "<p>This is a test</p>");
    EOS
  end

  def test_element_access
    assert_equal %($("hello");), @generator['hello']
  end

  def test_element_proxy_one_deep
    @generator['hello'].hide
    assert_equal %($("hello").hide();), @generator.to_s
  end

  def test_element_proxy_assignment
    @generator['hello'].width = 400
    assert_equal %($("hello").width = 400;), @generator.to_s
  end

  def test_element_proxy_two_deep
    @generator['hello'].hide("first").clean_whitespace
    assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s
  end
  
  def test_select_access
    assert_equal %($$("div.hello");), @generator.select('div.hello')
  end

  def test_select_proxy_one_deep
    @generator.select('p.welcome b').first.hide
    assert_equal %($$("p.welcome b").first().hide();), @generator.to_s
  end
  
  def test_visual_effect
    assert_equal %(new Effect.Puff("blah",{});), 
      @generator.visual_effect(:puff,'blah')
  end 
  
  def test_visual_effect_toggle
    assert_equal %(Effect.toggle("blah",'appear',{});), 
      @generator.visual_effect(:toggle_appear,'blah')
  end
  
  def test_sortable
    assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});), 
      @generator.sortable('blah', :url => { :action => "order" })
  end
  
  def test_draggable
    assert_equal %(new Draggable("blah", {});), 
      @generator.draggable('blah')
  end
  
  def test_drop_receiving
    assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});), 
      @generator.drop_receiving('blah', :url => { :action => "order" })
  end

  def test_collection_first_and_last
    @generator.select('p.welcome b').first.hide()
    @generator.select('p.welcome b').last.show()
    assert_equal <<-EOS.strip, @generator.to_s
$$("p.welcome b").first().hide();
$$("p.welcome b").last().show();
      EOS
  end

  def test_collection_proxy_with_each
    @generator.select('p.welcome b').each do |value|
      value.remove_class_name 'selected'
    end
    @generator.select('p.welcome b').each do |value, index|
      @generator.visual_effect :highlight, value
    end
    assert_equal <<-EOS.strip, @generator.to_s
$$("p.welcome b").each(function(value, index) {
value.removeClassName("selected");
});
$$("p.welcome b").each(function(value, index) {
new Effect.Highlight(value,{});
});
      EOS
  end

  def test_collection_proxy_on_collect
    @generator.select('p').collect('a') { |para| para.show }
    @generator.select('p').collect { |para| para.hide }
    assert_equal <<-EOS.strip, @generator.to_s
var a = $$("p").collect(function(value, index) {
return value.show();
});
$$("p").collect(function(value, index) {
return value.hide();
});
    EOS
    @generator = create_generator
  end

  def test_collection_proxy_with_grep
    @generator.select('p').grep 'a', /^a/ do |value|
      @generator << '(value.className == "welcome")'
    end
    @generator.select('p').grep 'b', /b$/ do |value, index|
      @generator.call 'alert', value
      @generator << '(value.className == "welcome")'
    end

    assert_equal <<-EOS.strip, @generator.to_s
var a = $$("p").grep(/^a/, function(value, index) {
return (value.className == "welcome");
});
var b = $$("p").grep(/b$/, function(value, index) {
alert(value);
return (value.className == "welcome");
});
    EOS
  end

  def test_collection_proxy_with_inject
    @generator.select('p').inject 'a', [] do |memo, value|
      @generator << '(value.className == "welcome")'
    end
    @generator.select('p').inject 'b', nil do |memo, value, index|
      @generator.call 'alert', memo
      @generator << '(value.className == "welcome")'
    end

    assert_equal <<-EOS.strip, @generator.to_s
var a = $$("p").inject([], function(memo, value, index) {
return (value.className == "welcome");
});
var b = $$("p").inject(null, function(memo, value, index) {
alert(memo);
return (value.className == "welcome");
});
    EOS
  end

  def test_collection_proxy_with_pluck
    @generator.select('p').pluck('a', 'className')
    assert_equal %(var a = $$("p").pluck("className");), @generator.to_s
  end

  def test_collection_proxy_with_zip
    ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('a', [4, 5, 6], [7, 8, 9])
    ActionView::Helpers::JavaScriptCollectionProxy.new(@generator, '[1, 2, 3]').zip('b', [4, 5, 6], [7, 8, 9]) do |array|
      @generator.call 'array.reverse'
    end

    assert_equal <<-EOS.strip, @generator.to_s
var a = [1, 2, 3].zip([4, 5, 6], [7, 8, 9]);
var b = [1, 2, 3].zip([4, 5, 6], [7, 8, 9], function(array) {
return array.reverse();
});
    EOS
  end
  
  def test_collection_proxy_with_find_all
    @generator.select('p').find_all 'a' do |value, index|
      @generator << '(value.className == "welcome")'
    end

    assert_equal <<-EOS.strip, @generator.to_s
var a = $$("p").findAll(function(value, index) {
return (value.className == "welcome");
});
    EOS
  end
  
  def test_debug_rjs
    ActionView::Base.debug_rjs = true
    @generator['welcome'].replace_html 'Welcome'
    assert_equal "try {\n$(\"welcome\").update(\"Welcome\");\n} catch (e) { alert('RJS error:\\n\\n' + e.toString()); alert('$(\\\"welcome\\\").update(\\\"Welcome\\\");'); throw e }", @generator.to_s
  ensure
    ActionView::Base.debug_rjs = false
  end

  def test_class_proxy
    @generator.form.focus('my_field')
    assert_equal "Form.focus(\"my_field\");", @generator.to_s
  end
end

require File.dirname(__FILE__) + '/../abstract_unit'

class ScriptaculousHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::JavaScriptHelper
  include ActionView::Helpers::PrototypeHelper
  include ActionView::Helpers::ScriptaculousHelper
  
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::TextHelper
  include ActionView::Helpers::FormHelper
  include ActionView::Helpers::CaptureHelper
  
  def setup
    @controller = Class.new do
      def url_for(options, *parameters_for_method_reference)
        url =  "http://www.example.com/"
        url << options[:action].to_s if options and options[:action]
        url
      end
    end.new
  end
  
  def test_effect
    assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, "posts")
    assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect("highlight", :posts)
    assert_equal "new Effect.Highlight(\"posts\",{});", visual_effect(:highlight, :posts)    
    assert_equal "new Effect.Fade(\"fademe\",{duration:4.0});", visual_effect(:fade, "fademe", :duration => 4.0)
    assert_equal "new Effect.Shake(element,{});", visual_effect(:shake)
    assert_equal "new Effect.DropOut(\"dropme\",{queue:'end'});", visual_effect(:drop_out, 'dropme', :queue => :end)

    # chop the queue params into a comma separated list
    beginning, ending = 'new Effect.DropOut("dropme",{queue:{', '}});'
    ve = [
      visual_effect(:drop_out, 'dropme', :queue => {:position => "end", :scope => "test", :limit => 2}),
      visual_effect(:drop_out, 'dropme', :queue => {:scope => :list, :limit => 2}),
      visual_effect(:drop_out, 'dropme', :queue => {:position => :end, :scope => :test, :limit => 2})
    ].collect { |v| v[beginning.length..-ending.length-1].split(',') }

    assert ve[0].include?("limit:2")
    assert ve[0].include?("scope:'test'")
    assert ve[0].include?("position:'end'")

    assert ve[1].include?("limit:2")
    assert ve[1].include?("scope:'list'")

    assert ve[2].include?("limit:2")
    assert ve[2].include?("scope:'test'")
    assert ve[2].include?("position:'end'")
  end
  
  def test_toggle_effects
    assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect(:toggle_appear,  "posts")
    assert_equal "Effect.toggle(\"posts\",'slide',{});",  visual_effect(:toggle_slide,   "posts")
    assert_equal "Effect.toggle(\"posts\",'blind',{});",  visual_effect(:toggle_blind,   "posts")
    assert_equal "Effect.toggle(\"posts\",'appear',{});", visual_effect("toggle_appear", "posts")
    assert_equal "Effect.toggle(\"posts\",'slide',{});",  visual_effect("toggle_slide",  "posts")
    assert_equal "Effect.toggle(\"posts\",'blind',{});",  visual_effect("toggle_blind",  "posts")
  end
  

  def test_sortable_element
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>), 
      sortable_element("mylist", :url => { :action => "order" })
    assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}, tag:'div'})\n//]]>\n</script>), 
      sortable_element("mylist", :tag => "div", :constraint => "horizontal", :url => { :action => "order" })
    assert_dom_equal %|<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:['list1','list2'], onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>|, 
      sortable_element("mylist", :containment => ['list1','list2'], :constraint => "horizontal", :url => { :action => "order" })
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nSortable.create(\"mylist\", {constraint:'horizontal', containment:'list1', onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize(\"mylist\")})}})\n//]]>\n</script>), 
      sortable_element("mylist", :containment => 'list1', :constraint => "horizontal", :url => { :action => "order" })
  end
  
  def test_draggable_element
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {})\n//]]>\n</script>),
      draggable_element("product_13")
    assert_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Draggable(\"product_13\", {revert:true})\n//]]>\n</script>),
      draggable_element("product_13", :revert => true)
  end
  
  def test_drop_receiving_element
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
      drop_receiving_element("droptarget1")
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
      drop_receiving_element("droptarget1", :accept => 'products')
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:'products', onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
      drop_receiving_element("droptarget1", :accept => 'products', :update => 'infobox')
    assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nDroppables.add(\"droptarget1\", {accept:['tshirts','mugs'], onDrop:function(element){new Ajax.Updater('infobox', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}})\n//]]>\n</script>),
      drop_receiving_element("droptarget1", :accept => ['tshirts','mugs'], :update => 'infobox')
  end
endrequire File.dirname(__FILE__) + '/../abstract_unit'

require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'

class TagHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::TagHelper
  include ActionView::Helpers::UrlHelper

  def test_tag
    assert_equal "<p class=\"show\" />", tag("p", "class" => "show")
    assert_equal tag("p", "class" => "show"), tag("p", :class => "show")
  end

  def test_tag_options
    assert_equal "<p class=\"elsewhere\" />", tag("p", "class" => "show", :class => "elsewhere")
  end

  def test_tag_options_rejects_nil_option
    assert_equal "<p />", tag("p", :ignored => nil)
  end

  def test_tag_options_accepts_blank_option
    assert_equal "<p included=\"\" />", tag("p", :included => '')
  end

  def test_tag_options_converts_boolean_option
    assert_equal '<p disabled="disabled" multiple="multiple" readonly="readonly" />',
      tag("p", :disabled => true, :multiple => true, :readonly => true)
  end

  def test_content_tag
    assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create")
    assert_equal content_tag("a", "Create", "href" => "create"),
                 content_tag("a", "Create", :href => "create")
  end
  
  def test_cdata_section
    assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
  end
end
require File.dirname(__FILE__) + '/../abstract_unit'
require "#{File.dirname(__FILE__)}/../testing_sandbox"

class TextHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::TextHelper
  include ActionView::Helpers::TagHelper
  include TestingSandbox
  
  def setup
    # This simulates the fact that instance variables are reset every time
    # a view is rendered.  The cycle helper depends on this behavior.
    @_cycles = nil if (defined? @_cycles)
  end
  
  def test_simple_format
    assert_equal "<p>crazy\n<br /> cross\n<br /> platform linebreaks</p>", simple_format("crazy\r\n cross\r platform linebreaks")
    assert_equal "<p>A paragraph</p>\n\n<p>and another one!</p>", simple_format("A paragraph\n\nand another one!")
    assert_equal "<p>A paragraph\n<br /> With a newline</p>", simple_format("A paragraph\n With a newline")
  end

  def test_truncate
    assert_equal "Hello World!", truncate("Hello World!", 12)
    assert_equal "Hello Wor...", truncate("Hello World!!", 12)
  end

  def test_truncate_multibyte_without_kcode
    result = execute_in_sandbox(<<-'CODE')
      require File.dirname(__FILE__) + '/../../activesupport/lib/active_support/core_ext/kernel'
      require "#{File.dirname(__FILE__)}/../lib/action_view/helpers/text_helper"
      include ActionView::Helpers::TextHelper
      truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10)
    CODE

    assert_equal "\354\225\210\353\205\225\355...", result
  end

  def test_truncate_multibyte_with_kcode
    result = execute_in_sandbox(<<-'CODE')
      $KCODE = "u"
      require 'jcode'

      require File.dirname(__FILE__) + '/../../activesupport/lib/active_support/core_ext/kernel'
      require "#{File.dirname(__FILE__)}/../lib/action_view/helpers/text_helper"
      include ActionView::Helpers::TextHelper
      truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254\353\236 \354\225\204\353\235\274\353\246\254\354\230\244", 10)
    CODE

    assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254\353\236 ...", result
  end

  def test_strip_links
    assert_equal "on my mind", strip_links("<a href='almost'>on my mind</a>")
  end

  def test_highlighter
    assert_equal(
      "This is a <strong class=\"highlight\">beautiful</strong> morning",
      highlight("This is a beautiful morning", "beautiful")
    )

    assert_equal(
      "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
      highlight("This is a beautiful morning, but also a beautiful day", "beautiful")
    )

    assert_equal(
      "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
      highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
    )
    
    assert_equal(
      "This text is not changed because we supplied an empty phrase",
      highlight("This text is not changed because we supplied an empty phrase", nil)
    )
  end

  def test_highlighter_with_regexp
    assert_equal(
      "This is a <strong class=\"highlight\">beautiful!</strong> morning",
      highlight("This is a beautiful! morning", "beautiful!")
    )

    assert_equal(
      "This is a <strong class=\"highlight\">beautiful! morning</strong>",
      highlight("This is a beautiful! morning", "beautiful! morning")
    )

    assert_equal(
      "This is a <strong class=\"highlight\">beautiful? morning</strong>",
      highlight("This is a beautiful? morning", "beautiful? morning")
    )
  end

  def test_excerpt
    assert_equal("...is a beautiful morni...", excerpt("This is a beautiful morning", "beautiful", 5))
    assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
    assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
    assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
    assert_nil excerpt("This is a beautiful morning", "day")
  end

  def test_excerpt_with_regex
    assert_equal('...is a beautiful! morn...', excerpt('This is a beautiful! morning', 'beautiful', 5))
    assert_equal('...is a beautiful? morn...', excerpt('This is a beautiful? morning', 'beautiful', 5))
  end
  
    
  def test_word_wrap
    assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", 15))
  end

  def test_pluralization
    assert_equal("1 count", pluralize(1, "count"))
    assert_equal("2 counts", pluralize(2, "count"))
  end

  def test_auto_linking
    email_raw    = 'david@loudthinking.com'
    email_result = %{<a href="mailto:#{email_raw}">#{email_raw}</a>}
    link_raw     = 'http://www.rubyonrails.com'
    link_result  = %{<a href="#{link_raw}">#{link_raw}</a>}
    link_result_with_options  = %{<a href="#{link_raw}" target="_blank">#{link_raw}</a>}
    link2_raw    = 'www.rubyonrails.com'
    link2_result = %{<a href="http://#{link2_raw}">#{link2_raw}</a>}
    link3_raw    = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281'
    link3_result = %{<a href="#{link3_raw}">#{link3_raw}</a>}
    link4_raw    = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123'
    link4_result = %{<a href="#{link4_raw}">#{link4_raw}</a>}
    link5_raw    = 'http://foo.example.com:3000/controller/action'
    link5_result = %{<a href="#{link5_raw}">#{link5_raw}</a>}

    assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses)
    assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls)
    assert_equal %(Go to #{link_raw}), auto_link("Go to #{link_raw}", :email_addresses)
    assert_equal %(Go to #{link_result} and say hello to #{email_result}), auto_link("Go to #{link_raw} and say hello to #{email_raw}")
    assert_equal %(<p>Link #{link_result}</p>), auto_link("<p>Link #{link_raw}</p>")
    assert_equal %(<p>#{link_result} Link</p>), auto_link("<p>#{link_raw} Link</p>")
    assert_equal %(<p>Link #{link_result_with_options}</p>), auto_link("<p>Link #{link_raw}</p>", :all, {:target => "_blank"})
    assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.))
    assert_equal %(<p>Go to #{link_result}, then say hello to #{email_result}.</p>), auto_link(%(<p>Go to #{link_raw}, then say hello to #{email_raw}.</p>))
    assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls)
    assert_equal %(Go to #{link2_raw}), auto_link("Go to #{link2_raw}", :email_addresses)
    assert_equal %(<p>Link #{link2_result}</p>), auto_link("<p>Link #{link2_raw}</p>")
    assert_equal %(<p>#{link2_result} Link</p>), auto_link("<p>#{link2_raw} Link</p>")
    assert_equal %(Go to #{link2_result}.), auto_link(%(Go to #{link2_raw}.))
    assert_equal %(<p>Say hello to #{email_result}, then go to #{link2_result}.</p>), auto_link(%(<p>Say hello to #{email_raw}, then go to #{link2_raw}.</p>))
    assert_equal %(Go to #{link3_result}), auto_link("Go to #{link3_raw}", :urls)
    assert_equal %(Go to #{link3_raw}), auto_link("Go to #{link3_raw}", :email_addresses)
    assert_equal %(<p>Link #{link3_result}</p>), auto_link("<p>Link #{link3_raw}</p>")
    assert_equal %(<p>#{link3_result} Link</p>), auto_link("<p>#{link3_raw} Link</p>")
    assert_equal %(Go to #{link3_result}.), auto_link(%(Go to #{link3_raw}.))
    assert_equal %(<p>Go to #{link3_result}. seriously, #{link3_result}? i think I'll say hello to #{email_result}. instead.</p>), auto_link(%(<p>Go to #{link3_raw}. seriously, #{link3_raw}? i think I'll say hello to #{email_raw}. instead.</p>))
    assert_equal %(<p>Link #{link4_result}</p>), auto_link("<p>Link #{link4_raw}</p>")
    assert_equal %(<p>#{link4_result} Link</p>), auto_link("<p>#{link4_raw} Link</p>")
    assert_equal %(<p>#{link5_result} Link</p>), auto_link("<p>#{link5_raw} Link</p>")
    assert_equal '', auto_link(nil)
    assert_equal '', auto_link('')
  end

  def test_auto_link_at_eol
    url1 = "http://api.rubyonrails.com/Foo.html"
    url2 = "http://www.ruby-doc.org/core/Bar.html"

    assert_equal %(<p><a href="#{url1}">#{url1}</a><br /><a href="#{url2}">#{url2}</a><br /></p>), auto_link("<p>#{url1}<br />#{url2}<br /></p>")
  end

  def test_auto_link_with_block
    url = "http://api.rubyonrails.com/Foo.html"
    email = "fantabulous@shiznadel.ic"

    assert_equal %(<p><a href="#{url}">#{url[0...7]}...</a><br /><a href="mailto:#{email}">#{email[0...7]}...</a><br /></p>), auto_link("<p>#{url}<br />#{email}<br /></p>") { |url| truncate(url, 10) }
  end

  def test_sanitize_form
    raw = "<form action=\"/foo/bar\" method=\"post\"><input></form>"
    result = sanitize(raw)
    assert_equal "&lt;form action='/foo/bar' method='post'><input>&lt;/form>", result
  end

  def test_sanitize_script
    raw = "<script language=\"Javascript\">blah blah blah</script>"
    result = sanitize(raw)
    assert_equal "&lt;script language='Javascript'>blah blah blah&lt;/script>", result
  end

  def test_sanitize_js_handlers
    raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
    result = sanitize(raw)
    assert_equal %{onthis="do that" <a name='foo' href='#'>hello</a>}, result
  end

  def test_sanitize_javascript_href
    raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>}
    result = sanitize(raw)
    assert_equal %{href="javascript:bang" <a name='hello'>foo</a>, <span>bar</span>}, result
  end
  
  def test_cycle_class
    value = Cycle.new("one", 2, "3")
    assert_equal("one", value.to_s)
    assert_equal("2", value.to_s)
    assert_equal("3", value.to_s)
    assert_equal("one", value.to_s)
    value.reset
    assert_equal("one", value.to_s)
    assert_equal("2", value.to_s)
    assert_equal("3", value.to_s)
  end
  
  def test_cycle_class_with_no_arguments
    assert_raise(ArgumentError) { value = Cycle.new() }
  end

  def test_cycle
    assert_equal("one", cycle("one", 2, "3"))
    assert_equal("2", cycle("one", 2, "3"))
    assert_equal("3", cycle("one", 2, "3"))
    assert_equal("one", cycle("one", 2, "3"))
    assert_equal("2", cycle("one", 2, "3"))
    assert_equal("3", cycle("one", 2, "3"))
  end
  
  def test_cycle_with_no_arguments
    assert_raise(ArgumentError) { value = cycle() }
  end
  
  def test_cycle_resets_with_new_values
    assert_equal("even", cycle("even", "odd"))
    assert_equal("odd", cycle("even", "odd"))
    assert_equal("even", cycle("even", "odd"))
    assert_equal("1", cycle(1, 2, 3))
    assert_equal("2", cycle(1, 2, 3))
    assert_equal("3", cycle(1, 2, 3))
    assert_equal("1", cycle(1, 2, 3))
  end
  
  def test_named_cycles
    assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
    assert_equal("red", cycle("red", "blue", :name => "colors"))
    assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
    assert_equal("blue", cycle("red", "blue", :name => "colors"))
    assert_equal("3", cycle(1, 2, 3, :name => "numbers"))
    assert_equal("red", cycle("red", "blue", :name => "colors"))
  end
  
  def test_default_named_cycle
    assert_equal("1", cycle(1, 2, 3))
    assert_equal("2", cycle(1, 2, 3, :name => "default"))
    assert_equal("3", cycle(1, 2, 3))
  end
  
  def test_reset_cycle
    assert_equal("1", cycle(1, 2, 3))
    assert_equal("2", cycle(1, 2, 3))
    reset_cycle
    assert_equal("1", cycle(1, 2, 3))
  end
  
  def test_reset_unknown_cycle
    reset_cycle("colors")
  end
  
  def test_recet_named_cycle
    assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
    assert_equal("red", cycle("red", "blue", :name => "colors"))
    reset_cycle("numbers")
    assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
    assert_equal("blue", cycle("red", "blue", :name => "colors"))
    assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
    assert_equal("red", cycle("red", "blue", :name => "colors"))
  end
  
  def test_cycle_no_instance_variable_clashes
    @cycles = %w{Specialized Fuji Giant}
    assert_equal("red", cycle("red", "blue"))
    assert_equal("blue", cycle("red", "blue"))
    assert_equal("red", cycle("red", "blue"))
    assert_equal(%w{Specialized Fuji Giant}, @cycles)
  end

  def test_strip_tags
    assert_equal("This is a test.", strip_tags("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>"))
    assert_equal("This is a test.", strip_tags("This is a test."))
    assert_equal(
    %{This is a test.\n\n\nIt no longer contains any HTML.\n}, strip_tags(
    %{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
    assert_equal("This has a  here.", strip_tags("This has a <!-- comment --> here."))
  end

end
require File.dirname(__FILE__) + '/../abstract_unit'

require File.dirname(__FILE__) + '/../../lib/action_view/helpers/url_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/asset_tag_helper'
require File.dirname(__FILE__) + '/../../lib/action_view/helpers/tag_helper'

RequestMock = Struct.new("Request", :request_uri)

class UrlHelperTest < Test::Unit::TestCase
  include ActionView::Helpers::AssetTagHelper
  include ActionView::Helpers::UrlHelper
  include ActionView::Helpers::TagHelper

  def setup
    @controller = Class.new do
      attr_accessor :url
      def url_for(options, *parameters_for_method_reference)
        url
      end
    end
    @controller = @controller.new
    @controller.url = "http://www.example.com"
  end
  
  def test_url_for_escapes_urls
    @controller.url = "http://www.example.com?a=b&c=d"
    assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd')
    assert_equal "http://www.example.com?a=b&amp;c=d", url_for(:a => 'b', :c => 'd', :escape => true)
    assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false)
  end
  
  # todo: missing test cases
  def test_button_to_with_straight_url
    assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com")
  end

  def test_button_to_with_query
    assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
  end

  def test_button_to_with_query_and_no_name
    assert_dom_equal "<form method=\"post\" action=\"http://www.example.com?q1=v1&amp;q2=v2\" class=\"button-to\"><div><input type=\"submit\" value=\"http://www.example.com?q1=v1&amp;q2=v2\" /></div></form>", button_to(nil, "http://www.example.com?q1=v1&q2=v2")
  end

  def test_button_to_with_javascript_confirm
    assert_dom_equal(
      "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input onclick=\"return confirm('Are you sure?');\" type=\"submit\" value=\"Hello\" /></div></form>",
      button_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
    )
  end

  def test_button_to_enabled_disabled
    assert_dom_equal(
      "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>",
      button_to("Hello", "http://www.example.com", :disabled => false)
    )
    assert_dom_equal(
      "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input disabled=\"disabled\" type=\"submit\" value=\"Hello\" /></div></form>",
      button_to("Hello", "http://www.example.com", :disabled => true)
    )
  end

  def test_link_tag_with_straight_url
    assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com")
  end

  def test_link_tag_with_query
    assert_dom_equal "<a href=\"http://www.example.com?q1=v1&amp;q2=v2\">Hello</a>", link_to("Hello", "http://www.example.com?q1=v1&amp;q2=v2")
  end

  def test_link_tag_with_query_and_no_name
    assert_dom_equal "<a href=\"http://www.example.com?q1=v1&amp;q2=v2\">http://www.example.com?q1=v1&amp;q2=v2</a>", link_to(nil, "http://www.example.com?q1=v1&amp;q2=v2")
  end

  def test_link_tag_with_img
    assert_dom_equal "<a href=\"http://www.example.com\"><img src='/favicon.jpg' /></a>", link_to("<img src='/favicon.jpg' />", "http://www.example.com")
  end

  def test_link_with_nil_html_options
    assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", {:action => 'myaction'}, nil)
  end

  def test_link_tag_with_custom_onclick
    assert_dom_equal "<a href=\"http://www.example.com\" onclick=\"alert('yay!')\">Hello</a>", link_to("Hello", "http://www.example.com", :onclick => "alert('yay!')")
  end
  
  def test_link_tag_with_javascript_confirm
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"return confirm('Are you sure?');\">Hello</a>",
      link_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
    )
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"return confirm('You can\\'t possibly be sure, can you?');\">Hello</a>", 
      link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure, can you?")
    )
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"return confirm('You can\\'t possibly be sure,\\n can you?');\">Hello</a>", 
      link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure,\n can you?")
    )
  end

  def test_link_tag_with_popup
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"window.open(this.href);return false;\">Hello</a>",
      link_to("Hello", "http://www.example.com", :popup => true)
    )
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"window.open(this.href);return false;\">Hello</a>", 
      link_to("Hello", "http://www.example.com", :popup => 'true')
    )
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"window.open(this.href,'window_name','width=300,height=300');return false;\">Hello</a>", 
      link_to("Hello", "http://www.example.com", :popup => ['window_name', 'width=300,height=300'])
    )
  end
  
  def test_link_tag_with_popup_and_javascript_confirm
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"if (confirm('Fo\\' sho\\'?')) { window.open(this.href); };return false;\">Hello</a>",
      link_to("Hello", "http://www.example.com", { :popup => true, :confirm => "Fo' sho'?" })
    )
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"if (confirm('Are you serious?')) { window.open(this.href,'window_name','width=300,height=300'); };return false;\">Hello</a>",
      link_to("Hello", "http://www.example.com", { :popup => ['window_name', 'width=300,height=300'], :confirm => "Are you serious?" })
    )
  end
  
  def test_link_tag_using_post_javascript
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit();return false;\">Hello</a>",
      link_to("Hello", "http://www.example.com", :post => true)
    )
  end
  
  def test_link_tag_using_post_javascript_and_confirm
    assert_dom_equal(
      "<a href=\"http://www.example.com\" onclick=\"if (confirm('Are you serious?')) { var f = document.createElement('form'); this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; f.submit(); };return false;\">Hello</a>",
      link_to("Hello", "http://www.example.com", :post => true, :confirm => "Are you serious?")
    )    
  end
  
  def test_link_tag_using_post_javascript_and_popup
    assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :post => true, :confirm => "Are you serious?") }
  end
  
  def test_link_to_unless
    assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog")
    assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_unless(false, "Listing", :action => "list", :controller => "weblog")
    assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1)
    assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name, options, html_options, *parameters_for_method_reference|
      "<strong>#{name}</strong>"
    }
    assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name|
      "<strong>#{name}</strong>"
    }    
    assert_equal "test", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) {
      "test"
    }    
  end
  
  def test_link_to_if
    assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog")
    assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_if(true, "Listing", :action => "list", :controller => "weblog")
    assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog", :id => 1)
  end

  def xtest_link_unless_current
    @request = RequestMock.new("http://www.example.com")
    assert_equal "Showing", link_to_unless_current("Showing", :action => "show", :controller => "weblog")
    @request = RequestMock.new("http://www.example.org")
    assert "<a href=\"http://www.example.com\">Listing</a>", link_to_unless_current("Listing", :action => "list", :controller => "weblog")

    @request = RequestMock.new("http://www.example.com")
    assert_equal "Showing", link_to_unless_current("Showing", :action => "show", :controller => "weblog", :id => 1)
  end
  
  def test_mail_to
    assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>", mail_to("david@loudthinking.com")
    assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", mail_to("david@loudthinking.com", "David Heinemeier Hansson")
    assert_dom_equal(
      "<a class=\"admin\" href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>",
      mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin")
    )
    assert_equal mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin"),
                 mail_to("david@loudthinking.com", "David Heinemeier Hansson", :class => "admin")
  end

  def test_mail_to_with_javascript
    assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript")
  end
  
  def test_mail_with_options
    assert_dom_equal(
      %(<a href="mailto:me@example.com?cc=ccaddress%40example.com&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;subject=This%20is%20an%20example%20email">My email</a>),
      mail_to("me@example.com", "My email", :cc => "ccaddress@example.com", :bcc => "bccaddress@example.com", :subject => "This is an example email", :body => "This is the body of the message.")
    )
  end
  
  def test_mail_to_with_img
    assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), mail_to('feedback@example.com', '<img src="/feedback.png" />')
  end

  def test_mail_to_with_hex
    assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex")
  end

  def test_mail_to_with_replace_options
    assert_dom_equal "<a href=\"mailto:wolfgang@stufenlos.net\">wolfgang(at)stufenlos(dot)net</a>", mail_to("wolfgang@stufenlos.net", nil, :replace_at => "(at)", :replace_dot => "(dot)")
    assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
    assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
    assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain(dot)com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
    assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
  end
end
module TestingSandbox

  # 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)
    test_name = "#{File.dirname(__FILE__)}/test.#{$$}.rb"
    res_name = "#{File.dirname(__FILE__)}/test.#{$$}.out"

    File.open(test_name, "w+") do |file|
      file.write(<<-CODE)
        $:.unshift "../lib"
        block = Proc.new do
          #{code}
        end
        print block.call
      CODE
    end

    system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
    File.read(res_name)
  ensure
    File.delete(test_name) rescue nil
    File.delete(res_name) rescue nil
  end

end
class DirectoryCategory < ActionWebService::Struct
  member :fullViewableName, :string
  member :specialEncoding,  :string
end

class ResultElement < ActionWebService::Struct
  member :summary,                   :string
  member :URL,                       :string
  member :snippet,                   :string
  member :title,                     :string
  member :cachedSize,                :string
  member :relatedInformationPresent, :bool
  member :hostName,                  :string
  member :directoryCategory,         DirectoryCategory
  member :directoryTitle,            :string
end

class GoogleSearchResult < ActionWebService::Struct
  member :documentFiltering,          :bool
  member :searchComments,             :string
  member :estimatedTotalResultsCount, :int
  member :estimateIsExact,            :bool
  member :resultElements,             [ResultElement]
  member :searchQuery,                :string
  member :startIndex,                 :int
  member :endIndex,                   :int
  member :searchTips,                 :string
  member :directoryCategories,        [DirectoryCategory]
  member :searchTime,                 :float
end

class GoogleSearchAPI < ActionWebService::API::Base
  inflect_names false

  api_method :doGetCachedPage,         :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
  api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]

  api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
    {:key=>:string},
    {:q=>:string},
    {:start=>:int},
    {:maxResults=>:int},
    {:filter=>:bool},
    {:restrict=>:string},
    {:safeSearch=>:bool},
    {:lr=>:string},
    {:ie=>:string},
    {:oe=>:string}
  ]
end
class GoogleSearchController < ApplicationController
  wsdl_service_name 'GoogleSearch'

  def doGetCachedPage
    "<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
  end

  def doSpellingSuggestion
    "%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
  end

  def doGoogleSearch
    resultElement = ResultElement.new
    resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
    resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
    resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
                            "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
    resultElement.title = "Teh Railz0r"
    resultElement.cachedSize = "Almost no lines of code!"
    resultElement.relatedInformationPresent = true
    resultElement.hostName = "rubyonrails.com"
    resultElement.directoryCategory = category("Web Development", "UTF-8")

    result = GoogleSearchResult.new
    result.documentFiltering = @params['filter']
    result.searchComments = ""
    result.estimatedTotalResultsCount = 322000
    result.estimateIsExact = false
    result.resultElements = [resultElement]
    result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
    result.startIndex = @params['start']
    result.endIndex = @params['start'] + @params['maxResults']
    result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
    result.searchTime = 0.000001

    # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
    # the Ruby SOAP collapses them into one instance and uses references all over the
    # place, confusing Mono. 
    #
    # This has recently been fixed:
    #   http://bugzilla.ximian.com/show_bug.cgi?id=72265
    result.directoryCategories = [
      category("Web Development", "UTF-8"),
      category("Programming", "US-ASCII"),
    ]

    result
  end

  private
    def category(name, encoding)
      cat = DirectoryCategory.new
      cat.fullViewableName = name.dup
      cat.specialEncoding = encoding.dup
      cat
    end
end
class DirectoryCategory < ActionWebService::Struct
  member :fullViewableName, :string
  member :specialEncoding,  :string
end

class ResultElement < ActionWebService::Struct
  member :summary,                   :string
  member :URL,                       :string
  member :snippet,                   :string
  member :title,                     :string
  member :cachedSize,                :string
  member :relatedInformationPresent, :bool
  member :hostName,                  :string
  member :directoryCategory,         DirectoryCategory
  member :directoryTitle,            :string
end

class GoogleSearchResult < ActionWebService::Struct
  member :documentFiltering,          :bool
  member :searchComments,             :string
  member :estimatedTotalResultsCount, :int
  member :estimateIsExact,            :bool
  member :resultElements,             [ResultElement]
  member :searchQuery,                :string
  member :startIndex,                 :int
  member :endIndex,                   :int
  member :searchTips,                 :string
  member :directoryCategories,        [DirectoryCategory]
  member :searchTime,                 :float
end

class GoogleSearchAPI < ActionWebService::API::Base
  inflect_names false

  api_method :doGetCachedPage,         :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
  api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]

  api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
    {:key=>:string},
    {:q=>:string},
    {:start=>:int},
    {:maxResults=>:int},
    {:filter=>:bool},
    {:restrict=>:string},
    {:safeSearch=>:bool},
    {:lr=>:string},
    {:ie=>:string},
    {:oe=>:string}
  ]
end

class GoogleSearchService < ActionWebService::Base
  web_service_api GoogleSearchAPI

  def doGetCachedPage(key, url)
    "<html><body>i am a cached page</body></html>"
  end

  def doSpellingSuggestion(key, phrase)
    "Did you mean 'teh'?"
  end

  def doGoogleSearch(key, q, start, maxResults, filter, restrict, safeSearch, lr, ie, oe)
    resultElement = ResultElement.new
    resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
    resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
    resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
                            "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
    resultElement.title = "Teh Railz0r"
    resultElement.cachedSize = "Almost no lines of code!"
    resultElement.relatedInformationPresent = true
    resultElement.hostName = "rubyonrails.com"
    resultElement.directoryCategory = category("Web Development", "UTF-8")

    result = GoogleSearchResult.new
    result.documentFiltering = filter
    result.searchComments = ""
    result.estimatedTotalResultsCount = 322000
    result.estimateIsExact = false
    result.resultElements = [resultElement]
    result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
    result.startIndex = start
    result.endIndex = start + maxResults
    result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
    result.searchTime = 0.000001

    # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
    # the Ruby SOAP collapses them into one instance and uses references all over the
    # place, confusing Mono. 
    #
    # This has recently been fixed:
    #   http://bugzilla.ximian.com/show_bug.cgi?id=72265
    result.directoryCategories = [
      category("Web Development", "UTF-8"),
      category("Programming", "US-ASCII"),
    ]

    result
  end

  private
    def category(name, encoding)
      cat = DirectoryCategory.new
      cat.fullViewableName = name.dup
      cat.specialEncoding = encoding.dup
      cat
    end
end
require 'google_search_service'

class SearchController < ApplicationController
  wsdl_service_name 'GoogleSearch'
  web_service_dispatching_mode :delegated
  web_service :beta3, GoogleSearchService.new
end
class DirectoryCategory < ActionWebService::Struct
  member :fullViewableName, :string
  member :specialEncoding,  :string
end

class ResultElement < ActionWebService::Struct
  member :summary,                   :string
  member :URL,                       :string
  member :snippet,                   :string
  member :title,                     :string
  member :cachedSize,                :string
  member :relatedInformationPresent, :bool
  member :hostName,                  :string
  member :directoryCategory,         DirectoryCategory
  member :directoryTitle,            :string
end

class GoogleSearchResult < ActionWebService::Struct
  member :documentFiltering,          :bool
  member :searchComments,             :string
  member :estimatedTotalResultsCount, :int
  member :estimateIsExact,            :bool
  member :resultElements,             [ResultElement]
  member :searchQuery,                :string
  member :startIndex,                 :int
  member :endIndex,                   :int
  member :searchTips,                 :string
  member :directoryCategories,        [DirectoryCategory]
  member :searchTime,                 :float
end

class GoogleSearchAPI < ActionWebService::API::Base
  inflect_names false

  api_method :doGetCachedPage,         :returns => [:string], :expects => [{:key=>:string}, {:url=>:string}]
  api_method :doGetSpellingSuggestion, :returns => [:string], :expects => [{:key=>:string}, {:phrase=>:string}]

  api_method :doGoogleSearch, :returns => [GoogleSearchResult], :expects => [
    {:key=>:string},
    {:q=>:string},
    {:start=>:int},
    {:maxResults=>:int},
    {:filter=>:bool},
    {:restrict=>:string},
    {:safeSearch=>:bool},
    {:lr=>:string},
    {:ie=>:string},
    {:oe=>:string}
  ]
end
class SearchController < ApplicationController
  web_service_api :google_search
  wsdl_service_name 'GoogleSearch'

  def doGetCachedPage
    "<html><body>i am a cached page. my key was %s, url was %s</body></html>" % [@params['key'], @params['url']]
  end

  def doSpellingSuggestion
    "%s: Did you mean '%s'?" % [@params['key'], @params['phrase']]
  end

  def doGoogleSearch
    resultElement = ResultElement.new
    resultElement.summary = "ONlamp.com: Rolling with Ruby on Rails"
    resultElement.URL = "http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html"
    resultElement.snippet = "Curt Hibbs shows off Ruby on Rails by building a simple application that requires " +
                            "almost no Ruby experience. ... Rolling with Ruby on Rails. ..."
    resultElement.title = "Teh Railz0r"
    resultElement.cachedSize = "Almost no lines of code!"
    resultElement.relatedInformationPresent = true
    resultElement.hostName = "rubyonrails.com"
    resultElement.directoryCategory = category("Web Development", "UTF-8")

    result = GoogleSearchResult.new
    result.documentFiltering = @params['filter']
    result.searchComments = ""
    result.estimatedTotalResultsCount = 322000
    result.estimateIsExact = false
    result.resultElements = [resultElement]
    result.searchQuery = "http://www.google.com/search?q=ruby+on+rails"
    result.startIndex = @params['start']
    result.endIndex = @params['start'] + @params['maxResults']
    result.searchTips = "\"on\" is a very common word and was not included in your search [details]"
    result.searchTime = 0.000001

    # For Mono, we have to clone objects if they're referenced by more than one place, otherwise
    # the Ruby SOAP collapses them into one instance and uses references all over the
    # place, confusing Mono. 
    #
    # This has recently been fixed:
    #   http://bugzilla.ximian.com/show_bug.cgi?id=72265
    result.directoryCategories = [
      category("Web Development", "UTF-8"),
      category("Programming", "US-ASCII"),
    ]

    result
  end

  private
    def category(name, encoding)
      cat = DirectoryCategory.new
      cat.fullViewableName = name.dup
      cat.specialEncoding = encoding.dup
      cat
    end
end
#
# see the blogger API spec at http://www.blogger.com/developers/api/1_docs/
# note that the method signatures are subtly different to metaWeblog, they
# are not identical. take care to ensure you handle the different semantics
# properly if you want to support blogger API too, to get maximum compatibility.
#

module Blog
  class Blog < ActionWebService::Struct
    member :url,      :string
    member :blogid,   :string
    member :blogName, :string
  end

  class User < ActionWebService::Struct
    member :nickname,  :string
    member :userid,    :string
    member :url,       :string
    member :email,     :string
    member :lastname,  :string
    member :firstname, :string
  end
end

#
# blogger
#
class BloggerAPI < ActionWebService::API::Base
  inflect_names false

  api_method :newPost, :returns => [:string], :expects => [
    {:appkey=>:string},
    {:blogid=>:string},
    {:username=>:string},
    {:password=>:string},
    {:content=>:string},
    {:publish=>:bool}
  ]

  api_method :editPost, :returns => [:bool], :expects => [
    {:appkey=>:string},
    {:postid=>:string},
    {:username=>:string},
    {:password=>:string},
    {:content=>:string},
    {:publish=>:bool}
  ]

  api_method :getUsersBlogs, :returns => [[Blog::Blog]], :expects => [
    {:appkey=>:string},
    {:username=>:string},
    {:password=>:string}
  ]

  api_method :getUserInfo, :returns => [Blog::User], :expects => [
    {:appkey=>:string},
    {:username=>:string},
    {:password=>:string}
  ]
end
require 'blogger_api'

class BloggerService < ActionWebService::Base
  web_service_api BloggerAPI

  def initialize
    @postid = 0
  end

  def newPost(key, id, user, pw, content, publish)
    $stderr.puts "id=#{id} user=#{user} pw=#{pw}, content=#{content.inspect} [#{publish}]"
    (@postid += 1).to_s
  end

  def editPost(key, post_id, user, pw, content, publish)
    $stderr.puts "id=#{post_id} user=#{user} pw=#{pw} content=#{content.inspect} [#{publish}]"
    true
  end

  def getUsersBlogs(key, user, pw)
    $stderr.puts "getting blogs for #{user}"
    blog = Blog::Blog.new(
      :url =>'http://blog',
      :blogid => 'myblog',
      :blogName => 'My Blog'
    )
    [blog]
  end

  def getUserInfo(key, user, pw)
    $stderr.puts "getting user info for #{user}"
    Blog::User.new(:nickname => 'user', :email => 'user@test.com')
  end
end
# 
# here lie structures, cousins of those on http://www.xmlrpc.com/metaWeblog
# but they don't necessarily the real world reflect
# so if you do, find that your client complains:
# please tell, of problems you suffered through
#

module Blog
  class Post < ActionWebService::Struct
    member :title,       :string
    member :link,        :string
    member :description, :string
    member :author,      :string
    member :category,    :string
    member :comments,    :string
    member :guid,        :string
    member :pubDate,     :string
  end

  class Category < ActionWebService::Struct
    member :description, :string
    member :htmlUrl,     :string
    member :rssUrl,      :string
  end
end

#
# metaWeblog
#
class MetaWeblogAPI < ActionWebService::API::Base
  inflect_names false

  api_method :newPost, :returns => [:string], :expects => [
    {:blogid=>:string},
    {:username=>:string},
    {:password=>:string},
    {:struct=>Blog::Post},
    {:publish=>:bool}
  ]

  api_method :editPost, :returns => [:bool], :expects => [
    {:postid=>:string},
    {:username=>:string},
    {:password=>:string},
    {:struct=>Blog::Post},
    {:publish=>:bool},
  ]

  api_method :getPost, :returns => [Blog::Post], :expects => [
    {:postid=>:string},
    {:username=>:string},
    {:password=>:string},
  ]

  api_method :getCategories, :returns => [[Blog::Category]], :expects => [
    {:blogid=>:string},
    {:username=>:string},
    {:password=>:string},
  ]

  api_method :getRecentPosts, :returns => [[Blog::Post]], :expects => [
    {:blogid=>:string},
    {:username=>:string},
    {:password=>:string},
    {:numberOfPosts=>:int},
  ]
end
require 'meta_weblog_api'

class MetaWeblogService < ActionWebService::Base
  web_service_api MetaWeblogAPI

  def initialize
    @postid = 0
  end

  def newPost(id, user, pw, struct, publish)
    $stderr.puts "id=#{id} user=#{user} pw=#{pw}, struct=#{struct.inspect} [#{publish}]"
    (@postid += 1).to_s
  end

  def editPost(post_id, user, pw, struct, publish)
    $stderr.puts "id=#{post_id} user=#{user} pw=#{pw} struct=#{struct.inspect} [#{publish}]"
    true
  end

  def getPost(post_id, user, pw)
    $stderr.puts "get post #{post_id}"
    Blog::Post.new(:title => 'hello world', :description => 'first post!')
  end

  def getCategories(id, user, pw)
    $stderr.puts "categories for #{user}"
    cat = Blog::Category.new(
      :description => 'Tech',
      :htmlUrl     => 'http://blog/tech',
      :rssUrl      => 'http://blog/tech.rss')
    [cat]
  end

  def getRecentPosts(id, user, pw, num)
    $stderr.puts "recent #{num} posts for #{user} on blog #{id}"
    post1 = Blog::Post.new(
      :title => 'first post!',
      :link => 'http://blog.xeraph.org/testOne.html',
      :description => 'this is the first post'
    )
    post2 = Blog::Post.new(
      :title => 'second post!',
      :link => 'http://blog.xeraph.org/testTwo.html',
      :description => 'this is the second post'
    )
    [post1, post2]
  end
end
#
# example controller implementing both blogger and metaWeblog APIs
# in a way that should be compatible with clients supporting both/either.
#
# test by pointing your client at http://URL/xmlrpc/api
# 

require 'meta_weblog_service'
require 'blogger_service'

class XmlrpcController < ApplicationController
  web_service_dispatching_mode :layered

  web_service :metaWeblog, MetaWeblogService.new
  web_service :blogger, BloggerService.new
end
module ActionWebService # :nodoc:
  module API # :nodoc:
    # A web service API class specifies the methods that will be available for
    # invocation for an API. It also contains metadata such as the method type
    # signature hints.
    #
    # It is not intended to be instantiated.
    #
    # It is attached to web service implementation classes like
    # ActionWebService::Base and ActionController::Base derivatives by using
    # <tt>container.web_service_api</tt>, where <tt>container</tt> is an
    # ActionController::Base or a ActionWebService::Base.
    #
    # See ActionWebService::Container::Direct::ClassMethods for an example
    # of use.
    class Base
      # Action WebService API subclasses should be reloaded by the dispatcher in Rails
      # when Dependencies.mechanism = :load.
      include Reloadable::Subclasses
      
      # Whether to transform the public API method names into camel-cased names 
      class_inheritable_option :inflect_names, true

      # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>.
      # The default is +false+; you should be aware of the security implications
      # of allowing this, and ensure that you don't allow remote callers to
      # easily overwrite data they should not have access to.
      class_inheritable_option :allow_active_record_expects, false

      # If present, the name of a method to call when the remote caller
      # tried to call a nonexistent method. Semantically equivalent to
      # +method_missing+.
      class_inheritable_option :default_api_method

      # Disallow instantiation
      private_class_method :new, :allocate
      
      class << self
        include ActionWebService::SignatureTypes

        # API methods have a +name+, which must be the Ruby method name to use when
        # performing the invocation on the web service object.
        #
        # The signatures for the method input parameters and return value can
        # by specified in +options+.
        #
        # A signature is an array of one or more parameter specifiers. 
        # A parameter specifier can be one of the following:
        #
        # * A symbol or string representing one of the Action Web Service base types.
        #   See ActionWebService::SignatureTypes for a canonical list of the base types.
        # * The Class object of the parameter type
        # * A single-element Array containing one of the two preceding items. This
        #   will cause Action Web Service to treat the parameter at that position
        #   as an array containing only values of the given type.
        # * A Hash containing as key the name of the parameter, and as value
        #   one of the three preceding items
        # 
        # If no method input parameter or method return value signatures are given,
        # the method is assumed to take no parameters and/or return no values of
        # interest, and any values that are received by the server will be
        # discarded and ignored.
        #
        # Valid options:
        # [<tt>:expects</tt>]             Signature for the method input parameters
        # [<tt>:returns</tt>]             Signature for the method return value
        # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
        def api_method(name, options={})
          unless options.is_a?(Hash)
            raise(ActionWebServiceError, "Expected a Hash for options")
          end
          validate_options([:expects, :returns, :expects_and_returns], options.keys)
          if options[:expects_and_returns]
            expects = options[:expects_and_returns]
            returns = options[:expects_and_returns]
          else
            expects = options[:expects]
            returns = options[:returns]
          end
          expects = canonical_signature(expects)
          returns = canonical_signature(returns)
          if expects
            expects.each do |type|
              type = type.element_type if type.is_a?(ArrayType)
              if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
                raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
              end
            end
          end
          name = name.to_sym
          public_name = public_api_method_name(name)
          method = Method.new(name, public_name, expects, returns)
          write_inheritable_hash("api_methods", name => method)
          write_inheritable_hash("api_public_method_names", public_name => name)
        end

        # Whether the given method name is a service method on this API
        def has_api_method?(name)
          api_methods.has_key?(name)
        end
  
        # Whether the given public method name has a corresponding service method
        # on this API
        def has_public_api_method?(public_name)
          api_public_method_names.has_key?(public_name)
        end
  
        # The corresponding public method name for the given service method name
        def public_api_method_name(name)
          if inflect_names
            name.to_s.camelize
          else
            name.to_s
          end
        end
  
        # The corresponding service method name for the given public method name
        def api_method_name(public_name)
          api_public_method_names[public_name]
        end
  
        # A Hash containing all service methods on this API, and their
        # associated metadata.
        def api_methods
          read_inheritable_attribute("api_methods") || {}
        end

        # The Method instance for the given public API method name, if any
        def public_api_method_instance(public_method_name)
          api_method_instance(api_method_name(public_method_name))
        end

        # The Method instance for the given API method name, if any
        def api_method_instance(method_name)
          api_methods[method_name]
        end

        # The Method instance for the default API method, if any
        def default_api_method_instance
          return nil unless name = default_api_method
          instance = read_inheritable_attribute("default_api_method_instance")
          if instance && instance.name == name
            return instance
          end
          instance = Method.new(name, public_api_method_name(name), nil, nil)
          write_inheritable_attribute("default_api_method_instance", instance)
          instance
        end

        private
          def api_public_method_names
            read_inheritable_attribute("api_public_method_names") || {}
          end
  
          def validate_options(valid_option_keys, supplied_option_keys)
            unknown_option_keys = supplied_option_keys - valid_option_keys
            unless unknown_option_keys.empty?
              raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
            end
          end
      end
    end

    # Represents an API method and its associated metadata, and provides functionality
    # to assist in commonly performed API method tasks.
    class Method
      attr :name
      attr :public_name
      attr :expects
      attr :returns

      def initialize(name, public_name, expects, returns)
        @name = name
        @public_name = public_name
        @expects = expects
        @returns = returns
        @caster = ActionWebService::Casting::BaseCaster.new(self)
      end
      
      # The list of parameter names for this method
      def param_names
        return [] unless @expects
        @expects.map{ |type| type.name }
      end

      # Casts a set of Ruby values into the expected Ruby values
      def cast_expects(params)
        @caster.cast_expects(params)
      end

      # Cast a Ruby return value into the expected Ruby value
      def cast_returns(return_value)
        @caster.cast_returns(return_value)
      end

      # Returns the index of the first expected parameter
      # with the given name
      def expects_index_of(param_name)
        return -1 if @expects.nil?
        (0..(@expects.length-1)).each do |i|
          return i if @expects[i].name.to_s == param_name.to_s
        end
        -1
      end

      # Returns a hash keyed by parameter name for the given
      # parameter list
      def expects_to_hash(params)
        return {} if @expects.nil?
        h = {}
        @expects.zip(params){ |type, param| h[type.name] = param }
        h
      end

      # Backwards compatibility with previous API
      def [](sig_type)
        case sig_type
        when :expects
          @expects.map{|x| compat_signature_entry(x)}
        when :returns
          @returns.map{|x| compat_signature_entry(x)}
        end
      end

      # String representation of this method
      def to_s
        fqn = ""
        fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ")
        fqn << "#{@public_name}("
        fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects
        fqn << ")"
        fqn
      end

      private
        def compat_signature_entry(entry)
          if entry.array?
            [compat_signature_entry(entry.element_type)]
          else
            if entry.spec.is_a?(Hash)
              {entry.spec.keys.first => entry.type_class}
            else
              entry.type_class
            end
          end
        end
    end
  end
end
module ActionWebService # :nodoc:
  class ActionWebServiceError < StandardError # :nodoc:
  end

  # An Action Web Service object implements a specified API.
  #
  # Used by controllers operating in _Delegated_ dispatching mode.
  #
  # ==== Example
  # 
  #   class PersonService < ActionWebService::Base
  #     web_service_api PersonAPI
  #
  #     def find_person(criteria)
  #       Person.find_all [...]
  #     end
  #
  #     def delete_person(id)
  #       Person.find_by_id(id).destroy
  #     end
  #   end
  #
  #   class PersonAPI < ActionWebService::API::Base
  #     api_method :find_person,   :expects => [SearchCriteria], :returns => [[Person]]
  #     api_method :delete_person, :expects => [:int]
  #   end
  #
  #   class SearchCriteria < ActionWebService::Struct
  #     member :firstname, :string
  #     member :lastname,  :string
  #     member :email,     :string
  #   end
  class Base
    # Action WebService subclasses should be reloaded by the dispatcher in Rails
    # when Dependencies.mechanism = :load.
    include Reloadable::Subclasses

    # Whether to report exceptions back to the caller in the protocol's exception
    # format
    class_inheritable_option :web_service_exception_reporting, true
  end
end
require 'time'
require 'date'
require 'xmlrpc/datetime'

module ActionWebService # :nodoc:
  module Casting # :nodoc:
    class CastingError < ActionWebServiceError # :nodoc:
    end

    # Performs casting of arbitrary values into the correct types for the signature
    class BaseCaster # :nodoc:
      def initialize(api_method)
        @api_method = api_method
      end

      # Coerces the parameters in +params+ (an Enumerable) into the types
      # this method expects
      def cast_expects(params)
        self.class.cast_expects(@api_method, params)
      end

      # Coerces the given +return_value+ into the type returned by this
      # method
      def cast_returns(return_value)
        self.class.cast_returns(@api_method, return_value)
      end

      class << self
        include ActionWebService::SignatureTypes

        def cast_expects(api_method, params) # :nodoc:
          return [] if api_method.expects.nil?
          api_method.expects.zip(params).map{ |type, param| cast(param, type) }
        end

        def cast_returns(api_method, return_value) # :nodoc:
          return nil if api_method.returns.nil?
          cast(return_value, api_method.returns[0])
        end

        def cast(value, signature_type) # :nodoc:
          return value if signature_type.nil? # signature.length != params.length
          return nil if value.nil?
          # XMLRPC protocol doesn't support nil values. It uses false instead.
          # It should never happen for SOAP.
          if signature_type.structured? && value.equal?(false)
            return nil
          end
          unless signature_type.array? || signature_type.structured?
            return value if canonical_type(value.class) == signature_type.type
          end
          if signature_type.array?
            unless value.respond_to?(:entries) && !value.is_a?(String)
              raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}"
            end
            value.entries.map do |entry|
              cast(entry, signature_type.element_type)
            end
          elsif signature_type.structured?
            cast_to_structured_type(value, signature_type)
          elsif !signature_type.custom?
            cast_base_type(value, signature_type)
          end
        end

        def cast_base_type(value, signature_type) # :nodoc:
          # This is a work-around for the fact that XML-RPC special-cases DateTime values into its own DateTime type
          # in order to support iso8601 dates. This doesn't work too well for us, so we'll convert it into a Time,
          # with the caveat that we won't be able to handle pre-1970 dates that are sent to us.
          # 
          # See http://dev.rubyonrails.com/ticket/2516
          value = value.to_time if value.is_a?(XMLRPC::DateTime)

          case signature_type.type
          when :int
            Integer(value)
          when :string
            value.to_s
          when :base64
            if value.is_a?(ActionWebService::Base64)
              value
            else
              ActionWebService::Base64.new(value.to_s)
            end
          when :bool
            return false if value.nil?
            return value if value == true || value == false
            case value.to_s.downcase
            when '1', 'true', 'y', 'yes'
              true
            when '0', 'false', 'n', 'no'
              false
            else
              raise CastingError, "Don't know how to cast #{value.class} into Boolean"
            end
          when :float
            Float(value)
          when :time
            value = "#{value['2']}/#{value['3']}/#{value['1']} #{value['4']}:#{value['5']}:#{value['6']}" if value.kind_of?(Hash)
            Time.parse(value.to_s)
          when :date
            value = "#{value['2']}/#{value['3']}/#{value['1']}" if value.kind_of?(Hash)
            Date.parse(value.to_s)
          when :datetime
            value = "#{value['2']}/#{value['3']}/#{value['1']} #{value['4']}:#{value['5']}:#{value['6']}" if value.kind_of?(Hash)
            DateTime.parse(value.to_s)
          end
        end

        def cast_to_structured_type(value, signature_type) # :nodoc:
          obj = nil
          obj = value if canonical_type(value.class) == canonical_type(signature_type.type)
          obj ||= signature_type.type_class.new
          if value.respond_to?(:each_pair)
            klass = signature_type.type_class
            value.each_pair do |name, val|
              type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil
              val = cast(val, type) if type
              # See http://dev.rubyonrails.com/ticket/3567
              val = val.to_time if val.is_a?(XMLRPC::DateTime)
              obj.__send__("#{name}=", val) if obj.respond_to?(name)
            end
          elsif value.respond_to?(:attributes)
            signature_type.each_member do |name, type|
              val = value.__send__(name)
              obj.__send__("#{name}=", cast(val, type)) if obj.respond_to?(name)
            end
          else
            raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
          end
          obj
        end
      end
    end
  end
end
module ActionWebService # :nodoc:
  module Client # :nodoc:
    class ClientError < StandardError # :nodoc:
    end

    class Base # :nodoc:
      def initialize(api, endpoint_uri)
        @api = api
        @endpoint_uri = endpoint_uri
      end

      def method_missing(name, *args) # :nodoc:
        call_name = method_name(name)
        return super(name, *args) if call_name.nil?
        self.perform_invocation(call_name, args)
      end

      private
        def method_name(name)
          if @api.has_api_method?(name.to_sym)
            name.to_s
          elsif @api.has_public_api_method?(name.to_s)
            @api.api_method_name(name.to_s).to_s
          end
        end
    end
  end
end
require 'soap/rpc/driver'
require 'uri'

module ActionWebService # :nodoc:
  module Client # :nodoc:

    # Implements SOAP client support (using RPC encoding for the messages).
    #
    # ==== Example Usage
    #
    #   class PersonAPI < ActionWebService::API::Base
    #     api_method :find_all, :returns => [[Person]]
    #   end
    #
    #   soap_client = ActionWebService::Client::Soap.new(PersonAPI, "http://...")
    #   persons = soap_client.find_all
    #
    class Soap < Base

      # Creates a new web service client using the SOAP RPC protocol.
      #
      # +api+ must be an ActionWebService::API::Base derivative, and
      # +endpoint_uri+ must point at the relevant URL to which protocol requests
      # will be sent with HTTP POST.
      #
      # Valid options:
      # [<tt>:namespace</tt>]    If the remote server has used a custom namespace to
      #                          declare its custom types, you can specify it here. This would
      #                          be the namespace declared with a [WebService(Namespace = "http://namespace")] attribute
      #                          in .NET, for example.
      # [<tt>:driver_options</tt>]    If you want to supply any custom SOAP RPC driver
      #                               options, you can provide them as a Hash here
      #
      # The <tt>:driver_options</tt> option can be used to configure the backend SOAP
      # RPC driver. An example of configuring the SOAP backend to do
      # client-certificate authenticated SSL connections to the server:
      #
      #   opts = {}
      #   opts['protocol.http.ssl_config.verify_mode'] = 'OpenSSL::SSL::VERIFY_PEER'
      #   opts['protocol.http.ssl_config.client_cert'] = client_cert_file_path
      #   opts['protocol.http.ssl_config.client_key'] = client_key_file_path
      #   opts['protocol.http.ssl_config.ca_file'] = ca_cert_file_path
      #   client = ActionWebService::Client::Soap.new(api, 'https://some/service', :driver_options => opts)
      def initialize(api, endpoint_uri, options={})
        super(api, endpoint_uri)
        @namespace = options[:namespace] || 'urn:ActionWebService'
        @driver_options = options[:driver_options] || {}
        @protocol = ActionWebService::Protocol::Soap::SoapProtocol.new @namespace
        @soap_action_base = options[:soap_action_base]
        @soap_action_base ||= URI.parse(endpoint_uri).path
        @driver = create_soap_rpc_driver(api, endpoint_uri)
        @driver_options.each do |name, value|
          @driver.options[name.to_s] = value
        end
      end

      protected
        def perform_invocation(method_name, args)
          method = @api.api_methods[method_name.to_sym]
          args = method.cast_expects(args.dup) rescue args
          return_value = @driver.send(method_name, *args)
          method.cast_returns(return_value.dup) rescue return_value
        end

        def soap_action(method_name)
          "#{@soap_action_base}/#{method_name}"
        end

      private
        def create_soap_rpc_driver(api, endpoint_uri)
          @protocol.register_api(api)
          driver = SoapDriver.new(endpoint_uri, nil)
          driver.mapping_registry = @protocol.marshaler.registry
          api.api_methods.each do |name, method|
            qname = XSD::QName.new(@namespace, method.public_name)
            action = soap_action(method.public_name)
            expects = method.expects
            returns = method.returns
            param_def = []
            if expects
              expects.each do |type|
                type_binding = @protocol.marshaler.lookup_type(type)
                if SOAP::Version >= "1.5.5"
                  param_def << ['in', type.name.to_s, [type_binding.type.type_class.to_s]]
                else
                  param_def << ['in', type.name, type_binding.mapping]
                end
              end
            end
            if returns
              type_binding = @protocol.marshaler.lookup_type(returns[0])
              if SOAP::Version >= "1.5.5"
                param_def << ['retval', 'return', [type_binding.type.type_class.to_s]]
              else
                param_def << ['retval', 'return', type_binding.mapping]
              end
            end
            driver.add_method(qname, action, method.name.to_s, param_def)
          end
          driver
        end

        class SoapDriver < SOAP::RPC::Driver # :nodoc:
          def add_method(qname, soapaction, name, param_def)
            @proxy.add_rpc_method(qname, soapaction, name, param_def)
            add_rpc_method_interface(name, param_def)
          end
        end
    end
  end
end
require 'uri'
require 'xmlrpc/client'

module ActionWebService # :nodoc:
  module Client # :nodoc:

    # Implements XML-RPC client support
    #
    # ==== Example Usage
    #
    #   class BloggerAPI < ActionWebService::API::Base
    #     inflect_names false
    #     api_method :getRecentPosts, :returns => [[Blog::Post]]
    #   end
    #
    #   blog = ActionWebService::Client::XmlRpc.new(BloggerAPI, "http://.../RPC", :handler_name => "blogger")
    #   posts = blog.getRecentPosts
    class XmlRpc < Base

      # Creates a new web service client using the XML-RPC protocol.
      #
      # +api+ must be an ActionWebService::API::Base derivative, and
      # +endpoint_uri+ must point at the relevant URL to which protocol requests
      # will be sent with HTTP POST.
      #
      # Valid options:
      # [<tt>:handler_name</tt>]    If the remote server defines its services inside special
      #                             handler (the Blogger API uses a <tt>"blogger"</tt> handler name for example),
      #                             provide it here, or your method calls will fail
      def initialize(api, endpoint_uri, options={})
        @api = api
        @handler_name = options[:handler_name]
        @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
        @client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
      end

      protected
        def perform_invocation(method_name, args)
          method = @api.api_methods[method_name.to_sym]
          if method.expects && method.expects.length != args.length
            raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})")
          end
          args = method.cast_expects(args.dup) rescue args
          if method.expects
            method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) }
          end
          ok, return_value = @client.call2(public_name(method_name), *args)
          return (method.cast_returns(return_value.dup) rescue return_value) if ok
          raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
        end

        def public_name(method_name)
          public_name = @api.public_api_method_name(method_name)
          @handler_name ? "#{@handler_name}.#{public_name}" : public_name
        end
    end
  end
end
require 'action_web_service/client/base'
require 'action_web_service/client/soap_client'
require 'action_web_service/client/xmlrpc_client'
module ActionWebService # :nodoc:
  module Container # :nodoc:
    module ActionController # :nodoc:
      def self.append_features(base) # :nodoc:
        class << base
          include ClassMethods
          alias_method :inherited_without_api, :inherited
          alias_method :inherited, :inherited_with_api
          alias_method :web_service_api_without_require, :web_service_api
          alias_method :web_service_api, :web_service_api_with_require
        end
      end

      module ClassMethods
        # Creates a client for accessing remote web services, using the
        # given +protocol+ to communicate with the +endpoint_uri+.
        #
        # ==== Example
        #
        #   class MyController < ActionController::Base
        #     web_client_api :blogger, :xmlrpc, "http://blogger.com/myblog/api/RPC2", :handler_name => 'blogger'
        #   end
        #
        # In this example, a protected method named <tt>blogger</tt> will
        # now exist on the controller, and calling it will return the
        # XML-RPC client object for working with that remote service.
        #
        # +options+ is the set of protocol client specific options (see
        # a protocol client class for details).
        #
        # If your API definition does not exist on the load path with the
        # correct rules for it to be found using +name+, you can pass in
        # the API definition class via +options+, using a key of <tt>:api</tt>
        def web_client_api(name, protocol, endpoint_uri, options={})
          unless method_defined?(name)
            api_klass = options.delete(:api) || require_web_service_api(name)
            class_eval do
              define_method(name) do
                create_web_service_client(api_klass, protocol, endpoint_uri, options)
              end
              protected name
            end
          end
        end

        def web_service_api_with_require(definition=nil) # :nodoc:
          return web_service_api_without_require if definition.nil?
          case definition
          when String, Symbol
            klass = require_web_service_api(definition)
          else
            klass = definition
          end
          web_service_api_without_require(klass)
        end

        def require_web_service_api(name) # :nodoc:
          case name
          when String, Symbol
            file_name = name.to_s.underscore + "_api"
            class_name = file_name.camelize
            class_names = [class_name, class_name.sub(/Api$/, 'API')]
            begin
              require_dependency(file_name)
            rescue LoadError => load_error
              requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
              msg = requiree == file_name ? "Missing API definition file in apis/#{file_name}.rb" : "Can't load file: #{requiree}"
              raise LoadError.new(msg).copy_blame!(load_error)
            end
            klass = nil
            class_names.each do |name|
              klass = name.constantize rescue nil
              break unless klass.nil?
            end
            unless klass
              raise(NameError, "neither #{class_names[0]} or #{class_names[1]} found")
            end
            klass
          else
            raise(ArgumentError, "expected String or Symbol argument")
          end
        end

        private
          def inherited_with_api(child)
            inherited_without_api(child)
            begin child.web_service_api(child.controller_path)
            rescue MissingSourceFile => e
              raise unless e.is_missing?("apis/#{child.controller_path}_api")
            end
          end
      end
    end
  end
end
module ActionWebService # :nodoc:
  module Container # :nodoc:
    module Delegated # :nodoc:
      class ContainerError < ActionWebServiceError # :nodoc:
      end
  
      def self.append_features(base) # :nodoc:
        super
        base.extend(ClassMethods)
        base.send(:include, ActionWebService::Container::Delegated::InstanceMethods)
      end
  
      module ClassMethods
        # Declares a web service that will provide access to the API of the given
        # +object+. +object+ must be an ActionWebService::Base derivative.
        #
        # Web service object creation can either be _immediate_, where the object
        # instance is given at class definition time, or _deferred_, where
        # object instantiation is delayed until request time.
        #
        # ==== Immediate web service object example
        #
        #   class ApiController < ApplicationController
        #     web_service_dispatching_mode :delegated
        #
        #     web_service :person, PersonService.new
        #   end
        #
        # For deferred instantiation, a block should be given instead of an
        # object instance. This block will be executed in controller instance
        # context, so it can rely on controller instance variables being present.
        #
        # ==== Deferred web service object example
        #
        #   class ApiController < ApplicationController
        #     web_service_dispatching_mode :delegated
        #
        #     web_service(:person) { PersonService.new(request.env) }
        #   end
        def web_service(name, object=nil, &block)
          if (object && block_given?) || (object.nil? && block.nil?)
            raise(ContainerError, "either service, or a block must be given")
          end
          name = name.to_sym
          if block_given?
            info = { name => { :block => block } }
          else
            info = { name => { :object => object } }
          end
          write_inheritable_hash("web_services", info)
          call_web_service_definition_callbacks(self, name, info)
        end
  
        # Whether this service contains a service with the given +name+
        def has_web_service?(name)
          web_services.has_key?(name.to_sym)
        end
  
        def web_services # :nodoc:
          read_inheritable_attribute("web_services") || {}
        end
  
        def add_web_service_definition_callback(&block) # :nodoc:
          write_inheritable_array("web_service_definition_callbacks", [block])
        end
  
        private
          def call_web_service_definition_callbacks(container_class, web_service_name, service_info)
            (read_inheritable_attribute("web_service_definition_callbacks") || []).each do |block|
              block.call(container_class, web_service_name, service_info)
            end
          end
      end
  
      module InstanceMethods # :nodoc:
        def web_service_object(web_service_name)
          info = self.class.web_services[web_service_name.to_sym]
          unless info
            raise(ContainerError, "no such web service '#{web_service_name}'")
          end
          service = info[:block]
          service ? self.instance_eval(&service) : info[:object]
        end
      end
    end
  end
end
module ActionWebService # :nodoc:
  module Container # :nodoc:
    module Direct # :nodoc:
      class ContainerError < ActionWebServiceError # :nodoc:
      end

      def self.append_features(base) # :nodoc:
        super
        base.extend(ClassMethods)
      end
  
      module ClassMethods
        # Attaches ActionWebService API +definition+ to the calling class.
        #
        # Action Controllers can have a default associated API, removing the need
        # to call this method if you follow the Action Web Service naming conventions.
        #
        # A controller with a class name of GoogleSearchController will
        # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
        # API definition class to be named <tt>GoogleSearchAPI</tt> or
        # <tt>GoogleSearchApi</tt>.
        #
        # ==== Service class example
        #
        #   class MyService < ActionWebService::Base
        #     web_service_api MyAPI
        #   end
        #
        #   class MyAPI < ActionWebService::API::Base
        #     ...
        #   end
        #
        # ==== Controller class example
        #
        #   class MyController < ActionController::Base
        #     web_service_api MyAPI
        #   end
        #
        #   class MyAPI < ActionWebService::API::Base
        #     ...
        #   end
        def web_service_api(definition=nil)
          if definition.nil?
            read_inheritable_attribute("web_service_api")
          else
            if definition.is_a?(Symbol)
              raise(ContainerError, "symbols can only be used for #web_service_api inside of a controller")
            end
            unless definition.respond_to?(:ancestors) && definition.ancestors.include?(ActionWebService::API::Base)
              raise(ContainerError, "#{definition.to_s} is not a valid API definition")
            end
            write_inheritable_attribute("web_service_api", definition)
            call_web_service_api_callbacks(self, definition)
          end
        end
  
        def add_web_service_api_callback(&block) # :nodoc:
          write_inheritable_array("web_service_api_callbacks", [block])
        end
  
        private
          def call_web_service_api_callbacks(container_class, definition)
            (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
              block.call(container_class, definition)
            end
          end
      end
    end
  end
end
require 'action_web_service/container/direct_container'
require 'action_web_service/container/delegated_container'
require 'action_web_service/container/action_controller_container'
require 'benchmark'

module ActionWebService # :nodoc:
  module Dispatcher # :nodoc:
    class DispatcherError < ActionWebService::ActionWebServiceError # :nodoc:
    end

    def self.append_features(base) # :nodoc:
      super
      base.class_inheritable_option(:web_service_dispatching_mode, :direct)
      base.class_inheritable_option(:web_service_exception_reporting, true)
      base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
    end

    module InstanceMethods # :nodoc:
      private
        def invoke_web_service_request(protocol_request)
          invocation = web_service_invocation(protocol_request)
          if invocation.is_a?(Array) && protocol_request.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
            xmlrpc_multicall_invoke(invocation)
          else
            web_service_invoke(invocation)
          end
        end
      
        def web_service_direct_invoke(invocation)
          @method_params = invocation.method_ordered_params
          arity = method(invocation.api_method.name).arity rescue 0
          if arity < 0 || arity > 0
            params = @method_params
          else
            params = []
          end
          web_service_filtered_invoke(invocation, params)
        end

        def web_service_delegated_invoke(invocation)
          web_service_filtered_invoke(invocation, invocation.method_ordered_params)
        end

        def web_service_filtered_invoke(invocation, params)
          cancellation_reason = nil
          return_value = invocation.service.perform_invocation(invocation.api_method.name, params) do |x|
            cancellation_reason = x
          end
          if cancellation_reason
            raise(DispatcherError, "request canceled: #{cancellation_reason}")
          end
          return_value
        end
        
        def web_service_invoke(invocation)
          case web_service_dispatching_mode
          when :direct
            return_value = web_service_direct_invoke(invocation)
          when :delegated, :layered
            return_value = web_service_delegated_invoke(invocation)
          end
          web_service_create_response(invocation.protocol, invocation.protocol_options, invocation.api, invocation.api_method, return_value)
        end
        
        def xmlrpc_multicall_invoke(invocations)
          responses = []
          invocations.each do |invocation|
            if invocation.is_a?(Hash)
              responses << invocation
              next
            end
            begin
              case web_service_dispatching_mode
              when :direct
                return_value = web_service_direct_invoke(invocation)
              when :delegated, :layered
                return_value = web_service_delegated_invoke(invocation)
              end
              api_method = invocation.api_method
              if invocation.api.has_api_method?(api_method.name)
                return_value = api_method.cast_returns(return_value)
              end
              responses << [return_value]
            rescue Exception => e
              responses << { 'faultCode' => 3, 'faultString' => e.message }
            end
          end
          invocation = invocations[0]
          invocation.protocol.encode_response('system.multicall', responses, nil, invocation.protocol_options)
        end

        def web_service_invocation(request, level = 0)
          public_method_name = request.method_name
          invocation = Invocation.new
          invocation.protocol = request.protocol
          invocation.protocol_options = request.protocol_options
          invocation.service_name = request.service_name
          if web_service_dispatching_mode == :layered
            case invocation.protocol
            when Protocol::Soap::SoapProtocol
              soap_action = request.protocol_options[:soap_action]
              if soap_action && soap_action =~ /^\/\w+\/(\w+)\//
                invocation.service_name = $1
              end
            when Protocol::XmlRpc::XmlRpcProtocol
              if request.method_name =~ /^([^\.]+)\.(.*)$/
                public_method_name = $2
                invocation.service_name = $1
              end
            end
          end
          if invocation.protocol.is_a? Protocol::XmlRpc::XmlRpcProtocol
            if public_method_name == 'multicall' && invocation.service_name == 'system'
              if level > 0
                raise(DispatcherError, "Recursive system.multicall invocations not allowed")
              end
              multicall = request.method_params.dup
              unless multicall.is_a?(Array) && multicall[0].is_a?(Array)
                raise(DispatcherError, "Malformed multicall (expected array of Hash elements)")
              end
              multicall = multicall[0]
              return multicall.map do |item|
                raise(DispatcherError, "Multicall elements must be Hash") unless item.is_a?(Hash)
                raise(DispatcherError, "Multicall elements must contain a 'methodName' key") unless item.has_key?('methodName')
                method_name = item['methodName']
                params = item.has_key?('params') ? item['params'] : []
                multicall_request = request.dup
                multicall_request.method_name = method_name
                multicall_request.method_params = params
                begin
                  web_service_invocation(multicall_request, level + 1)
                rescue Exception => e
                  {'faultCode' => 4, 'faultMessage' => e.message}
                end
              end
            end
          end
          case web_service_dispatching_mode
          when :direct
            invocation.api = self.class.web_service_api
            invocation.service = self
          when :delegated, :layered
            invocation.service = web_service_object(invocation.service_name)
            invocation.api = invocation.service.class.web_service_api
          end
          if invocation.api.nil?
            raise(DispatcherError, "no API attached to #{invocation.service.class}")
          end
          invocation.protocol.register_api(invocation.api)
          request.api = invocation.api
          if invocation.api.has_public_api_method?(public_method_name)
            invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
          else
            if invocation.api.default_api_method.nil?
              raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
            else
              invocation.api_method = invocation.api.default_api_method_instance
            end
          end
          if invocation.service.nil?
            raise(DispatcherError, "no service available for service name #{invocation.service_name}")
          end
          unless invocation.service.respond_to?(invocation.api_method.name)
            raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
          end
          request.api_method = invocation.api_method
          begin
            invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
          rescue
            logger.warn "Casting of method parameters failed" unless logger.nil?
            invocation.method_ordered_params = request.method_params
          end
          request.method_params = invocation.method_ordered_params
          invocation.method_named_params = {}
          invocation.api_method.param_names.inject(0) do |m, n|
            invocation.method_named_params[n] = invocation.method_ordered_params[m]
            m + 1
          end
          invocation
        end

        def web_service_create_response(protocol, protocol_options, api, api_method, return_value)
          if api.has_api_method?(api_method.name)
            return_type = api_method.returns ? api_method.returns[0] : nil
            return_value = api_method.cast_returns(return_value)
          else
            return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
          end
          protocol.encode_response(api_method.public_name + 'Response', return_value, return_type, protocol_options)
        end

        class Invocation # :nodoc:
          attr_accessor :protocol
          attr_accessor :protocol_options
          attr_accessor :service_name
          attr_accessor :api
          attr_accessor :api_method
          attr_accessor :method_ordered_params
          attr_accessor :method_named_params
          attr_accessor :service
        end
    end
  end
end
require 'benchmark'
require 'builder/xmlmarkup'

module ActionWebService # :nodoc:
  module Dispatcher # :nodoc:
    module ActionController # :nodoc:
      def self.append_features(base) # :nodoc:
        super
        class << base
          include ClassMethods
          alias_method :inherited_without_action_controller, :inherited
          alias_method :inherited, :inherited_with_action_controller
        end
        base.class_eval do
          alias_method :web_service_direct_invoke_without_controller, :web_service_direct_invoke
        end
        base.add_web_service_api_callback do |klass, api|
          if klass.web_service_dispatching_mode == :direct
            klass.class_eval 'def api; dispatch_web_service_request; end'
          end
        end
        base.add_web_service_definition_callback do |klass, name, info|
          if klass.web_service_dispatching_mode == :delegated
            klass.class_eval "def #{name}; dispatch_web_service_request; end"
          elsif klass.web_service_dispatching_mode == :layered
            klass.class_eval 'def api; dispatch_web_service_request; end'
          end
        end
        base.send(:include, ActionWebService::Dispatcher::ActionController::InstanceMethods)
      end

      module ClassMethods # :nodoc:
        def inherited_with_action_controller(child)
          inherited_without_action_controller(child)
          child.send(:include, ActionWebService::Dispatcher::ActionController::WsdlAction)
        end
      end

      module InstanceMethods # :nodoc:
        private
          def dispatch_web_service_request
            exception = nil
            begin
              ws_request = discover_web_service_request(request)
            rescue Exception => e
              exception = e
            end
            if ws_request
              ws_response = nil
              exception = nil
              bm = Benchmark.measure do
                begin
                  ws_response = invoke_web_service_request(ws_request)
                rescue Exception => e
                  exception = e
                end
              end
              log_request(ws_request, request.raw_post)
              if exception
                log_error(exception) unless logger.nil?
                send_web_service_error_response(ws_request, exception)
              else
                send_web_service_response(ws_response, bm.real)
              end
            else
              exception ||= DispatcherError.new("Malformed SOAP or XML-RPC protocol message")
              log_error(exception) unless logger.nil?
              send_web_service_error_response(ws_request, exception)
            end
          rescue Exception => e
            log_error(e) unless logger.nil?
            send_web_service_error_response(ws_request, e)
          end

          def send_web_service_response(ws_response, elapsed=nil)
            log_response(ws_response, elapsed)
            options = { :type => ws_response.content_type, :disposition => 'inline' }
            send_data(ws_response.body, options)
          end

          def send_web_service_error_response(ws_request, exception)
            if ws_request
              unless self.class.web_service_exception_reporting
                exception = DispatcherError.new("Internal server error (exception raised)")
              end
              api_method = ws_request.api_method
              public_method_name = api_method ? api_method.public_name : ws_request.method_name
              return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
              ws_response = ws_request.protocol.encode_response(public_method_name + 'Response', exception, return_type, ws_request.protocol_options)
              send_web_service_response(ws_response)
            else
              if self.class.web_service_exception_reporting
                message = exception.message
                backtrace = "\nBacktrace:\n#{exception.backtrace.join("\n")}"
              else
                message = "Exception raised"
                backtrace = ""
              end
              render_text("Internal protocol error: #{message}#{backtrace}", "500 Internal Protocol Error")
            end
          end

          def web_service_direct_invoke(invocation)
            invocation.method_named_params.each do |name, value|
              params[name] = value
            end
            params['action'] = invocation.api_method.name.to_s
            if before_action == false
              raise(DispatcherError, "Method filtered")
            end
            return_value = web_service_direct_invoke_without_controller(invocation)
            after_action
            return_value
          end

          def log_request(ws_request, body)
            unless logger.nil?
              name = ws_request.method_name
              api_method = ws_request.api_method
              params = ws_request.method_params
              if api_method && api_method.expects
                params = api_method.expects.zip(params).map{ |type, param| "#{type.name}=>#{param.inspect}" }
              else
                params = params.map{ |param| param.inspect }
              end
              service = ws_request.service_name
              logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
              logger.debug(indent(body))
            end
          end

          def log_response(ws_response, elapsed=nil)
            unless logger.nil?
              elapsed = (elapsed ? " (%f):" % elapsed : ":")
              logger.debug("\nWeb Service Response" + elapsed + " => #{ws_response.return_value.inspect}")
              logger.debug(indent(ws_response.body))
            end
          end

          def indent(body)
            body.split(/\n/).map{|x| "  #{x}"}.join("\n")
          end
      end

      module WsdlAction # :nodoc:
        XsdNs             = 'http://www.w3.org/2001/XMLSchema'
        WsdlNs            = 'http://schemas.xmlsoap.org/wsdl/'
        SoapNs            = 'http://schemas.xmlsoap.org/wsdl/soap/'
        SoapEncodingNs    = 'http://schemas.xmlsoap.org/soap/encoding/'
        SoapHttpTransport = 'http://schemas.xmlsoap.org/soap/http'

        def wsdl
          case request.method
          when :get
            begin
              options = { :type => 'text/xml', :disposition => 'inline' }
              send_data(to_wsdl, options)
            rescue Exception => e
              log_error(e) unless logger.nil?
            end
          when :post
            render_text('POST not supported', '500 POST not supported')
          end
        end

        private
          def base_uri
            host = request.env['HTTP_HOST'] || request.env['SERVER_NAME'] || 'localhost'
            relative_url_root = request.relative_url_root
            scheme = request.ssl? ? 'https' : 'http'
            '%s://%s%s/%s/' % [scheme, host, relative_url_root, self.class.controller_path]
          end

          def to_wsdl
            xml = ''
            dispatching_mode = web_service_dispatching_mode
            global_service_name = wsdl_service_name
            namespace = wsdl_namespace || 'urn:ActionWebService'
            soap_action_base = "/#{controller_name}"

            marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
            apis = {}
            case dispatching_mode
            when :direct
              api = self.class.web_service_api
              web_service_name = controller_class_name.sub(/Controller$/, '').underscore
              apis[web_service_name] = [api, register_api(api, marshaler)]
            when :delegated, :layered
              self.class.web_services.each do |web_service_name, info|
                service = web_service_object(web_service_name)
                api = service.class.web_service_api
                apis[web_service_name] = [api, register_api(api, marshaler)]
              end
            end
            custom_types = []
            apis.values.each do |api, bindings|
              bindings.each do |b|
                custom_types << b unless custom_types.include?(b)
              end
            end

            xm = Builder::XmlMarkup.new(:target => xml, :indent => 2)
            xm.instruct!
            xm.definitions('name'            => wsdl_service_name,
                           'targetNamespace' => namespace,
                           'xmlns:typens'    => namespace,
                           'xmlns:xsd'       => XsdNs,
                           'xmlns:soap'      => SoapNs,
                           'xmlns:soapenc'   => SoapEncodingNs,
                           'xmlns:wsdl'      => WsdlNs,
                           'xmlns'           => WsdlNs) do
              # Generate XSD
              if custom_types.size > 0
                xm.types do
                  xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
                    custom_types.each do |binding|
                      case
                      when binding.type.array?
                        xm.xsd(:complexType, 'name' => binding.type_name) do
                          xm.xsd(:complexContent) do
                            xm.xsd(:restriction, 'base' => 'soapenc:Array') do
                              xm.xsd(:attribute, 'ref' => 'soapenc:arrayType',
                                                 'wsdl:arrayType' => binding.element_binding.qualified_type_name('typens') + '[]')
                            end
                          end
                        end
                      when binding.type.structured?
                        xm.xsd(:complexType, 'name' => binding.type_name) do
                          xm.xsd(:all) do
                            binding.type.each_member do |name, type|
                              b = marshaler.register_type(type)
                              xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
                            end
                          end
                        end
                      end
                    end
                  end
                end
              end

              # APIs
              apis.each do |api_name, values|
                api = values[0]
                api.api_methods.each do |name, method|
                  gen = lambda do |msg_name, direction|
                    xm.message('name' => message_name_for(api_name, msg_name)) do
                      sym = nil
                      if direction == :out
                        returns = method.returns
                        if returns
                          binding = marshaler.register_type(returns[0])
                          xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
                        end
                      else
                        expects = method.expects
                        expects.each do |type|
                          binding = marshaler.register_type(type)
                          xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
                        end if expects
                      end
                    end
                  end
                  public_name = method.public_name
                  gen.call(public_name, :in)
                  gen.call("#{public_name}Response", :out)
                end

                # Port
                port_name = port_name_for(global_service_name, api_name)
                xm.portType('name' => port_name) do
                  api.api_methods.each do |name, method|
                    xm.operation('name' => method.public_name) do
                      xm.input('message' => "typens:" + message_name_for(api_name, method.public_name))
                      xm.output('message' => "typens:" + message_name_for(api_name, "#{method.public_name}Response"))
                    end
                  end
                end

                # Bind it
                binding_name = binding_name_for(global_service_name, api_name)
                xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
                  xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
                  api.api_methods.each do |name, method|
                    xm.operation('name' => method.public_name) do
                      case web_service_dispatching_mode
                      when :direct
                        soap_action = soap_action_base + "/api/" + method.public_name
                      when :delegated, :layered
                        soap_action = soap_action_base \
                                    + "/" + api_name.to_s \
                                    + "/" + method.public_name
                      end
                      xm.soap(:operation, 'soapAction' => soap_action)
                      xm.input do
                        xm.soap(:body,
                                'use'           => 'encoded',
                                'namespace'     => namespace,
                                'encodingStyle' => SoapEncodingNs)
                      end
                      xm.output do
                        xm.soap(:body,
                                'use'           => 'encoded',
                                'namespace'     => namespace,
                                'encodingStyle' => SoapEncodingNs)
                      end
                    end
                  end
                end
              end

              # Define it
              xm.service('name' => "#{global_service_name}Service") do
                apis.each do |api_name, values|
                  port_name = port_name_for(global_service_name, api_name)
                  binding_name = binding_name_for(global_service_name,  api_name)
                  case web_service_dispatching_mode
                  when :direct, :layered
                    binding_target = 'api'
                  when :delegated
                    binding_target = api_name.to_s
                  end
                  xm.port('name' => port_name, 'binding' => "typens:#{binding_name}") do
                    xm.soap(:address, 'location' => "#{base_uri}#{binding_target}")
                  end
                end
              end
            end
          end

          def port_name_for(global_service, service)
            "#{global_service}#{service.to_s.camelize}Port"
          end

          def binding_name_for(global_service, service)
            "#{global_service}#{service.to_s.camelize}Binding"
          end

          def message_name_for(api_name, message_name)
            mode = web_service_dispatching_mode
            if mode == :layered || mode == :delegated
              api_name.to_s + '-' + message_name
            else
              message_name
            end
          end

          def register_api(api, marshaler)
            bindings = {}
            traverse_custom_types(api, marshaler, bindings) do |binding|
              bindings[binding] = nil unless bindings.has_key?(binding)
              element_binding = binding.element_binding
              bindings[element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
            end
            bindings.keys
          end

          def traverse_custom_types(api, marshaler, bindings, &block)
            api.api_methods.each do |name, method|
              expects, returns = method.expects, method.returns
              expects.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if expects
              returns.each{ |type| traverse_type(marshaler, type, bindings, &block) if type.custom? } if returns
            end
          end

          def traverse_type(marshaler, type, bindings, &block)
            binding = marshaler.register_type(type)
            return if bindings.has_key?(binding)
            bindings[binding] = nil
            yield binding
            if type.array?
              yield marshaler.register_type(type.element_type)
              type = type.element_type
            end
            type.each_member{ |name, type| traverse_type(marshaler, type, bindings, &block) } if type.structured?
          end
       end
    end
  end
end
require 'action_web_service/dispatcher/abstract'
require 'action_web_service/dispatcher/action_controller_dispatcher'
module ActionWebService # :nodoc:
  module Invocation # :nodoc:
    class InvocationError < ActionWebService::ActionWebServiceError # :nodoc:
    end

    def self.append_features(base) # :nodoc:
      super
      base.extend(ClassMethods)
      base.send(:include, ActionWebService::Invocation::InstanceMethods)
    end

    # Invocation interceptors provide a means to execute custom code before
    # and after method invocations on ActionWebService::Base objects.
    #
    # When running in _Direct_ dispatching mode, ActionController filters
    # should be used for this functionality instead.
    #
    # The semantics of invocation interceptors are the same as ActionController
    # filters, and accept the same parameters and options.
    #
    # A _before_ interceptor can also cancel execution by returning +false+,
    # or returning a <tt>[false, "cancel reason"]</tt> array if it wishes to supply
    # a reason for canceling the request.
    #
    # === Example
    #
    #   class CustomService < ActionWebService::Base
    #     before_invocation :intercept_add, :only => [:add]
    #
    #     def add(a, b)
    #       a + b
    #     end
    #
    #     private
    #       def intercept_add
    #         return [false, "permission denied"] # cancel it
    #       end
    #   end
    #
    # Options:
    # [<tt>:except</tt>]  A list of methods for which the interceptor will NOT be called
    # [<tt>:only</tt>]    A list of methods for which the interceptor WILL be called
    module ClassMethods
      # Appends the given +interceptors+ to be called
      # _before_ method invocation.
      def append_before_invocation(*interceptors, &block)
        conditions = extract_conditions!(interceptors)
        interceptors << block if block_given?
        add_interception_conditions(interceptors, conditions)
        append_interceptors_to_chain("before", interceptors)
      end

      # Prepends the given +interceptors+ to be called
      # _before_ method invocation.
      def prepend_before_invocation(*interceptors, &block)
        conditions = extract_conditions!(interceptors)
        interceptors << block if block_given?
        add_interception_conditions(interceptors, conditions)
        prepend_interceptors_to_chain("before", interceptors)
      end

      alias :before_invocation :append_before_invocation

      # Appends the given +interceptors+ to be called
      # _after_ method invocation.
      def append_after_invocation(*interceptors, &block)
        conditions = extract_conditions!(interceptors)
        interceptors << block if block_given?
        add_interception_conditions(interceptors, conditions)
        append_interceptors_to_chain("after", interceptors)
      end

      # Prepends the given +interceptors+ to be called
      # _after_ method invocation.
      def prepend_after_invocation(*interceptors, &block)
        conditions = extract_conditions!(interceptors)
        interceptors << block if block_given?
        add_interception_conditions(interceptors, conditions)
        prepend_interceptors_to_chain("after", interceptors)
      end

      alias :after_invocation :append_after_invocation

      def before_invocation_interceptors # :nodoc:
        read_inheritable_attribute("before_invocation_interceptors")
      end

      def after_invocation_interceptors # :nodoc:
        read_inheritable_attribute("after_invocation_interceptors")
      end

      def included_intercepted_methods # :nodoc:
        read_inheritable_attribute("included_intercepted_methods") || {}
      end
      
      def excluded_intercepted_methods # :nodoc:
        read_inheritable_attribute("excluded_intercepted_methods") || {}
      end

      private
        def append_interceptors_to_chain(condition, interceptors)
          write_inheritable_array("#{condition}_invocation_interceptors", interceptors)
        end

        def prepend_interceptors_to_chain(condition, interceptors)
          interceptors = interceptors + read_inheritable_attribute("#{condition}_invocation_interceptors")
          write_inheritable_attribute("#{condition}_invocation_interceptors", interceptors)
        end

        def extract_conditions!(interceptors)
          return nil unless interceptors.last.is_a? Hash
          interceptors.pop
        end

        def add_interception_conditions(interceptors, conditions)
          return unless conditions
          included, excluded = conditions[:only], conditions[:except]
          write_inheritable_hash("included_intercepted_methods", condition_hash(interceptors, included)) && return if included
          write_inheritable_hash("excluded_intercepted_methods", condition_hash(interceptors, excluded)) if excluded
        end

        def condition_hash(interceptors, *methods)
          interceptors.inject({}) {|hash, interceptor| hash.merge(interceptor => methods.flatten.map {|method| method.to_s})}
        end
    end

    module InstanceMethods # :nodoc:
      def self.append_features(base)
        super
        base.class_eval do
          alias_method :perform_invocation_without_interception, :perform_invocation
          alias_method :perform_invocation, :perform_invocation_with_interception
        end
      end

      def perform_invocation_with_interception(method_name, params, &block)
        return if before_invocation(method_name, params, &block) == false
        return_value = perform_invocation_without_interception(method_name, params)
        after_invocation(method_name, params, return_value)
        return_value
      end

      def perform_invocation(method_name, params)
        send(method_name, *params)
      end

      def before_invocation(name, args, &block)
        call_interceptors(self.class.before_invocation_interceptors, [name, args], &block)
      end

      def after_invocation(name, args, result)
        call_interceptors(self.class.after_invocation_interceptors, [name, args, result])
      end

      private

        def call_interceptors(interceptors, interceptor_args, &block)
          if interceptors and not interceptors.empty?
            interceptors.each do |interceptor|
              next if method_exempted?(interceptor, interceptor_args[0].to_s)
              result = case
                when interceptor.is_a?(Symbol)
                  self.send(interceptor, *interceptor_args)
                when interceptor_block?(interceptor)
                  interceptor.call(self, *interceptor_args)
                when interceptor_class?(interceptor)
                  interceptor.intercept(self, *interceptor_args)
                else
                  raise(
                    InvocationError,
                    "Interceptors need to be either a symbol, proc/method, or a class implementing a static intercept method"
                  )
              end
              reason = nil
              if result.is_a?(Array)
                reason = result[1] if result[1]
                result = result[0]
              end
              if result == false
                block.call(reason) if block && reason
                return false
              end
            end
          end
        end

        def interceptor_block?(interceptor)
          interceptor.respond_to?("call") && (interceptor.arity == 3 || interceptor.arity == -1)
        end
        
        def interceptor_class?(interceptor)
          interceptor.respond_to?("intercept")
        end

        def method_exempted?(interceptor, method_name)
          case
            when self.class.included_intercepted_methods[interceptor]
              !self.class.included_intercepted_methods[interceptor].include?(method_name)
            when self.class.excluded_intercepted_methods[interceptor] 
              self.class.excluded_intercepted_methods[interceptor].include?(method_name)
          end
        end
    end
  end
end
module ActionWebService # :nodoc:
  module Protocol # :nodoc:
    class ProtocolError < ActionWebServiceError # :nodoc:
    end

    class AbstractProtocol # :nodoc:
      def setup(controller)
      end

      def decode_action_pack_request(action_pack_request)
      end

      def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
        klass = options[:request_class] || SimpleActionPackRequest
        request = klass.new
        request.request_parameters['action'] = service_name.to_s
        request.env['RAW_POST_DATA'] = raw_body
        request.env['REQUEST_METHOD'] = 'POST'
        request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
        request
      end

      def decode_request(raw_request, service_name, protocol_options={})
      end

      def encode_request(method_name, params, param_types)
      end

      def decode_response(raw_response)
      end

      def encode_response(method_name, return_value, return_type, protocol_options={})
      end

      def protocol_client(api, protocol_name, endpoint_uri, options)
      end

      def register_api(api)
      end
    end

    class Request # :nodoc:
      attr :protocol
      attr_accessor :method_name
      attr_accessor :method_params
      attr :service_name
      attr_accessor :api
      attr_accessor :api_method
      attr :protocol_options

      def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil, protocol_options=nil)
        @protocol = protocol
        @method_name = method_name
        @method_params = method_params
        @service_name = service_name
        @api = api
        @api_method = api_method
        @protocol_options = protocol_options || {}
      end
    end

    class Response # :nodoc:
      attr :body
      attr :content_type
      attr :return_value

      def initialize(body, content_type, return_value)
        @body = body
        @content_type = content_type
        @return_value = return_value
      end
    end

    class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc:
      def initialize
        @env = {}
        @qparams = {}
        @rparams = {}
        @cookies = {}
        reset_session
      end

      def query_parameters
        @qparams
      end

      def request_parameters
        @rparams
      end

      def env
        @env
      end

      def host
        ''
      end

      def cookies
        @cookies
      end

      def session
        @session
      end

      def reset_session
        @session = {}
      end
    end
  end
end
module ActionWebService # :nodoc:
  module Protocol # :nodoc:
    module Discovery # :nodoc:
      def self.included(base)
        base.extend(ClassMethods)
        base.send(:include, ActionWebService::Protocol::Discovery::InstanceMethods)
      end

      module ClassMethods # :nodoc:
        def register_protocol(klass)
          write_inheritable_array("web_service_protocols", [klass])
        end
      end

      module InstanceMethods # :nodoc:
        private
          def discover_web_service_request(action_pack_request)
            (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
              protocol = protocol.create(self)
              request = protocol.decode_action_pack_request(action_pack_request)
              return request unless request.nil?
            end
            nil
          end

          def create_web_service_client(api, protocol_name, endpoint_uri, options)
            (self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
              protocol = protocol.create(self)
              client = protocol.protocol_client(api, protocol_name, endpoint_uri, options)
              return client unless client.nil?
            end
            nil
          end
      end
    end
  end
end
require 'soap/mapping'

module ActionWebService
  module Protocol
    module Soap
      # Workaround for SOAP4R return values changing
      class Registry < SOAP::Mapping::Registry
        if SOAP::Version >= "1.5.4"
          def find_mapped_soap_class(obj_class)
            return @map.instance_eval { @obj2soap[obj_class][0] }
          end

          def find_mapped_obj_class(soap_class)
            return @map.instance_eval { @soap2obj[soap_class][0] }
          end
        end
      end

      class SoapMarshaler
        attr :namespace
        attr :registry

        def initialize(namespace=nil)
          @namespace = namespace || 'urn:ActionWebService'
          @registry = Registry.new
          @type2binding = {}
          register_static_factories
        end

        def soap_to_ruby(obj)
          SOAP::Mapping.soap2obj(obj, @registry)
        end

        def ruby_to_soap(obj)
          soap = SOAP::Mapping.obj2soap(obj, @registry)
          soap.elename = XSD::QName.new if SOAP::Version >= "1.5.5" && soap.elename == XSD::QName::EMPTY
          soap
        end

        def register_type(type)
          return @type2binding[type] if @type2binding.has_key?(type)

          if type.array?
            array_mapping = @registry.find_mapped_soap_class(Array)
            qname = XSD::QName.new(@namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
            element_type_binding = register_type(type.element_type)
            @type2binding[type] = SoapBinding.new(self, qname, type, array_mapping, element_type_binding)
          elsif (mapping = @registry.find_mapped_soap_class(type.type_class) rescue nil)
            qname = mapping[2] ? mapping[2][:type] : nil
            qname ||= soap_base_type_name(mapping[0])
            @type2binding[type] = SoapBinding.new(self, qname, type, mapping)
          else
            qname = XSD::QName.new(@namespace, soap_type_name(type.type_class.name))
            @registry.add(type.type_class,
              SOAP::SOAPStruct,
              typed_struct_factory(type.type_class),
              { :type => qname })
            mapping = @registry.find_mapped_soap_class(type.type_class)
            @type2binding[type] = SoapBinding.new(self, qname, type, mapping)
          end

          if type.structured?
            type.each_member do |m_name, m_type|
              register_type(m_type)
            end
          end
          
          @type2binding[type]
        end
        alias :lookup_type :register_type

        def annotate_arrays(binding, value)
          if value.nil?
            return
          elsif binding.type.array?
            mark_typed_array(value, binding.element_binding.qname)
            if binding.element_binding.type.custom?
              value.each do |element|
                annotate_arrays(binding.element_binding, element)
              end
            end
          elsif binding.type.structured?
            binding.type.each_member do |name, type|
              member_binding = register_type(type)
              member_value = value.respond_to?('[]') ? value[name] : value.send(name)
              annotate_arrays(member_binding, member_value) if type.custom?
            end
          end
        end

        private
          def typed_struct_factory(type_class)
            if Object.const_defined?('ActiveRecord')
              if type_class.ancestors.include?(ActiveRecord::Base)
                qname =  XSD::QName.new(@namespace, soap_type_name(type_class.name))
                type_class.instance_variable_set('@qname', qname)
                return SoapActiveRecordStructFactory.new
              end
            end
            SOAP::Mapping::Registry::TypedStructFactory
          end

          def mark_typed_array(array, qname)
            (class << array; self; end).class_eval do 
              define_method(:arytype) do
                qname
              end
            end
          end

          def soap_base_type_name(type)
            xsd_type = type.ancestors.find{ |c| c.const_defined? 'Type' }
            xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
          end

          def soap_type_name(type_name)
            type_name.gsub(/::/, '..')
          end

          def register_static_factories
            @registry.add(ActionWebService::Base64,
                          SOAP::SOAPBase64,
                          SoapBase64Factory.new,
                          nil)
            mapping = @registry.find_mapped_soap_class(ActionWebService::Base64)
            @type2binding[ActionWebService::Base64] =
              SoapBinding.new(self, SOAP::SOAPBase64::Type,
                              ActionWebService::Base64, mapping)
            @registry.add(Array,
                          SOAP::SOAPArray,
                          SoapTypedArrayFactory.new,
                          nil)
          end
      end

      class SoapBinding
        attr :qname
        attr :type
        attr :mapping
        attr :element_binding

        def initialize(marshaler, qname, type, mapping, element_binding=nil)
          @marshaler = marshaler
          @qname = qname
          @type = type
          @mapping = mapping
          @element_binding = element_binding
        end

        def type_name
          @type.custom? ? @qname.name : nil
        end

        def qualified_type_name(ns=nil)
          if @type.custom?
            "#{ns ? ns : @qname.namespace}:#{@qname.name}"
          else
            ns = XSD::NS.new
            ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
            ns.assign(SOAP::EncodingNamespace, "soapenc")
            xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
            return ns.name(XSD::AnyTypeName) unless xsd_klass
            ns.name(xsd_klass.const_get('Type'))
          end
        end

        def eql?(other)
          @qname == other.qname
        end
        alias :== :eql?

        def hash
          @qname.hash
        end
      end

      class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
        def obj2soap(soap_class, obj, info, map)
          unless obj.is_a?(ActiveRecord::Base)
            return nil
          end
          soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
          obj.class.columns.each do |column|
            key = column.name.to_s
            value = obj.send(key)
            soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
          end
          soap_obj
        end

        def soap2obj(obj_class, node, info, map)
          unless node.type == obj_class.instance_variable_get('@qname')
            return false
          end
          obj = obj_class.new
          node.each do |key, value|
            obj[key] = value.data
          end
          obj.instance_variable_set('@new_record', false)
          return true, obj
        end
      end

      class SoapTypedArrayFactory < SOAP::Mapping::Factory
        def obj2soap(soap_class, obj, info, map)
          unless obj.respond_to?(:arytype)
            return nil
          end
          soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
          mark_marshalled_obj(obj, soap_obj)
          obj.each do |item|
            child = SOAP::Mapping._obj2soap(item, map)
            soap_obj.add(child)
          end
          soap_obj
        end
      
        def soap2obj(obj_class, node, info, map)
          return false
        end
      end

      class SoapBase64Factory < SOAP::Mapping::Factory
        def obj2soap(soap_class, obj, info, map)
          unless obj.is_a?(ActionWebService::Base64)
            return nil
          end
          return soap_class.new(obj)
        end

        def soap2obj(obj_class, node, info, map)
          unless node.type == SOAP::SOAPBase64::Type
            return false
          end
          return true, obj_class.new(node.string)
        end
      end

    end
  end
end
require 'action_web_service/protocol/soap_protocol/marshaler'
require 'soap/streamHandler'
require 'action_web_service/client/soap_client'

module ActionWebService # :nodoc:
  module API # :nodoc:
    class Base # :nodoc:
      def self.soap_client(endpoint_uri, options={})
        ActionWebService::Client::Soap.new self, endpoint_uri, options
      end
    end
  end

  module Protocol # :nodoc:
    module Soap # :nodoc:
      def self.included(base)
        base.register_protocol(SoapProtocol)
        base.class_inheritable_option(:wsdl_service_name)
        base.class_inheritable_option(:wsdl_namespace)
      end
      
      class SoapProtocol < AbstractProtocol # :nodoc:
        AWSEncoding = 'UTF-8'
        XSDEncoding = 'UTF8'

        attr :marshaler

        def initialize(namespace=nil)
          namespace ||= 'urn:ActionWebService'
          @marshaler = SoapMarshaler.new namespace
        end

        def self.create(controller)
          SoapProtocol.new(controller.wsdl_namespace)
        end

        def decode_action_pack_request(action_pack_request)
          return nil unless soap_action = has_valid_soap_action?(action_pack_request)
          service_name = action_pack_request.parameters['action']
          input_encoding = parse_charset(action_pack_request.env['HTTP_CONTENT_TYPE'])
          protocol_options = { 
            :soap_action => soap_action,
            :charset  => input_encoding
          }
          decode_request(action_pack_request.raw_post, service_name, protocol_options)
        end

        def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
          request = super
          request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
          request
        end

        def decode_request(raw_request, service_name, protocol_options={})
          envelope = SOAP::Processor.unmarshal(raw_request, :charset => protocol_options[:charset])
          unless envelope
            raise ProtocolError, "Failed to parse SOAP request message"
          end
          request = envelope.body.request
          method_name = request.elename.name
          params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) }
          Request.new(self, method_name, params, service_name, nil, nil, protocol_options)
        end

        def encode_request(method_name, params, param_types)
          param_types.each{ |type| marshaler.register_type(type) } if param_types
          qname = XSD::QName.new(marshaler.namespace, method_name)
          param_def = []
          if param_types
            params = param_types.zip(params).map do |type, param|
              param_def << ['in', type.name, marshaler.lookup_type(type).mapping]
              [type.name, marshaler.ruby_to_soap(param)]
            end
          else
            params = []
          end
          request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
          request.set_param(params)
          envelope = create_soap_envelope(request)
          SOAP::Processor.marshal(envelope)
        end

        def decode_response(raw_response)
          envelope = SOAP::Processor.unmarshal(raw_response)
          unless envelope
            raise ProtocolError, "Failed to parse SOAP request message"
          end
          method_name = envelope.body.request.elename.name
          return_value = envelope.body.response
          return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil?
          [method_name, return_value]
        end

        def encode_response(method_name, return_value, return_type, protocol_options={})
          if return_type
            return_binding = marshaler.register_type(return_type)
            marshaler.annotate_arrays(return_binding, return_value)
          end
          qname = XSD::QName.new(marshaler.namespace, method_name)
          if return_value.nil?
            response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
          else
            if return_value.is_a?(Exception)
              detail = SOAP::Mapping::SOAPException.new(return_value)
              response = SOAP::SOAPFault.new(
                SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']),
                SOAP::SOAPString.new(return_value.to_s),
                SOAP::SOAPString.new(self.class.name),
                marshaler.ruby_to_soap(detail))
            else
              if return_type
                param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]]
                response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
                response.retval = marshaler.ruby_to_soap(return_value)
              else
                response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
              end
            end
          end
          envelope = create_soap_envelope(response)

          # FIXME: This is not thread-safe, but StringFactory_ in SOAP4R only
          #        reads target encoding from the XSD::Charset.encoding variable.
          #        This is required to ensure $KCODE strings are converted
          #        correctly to UTF-8 for any values of $KCODE.
          previous_encoding = XSD::Charset.encoding
          XSD::Charset.encoding = XSDEncoding
          response_body = SOAP::Processor.marshal(envelope, :charset => AWSEncoding)
          XSD::Charset.encoding = previous_encoding

          Response.new(response_body, "text/xml; charset=#{AWSEncoding}", return_value)
        end

        def protocol_client(api, protocol_name, endpoint_uri, options={})
          return nil unless protocol_name == :soap
          ActionWebService::Client::Soap.new(api, endpoint_uri, options)
        end

        def register_api(api)
          api.api_methods.each do |name, method|
            method.expects.each{ |type| marshaler.register_type(type) } if method.expects
            method.returns.each{ |type| marshaler.register_type(type) } if method.returns
          end
        end

        private
          def has_valid_soap_action?(request)
            return nil unless request.method == :post
            soap_action = request.env['HTTP_SOAPACTION']
            return nil unless soap_action
            soap_action = soap_action.dup
            soap_action.gsub!(/^"/, '')
            soap_action.gsub!(/"$/, '')
            soap_action.strip!
            return nil if soap_action.empty?
            soap_action
          end

          def create_soap_envelope(body)
            header = SOAP::SOAPHeader.new
            body = SOAP::SOAPBody.new(body)
            SOAP::SOAPEnvelope.new(header, body)
          end

          def parse_charset(content_type)
            return AWSEncoding if content_type.nil?
            if /^text\/xml(?:\s*;\s*charset=([^"]+|"[^"]+"))$/i =~ content_type
              $1
            else
              AWSEncoding
            end
          end
      end
    end
  end
end
require 'xmlrpc/marshal'
require 'action_web_service/client/xmlrpc_client'

module XMLRPC # :nodoc:
  class FaultException # :nodoc:
    alias :message :faultString
  end
end

module ActionWebService # :nodoc:
  module API # :nodoc: 
    class Base # :nodoc:
      def self.xmlrpc_client(endpoint_uri, options={})
        ActionWebService::Client::XmlRpc.new self, endpoint_uri, options
      end
    end
  end

  module Protocol # :nodoc:
    module XmlRpc # :nodoc:
      def self.included(base)
        base.register_protocol(XmlRpcProtocol)
      end
      
      class XmlRpcProtocol < AbstractProtocol # :nodoc:
        def self.create(controller)
          XmlRpcProtocol.new
        end

        def decode_action_pack_request(action_pack_request)
          service_name = action_pack_request.parameters['action']
          decode_request(action_pack_request.raw_post, service_name)
        end

        def decode_request(raw_request, service_name)
          method_name, params = XMLRPC::Marshal.load_call(raw_request)
          Request.new(self, method_name, params, service_name)
        end

        def encode_request(method_name, params, param_types)
          if param_types
            params = params.dup
            param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) }
          end
          XMLRPC::Marshal.dump_call(method_name, *params)
        end

        def decode_response(raw_response)
          [nil, XMLRPC::Marshal.load_response(raw_response)]
        end

        def encode_response(method_name, return_value, return_type, protocol_options={})
          if return_value && return_type
            return_value = value_to_xmlrpc_wire_format(return_value, return_type)
          end
          return_value = false if return_value.nil?
          raw_response = XMLRPC::Marshal.dump_response(return_value)
          Response.new(raw_response, 'text/xml', return_value)
        end

        def protocol_client(api, protocol_name, endpoint_uri, options={})
          return nil unless protocol_name == :xmlrpc
          ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
        end

        def value_to_xmlrpc_wire_format(value, value_type)
          if value_type.array?
            value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) }
          else
            if value.is_a?(ActionWebService::Struct)
              struct = {}
              value.class.members.each do |name, type|
                member_value = value[name]
                next if member_value.nil?
                struct[name.to_s] = value_to_xmlrpc_wire_format(member_value, type)
              end
              struct
            elsif value.is_a?(ActiveRecord::Base)
              struct = {}
              value.attributes.each do |key, member_value|
                next if member_value.nil?
                struct[key.to_s] = member_value
              end
              struct
            elsif value.is_a?(ActionWebService::Base64)
              XMLRPC::Base64.new(value)
            elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
              XMLRPC::FaultException.new(2, value.message)
            else
              value
            end
          end
        end
      end
    end
  end
end
require 'action_web_service/protocol/abstract'
require 'action_web_service/protocol/discovery'
require 'action_web_service/protocol/soap_protocol'
require 'action_web_service/protocol/xmlrpc_protocol'
require 'benchmark'
require 'pathname'

module ActionWebService
  module Scaffolding # :nodoc:
    class ScaffoldingError < ActionWebServiceError # :nodoc:
    end

    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    # Web service invocation scaffolding provides a way to quickly invoke web service methods in a controller. The
    # generated scaffold actions have default views to let you enter the method parameters and view the
    # results.
    #
    # Example:
    #
    #   class ApiController < ActionController
    #     web_service_scaffold :invoke
    #   end
    #
    # This example generates an +invoke+ action in the +ApiController+ that you can navigate to from
    # your browser, select the API method, enter its parameters, and perform the invocation.
    #
    # If you want to customize the default views, create the following views in "app/views":
    #
    # * <tt>action_name/methods.rhtml</tt>
    # * <tt>action_name/parameters.rhtml</tt>
    # * <tt>action_name/result.rhtml</tt>
    # * <tt>action_name/layout.rhtml</tt>
    #
    # Where <tt>action_name</tt> is the name of the action you gave to ClassMethods#web_service_scaffold.
    #
    # You can use the default views in <tt>RAILS_DIR/lib/action_web_service/templates/scaffolds</tt> as
    # a guide.
    module ClassMethods
      # Generates web service invocation scaffolding for the current controller. The given action name
      # can then be used as the entry point for invoking API methods from a web browser.
      def web_service_scaffold(action_name)
        add_template_helper(Helpers)
        module_eval <<-"end_eval", __FILE__, __LINE__
          def #{action_name}
            if request.method == :get
              setup_invocation_assigns
              render_invocation_scaffold 'methods'
            end
          end

          def #{action_name}_method_params
            if request.method == :get
              setup_invocation_assigns
              render_invocation_scaffold 'parameters'
            end
          end

          def #{action_name}_submit
            if request.method == :post
              setup_invocation_assigns
              protocol_name = params['protocol'] ? params['protocol'].to_sym : :soap
              case protocol_name
              when :soap
                @protocol = Protocol::Soap::SoapProtocol.create(self)
              when :xmlrpc
                @protocol = Protocol::XmlRpc::XmlRpcProtocol.create(self)
              end
              bm = Benchmark.measure do
                @protocol.register_api(@scaffold_service.api)
                post_params = params['method_params'] ? params['method_params'].dup : nil
                params = []
                @scaffold_method.expects.each_with_index do |spec, i|
                  params << post_params[i.to_s]                                            
                end if @scaffold_method.expects
                params = @scaffold_method.cast_expects(params)
                method_name = public_method_name(@scaffold_service.name, @scaffold_method.public_name)
                @method_request_xml = @protocol.encode_request(method_name, params, @scaffold_method.expects)
                new_request = @protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
                prepare_request(new_request, @scaffold_service.name, @scaffold_method.public_name)
                @request = new_request
                if @scaffold_container.dispatching_mode != :direct
                  request.parameters['action'] = @scaffold_service.name
                end
                dispatch_web_service_request
                @method_response_xml = @response.body
                method_name, obj = @protocol.decode_response(@method_response_xml)
                return if handle_invocation_exception(obj)
                @method_return_value = @scaffold_method.cast_returns(obj)
              end
              @method_elapsed = bm.real
              add_instance_variables_to_assigns
              reset_invocation_response
              render_invocation_scaffold 'result'
            end
          end

          private
            def setup_invocation_assigns
              @scaffold_class = self.class
              @scaffold_action_name = "#{action_name}"
              @scaffold_container = WebServiceModel::Container.new(self)
              if params['service'] && params['method']
                @scaffold_service = @scaffold_container.services.find{ |x| x.name == params['service'] }
                @scaffold_method = @scaffold_service.api_methods[params['method']]
              end
              add_instance_variables_to_assigns
            end

            def render_invocation_scaffold(action)
              customized_template = "\#{self.class.controller_path}/#{action_name}/\#{action}"
              default_template = scaffold_path(action)
              if template_exists?(customized_template)
                content = @template.render_file(customized_template)
              else
                content = @template.render_file(default_template, false)
              end
              @template.instance_variable_set("@content_for_layout", content)
              if self.active_layout.nil?
                render_file(scaffold_path("layout"))
              else
                render_file(self.active_layout, "200 OK", true)
              end
            end

            def scaffold_path(template_name)
              File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
            end

            def reset_invocation_response
              erase_render_results
              @response.headers = ::ActionController::AbstractResponse::DEFAULT_HEADERS.merge("cookie" => [])
            end

            def public_method_name(service_name, method_name)
              if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol)
                service_name + '.' + method_name
              else
                method_name
              end
            end

            def prepare_request(new_request, service_name, method_name)
              new_request.parameters.update(request.parameters)
              request.env.each{ |k, v| new_request.env[k] = v unless new_request.env.has_key?(k) }
              if web_service_dispatching_mode == :layered && @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
                new_request.env['HTTP_SOAPACTION'] = "/\#{controller_name()}/\#{service_name}/\#{method_name}"
              end
            end

            def handle_invocation_exception(obj)
              exception = nil
              if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception)
                exception = obj.detail.cause
              elsif obj.is_a?(XMLRPC::FaultException)
                exception = obj
              end
              return unless exception
              reset_invocation_response
              rescue_action(exception)
              true
            end
        end_eval
      end
    end

    module Helpers # :nodoc:
      def method_parameter_input_fields(method, type, field_name_base, idx, was_structured=false)
        if type.array?
          return content_tag('em', "Typed array input fields not supported yet (#{type.name})")
        end
        if type.structured?
          return content_tag('em', "Nested structural types not supported yet (#{type.name})") if was_structured
          parameters = ""
          type.each_member do |member_name, member_type|
            label = method_parameter_label(member_name, member_type)
            nested_content = method_parameter_input_fields(
              method,
              member_type,
              "#{field_name_base}[#{idx}][#{member_name}]",
              idx,
              true)
            if member_type.custom?
              parameters << content_tag('li', label)
              parameters << content_tag('ul', nested_content)
            else
              parameters << content_tag('li', label + ' ' + nested_content)
            end
          end
          content_tag('ul', parameters)
        else
          # If the data source was structured previously we already have the index set          
          field_name_base = "#{field_name_base}[#{idx}]" unless was_structured
          
          case type.type
          when :int
            text_field_tag "#{field_name_base}"
          when :string
            text_field_tag "#{field_name_base}"
          when :base64
            text_area_tag "#{field_name_base}", nil, :size => "40x5"
          when :bool
            radio_button_tag("#{field_name_base}", "true") + " True" +
            radio_button_tag("#{field_name_base}", "false") + "False"
          when :float
            text_field_tag "#{field_name_base}"
          when :time, :datetime
            time = Time.now
            i = 0
            %w|year month day hour minute second|.map do |name|
              i += 1
              send("select_#{name}", time, :prefix => "#{field_name_base}[#{i}]", :discard_type => true)
            end.join
          when :date
            date = Date.today
            i = 0
            %w|year month day|.map do |name|
              i += 1
              send("select_#{name}", date, :prefix => "#{field_name_base}[#{i}]", :discard_type => true)
            end.join
          end
        end
      end

      def method_parameter_label(name, type)
        name.to_s.capitalize + ' (' + type.human_name(false) + ')'
      end

      def service_method_list(service)
        action = @scaffold_action_name + '_method_params'
        methods = service.api_methods_full.map do |desc, name|
          content_tag("li", link_to(desc, :action => action, :service => service.name, :method => name))
        end
        content_tag("ul", methods.join("\n"))
      end
    end

    module WebServiceModel # :nodoc:
      class Container # :nodoc:
        attr :services
        attr :dispatching_mode

        def initialize(real_container)
          @real_container = real_container
          @dispatching_mode = @real_container.class.web_service_dispatching_mode
          @services = []
          if @dispatching_mode == :direct
            @services << Service.new(@real_container.controller_name, @real_container)
          else
            @real_container.class.web_services.each do |name, obj|
              @services << Service.new(name, @real_container.instance_eval{ web_service_object(name) })
            end
          end
        end
      end

      class Service # :nodoc:
        attr :name
        attr :object
        attr :api
        attr :api_methods
        attr :api_methods_full

        def initialize(name, real_service)
          @name = name.to_s
          @object = real_service
          @api = @object.class.web_service_api
          if @api.nil?
            raise ScaffoldingError, "No web service API attached to #{object.class}"
          end
          @api_methods = {}
          @api_methods_full = []
          @api.api_methods.each do |name, method|
            @api_methods[method.public_name.to_s] = method
            @api_methods_full << [method.to_s, method.public_name.to_s]
          end
        end

        def to_s
          self.name.camelize
        end
      end
    end
  end
end
module ActionWebService
  # To send structured types across the wire, derive from ActionWebService::Struct,
  # and use +member+ to declare structure members.
  #
  # ActionWebService::Struct should be used in method signatures when you want to accept or return
  # structured types that have no Active Record model class representations, or you don't
  # want to expose your entire Active Record model to remote callers.
  #
  # === Example
  #
  #   class Person < ActionWebService::Struct
  #     member :id,         :int
  #     member :firstnames, [:string]
  #     member :lastname,   :string
  #     member :email,      :string
  #   end
  #   person = Person.new(:id => 5, :firstname => 'john', :lastname => 'doe')
  #
  # Active Record model classes are already implicitly supported in method
  # signatures.
  class Struct
    # Action WebService Struct subclasses should be reloaded by the dispatcher in Rails
    # when Dependencies.mechanism = :load.
    include Reloadable::Subclasses
    
    # If a Hash is given as argument to an ActionWebService::Struct constructor,
    # it can contain initial values for the structure member.
    def initialize(values={})
      if values.is_a?(Hash)
        values.map{|k,v| __send__('%s=' % k.to_s, v)}
      end
    end

    # The member with the given name
    def [](name)
      send(name.to_s)
    end

    # Iterates through each member
    def each_pair(&block)
      self.class.members.each do |name, type|
        yield name, self.__send__(name)
      end
    end

    class << self
      # Creates a structure member with the specified +name+ and +type+. Generates
      # accessor methods for reading and writing the member value.
      def member(name, type)
        name = name.to_sym
        type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0)
        write_inheritable_hash("struct_members", name => type)
        class_eval <<-END
          def #{name}; @#{name}; end
          def #{name}=(value); @#{name} = value; end
        END
      end
  
      def members # :nodoc:
        read_inheritable_attribute("struct_members") || {}
      end

      def member_type(name) # :nodoc:
        members[name.to_sym]
      end
    end
  end
end
class Class # :nodoc:
  def class_inheritable_option(sym, default_value=nil)
    write_inheritable_attribute sym, default_value
    class_eval <<-EOS
      def self.#{sym}(value=nil)
        if !value.nil?
          write_inheritable_attribute(:#{sym}, value)
        else
          read_inheritable_attribute(:#{sym})
        end
      end
      
      def self.#{sym}=(value)
        write_inheritable_attribute(:#{sym}, value)
      end

      def #{sym}
        self.class.#{sym}
      end

      def #{sym}=(value)
        self.class.#{sym} = value
      end
    EOS
  end
end
module ActionWebService # :nodoc:
  # Action Web Service supports the following base types in a signature:
  #
  # [<tt>:int</tt>]      Represents an integer value, will be cast to an integer using <tt>Integer(value)</tt>
  # [<tt>:string</tt>]   Represents a string value, will be cast to an string using the <tt>to_s</tt> method on an object
  # [<tt>:base64</tt>]   Represents a Base 64 value, will contain the binary bytes of a Base 64 value sent by the caller
  # [<tt>:bool</tt>]     Represents a boolean value, whatever is passed will be cast to boolean (<tt>true</tt>, '1', 'true', 'y', 'yes' are taken to represent true; <tt>false</tt>, '0', 'false', 'n', 'no' and <tt>nil</tt> represent false)
  # [<tt>:float</tt>]    Represents a floating point value, will be cast to a float using <tt>Float(value)</tt>
  # [<tt>:time</tt>]     Represents a timestamp, will be cast to a <tt>Time</tt> object
  # [<tt>:datetime</tt>] Represents a timestamp, will be cast to a <tt>DateTime</tt> object
  # [<tt>:date</tt>]     Represents a date, will be cast to a <tt>Date</tt> object
  #
  # For structured types, you'll need to pass in the Class objects of
  # ActionWebService::Struct and ActiveRecord::Base derivatives.
  module SignatureTypes
    def canonical_signature(signature) # :nodoc:
      return nil if signature.nil?
      unless signature.is_a?(Array)
        raise(ActionWebServiceError, "Expected signature to be an Array")
      end
      i = -1
      signature.map{ |spec| canonical_signature_entry(spec, i += 1) }
    end

    def canonical_signature_entry(spec, i) # :nodoc:
      orig_spec = spec
      name = "param#{i}"
      if spec.is_a?(Hash)
        name, spec = spec.keys.first, spec.values.first
      end
      type = spec
      if spec.is_a?(Array)
        ArrayType.new(orig_spec, canonical_signature_entry(spec[0], 0), name)
      else
        type = canonical_type(type)
        if type.is_a?(Symbol)
          BaseType.new(orig_spec, type, name)
        else
          StructuredType.new(orig_spec, type, name)
        end
      end
    end

    def canonical_type(type) # :nodoc:
      type_name = symbol_name(type) || class_to_type_name(type)
      type = type_name || type
      return canonical_type_name(type) if type.is_a?(Symbol)
      type
    end

    def canonical_type_name(name) # :nodoc:
      name = name.to_sym
      case name
        when :int, :integer, :fixnum, :bignum
          :int
        when :string, :text
          :string
        when :base64, :binary
          :base64
        when :bool, :boolean
          :bool
        when :float, :double
          :float
        when :time, :timestamp
          :time
        when :datetime
          :datetime
        when :date
          :date
        else
          raise(TypeError, "#{name} is not a valid base type")
      end
    end

    def canonical_type_class(type) # :nodoc:
      type = canonical_type(type)
      type.is_a?(Symbol) ? type_name_to_class(type) : type
    end

    def symbol_name(name) # :nodoc:
      return name.to_sym if name.is_a?(Symbol) || name.is_a?(String)
      nil
    end

    def class_to_type_name(klass) # :nodoc:
      klass = klass.class unless klass.is_a?(Class)
      if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass)
        :int
      elsif klass == String
        :string
      elsif klass == Base64
        :base64
      elsif klass == TrueClass || klass == FalseClass
        :bool
      elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass)
        :float
      elsif klass == Time
        :time
      elsif klass == DateTime
        :datetime
      elsif klass == Date
        :date
      else
        nil
      end
    end

    def type_name_to_class(name) # :nodoc:
      case canonical_type_name(name)
      when :int
        Integer
      when :string
        String
      when :base64
        Base64
      when :bool
        TrueClass
      when :float
        Float
      when :time
        Time
      when :date
        Date
      when :datetime
        DateTime
      else
        nil
      end
    end

    def derived_from?(ancestor, child) # :nodoc:
      child.ancestors.include?(ancestor)
    end

    module_function :type_name_to_class
    module_function :class_to_type_name
    module_function :symbol_name
    module_function :canonical_type_class
    module_function :canonical_type_name
    module_function :canonical_type
    module_function :canonical_signature_entry
    module_function :canonical_signature
    module_function :derived_from?
  end

  class BaseType # :nodoc:
    include SignatureTypes

    attr :spec
    attr :type
    attr :type_class
    attr :name

    def initialize(spec, type, name)
      @spec = spec
      @type = canonical_type(type)
      @type_class = canonical_type_class(@type)
      @name = name
    end

    def custom?
      false
    end

    def array?
      false
    end

    def structured?
      false
    end

    def human_name(show_name=true)
      type_type = array? ? element_type.type.to_s : self.type.to_s
      str = array? ? (type_type + '[]') : type_type
      show_name ? (str + " " + name.to_s) : str
    end
  end

  class ArrayType < BaseType # :nodoc:
    attr :element_type

    def initialize(spec, element_type, name)
      super(spec, Array, name)
      @element_type = element_type
    end

    def custom?
      true
    end

    def array?
      true
    end
  end

  class StructuredType < BaseType # :nodoc:
    def each_member
      if @type_class.respond_to?(:members)
        @type_class.members.each do |name, type|
          yield name, type
        end
      elsif @type_class.respond_to?(:columns)
        i = -1
        @type_class.columns.each do |column|
          yield column.name, canonical_signature_entry(column.type, i += 1)
        end
      end
    end

    def custom?
      true
    end

    def structured?
      true
    end
  end

  class Base64 < String # :nodoc:
  end
end
require 'test/unit'

module Test # :nodoc:
  module Unit # :nodoc:
    class TestCase # :nodoc:
      private
        # invoke the specified API method
        def invoke_direct(method_name, *args)
          prepare_request('api', 'api', method_name, *args)
          @controller.process(@request, @response)
          decode_rpc_response
        end
        alias_method :invoke, :invoke_direct

        # invoke the specified API method on the specified service
        def invoke_delegated(service_name, method_name, *args)
          prepare_request(service_name.to_s, service_name, method_name, *args)
          @controller.process(@request, @response)
          decode_rpc_response
        end

        # invoke the specified layered API method on the correct service
        def invoke_layered(service_name, method_name, *args)
          prepare_request('api', service_name, method_name, *args)
          @controller.process(@request, @response)
          decode_rpc_response
        end

        # ---------------------- internal ---------------------------

        def prepare_request(action, service_name, api_method_name, *args)
          @request.recycle!
          @request.request_parameters['action'] = action
          @request.env['REQUEST_METHOD'] = 'POST'
          @request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
          @request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args)
          case protocol
          when ActionWebService::Protocol::Soap::SoapProtocol
            soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}" 
            @request.env['HTTP_SOAPACTION'] = soap_action
          when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
            @request.env.delete('HTTP_SOAPACTION')
          end
        end

        def encode_rpc_call(service_name, api_method_name, *args)
          case @controller.web_service_dispatching_mode
          when :direct
            api = @controller.class.web_service_api
          when :delegated, :layered
            api = @controller.web_service_object(service_name.to_sym).class.web_service_api
          end
          protocol.register_api(api)
          method = api.api_methods[api_method_name.to_sym]
          raise ArgumentError, "wrong number of arguments for rpc call (#{args.length} for #{method.expects.length})" unless args.length == method.expects.length
          protocol.encode_request(public_method_name(service_name, api_method_name), args.dup, method.expects)
        end

        def decode_rpc_response
          public_method_name, return_value = protocol.decode_response(@response.body)
          exception = is_exception?(return_value)
          raise exception if exception
          return_value
        end

        def public_method_name(service_name, api_method_name)
          public_name = service_api(service_name).public_api_method_name(api_method_name)
          if @controller.web_service_dispatching_mode == :layered && protocol.is_a?(ActionWebService::Protocol::XmlRpc::XmlRpcProtocol)
            '%s.%s' % [service_name.to_s, public_name]
          else
            public_name
          end
        end

        def service_api(service_name)
          case @controller.web_service_dispatching_mode
          when :direct
            @controller.class.web_service_api
          when :delegated, :layered
            @controller.web_service_object(service_name.to_sym).class.web_service_api
          end
        end

        def protocol
          if @protocol.nil?
            @protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.create(@controller)
          else
            case @protocol
            when :xmlrpc
              @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@controller)
            when :soap
              @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@controller)
            else
              @protocol
            end
          end
        end

        def is_exception?(obj)
          case protocol
          when :soap, ActionWebService::Protocol::Soap::SoapProtocol
            (obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \
            obj.detail.cause.is_a?(Exception)) ? obj.detail.cause : nil
          when :xmlrpc, ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
            obj.is_a?(XMLRPC::FaultException) ? obj : nil
          end
        end
    end
  end
end
module ActionWebService
  module VERSION #:nodoc:
    MAJOR = 1
    MINOR = 1
    TINY  = 6

    STRING = [MAJOR, MINOR, TINY].join('.')
  end
end
#--
# Copyright (C) 2005 Leon Breedt
#
# 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.
#++

begin
  require 'active_support'
  require 'action_controller'
  require 'active_record'
rescue LoadError
  require 'rubygems'
  require_gem 'activesupport', '>= 1.0.2'
  require_gem 'actionpack', '>= 1.6.0'
  require_gem 'activerecord', '>= 1.9.0'
end

$:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/")

require 'action_web_service/support/class_inheritable_options'
require 'action_web_service/support/signature_types'
require 'action_web_service/base'
require 'action_web_service/client'
require 'action_web_service/invocation'
require 'action_web_service/api'
require 'action_web_service/casting'
require 'action_web_service/struct'
require 'action_web_service/container'
require 'action_web_service/protocol'
require 'action_web_service/dispatcher'
require 'action_web_service/scaffolding'

ActionWebService::Base.class_eval do
  include ActionWebService::Container::Direct
  include ActionWebService::Invocation
end

ActionController::Base.class_eval do
  include ActionWebService::Protocol::Discovery
  include ActionWebService::Protocol::Soap
  include ActionWebService::Protocol::XmlRpc
  include ActionWebService::Container::Direct
  include ActionWebService::Container::Delegated
  include ActionWebService::Container::ActionController
  include ActionWebService::Invocation
  include ActionWebService::Dispatcher
  include ActionWebService::Dispatcher::ActionController
  include ActionWebService::Scaffolding
end
#
# setup.rb
#
# Copyright (c) 2000-2004 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.

#

unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
  module Enumerable
    alias map collect
  end
end

unless File.respond_to?(:read)   # Ruby 1.6
  def File.read(fname)
    open(fname) {|f|
      return f.read
    }
  end
end

def File.binread(fname)
  open(fname, 'rb') {|f|
    return f.read
  }
end

# for corrupted windows stat(2)
def File.dir?(path)
  File.directory?((path[-1,1] == '/') ? path : path + '/')
end


class SetupError < StandardError; end

def setup_rb_error(msg)
  raise SetupError, msg
end

#
# Config
#

if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
  ARGV.delete(arg)
  require arg.split(/=/, 2)[1]
  $".push 'rbconfig.rb'
else
  require 'rbconfig'
end

def multipackage_install?
  FileTest.directory?(File.dirname($0) + '/packages')
end


class ConfigItem
  def initialize(name, template, default, desc)
    @name = name.freeze
    @template = template
    @value = default
    @default = default.dup.freeze
    @description = desc
  end

  attr_reader :name
  attr_reader :description

  attr_accessor :default
  alias help_default default

  def help_opt
    "--#{@name}=#{@template}"
  end

  def value
    @value
  end

  def eval(table)
    @value.gsub(%r<\$([^/]+)>) { table[$1] }
  end

  def set(val)
    @value = check(val)
  end

  private

  def check(val)
    setup_rb_error "config: --#{name} requires argument" unless val
    val
  end
end

class BoolItem < ConfigItem
  def config_type
    'bool'
  end

  def help_opt
    "--#{@name}"
  end

  private

  def check(val)
    return 'yes' unless val
    unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
      setup_rb_error "config: --#{@name} accepts only yes/no for argument"
    end
    (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
  end
end

class PathItem < ConfigItem
  def config_type
    'path'
  end

  private

  def check(path)
    setup_rb_error "config: --#{@name} requires argument"  unless path
    path[0,1] == '$' ? path : File.expand_path(path)
  end
end

class ProgramItem < ConfigItem
  def config_type
    'program'
  end
end

class SelectItem < ConfigItem
  def initialize(name, template, default, desc)
    super
    @ok = template.split('/')
  end

  def config_type
    'select'
  end

  private

  def check(val)
    unless @ok.include?(val.strip)
      setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
    end
    val.strip
  end
end

class PackageSelectionItem < ConfigItem
  def initialize(name, template, default, help_default, desc)
    super name, template, default, desc
    @help_default = help_default
  end

  attr_reader :help_default

  def config_type
    'package'
  end

  private

  def check(val)
    unless File.dir?("packages/#{val}")
      setup_rb_error "config: no such package: #{val}"
    end
    val
  end
end

class ConfigTable_class

  def initialize(items)
    @items = items
    @table = {}
    items.each do |i|
      @table[i.name] = i
    end
    ALIASES.each do |ali, name|
      @table[ali] = @table[name]
    end
  end

  include Enumerable

  def each(&block)
    @items.each(&block)
  end

  def key?(name)
    @table.key?(name)
  end

  def lookup(name)
    @table[name] or raise ArgumentError, "no such config item: #{name}"
  end

  def add(item)
    @items.push item
    @table[item.name] = item
  end

  def remove(name)
    item = lookup(name)
    @items.delete_if {|i| i.name == name }
    @table.delete_if {|name, i| i.name == name }
    item
  end

  def new
    dup()
  end

  def savefile
    '.config'
  end

  def load
    begin
      t = dup()
      File.foreach(savefile()) do |line|
        k, v = *line.split(/=/, 2)
        t[k] = v.strip
      end
      t
    rescue Errno::ENOENT
      setup_rb_error $!.message + "#{File.basename($0)} config first"
    end
  end

  def save
    @items.each {|i| i.value }
    File.open(savefile(), 'w') {|f|
      @items.each do |i|
        f.printf "%s=%s\n", i.name, i.value if i.value
      end
    }
  end

  def [](key)
    lookup(key).eval(self)
  end

  def []=(key, val)
    lookup(key).set val
  end

end

c = ::Config::CONFIG

rubypath = c['bindir'] + '/' + c['ruby_install_name']

major = c['MAJOR'].to_i
minor = c['MINOR'].to_i
teeny = c['TEENY'].to_i
version = "#{major}.#{minor}"

# ruby ver. >= 1.4.4?
newpath_p = ((major >= 2) or
             ((major == 1) and
              ((minor >= 5) or
               ((minor == 4) and (teeny >= 4)))))

if c['rubylibdir']
  # V < 1.6.3
  _stdruby         = c['rubylibdir']
  _siteruby        = c['sitedir']
  _siterubyver     = c['sitelibdir']
  _siterubyverarch = c['sitearchdir']
elsif newpath_p
  # 1.4.4 <= V <= 1.6.3
  _stdruby         = "$prefix/lib/ruby/#{version}"
  _siteruby        = c['sitedir']
  _siterubyver     = "$siteruby/#{version}"
  _siterubyverarch = "$siterubyver/#{c['arch']}"
else
  # V < 1.4.4
  _stdruby         = "$prefix/lib/ruby/#{version}"
  _siteruby        = "$prefix/lib/ruby/#{version}/site_ruby"
  _siterubyver     = _siteruby
  _siterubyverarch = "$siterubyver/#{c['arch']}"
end
libdir = '-* dummy libdir *-'
stdruby = '-* dummy rubylibdir *-'
siteruby = '-* dummy site_ruby *-'
siterubyver = '-* dummy site_ruby version *-'
parameterize = lambda {|path|
  path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\
      .sub(/\A#{Regexp.quote(libdir)}/,      '$libdir')\
      .sub(/\A#{Regexp.quote(stdruby)}/,     '$stdruby')\
      .sub(/\A#{Regexp.quote(siteruby)}/,    '$siteruby')\
      .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver')
}
libdir          = parameterize.call(c['libdir'])
stdruby         = parameterize.call(_stdruby)
siteruby        = parameterize.call(_siteruby)
siterubyver     = parameterize.call(_siterubyver)
siterubyverarch = parameterize.call(_siterubyverarch)

if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
  makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
else
  makeprog = 'make'
end

common_conf = [
  PathItem.new('prefix', 'path', c['prefix'],
               'path prefix of target environment'),
  PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
               'the directory for commands'),
  PathItem.new('libdir', 'path', libdir,
               'the directory for libraries'),
  PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
               'the directory for shared data'),
  PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
               'the directory for man pages'),
  PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
               'the directory for man pages'),
  PathItem.new('stdruby', 'path', stdruby,
               'the directory for standard ruby libraries'),
  PathItem.new('siteruby', 'path', siteruby,
      'the directory for version-independent aux ruby libraries'),
  PathItem.new('siterubyver', 'path', siterubyver,
               'the directory for aux ruby libraries'),
  PathItem.new('siterubyverarch', 'path', siterubyverarch,
               'the directory for aux ruby binaries'),
  PathItem.new('rbdir', 'path', '$siterubyver',
               'the directory for ruby scripts'),
  PathItem.new('sodir', 'path', '$siterubyverarch',
               'the directory for ruby extentions'),
  PathItem.new('rubypath', 'path', rubypath,
               'the path to set to #! line'),
  ProgramItem.new('rubyprog', 'name', rubypath,
                  'the ruby program using for installation'),
  ProgramItem.new('makeprog', 'name', makeprog,
                  'the make program to compile ruby extentions'),
  SelectItem.new('shebang', 'all/ruby/never', 'ruby',
                 'shebang line (#!) editing mode'),
  BoolItem.new('without-ext', 'yes/no', 'no',
               'does not compile/install ruby extentions')
]
class ConfigTable_class   # open again
  ALIASES = {
    'std-ruby'         => 'stdruby',
    'site-ruby-common' => 'siteruby',     # For backward compatibility
    'site-ruby'        => 'siterubyver',  # For backward compatibility
    'bin-dir'          => 'bindir',
    'bin-dir'          => 'bindir',
    'rb-dir'           => 'rbdir',
    'so-dir'           => 'sodir',
    'data-dir'         => 'datadir',
    'ruby-path'        => 'rubypath',
    'ruby-prog'        => 'rubyprog',
    'ruby'             => 'rubyprog',
    'make-prog'        => 'makeprog',
    'make'             => 'makeprog'
  }
end
multipackage_conf = [
  PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
                           'package names that you want to install'),
  PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
                           'package names that you do not want to install')
]
if multipackage_install?
  ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf)
else
  ConfigTable = ConfigTable_class.new(common_conf)
end


module MetaConfigAPI

  def eval_file_ifexist(fname)
    instance_eval File.read(fname), fname, 1 if File.file?(fname)
  end

  def config_names
    ConfigTable.map {|i| i.name }
  end

  def config?(name)
    ConfigTable.key?(name)
  end

  def bool_config?(name)
    ConfigTable.lookup(name).config_type == 'bool'
  end

  def path_config?(name)
    ConfigTable.lookup(name).config_type == 'path'
  end

  def value_config?(name)
    case ConfigTable.lookup(name).config_type
    when 'bool', 'path'
      true
    else
      false
    end
  end

  def add_config(item)
    ConfigTable.add item
  end

  def add_bool_config(name, default, desc)
    ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
  end

  def add_path_config(name, default, desc)
    ConfigTable.add PathItem.new(name, 'path', default, desc)
  end

  def set_config_default(name, default)
    ConfigTable.lookup(name).default = default
  end

  def remove_config(name)
    ConfigTable.remove(name)
  end

end


#
# File Operations
#

module FileOperations

  def mkdir_p(dirname, prefix = nil)
    dirname = prefix + File.expand_path(dirname) if prefix
    $stderr.puts "mkdir -p #{dirname}" if verbose?
    return if no_harm?

    # does not check '/'... it's too abnormal case
    dirs = File.expand_path(dirname).split(%r<(?=/)>)
    if /\A[a-z]:\z/i =~ dirs[0]
      disk = dirs.shift
      dirs[0] = disk + dirs[0]
    end
    dirs.each_index do |idx|
      path = dirs[0..idx].join('')
      Dir.mkdir path unless File.dir?(path)
    end
  end

  def rm_f(fname)
    $stderr.puts "rm -f #{fname}" if verbose?
    return if no_harm?

    if File.exist?(fname) or File.symlink?(fname)
      File.chmod 0777, fname
      File.unlink fname
    end
  end

  def rm_rf(dn)
    $stderr.puts "rm -rf #{dn}" if verbose?
    return if no_harm?

    Dir.chdir dn
    Dir.foreach('.') do |fn|
      next if fn == '.'
      next if fn == '..'
      if File.dir?(fn)
        verbose_off {
          rm_rf fn
        }
      else
        verbose_off {
          rm_f fn
        }
      end
    end
    Dir.chdir '..'
    Dir.rmdir dn
  end

  def move_file(src, dest)
    File.unlink dest if File.exist?(dest)
    begin
      File.rename src, dest
    rescue
      File.open(dest, 'wb') {|f| f.write File.binread(src) }
      File.chmod File.stat(src).mode, dest
      File.unlink src
    end
  end

  def install(from, dest, mode, prefix = nil)
    $stderr.puts "install #{from} #{dest}" if verbose?
    return if no_harm?

    realdest = prefix ? prefix + File.expand_path(dest) : dest
    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
    str = File.binread(from)
    if diff?(str, realdest)
      verbose_off {
        rm_f realdest if File.exist?(realdest)
      }
      File.open(realdest, 'wb') {|f|
        f.write str
      }
      File.chmod mode, realdest

      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
        if prefix
          f.puts realdest.sub(prefix, '')
        else
          f.puts realdest
        end
      }
    end
  end

  def diff?(new_content, path)
    return true unless File.exist?(path)
    new_content != File.binread(path)
  end

  def command(str)
    $stderr.puts str if verbose?
    system str or raise RuntimeError, "'system #{str}' failed"
  end

  def ruby(str)
    command config('rubyprog') + ' ' + str
  end
  
  def make(task = '')
    command config('makeprog') + ' ' + task
  end

  def extdir?(dir)
    File.exist?(dir + '/MANIFEST')
  end

  def all_files_in(dirname)
    Dir.open(dirname) {|d|
      return d.select {|ent| File.file?("#{dirname}/#{ent}") }
    }
  end

  REJECT_DIRS = %w(
    CVS SCCS RCS CVS.adm .svn
  )

  def all_dirs_in(dirname)
    Dir.open(dirname) {|d|
      return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
    }
  end

end


#
# Main Installer
#

module HookUtils

  def run_hook(name)
    try_run_hook "#{curr_srcdir()}/#{name}" or
    try_run_hook "#{curr_srcdir()}/#{name}.rb"
  end

  def try_run_hook(fname)
    return false unless File.file?(fname)
    begin
      instance_eval File.read(fname), fname, 1
    rescue
      setup_rb_error "hook #{fname} failed:\n" + $!.message
    end
    true
  end

end


module HookScriptAPI

  def get_config(key)
    @config[key]
  end

  alias config get_config

  def set_config(key, val)
    @config[key] = val
  end

  #
  # srcdir/objdir (works only in the package directory)
  #

  #abstract srcdir_root
  #abstract objdir_root
  #abstract relpath

  def curr_srcdir
    "#{srcdir_root()}/#{relpath()}"
  end

  def curr_objdir
    "#{objdir_root()}/#{relpath()}"
  end

  def srcfile(path)
    "#{curr_srcdir()}/#{path}"
  end

  def srcexist?(path)
    File.exist?(srcfile(path))
  end

  def srcdirectory?(path)
    File.dir?(srcfile(path))
  end
  
  def srcfile?(path)
    File.file? srcfile(path)
  end

  def srcentries(path = '.')
    Dir.open("#{curr_srcdir()}/#{path}") {|d|
      return d.to_a - %w(. ..)
    }
  end

  def srcfiles(path = '.')
    srcentries(path).select {|fname|
      File.file?(File.join(curr_srcdir(), path, fname))
    }
  end

  def srcdirectories(path = '.')
    srcentries(path).select {|fname|
      File.dir?(File.join(curr_srcdir(), path, fname))
    }
  end

end


class ToplevelInstaller

  Version   = '3.3.1'
  Copyright = 'Copyright (c) 2000-2004 Minero Aoki'

  TASKS = [
    [ 'all',      'do config, setup, then install' ],
    [ 'config',   'saves your configurations' ],
    [ 'show',     'shows current configuration' ],
    [ 'setup',    'compiles ruby extentions and others' ],
    [ 'install',  'installs files' ],
    [ 'clean',    "does `make clean' for each extention" ],
    [ 'distclean',"does `make distclean' for each extention" ]
  ]

  def ToplevelInstaller.invoke
    instance().invoke
  end

  @singleton = nil

  def ToplevelInstaller.instance
    @singleton ||= new(File.dirname($0))
    @singleton
  end

  include MetaConfigAPI

  def initialize(ardir_root)
    @config = nil
    @options = { 'verbose' => true }
    @ardir = File.expand_path(ardir_root)
  end

  def inspect
    "#<#{self.class} #{__id__()}>"
  end

  def invoke
    run_metaconfigs
    case task = parsearg_global()
    when nil, 'all'
      @config = load_config('config')
      parsearg_config
      init_installers
      exec_config
      exec_setup
      exec_install
    else
      @config = load_config(task)
      __send__ "parsearg_#{task}"
      init_installers
      __send__ "exec_#{task}"
    end
  end
  
  def run_metaconfigs
    eval_file_ifexist "#{@ardir}/metaconfig"
  end

  def load_config(task)
    case task
    when 'config'
      ConfigTable.new
    when 'clean', 'distclean'
      if File.exist?(ConfigTable.savefile)
      then ConfigTable.load
      else ConfigTable.new
      end
    else
      ConfigTable.load
    end
  end

  def init_installers
    @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
  end

  #
  # Hook Script API bases
  #

  def srcdir_root
    @ardir
  end

  def objdir_root
    '.'
  end

  def relpath
    '.'
  end

  #
  # Option Parsing
  #

  def parsearg_global
    valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/

    while arg = ARGV.shift
      case arg
      when /\A\w+\z/
        setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg
        return arg

      when '-q', '--quiet'
        @options['verbose'] = false

      when       '--verbose'
        @options['verbose'] = true

      when '-h', '--help'
        print_usage $stdout
        exit 0

      when '-v', '--version'
        puts "#{File.basename($0)} version #{Version}"
        exit 0
      
      when '--copyright'
        puts Copyright
        exit 0

      else
        setup_rb_error "unknown global option '#{arg}'"
      end
    end

    nil
  end


  def parsearg_no_options
    unless ARGV.empty?
      setup_rb_error "#{task}:  unknown options: #{ARGV.join ' '}"
    end
  end

  alias parsearg_show       parsearg_no_options
  alias parsearg_setup      parsearg_no_options
  alias parsearg_clean      parsearg_no_options
  alias parsearg_distclean  parsearg_no_options

  def parsearg_config
    re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/
    @options['config-opt'] = []

    while i = ARGV.shift
      if /\A--?\z/ =~ i
        @options['config-opt'] = ARGV.dup
        break
      end
      m = re.match(i)  or setup_rb_error "config: unknown option #{i}"
      name, value = *m.to_a[1,2]
      @config[name] = value
    end
  end

  def parsearg_install
    @options['no-harm'] = false
    @options['install-prefix'] = ''
    while a = ARGV.shift
      case a
      when /\A--no-harm\z/
        @options['no-harm'] = true
      when /\A--prefix=(.*)\z/
        path = $1
        path = File.expand_path(path) unless path[0,1] == '/'
        @options['install-prefix'] = path
      else
        setup_rb_error "install: unknown option #{a}"
      end
    end
  end

  def print_usage(out)
    out.puts 'Typical Installation Procedure:'
    out.puts "  $ ruby #{File.basename $0} config"
    out.puts "  $ ruby #{File.basename $0} setup"
    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
    out.puts
    out.puts 'Detailed Usage:'
    out.puts "  ruby #{File.basename $0} <global option>"
    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"

    fmt = "  %-24s %s\n"
    out.puts
    out.puts 'Global options:'
    out.printf fmt, '-q,--quiet',   'suppress message outputs'
    out.printf fmt, '   --verbose', 'output messages verbosely'
    out.printf fmt, '-h,--help',    'print this message'
    out.printf fmt, '-v,--version', 'print version and quit'
    out.printf fmt, '   --copyright',  'print copyright and quit'
    out.puts
    out.puts 'Tasks:'
    TASKS.each do |name, desc|
      out.printf fmt, name, desc
    end

    fmt = "  %-24s %s [%s]\n"
    out.puts
    out.puts 'Options for CONFIG or ALL:'
    ConfigTable.each do |item|
      out.printf fmt, item.help_opt, item.description, item.help_default
    end
    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
    out.puts
    out.puts 'Options for INSTALL:'
    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
    out.printf fmt, '--prefix=path',  'install path prefix', '$prefix'
    out.puts
  end

  #
  # Task Handlers
  #

  def exec_config
    @installer.exec_config
    @config.save   # must be final
  end

  def exec_setup
    @installer.exec_setup
  end

  def exec_install
    @installer.exec_install
  end

  def exec_show
    ConfigTable.each do |i|
      printf "%-20s %s\n", i.name, i.value
    end
  end

  def exec_clean
    @installer.exec_clean
  end

  def exec_distclean
    @installer.exec_distclean
  end

end


class ToplevelInstallerMulti < ToplevelInstaller

  include HookUtils
  include HookScriptAPI
  include FileOperations

  def initialize(ardir)
    super
    @packages = all_dirs_in("#{@ardir}/packages")
    raise 'no package exists' if @packages.empty?
  end

  def run_metaconfigs
    eval_file_ifexist "#{@ardir}/metaconfig"
    @packages.each do |name|
      eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
    end
  end

  def init_installers
    @installers = {}
    @packages.each do |pack|
      @installers[pack] = Installer.new(@config, @options,
                                       "#{@ardir}/packages/#{pack}",
                                       "packages/#{pack}")
    end

    with    = extract_selection(config('with'))
    without = extract_selection(config('without'))
    @selected = @installers.keys.select {|name|
                  (with.empty? or with.include?(name)) \
                      and not without.include?(name)
                }
  end

  def extract_selection(list)
    a = list.split(/,/)
    a.each do |name|
      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
    end
    a
  end

  def print_usage(f)
    super
    f.puts 'Inluded packages:'
    f.puts '  ' + @packages.sort.join(' ')
    f.puts
  end

  #
  # multi-package metaconfig API
  #

  attr_reader :packages

  def declare_packages(list)
    raise 'package list is empty' if list.empty?
    list.each do |name|
      raise "directory packages/#{name} does not exist"\
              unless File.dir?("#{@ardir}/packages/#{name}")
    end
    @packages = list
  end

  #
  # Task Handlers
  #

  def exec_config
    run_hook 'pre-config'
    each_selected_installers {|inst| inst.exec_config }
    run_hook 'post-config'
    @config.save   # must be final
  end

  def exec_setup
    run_hook 'pre-setup'
    each_selected_installers {|inst| inst.exec_setup }
    run_hook 'post-setup'
  end

  def exec_install
    run_hook 'pre-install'
    each_selected_installers {|inst| inst.exec_install }
    run_hook 'post-install'
  end

  def exec_clean
    rm_f ConfigTable.savefile
    run_hook 'pre-clean'
    each_selected_installers {|inst| inst.exec_clean }
    run_hook 'post-clean'
  end

  def exec_distclean
    rm_f ConfigTable.savefile
    run_hook 'pre-distclean'
    each_selected_installers {|inst| inst.exec_distclean }
    run_hook 'post-distclean'
  end

  #
  # lib
  #

  def each_selected_installers
    Dir.mkdir 'packages' unless File.dir?('packages')
    @selected.each do |pack|
      $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
      Dir.chdir "packages/#{pack}"
      yield @installers[pack]
      Dir.chdir '../..'
    end
  end

  def verbose?
    @options['verbose']
  end

  def no_harm?
    @options['no-harm']
  end

end


class Installer

  FILETYPES = %w( bin lib ext data )

  include HookScriptAPI
  include HookUtils
  include FileOperations

  def initialize(config, opt, srcroot, objroot)
    @config = config
    @options = opt
    @srcdir = File.expand_path(srcroot)
    @objdir = File.expand_path(objroot)
    @currdir = '.'
  end

  def inspect
    "#<#{self.class} #{File.basename(@srcdir)}>"
  end

  #
  # Hook Script API base methods
  #

  def srcdir_root
    @srcdir
  end

  def objdir_root
    @objdir
  end

  def relpath
    @currdir
  end

  #
  # configs/options
  #

  def no_harm?
    @options['no-harm']
  end

  def verbose?
    @options['verbose']
  end

  def verbose_off
    begin
      save, @options['verbose'] = @options['verbose'], false
      yield
    ensure
      @options['verbose'] = save
    end
  end

  #
  # TASK config
  #

  def exec_config
    exec_task_traverse 'config'
  end

  def config_dir_bin(rel)
  end

  def config_dir_lib(rel)
  end

  def config_dir_ext(rel)
    extconf if extdir?(curr_srcdir())
  end

  def extconf
    opt = @options['config-opt'].join(' ')
    command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}"
  end

  def config_dir_data(rel)
  end

  #
  # TASK setup
  #

  def exec_setup
    exec_task_traverse 'setup'
  end

  def setup_dir_bin(rel)
    all_files_in(curr_srcdir()).each do |fname|
      adjust_shebang "#{curr_srcdir()}/#{fname}"
    end
  end

  def adjust_shebang(path)
    return if no_harm?
    tmpfile = File.basename(path) + '.tmp'
    begin
      File.open(path, 'rb') {|r|
        first = r.gets
        return unless File.basename(config('rubypath')) == 'ruby'
        return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby'
        $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
        File.open(tmpfile, 'wb') {|w|
          w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath'))
          w.write r.read
        }
        move_file tmpfile, File.basename(path)
      }
    ensure
      File.unlink tmpfile if File.exist?(tmpfile)
    end
  end

  def setup_dir_lib(rel)
  end

  def setup_dir_ext(rel)
    make if extdir?(curr_srcdir())
  end

  def setup_dir_data(rel)
  end

  #
  # TASK install
  #

  def exec_install
    rm_f 'InstalledFiles'
    exec_task_traverse 'install'
  end

  def install_dir_bin(rel)
    install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755
  end

  def install_dir_lib(rel)
    install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644
  end

  def install_dir_ext(rel)
    return unless extdir?(curr_srcdir())
    install_files ruby_extentions('.'),
                  "#{config('sodir')}/#{File.dirname(rel)}",
                  0555
  end

  def install_dir_data(rel)
    install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644
  end

  def install_files(list, dest, mode)
    mkdir_p dest, @options['install-prefix']
    list.each do |fname|
      install fname, dest, mode, @options['install-prefix']
    end
  end

  def ruby_scripts
    collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
  end
  
  # picked up many entries from cvs-1.11.1/src/ignore.c
  reject_patterns = %w( 
    core RCSLOG tags TAGS .make.state
    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
    *~ *.old *.bak *.BAK *.orig *.rej _$* *$

    *.org *.in .*
  )
  mapping = {
    '.' => '\.',
    '$' => '\$',
    '#' => '\#',
    '*' => '.*'
  }
  REJECT_PATTERNS = Regexp.new('\A(?:' +
                               reject_patterns.map {|pat|
                                 pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
                               }.join('|') +
                               ')\z')

  def collect_filenames_auto
    mapdir((existfiles() - hookfiles()).reject {|fname|
             REJECT_PATTERNS =~ fname
           })
  end

  def existfiles
    all_files_in(curr_srcdir()) | all_files_in('.')
  end

  def hookfiles
    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
      %w( config setup install clean ).map {|t| sprintf(fmt, t) }
    }.flatten
  end

  def mapdir(filelist)
    filelist.map {|fname|
      if File.exist?(fname)   # objdir
        fname
      else                    # srcdir
        File.join(curr_srcdir(), fname)
      end
    }
  end

  def ruby_extentions(dir)
    Dir.open(dir) {|d|
      ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname }
      if ents.empty?
        setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
      end
      return ents
    }
  end

  #
  # TASK clean
  #

  def exec_clean
    exec_task_traverse 'clean'
    rm_f ConfigTable.savefile
    rm_f 'InstalledFiles'
  end

  def clean_dir_bin(rel)
  end

  def clean_dir_lib(rel)
  end

  def clean_dir_ext(rel)
    return unless extdir?(curr_srcdir())
    make 'clean' if File.file?('Makefile')
  end

  def clean_dir_data(rel)
  end

  #
  # TASK distclean
  #

  def exec_distclean
    exec_task_traverse 'distclean'
    rm_f ConfigTable.savefile
    rm_f 'InstalledFiles'
  end

  def distclean_dir_bin(rel)
  end

  def distclean_dir_lib(rel)
  end

  def distclean_dir_ext(rel)
    return unless extdir?(curr_srcdir())
    make 'distclean' if File.file?('Makefile')
  end

  #
  # lib
  #

  def exec_task_traverse(task)
    run_hook "pre-#{task}"
    FILETYPES.each do |type|
      if config('without-ext') == 'yes' and type == 'ext'
        $stderr.puts 'skipping ext/* by user option' if verbose?
        next
      end
      traverse task, type, "#{task}_dir_#{type}"
    end
    run_hook "post-#{task}"
  end

  def traverse(task, rel, mid)
    dive_into(rel) {
      run_hook "pre-#{task}"
      __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
      all_dirs_in(curr_srcdir()).each do |d|
        traverse task, "#{rel}/#{d}", mid
      end
      run_hook "post-#{task}"
    }
  end

  def dive_into(rel)
    return unless File.dir?("#{@srcdir}/#{rel}")

    dir = File.basename(rel)
    Dir.mkdir dir unless File.dir?(dir)
    prevdir = Dir.pwd
    Dir.chdir dir
    $stderr.puts '---> ' + rel if verbose?
    @currdir = rel
    yield
    Dir.chdir prevdir
    $stderr.puts '<--- ' + rel if verbose?
    @currdir = File.dirname(rel)
  end

end


if $0 == __FILE__
  begin
    if multipackage_install?
      ToplevelInstallerMulti.invoke
    else
      ToplevelInstaller.invoke
    end
  rescue SetupError
    raise if $DEBUG
    $stderr.puts $!.message
    $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
    exit 1
  end
end
require File.dirname(__FILE__) + '/abstract_unit'
require 'webrick'
require 'webrick/log'
require 'singleton'

module ClientTest
  class Person < ActionWebService::Struct
    member :firstnames, [:string]
    member :lastname,   :string

    def ==(other)
      firstnames == other.firstnames && lastname == other.lastname
    end
  end
  
  class Inner < ActionWebService::Struct
    member :name, :string
  end
  
  class Outer < ActionWebService::Struct
    member :name, :string
    member :inner, Inner
  end

  class User < ActiveRecord::Base
  end
  
  module Accounting
    class User < ActiveRecord::Base
    end
  end
  
  class WithModel < ActionWebService::Struct
    member :user, User
    member :users, [User]
  end
  
  class WithMultiDimArray < ActionWebService::Struct
    member :pref, [[:string]]
  end

  class API < ActionWebService::API::Base
    api_method :void
    api_method :normal,               :expects => [:int, :int], :returns => [:int]
    api_method :array_return,         :returns => [[Person]]
    api_method :struct_pass,          :expects => [[Person]], :returns => [:bool]
    api_method :nil_struct_return,    :returns => [Person] 
    api_method :inner_nil,            :returns => [Outer]
    api_method :client_container,     :returns => [:int]
    api_method :named_parameters,     :expects => [{:key=>:string}, {:id=>:int}]
    api_method :thrower
    api_method :user_return,          :returns => [User]
    api_method :with_model_return,    :returns => [WithModel]
    api_method :scoped_model_return,  :returns => [Accounting::User]
    api_method :multi_dim_return,     :returns => [WithMultiDimArray]
  end
  
  class NullLogOut
    def <<(*args); end
  end

  class Container < ActionController::Base
    web_service_api API

    attr_accessor :value_void
    attr_accessor :value_normal
    attr_accessor :value_array_return
    attr_accessor :value_struct_pass
    attr_accessor :value_named_parameters

    def initialize
      @session = @assigns = {}
      @value_void = nil
      @value_normal = nil
      @value_array_return = nil
      @value_struct_pass = nil
      @value_named_parameters = nil
    end

    def void
      @value_void = @method_params
    end

    def normal
      @value_normal = @method_params
      5
    end

    def array_return
      person = Person.new
      person.firstnames = ["one", "two"]
      person.lastname = "last"
      @value_array_return = [person]
    end

    def struct_pass
      @value_struct_pass = @method_params
      true
    end
    
    def nil_struct_return
      nil
    end
    
    def inner_nil
      Outer.new :name => 'outer', :inner => nil
    end

    def client_container
      50
    end

    def named_parameters
      @value_named_parameters = @method_params
    end

    def thrower
      raise "Hi"
    end
    
    def user_return
      User.find(1)
    end
    
    def with_model_return
      WithModel.new :user => User.find(1), :users => User.find(:all)
    end
    
    def scoped_model_return
      Accounting::User.find(1)
    end
    
    def multi_dim_return
      WithMultiDimArray.new :pref => [%w{pref1 value1}, %w{pref2 value2}]
    end
  end

  class AbstractClientLet < WEBrick::HTTPServlet::AbstractServlet
    def initialize(controller)
      @controller = controller
    end

    def get_instance(*args)
      self
    end

    def require_path_info?
      false
    end
  
    def do_GET(req, res)
      raise WEBrick::HTTPStatus::MethodNotAllowed, "GET request not allowed."
    end
  
    def do_POST(req, res)
      raise NotImplementedError
    end
  end

  class AbstractServer
    include ClientTest
    include Singleton
    attr :container
    def initialize
      @container = Container.new
      @clientlet = create_clientlet(@container)
      log = WEBrick::BasicLog.new(NullLogOut.new)
      @server = WEBrick::HTTPServer.new(:Port => server_port, :Logger => log, :AccessLog => log)
      @server.mount('/', @clientlet)
      @thr = Thread.new { @server.start }
      until @server.status == :Running; end
      at_exit { @server.stop; @thr.join }
    end
    
    protected
      def create_clientlet
        raise NotImplementedError
      end

      def server_port
        raise NotImplementedError
      end
  end
end
require File.dirname(__FILE__) + '/abstract_unit'
require 'stringio'

class ActionController::Base; def rescue_action(e) raise e end; end

module DispatcherTest
  Utf8String = "One World Caf\303\251"
  WsdlNamespace = 'http://rubyonrails.com/some/namespace'

  class Node < ActiveRecord::Base
    def initialize(*args)
      super(*args)
      @new_record = false
    end

    class << self
      def name
        "DispatcherTest::Node"
      end

      def columns(*args)
        [
          ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'),
          ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'),
          ActiveRecord::ConnectionAdapters::Column.new('description', nil, 'string'),
        ]
      end

      def connection
        self
      end
    end
  end

  class Person < ActionWebService::Struct
    member :id, :int
    member :name, :string

    def ==(other)
      self.id == other.id && self.name == other.name
    end
  end

  class API < ActionWebService::API::Base
    api_method :add, :expects => [:int, :int], :returns => [:int]
    api_method :interceptee
    api_method :struct_return, :returns => [[Node]]
    api_method :void
  end

  class DirectAPI < ActionWebService::API::Base
    api_method :add, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
    api_method :add2, :expects => [{:a=>:int}, {:b=>:int}], :returns => [:int]
    api_method :before_filtered
    api_method :after_filtered, :returns => [[:int]]
    api_method :struct_return, :returns => [[Node]]
    api_method :struct_pass, :expects => [{:person => Person}]
    api_method :base_struct_return, :returns => [[Person]]
    api_method :hash_struct_return, :returns => [[Person]]
    api_method :thrower
    api_method :void
    api_method :test_utf8, :returns => [:string]
    api_method :hex, :expects => [:base64], :returns => [:string]
    api_method :unhex, :expects => [:string], :returns => [:base64]
    api_method :time, :expects => [:time], :returns => [:time]
  end

  class VirtualAPI < ActionWebService::API::Base
    default_api_method :fallback
  end
 
  class Service < ActionWebService::Base
    web_service_api API

    before_invocation :do_intercept, :only => [:interceptee]

    attr :added
    attr :intercepted
    attr :void_called

    def initialize
      @void_called = false
    end

    def add(a, b)
      @added = a + b
    end

    def interceptee
      @intercepted = false
    end

    def struct_return
      n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
      n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
      [n1, n2]
    end

    def void(*args)
      @void_called = args
    end

    def do_intercept(name, args)
      [false, "permission denied"]
    end
  end

  class MTAPI < ActionWebService::API::Base
    inflect_names false
    api_method :getCategories, :returns => [[:string]]
    api_method :bool, :returns => [:bool]
    api_method :alwaysFail
  end

  class BloggerAPI < ActionWebService::API::Base
    inflect_names false
    api_method :getCategories, :returns => [[:string]]
    api_method :str, :expects => [:int], :returns => [:string]
    api_method :alwaysFail
  end

  class MTService < ActionWebService::Base
    web_service_api MTAPI

    def getCategories
      ["mtCat1", "mtCat2"]
    end
    
    def bool
      'y'
    end
    
    def alwaysFail
      raise "MT AlwaysFail"
    end
  end

  class BloggerService < ActionWebService::Base
    web_service_api BloggerAPI

    def getCategories
      ["bloggerCat1", "bloggerCat2"]
    end

    def str(int)
      unless int.is_a?(Integer)
        raise "Not an integer!"
      end
      500 + int
    end

    def alwaysFail
      raise "Blogger AlwaysFail"
    end
  end

  class AbstractController < ActionController::Base
    def generate_wsdl
      @request ||= ::ActionController::TestRequest.new
      to_wsdl
    end
  end
 
  class DelegatedController < AbstractController
    web_service_dispatching_mode :delegated
    wsdl_namespace WsdlNamespace
  
    web_service(:test_service) { @service ||= Service.new; @service }
  end

  class LayeredController < AbstractController
    web_service_dispatching_mode :layered
    wsdl_namespace WsdlNamespace

    web_service(:mt) { @mt_service ||= MTService.new; @mt_service }
    web_service(:blogger) { @blogger_service ||= BloggerService.new; @blogger_service }
  end
 
  class DirectController < AbstractController
    web_service_api DirectAPI
    web_service_dispatching_mode :direct
    wsdl_namespace WsdlNamespace

    before_invocation :alwaysfail, :only => [:before_filtered]
    after_invocation :alwaysok, :only => [:after_filtered]

    attr :added
    attr :added2
    attr :before_filter_called
    attr :before_filter_target_called
    attr :after_filter_called
    attr :after_filter_target_called
    attr :void_called
    attr :struct_pass_value

    def initialize
      @before_filter_called = false
      @before_filter_target_called = false
      @after_filter_called = false
      @after_filter_target_called = false
      @void_called = false
      @struct_pass_value = false
    end
  
    def add
      @added = @params['a'] + @params['b']
    end

    def add2(a, b)
      @added2 = a + b
    end

    def before_filtered
      @before_filter_target_called = true
    end

    def after_filtered
      @after_filter_target_called = true
      [5, 6, 7]
    end

    def thrower
      raise "Hi, I'm an exception"
    end

    def struct_return
      n1 = Node.new('id' => 1, 'name' => 'node1', 'description' => 'Node 1')
      n2 = Node.new('id' => 2, 'name' => 'node2', 'description' => 'Node 2')
      [n1, n2]
    end

    def struct_pass(person)
      @struct_pass_value = person
    end

    def base_struct_return
      p1 = Person.new('id' => 1, 'name' => 'person1')
      p2 = Person.new('id' => 2, 'name' => 'person2')
      [p1, p2]
    end

    def hash_struct_return
      p1 = { :id => '1', 'name' => 'test' }
      p2 = { 'id' => '2', :name => 'person2' }
      [p1, p2]
    end
    
    def void
      @void_called = @method_params
    end

    def test_utf8
      Utf8String
    end

    def hex(s)
      return s.unpack("H*")[0]
    end

    def unhex(s)
      return [s].pack("H*")
    end

    def time(t)
      t
    end

    protected
      def alwaysfail(method_name, params)
        @before_filter_called = true
        false
      end

      def alwaysok(method_name, params, return_value)
        @after_filter_called = true
      end
  end

  class VirtualController < AbstractController
    web_service_api VirtualAPI
    wsdl_namespace WsdlNamespace

    def fallback
      "fallback!"
    end
  end
end

module DispatcherCommonTests
  def test_direct_dispatching
    assert_equal(70, do_method_call(@direct_controller, 'Add', 20, 50))
    assert_equal(70, @direct_controller.added)
    assert_equal(50, do_method_call(@direct_controller, 'Add2', 25, 25))
    assert_equal(50, @direct_controller.added2)
    assert(@direct_controller.void_called == false)
    assert(do_method_call(@direct_controller, 'Void', 3, 4, 5).nil?)
    assert(@direct_controller.void_called == [])
    result = do_method_call(@direct_controller, 'BaseStructReturn')
    assert(result[0].is_a?(DispatcherTest::Person))
    assert(result[1].is_a?(DispatcherTest::Person))
    assert_equal("cafe", do_method_call(@direct_controller, 'Hex', "\xca\xfe"))
    assert_equal("\xca\xfe", do_method_call(@direct_controller, 'Unhex', "cafe"))
    time = Time.gm(1998, "Feb", 02, 15, 12, 01)
    assert_equal(time, do_method_call(@direct_controller, 'Time', time))
  end

  def test_direct_entrypoint
    assert(@direct_controller.respond_to?(:api))
  end
  
  def test_virtual_dispatching
    assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualOne'))
    assert_equal("fallback!", do_method_call(@virtual_controller, 'VirtualTwo'))
  end

  def test_direct_filtering
    assert_equal(false, @direct_controller.before_filter_called)
    assert_equal(false, @direct_controller.before_filter_target_called)
    do_method_call(@direct_controller, 'BeforeFiltered')
    assert_equal(true, @direct_controller.before_filter_called)
    assert_equal(false, @direct_controller.before_filter_target_called)
    assert_equal(false, @direct_controller.after_filter_called)
    assert_equal(false, @direct_controller.after_filter_target_called)
    assert_equal([5, 6, 7], do_method_call(@direct_controller, 'AfterFiltered'))
    assert_equal(true, @direct_controller.after_filter_called)
    assert_equal(true, @direct_controller.after_filter_target_called)
  end

  def test_delegated_dispatching
    assert_equal(130, do_method_call(@delegated_controller, 'Add', 50, 80))
    service = @delegated_controller.web_service_object(:test_service)
    assert_equal(130, service.added)
    @delegated_controller.web_service_exception_reporting = true
    assert(service.intercepted.nil?)
    result = do_method_call(@delegated_controller, 'Interceptee')
    assert(service.intercepted.nil?)
    assert(is_exception?(result))
    assert_match(/permission denied/, exception_message(result))
    result = do_method_call(@delegated_controller, 'NonExistentMethod')
    assert(is_exception?(result))
    assert_match(/NonExistentMethod/, exception_message(result))
    assert(service.void_called == false)
    assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5).nil?)
    assert(service.void_called == [])
  end

  def test_garbage_request
    [@direct_controller, @delegated_controller].each do |controller|
      controller.class.web_service_exception_reporting = true
      send_garbage_request = lambda do
        service_name = service_name(controller)
        request = protocol.encode_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest)
        response = ActionController::TestResponse.new
        controller.process(request, response)
        # puts response.body
        assert(response.headers['Status'] =~ /^500/)
      end
      send_garbage_request.call
      controller.class.web_service_exception_reporting = false
      send_garbage_request.call
    end
  end

  def test_exception_marshaling
    @direct_controller.web_service_exception_reporting = true
    result = do_method_call(@direct_controller, 'Thrower')
    assert(is_exception?(result))
    assert_equal("Hi, I'm an exception", exception_message(result))
    @direct_controller.web_service_exception_reporting = false
    result = do_method_call(@direct_controller, 'Thrower')
    assert(exception_message(result) != "Hi, I'm an exception")
  end

  def test_ar_struct_return
    [@direct_controller, @delegated_controller].each do |controller|
      result = do_method_call(controller, 'StructReturn')
      assert(result[0].is_a?(DispatcherTest::Node))
      assert(result[1].is_a?(DispatcherTest::Node))
      assert_equal('node1', result[0].name)
      assert_equal('node2', result[1].name)
    end
  end

  def test_casting
    assert_equal 70, do_method_call(@direct_controller, 'Add', "50", "20")
    assert_equal false, @direct_controller.struct_pass_value
    person = DispatcherTest::Person.new(:id => 1, :name => 'test') 
    result = do_method_call(@direct_controller, 'StructPass', person)
    assert(nil == result || true == result)
    assert_equal person, @direct_controller.struct_pass_value
    assert !person.equal?(@direct_controller.struct_pass_value)
    result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test'})
    case
    when soap?
      assert_equal(person, @direct_controller.struct_pass_value)
      assert !person.equal?(@direct_controller.struct_pass_value)
    when xmlrpc?
      assert_equal(person, @direct_controller.struct_pass_value)
      assert !person.equal?(@direct_controller.struct_pass_value)
    end
    assert_equal person, do_method_call(@direct_controller, 'HashStructReturn')[0]
    result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test', 'nonexistent_attribute' => 'value'})
    case
    when soap?
      assert_equal(person, @direct_controller.struct_pass_value)
      assert !person.equal?(@direct_controller.struct_pass_value)
    when xmlrpc?
      assert_equal(person, @direct_controller.struct_pass_value)
      assert !person.equal?(@direct_controller.struct_pass_value)
    end
  end

  def test_logging
    buf = ""
    ActionController::Base.logger = Logger.new(StringIO.new(buf))
    test_casting
    test_garbage_request
    test_exception_marshaling
    ActionController::Base.logger = nil
    assert_match /Web Service Response/, buf
    assert_match /Web Service Request/, buf
  end

  protected
    def service_name(container)
      raise NotImplementedError
    end

    def exception_message(obj)
      raise NotImplementedError
    end

    def is_exception?(obj)
      raise NotImplementedError
    end

    def protocol
      @protocol
    end

    def soap?
      protocol.is_a? ActionWebService::Protocol::Soap::SoapProtocol
    end

    def xmlrpc?
      protocol.is_a? ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
    end

    def do_method_call(container, public_method_name, *params)
      request_env = {}
      mode = container.web_service_dispatching_mode
      case mode
      when :direct
        service_name = service_name(container)
        api = container.class.web_service_api
        method = api.public_api_method_instance(public_method_name)
      when :delegated
        service_name = service_name(container)
        api = container.web_service_object(service_name).class.web_service_api
        method = api.public_api_method_instance(public_method_name)
      when :layered
        service_name = nil
        real_method_name = nil
        if public_method_name =~ /^([^\.]+)\.(.*)$/
          service_name = $1
          real_method_name = $2
        end
        if soap?
          public_method_name = real_method_name
          request_env['HTTP_SOAPACTION'] = "/soap/#{service_name}/#{real_method_name}"
        end
        api = container.web_service_object(service_name.to_sym).class.web_service_api rescue nil
        method = api.public_api_method_instance(real_method_name) rescue nil
        service_name = self.service_name(container)
      end
      protocol.register_api(api)
      virtual = false
      unless method
        virtual = true
        method ||= ActionWebService::API::Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil)
      end
      body = protocol.encode_request(public_method_name, params.dup, method.expects)
      # puts body
      ap_request = protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest)
      ap_request.env.update(request_env)
      ap_response = ActionController::TestResponse.new
      container.process(ap_request, ap_response)
      # puts ap_response.body
      @response_body = ap_response.body
      public_method_name, return_value = protocol.decode_response(ap_response.body)
      unless is_exception?(return_value) || virtual
        return_value = method.cast_returns(return_value)
      end
      if soap?
        # http://dev.rubyonrails.com/changeset/920
        assert_match(/Response$/, public_method_name) unless public_method_name == "fault"
      end
      return_value
    end
end
ENV["RAILS_ENV"] = "test"
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
$:.unshift(File.dirname(__FILE__) + '/../../actionpack/lib')
$:.unshift(File.dirname(__FILE__) + '/../../activerecord/lib')

require 'test/unit'
require 'action_web_service'
require 'action_controller'
require 'action_controller/test_process'

ActionController::Base.logger = nil
ActionController::Base.ignore_missing_templates = true

begin
  PATH_TO_AR = File.dirname(__FILE__) + '/../../activerecord'
  require "#{PATH_TO_AR}/lib/active_record" unless Object.const_defined?(:ActiveRecord)
  require "#{PATH_TO_AR}/lib/active_record/fixtures" unless Object.const_defined?(:Fixtures)
rescue Object => e
  fail "\nFailed to load activerecord: #{e}"
end

ActiveRecord::Base.establish_connection(
  :adapter  => "mysql",
  :username => "rails",
  :encoding => "utf8",
  :database => "activewebservice_unittest"
)
ActiveRecord::Base.connection

Test::Unit::TestCase.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"

# restore default raw_post functionality
class ActionController::TestRequest
  def raw_post
    super
  end
endrequire File.dirname(__FILE__) + '/abstract_unit'

module APITest
  class API < ActionWebService::API::Base
    api_method :void
    api_method :expects_and_returns, :expects_and_returns => [:string]
    api_method :expects,             :expects => [:int, :bool]
    api_method :returns,             :returns => [:int, [:string]]
    api_method :named_signature,     :expects => [{:appkey=>:int}, {:publish=>:bool}]
    api_method :string_types,        :expects => ['int', 'string', 'bool', 'base64']
    api_method :class_types,         :expects => [TrueClass, Bignum, String]
  end
end

class TC_API < Test::Unit::TestCase
  API = APITest::API

  def test_api_method_declaration
    %w(
      void
      expects_and_returns
      expects
      returns
      named_signature
      string_types
      class_types
    ).each do |name|
      name = name.to_sym
      public_name = API.public_api_method_name(name)
      assert(API.has_api_method?(name))
      assert(API.has_public_api_method?(public_name))
      assert(API.api_method_name(public_name) == name)
      assert(API.api_methods.has_key?(name))
    end
  end

  def test_signature_canonicalization
    assert_equal(nil, API.api_methods[:void].expects)
    assert_equal(nil, API.api_methods[:void].returns)
    assert_equal([String], API.api_methods[:expects_and_returns].expects.map{|x| x.type_class})
    assert_equal([String], API.api_methods[:expects_and_returns].returns.map{|x| x.type_class})
    assert_equal([Integer, TrueClass], API.api_methods[:expects].expects.map{|x| x.type_class})
    assert_equal(nil, API.api_methods[:expects].returns)
    assert_equal(nil, API.api_methods[:returns].expects)
    assert_equal([Integer, [String]], API.api_methods[:returns].returns.map{|x| x.array?? [x.element_type.type_class] : x.type_class})
    assert_equal([[:appkey, Integer], [:publish, TrueClass]], API.api_methods[:named_signature].expects.map{|x| [x.name, x.type_class]})
    assert_equal(nil, API.api_methods[:named_signature].returns)
    assert_equal([Integer, String, TrueClass, ActionWebService::Base64], API.api_methods[:string_types].expects.map{|x| x.type_class})
    assert_equal(nil, API.api_methods[:string_types].returns)
    assert_equal([TrueClass, Integer, String], API.api_methods[:class_types].expects.map{|x| x.type_class})
    assert_equal(nil, API.api_methods[:class_types].returns)
  end

  def test_not_instantiable
    assert_raises(NoMethodError) do
      API.new
    end
  end

  def test_api_errors
    assert_raises(ActionWebService::ActionWebServiceError) do
      klass = Class.new(ActionWebService::API::Base) do
        api_method :test, :expects => [ActiveRecord::Base]
      end
    end
    klass = Class.new(ActionWebService::API::Base) do
      allow_active_record_expects true
      api_method :test2, :expects => [ActiveRecord::Base]
    end
    assert_raises(ActionWebService::ActionWebServiceError) do
      klass = Class.new(ActionWebService::API::Base) do
        api_method :test, :invalid => [:int]
      end
    end
  end

  def test_parameter_names
    method = API.api_methods[:named_signature]
    assert_equal 0, method.expects_index_of(:appkey)
    assert_equal 1, method.expects_index_of(:publish)
    assert_equal 1, method.expects_index_of('publish')
    assert_equal 0, method.expects_index_of('appkey')
    assert_equal -1, method.expects_index_of('blah')
    assert_equal -1, method.expects_index_of(:missing)
    assert_equal -1, API.api_methods[:void].expects_index_of('test')
  end

  def test_parameter_hash
    method = API.api_methods[:named_signature]
    hash = method.expects_to_hash([5, false])
    assert_equal({:appkey => 5, :publish => false}, hash)
  end

  def test_api_methods_compat
    sig = API.api_methods[:named_signature][:expects]
    assert_equal [{:appkey=>Integer}, {:publish=>TrueClass}], sig
  end

  def test_to_s
    assert_equal 'void Expects(int param0, bool param1)', APITest::API.api_methods[:expects].to_s
  end
end
class AutoLoadAPI < ActionWebService::API::Base
  api_method :void
end


require File.dirname(__FILE__) + '/abstract_unit'

module BaseTest
  class API < ActionWebService::API::Base
    api_method :add, :expects => [:int, :int], :returns => [:int]
    api_method :void
  end

  class PristineAPI < ActionWebService::API::Base
    inflect_names false

    api_method :add
    api_method :under_score
  end

  class Service < ActionWebService::Base
    web_service_api API

    def add(a, b)
    end
  
    def void
    end
  end
  
  class PristineService < ActionWebService::Base
    web_service_api PristineAPI

    def add
    end

    def under_score
    end
  end
end

class TC_Base < Test::Unit::TestCase
  def test_options
    assert(BaseTest::PristineService.web_service_api.inflect_names == false)
    assert(BaseTest::Service.web_service_api.inflect_names == true)
  end
end
require File.dirname(__FILE__) + '/abstract_unit'

module CastingTest
  class API < ActionWebService::API::Base
    api_method :int,       :expects => [:int]
    api_method :str,       :expects => [:string]
    api_method :base64,    :expects => [:base64]
    api_method :bool,      :expects => [:bool]
    api_method :float,     :expects => [:float]
    api_method :time,      :expects => [:time]
    api_method :datetime,  :expects => [:datetime]
    api_method :date,      :expects => [:date]

    api_method :int_array,   :expects => [[:int]]
    api_method :str_array,   :expects => [[:string]]
    api_method :bool_array,  :expects => [[:bool]]
  end
end

class TC_Casting < Test::Unit::TestCase
  include CastingTest

  def test_base_type_casting_valid
    assert_equal 10000,   cast_expects(:int, '10000')[0]
    assert_equal '10000', cast_expects(:str, 10000)[0]
    base64 = cast_expects(:base64, 10000)[0]
    assert_equal '10000', base64
    assert_instance_of ActionWebService::Base64, base64
    [1, '1', 'true', 'y', 'yes'].each do |val|
      assert_equal true, cast_expects(:bool, val)[0]
    end
    [0, '0', 'false', 'n', 'no'].each do |val|
      assert_equal false, cast_expects(:bool, val)[0]
    end
    assert_equal 3.14159, cast_expects(:float, '3.14159')[0]
    now = Time.at(Time.now.tv_sec)
    casted = cast_expects(:time, now.to_s)[0]
    assert_equal now, casted
    now = DateTime.now
    assert_equal now.to_s, cast_expects(:datetime, now.to_s)[0].to_s
    today = Date.today
    assert_equal today, cast_expects(:date, today.to_s)[0]
  end

  def test_base_type_casting_invalid
    assert_raises ArgumentError do
      cast_expects(:int, 'this is not a number')
    end
    assert_raises ActionWebService::Casting::CastingError do
      # neither true or false ;)
      cast_expects(:bool, 'i always lie')
    end
    assert_raises ArgumentError do
      cast_expects(:float, 'not a float')
    end
    assert_raises ArgumentError do
      cast_expects(:time, '111111111111111111111111111111111')
    end
    assert_raises ArgumentError do
      cast_expects(:datetime, '-1')
    end
    assert_raises ArgumentError do
      cast_expects(:date, '')
    end
  end

  def test_array_type_casting
    assert_equal [1, 2, 3213992, 4], cast_expects(:int_array, ['1', '2', '3213992', '4'])[0]
    assert_equal ['one', 'two', '5.0', '200', nil, 'true'], cast_expects(:str_array, [:one, 'two', 5.0, 200, nil, true])[0]
    assert_equal [true, nil, true, true, false], cast_expects(:bool_array, ['1', nil, 'y', true, 'false'])[0]
  end

  def test_array_type_casting_failure
    assert_raises ActionWebService::Casting::CastingError do
      cast_expects(:bool_array, ['false', 'blahblah'])
    end
    assert_raises ArgumentError do
      cast_expects(:int_array, ['1', '2.021', '4'])
    end
  end

  private
    def cast_expects(method_name, *args)
      API.api_method_instance(method_name.to_sym).cast_expects([*args])
    end
end
require File.dirname(__FILE__) + '/abstract_client'


module ClientSoapTest
  PORT = 8998

  class SoapClientLet < ClientTest::AbstractClientLet
    def do_POST(req, res)
      test_request = ActionController::TestRequest.new
      test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1]
      test_request.env['REQUEST_METHOD'] = "POST"
      test_request.env['HTTP_CONTENTTYPE'] = 'text/xml'
      test_request.env['HTTP_SOAPACTION'] = req.header['soapaction'][0]
      test_request.env['RAW_POST_DATA'] = req.body
      response = ActionController::TestResponse.new
      @controller.process(test_request, response)
      res.header['content-type'] = 'text/xml'
      res.body = response.body
    rescue Exception => e
      $stderr.puts e.message
      $stderr.puts e.backtrace.join("\n")
    end
  end

  class ClientContainer < ActionController::Base
    web_client_api :client, :soap, "http://localhost:#{PORT}/client/api", :api => ClientTest::API
    web_client_api :invalid, :null, "", :api => true

    def get_client
      client
    end

    def get_invalid
      invalid
    end
  end

  class SoapServer < ClientTest::AbstractServer
    def create_clientlet(controller)
      SoapClientLet.new(controller)
    end

    def server_port
      PORT
    end
  end
end

class TC_ClientSoap < Test::Unit::TestCase
  include ClientTest
  include ClientSoapTest
  
  fixtures :users

  def setup
    @server = SoapServer.instance
    @container = @server.container
    @client = ActionWebService::Client::Soap.new(API, "http://localhost:#{@server.server_port}/client/api")
  end

  def test_void
    assert(@container.value_void.nil?)
    @client.void
    assert(!@container.value_void.nil?)
  end

  def test_normal
    assert(@container.value_normal.nil?)
    assert_equal(5, @client.normal(5, 6))
    assert_equal([5, 6], @container.value_normal)
    assert_equal(5, @client.normal("7", "8"))
    assert_equal([7, 8], @container.value_normal)
    assert_equal(5, @client.normal(true, false))
  end

  def test_array_return
    assert(@container.value_array_return.nil?)
    new_person = Person.new
    new_person.firstnames = ["one", "two"]
    new_person.lastname = "last"
    assert_equal([new_person], @client.array_return)
    assert_equal([new_person], @container.value_array_return)
  end

  def test_struct_pass
    assert(@container.value_struct_pass.nil?)
    new_person = Person.new
    new_person.firstnames = ["one", "two"]
    new_person.lastname = "last"
    assert_equal(true, @client.struct_pass([new_person]))
    assert_equal([[new_person]], @container.value_struct_pass)
  end
  
  def test_nil_struct_return
    assert_nil @client.nil_struct_return
  end
  
  def test_inner_nil
    outer = @client.inner_nil
    assert_equal 'outer', outer.name
    assert_nil outer.inner
  end

  def test_client_container
    assert_equal(50, ClientContainer.new.get_client.client_container)
    assert(ClientContainer.new.get_invalid.nil?)
  end

  def test_named_parameters
    assert(@container.value_named_parameters.nil?)
    assert(@client.named_parameters("key", 5).nil?)
    assert_equal(["key", 5], @container.value_named_parameters)
  end

  def test_capitalized_method_name
    @container.value_normal = nil
    assert_equal(5, @client.Normal(5, 6))
    assert_equal([5, 6], @container.value_normal)
    @container.value_normal = nil
  end
  
  def test_model_return
    user = @client.user_return
    assert_equal 1, user.id
    assert_equal 'Kent', user.name
    assert user.active?
    assert_kind_of Date, user.created_on
    assert_equal Date.today, user.created_on
  end
  
  def test_with_model
    with_model = @client.with_model_return
    assert_equal 'Kent', with_model.user.name
    assert_equal 2, with_model.users.size
    with_model.users.each do |user|
      assert_kind_of User, user
    end
  end
  
  def test_scoped_model_return
    scoped_model = @client.scoped_model_return
    assert_kind_of Accounting::User, scoped_model
    assert_equal 'Kent', scoped_model.name
  end
  
  def test_multi_dim_return
    md_struct = @client.multi_dim_return
    assert_kind_of Array, md_struct.pref
    assert_equal 2, md_struct.pref.size
    assert_kind_of Array, md_struct.pref[0]
  end
end
require File.dirname(__FILE__) + '/abstract_client'


module ClientXmlRpcTest
  PORT = 8999

  class XmlRpcClientLet < ClientTest::AbstractClientLet
    def do_POST(req, res)
      test_request = ActionController::TestRequest.new
      test_request.request_parameters['action'] = req.path.gsub(/^\//, '').split(/\//)[1]
      test_request.env['REQUEST_METHOD'] = "POST"
      test_request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
      test_request.env['RAW_POST_DATA'] = req.body
      response = ActionController::TestResponse.new
      @controller.process(test_request, response) 
      res.header['content-type'] = 'text/xml'
      res.body = response.body
      # puts res.body
    rescue Exception => e
      $stderr.puts e.message
      $stderr.puts e.backtrace.join("\n")
    end
  end

  class ClientContainer < ActionController::Base
    web_client_api :client, :xmlrpc, "http://localhost:#{PORT}/client/api", :api => ClientTest::API

    def get_client
      client
    end
  end

  class XmlRpcServer < ClientTest::AbstractServer
    def create_clientlet(controller)
      XmlRpcClientLet.new(controller)
    end

    def server_port
      PORT
    end
  end
end

class TC_ClientXmlRpc < Test::Unit::TestCase
  include ClientTest
  include ClientXmlRpcTest
  
  fixtures :users

  def setup
    @server = XmlRpcServer.instance
    @container = @server.container
    @client = ActionWebService::Client::XmlRpc.new(API, "http://localhost:#{@server.server_port}/client/api")
  end

  def test_void
    assert(@container.value_void.nil?)
    @client.void
    assert(!@container.value_void.nil?)
  end

  def test_normal
    assert(@container.value_normal.nil?)
    assert_equal(5, @client.normal(5, 6))
    assert_equal([5, 6], @container.value_normal)
    assert_equal(5, @client.normal("7", "8"))
    assert_equal([7, 8], @container.value_normal)
    assert_equal(5, @client.normal(true, false))
  end

  def test_array_return
    assert(@container.value_array_return.nil?)
    new_person = Person.new
    new_person.firstnames = ["one", "two"]
    new_person.lastname = "last"
    assert_equal([new_person], @client.array_return)
    assert_equal([new_person], @container.value_array_return)
  end

  def test_struct_pass
    assert(@container.value_struct_pass.nil?)
    new_person = Person.new
    new_person.firstnames = ["one", "two"]
    new_person.lastname = "last"
    assert_equal(true, @client.struct_pass([new_person]))
    assert_equal([[new_person]], @container.value_struct_pass)
  end
  
  def test_nil_struct_return
    assert_equal false, @client.nil_struct_return
  end

  def test_inner_nil
    outer = @client.inner_nil
    assert_equal 'outer', outer.name
    assert_nil outer.inner
  end

  def test_client_container
    assert_equal(50, ClientContainer.new.get_client.client_container)
  end

  def test_named_parameters
    assert(@container.value_named_parameters.nil?)
    assert_equal(false, @client.named_parameters("xxx", 7))
    assert_equal(["xxx", 7], @container.value_named_parameters)
  end

  def test_exception
    assert_raises(ActionWebService::Client::ClientError) do
      assert(@client.thrower)
    end
  end

  def test_invalid_signature
    assert_raises(ArgumentError) do
      @client.normal
    end
  end

  def test_model_return
    user = @client.user_return
    assert_equal 1, user.id
    assert_equal 'Kent', user.name
    assert user.active?
    assert_kind_of Time, user.created_on
    assert_equal Time.utc(Time.now.year, Time.now.month, Time.now.day), user.created_on
  end

  def test_with_model
    with_model = @client.with_model_return
    assert_equal 'Kent', with_model.user.name
    assert_equal 2, with_model.users.size
    with_model.users.each do |user|
      assert_kind_of User, user
    end
  end
  
  def test_scoped_model_return
    scoped_model = @client.scoped_model_return
    assert_kind_of Accounting::User, scoped_model
    assert_equal 'Kent', scoped_model.name
  end

  def test_multi_dim_return
    md_struct = @client.multi_dim_return
    assert_kind_of Array, md_struct.pref
    assert_equal 2, md_struct.pref.size
    assert_kind_of Array, md_struct.pref[0]
  end
end
require File.dirname(__FILE__) + '/abstract_unit'

module ContainerTest
  $immediate_service = Object.new
  $deferred_service = Object.new
  
  class DelegateContainer < ActionController::Base
    web_service_dispatching_mode :delegated
  
    attr :flag
    attr :previous_flag
  
    def initialize
      @previous_flag = nil
      @flag = true
    end
  
    web_service :immediate_service, $immediate_service
    web_service(:deferred_service) { @previous_flag = @flag; @flag = false; $deferred_service }
  end
  
  class DirectContainer < ActionController::Base
    web_service_dispatching_mode :direct
  end

  class InvalidContainer
    include ActionWebService::Container::Direct
  end
end

class TC_Container < Test::Unit::TestCase
  include ContainerTest

  def setup
    @delegate_container = DelegateContainer.new
    @direct_container = DirectContainer.new
  end

  def test_registration
    assert(DelegateContainer.has_web_service?(:immediate_service))
    assert(DelegateContainer.has_web_service?(:deferred_service))
    assert(!DelegateContainer.has_web_service?(:fake_service))
    assert_raises(ActionWebService::Container::Delegated::ContainerError) do
      DelegateContainer.web_service('invalid')
    end
  end

  def test_service_object
    assert_raises(ActionWebService::Container::Delegated::ContainerError) do
      @delegate_container.web_service_object(:nonexistent)
    end
    assert(@delegate_container.flag == true)
    assert(@delegate_container.web_service_object(:immediate_service) == $immediate_service)
    assert(@delegate_container.previous_flag.nil?)
    assert(@delegate_container.flag == true)
    assert(@delegate_container.web_service_object(:deferred_service) == $deferred_service)
    assert(@delegate_container.previous_flag == true)
    assert(@delegate_container.flag == false)
  end

  def test_direct_container
    assert(DirectContainer.web_service_dispatching_mode == :direct)
  end

  def test_validity
    assert_raises(ActionWebService::Container::Direct::ContainerError) do 
      InvalidContainer.web_service_api :test
    end
    assert_raises(ActionWebService::Container::Direct::ContainerError) do 
      InvalidContainer.web_service_api 50.0
    end
  end
end
$:.unshift(File.dirname(__FILE__) + '/apis')
require File.dirname(__FILE__) + '/abstract_dispatcher'
require 'wsdl/parser'

class ActionController::Base
  class << self
    alias :inherited_without_name_error :inherited
    def inherited(child)
      begin
        inherited_without_name_error(child)
      rescue NameError => e
      end
    end
  end
end

class AutoLoadController < ActionController::Base; end
class FailingAutoLoadController < ActionController::Base; end
class BrokenAutoLoadController < ActionController::Base; end

class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
  include DispatcherTest
  include DispatcherCommonTests

  def setup
    @direct_controller = DirectController.new
    @delegated_controller = DelegatedController.new
    @virtual_controller = VirtualController.new
    @layered_controller = LayeredController.new
    @protocol = ActionWebService::Protocol::Soap::SoapProtocol.create(@direct_controller)
  end

  def test_wsdl_generation
    ensure_valid_wsdl_generation DelegatedController.new, DispatcherTest::WsdlNamespace
    ensure_valid_wsdl_generation DirectController.new, DispatcherTest::WsdlNamespace
  end

  def test_wsdl_action
    delegated_types = ensure_valid_wsdl_action DelegatedController.new
    delegated_names = delegated_types.map{|x| x.name.name}
    assert(delegated_names.include?('DispatcherTest..NodeArray'))
    assert(delegated_names.include?('DispatcherTest..Node'))
    direct_types = ensure_valid_wsdl_action DirectController.new
    direct_names = direct_types.map{|x| x.name.name}
    assert(direct_names.include?('DispatcherTest..NodeArray'))
    assert(direct_names.include?('DispatcherTest..Node'))
    assert(direct_names.include?('IntegerArray'))
  end

  def test_autoloading
    assert(!AutoLoadController.web_service_api.nil?)
    assert(AutoLoadController.web_service_api.has_public_api_method?('Void'))
    assert(FailingAutoLoadController.web_service_api.nil?)
    assert_raises(MissingSourceFile) do
      FailingAutoLoadController.require_web_service_api :blah
    end
    assert_raises(ArgumentError) do
      FailingAutoLoadController.require_web_service_api 50.0
    end
    assert(BrokenAutoLoadController.web_service_api.nil?)
  end

  def test_layered_dispatching
    mt_cats = do_method_call(@layered_controller, 'mt.getCategories')
    assert_equal(["mtCat1", "mtCat2"], mt_cats)
    blogger_cats = do_method_call(@layered_controller, 'blogger.getCategories')
    assert_equal(["bloggerCat1", "bloggerCat2"], blogger_cats)
  end

  def test_utf8
    @direct_controller.web_service_exception_reporting = true
    $KCODE = 'u'
    assert_equal(Utf8String, do_method_call(@direct_controller, 'TestUtf8'))
    retval = SOAP::Processor.unmarshal(@response_body).body.response
    assert retval.is_a?(SOAP::SOAPString)

    # If $KCODE is not set to UTF-8, any strings with non-ASCII UTF-8 data
    # will be sent back as base64 by SOAP4R. By the time we get it here though,
    # it will be decoded back into a string. So lets read the base64 value
    # from the message body directly.
    $KCODE = 'NONE'
    do_method_call(@direct_controller, 'TestUtf8')
    retval = SOAP::Processor.unmarshal(@response_body).body.response
    assert retval.is_a?(SOAP::SOAPBase64)
    assert_equal "T25lIFdvcmxkIENhZsOp", retval.data.to_s
  end

  protected
    def exception_message(soap_fault_exception)
      soap_fault_exception.detail.cause.message
    end

    def is_exception?(obj)
      obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && \
      obj.detail.cause.is_a?(Exception)
    end

    def service_name(container)
      container.is_a?(DelegatedController) ? 'test_service' : 'api'
    end

    def ensure_valid_wsdl_generation(controller, expected_namespace)
      wsdl = controller.generate_wsdl
      ensure_valid_wsdl(controller, wsdl, expected_namespace)
    end

    def ensure_valid_wsdl(controller, wsdl, expected_namespace)
      definitions = WSDL::Parser.new.parse(wsdl)
      assert(definitions.is_a?(WSDL::Definitions))
      definitions.bindings.each do |binding|
        assert(binding.name.name.index(':').nil?)
      end
      definitions.services.each do |service|
        service.ports.each do |port|
          assert(port.name.name.index(':').nil?)
        end
      end
      types = definitions.collect_complextypes.map{|x| x.name}
      types.each do |type|
        assert(type.namespace == expected_namespace)
      end
      location = definitions.services[0].ports[0].soap_address.location
      if controller.is_a?(DelegatedController)
        assert_match %r{http://localhost/dispatcher_test/delegated/test_service$}, location
      elsif controller.is_a?(DirectController)
        assert_match %r{http://localhost/dispatcher_test/direct/api$}, location
      end
      definitions.collect_complextypes
    end

    def ensure_valid_wsdl_action(controller)
      test_request = ActionController::TestRequest.new({ 'action' => 'wsdl' })
      test_request.env['REQUEST_METHOD'] = 'GET'
      test_request.env['HTTP_HOST'] = 'localhost'
      test_response = ActionController::TestResponse.new
      wsdl = controller.process(test_request, test_response).body
      ensure_valid_wsdl(controller, wsdl, DispatcherTest::WsdlNamespace)
    end
end
require File.dirname(__FILE__) + '/abstract_dispatcher'

class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase
  include DispatcherTest
  include DispatcherCommonTests

  def setup
    @direct_controller = DirectController.new
    @delegated_controller = DelegatedController.new
    @layered_controller = LayeredController.new
    @virtual_controller = VirtualController.new
    @protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.create(@direct_controller)
  end

  def test_layered_dispatching
    mt_cats = do_method_call(@layered_controller, 'mt.getCategories')
    assert_equal(["mtCat1", "mtCat2"], mt_cats)
    blogger_cats = do_method_call(@layered_controller, 'blogger.getCategories')
    assert_equal(["bloggerCat1", "bloggerCat2"], blogger_cats)
  end

  def test_multicall
    response = do_method_call(@layered_controller, 'system.multicall', [
      {'methodName' => 'mt.getCategories'},
      {'methodName' => 'blogger.getCategories'},
      {'methodName' => 'mt.bool'},
      {'methodName' => 'blogger.str', 'params' => ['2000']},
      {'methodName' => 'mt.alwaysFail'},
      {'methodName' => 'blogger.alwaysFail'},
      {'methodName' => 'mt.blah'},
      {'methodName' => 'blah.blah'}
    ])
    assert_equal [
      [["mtCat1", "mtCat2"]],
      [["bloggerCat1", "bloggerCat2"]],
      [true],
      ["2500"],
      {"faultCode" => 3, "faultString" => "MT AlwaysFail"},
      {"faultCode" => 3, "faultString" => "Blogger AlwaysFail"},
      {"faultCode" => 4, "faultMessage" => "no such method 'blah' on API DispatcherTest::MTAPI"},
      {"faultCode" => 4, "faultMessage" => "no such web service 'blah'"}
    ], response
  end

  protected
    def exception_message(xmlrpc_fault_exception)
      xmlrpc_fault_exception.faultString
    end

    def is_exception?(obj)
      obj.is_a?(XMLRPC::FaultException)
    end

    def service_name(container)
      container.is_a?(DelegatedController) ? 'test_service' : 'api'
    end
end
require File.dirname(__FILE__) + '/abstract_unit'

module InvocationTest
  class API < ActionWebService::API::Base
    api_method :add,             :expects => [:int, :int], :returns => [:int]
    api_method :transmogrify,    :expects_and_returns => [:string]
    api_method :fail_with_reason
    api_method :fail_generic
    api_method :no_before
    api_method :no_after
    api_method :only_one
    api_method :only_two
  end

  class Interceptor
    attr :args

    def initialize
      @args = nil
    end

    def intercept(*args)
      @args = args
    end
  end

  InterceptorClass = Interceptor.new 

  class Service < ActionController::Base
    web_service_api API

    before_invocation :intercept_before, :except => [:no_before]
    after_invocation :intercept_after, :except => [:no_after]
    prepend_after_invocation :intercept_after_first, :except => [:no_after]
    prepend_before_invocation :intercept_only, :only => [:only_one, :only_two]
    after_invocation(:only => [:only_one]) do |*args| 
      args[0].instance_variable_set('@block_invoked', args[1])
    end
    after_invocation InterceptorClass, :only => [:only_one]

    attr_accessor :before_invoked
    attr_accessor :after_invoked
    attr_accessor :after_first_invoked
    attr_accessor :only_invoked
    attr_accessor :block_invoked
    attr_accessor :invocation_result
  
    def initialize
      @before_invoked = nil
      @after_invoked = nil
      @after_first_invoked = nil
      @only_invoked = nil
      @invocation_result = nil
      @block_invoked = nil
    end
  
    def add(a, b)
      a + b
    end
  
    def transmogrify(str)
      str.upcase
    end
    
    def fail_with_reason
    end
  
    def fail_generic
    end
  
    def no_before
      5
    end
  
    def no_after
    end
  
    def only_one
    end
  
    def only_two
    end
  
    protected
      def intercept_before(name, args)
        @before_invoked = name
        return [false, "permission denied"] if name == :fail_with_reason
        return false if name == :fail_generic
      end
  
      def intercept_after(name, args, result)
        @after_invoked = name
        @invocation_result = result
      end

      def intercept_after_first(name, args, result)
        @after_first_invoked = name
      end
  
      def intercept_only(name, args)
        raise "Interception error" unless name == :only_one || name == :only_two
        @only_invoked = name
      end
  end
end

class TC_Invocation < Test::Unit::TestCase
  include ActionWebService::Invocation

  def setup
    @service = InvocationTest::Service.new
  end

  def test_invocation
    assert(perform_invocation(:add, 5, 10) == 15)
    assert(perform_invocation(:transmogrify, "hello") == "HELLO")
    assert_raises(NoMethodError) do
      perform_invocation(:nonexistent_method_xyzzy)
    end
  end

  def test_interceptor_registration
    assert(InvocationTest::Service.before_invocation_interceptors.length == 2)
    assert(InvocationTest::Service.after_invocation_interceptors.length == 4)
    assert_equal(:intercept_only, InvocationTest::Service.before_invocation_interceptors[0])
    assert_equal(:intercept_after_first, InvocationTest::Service.after_invocation_interceptors[0])
  end

  def test_interception
    assert(@service.before_invoked.nil?)
    assert(@service.after_invoked.nil?)
    assert(@service.only_invoked.nil?)
    assert(@service.block_invoked.nil?)
    assert(@service.invocation_result.nil?)
    perform_invocation(:add, 20, 50)
    assert(@service.before_invoked == :add)
    assert(@service.after_invoked == :add)
    assert(@service.invocation_result == 70)
  end

  def test_interception_canceling
    reason = nil
    perform_invocation(:fail_with_reason){|r| reason = r}
    assert(@service.before_invoked == :fail_with_reason)
    assert(@service.after_invoked.nil?)
    assert(@service.invocation_result.nil?)
    assert(reason == "permission denied")
    reason = true
    @service.before_invoked = @service.after_invoked = @service.invocation_result = nil
    perform_invocation(:fail_generic){|r| reason = r}
    assert(@service.before_invoked == :fail_generic)
    assert(@service.after_invoked.nil?)
    assert(@service.invocation_result.nil?)
    assert(reason == true)
  end

  def test_interception_except_conditions
    perform_invocation(:no_before)
    assert(@service.before_invoked.nil?)
    assert(@service.after_first_invoked == :no_before)
    assert(@service.after_invoked == :no_before)
    assert(@service.invocation_result == 5)
    @service.before_invoked = @service.after_invoked = @service.invocation_result = nil
    perform_invocation(:no_after)
    assert(@service.before_invoked == :no_after)
    assert(@service.after_invoked.nil?)
    assert(@service.invocation_result.nil?)
  end

  def test_interception_only_conditions
    assert(@service.only_invoked.nil?)
    perform_invocation(:only_one)
    assert(@service.only_invoked == :only_one)
    assert(@service.block_invoked == :only_one)
    assert(InvocationTest::InterceptorClass.args[1] == :only_one)
    @service.only_invoked = nil
    perform_invocation(:only_two)
    assert(@service.only_invoked == :only_two)
  end

  private
    def perform_invocation(method_name, *args, &block)
      @service.perform_invocation(method_name, args, &block)
    end
end
require File.dirname(__FILE__) + '/abstract_unit'

ActionController::Routing::Routes.draw do |map|
  map.connect '', :controller => 'scaffolded'
  map.connect ':controller/:action/:id'
end

ActionController::Base.template_root = '.'

class ScaffoldPerson < ActionWebService::Struct
  member :id,     :int
  member :name,   :string
  member :birth,  :date

  def ==(other)
    self.id == other.id && self.name == other.name
  end
end

class ScaffoldedControllerTestAPI < ActionWebService::API::Base
  api_method :hello, :expects => [{:integer=>:int}, :string], :returns => [:bool]
  api_method :hello_struct_param, :expects => [{:person => ScaffoldPerson}], :returns => [:bool]
  api_method :date_of_birth, :expects => [ScaffoldPerson], :returns => [:string]
  api_method :bye,   :returns => [[ScaffoldPerson]]
  api_method :date_diff, :expects => [{:start_date => :date}, {:end_date => :date}], :returns => [:int]
  api_method :time_diff, :expects => [{:start_time => :time}, {:end_time => :time}], :returns => [:int]
  api_method :base64_upcase, :expects => [:base64], :returns => [:base64]
end

class ScaffoldedController < ActionController::Base
  web_service_api ScaffoldedControllerTestAPI
  web_service_scaffold :scaffold_invoke

  def hello(int, string)
    0
  end
  
  def hello_struct_param(person)
    0
  end
  
  def date_of_birth(person)
    person.birth.to_s
  end

  def bye
    [ScaffoldPerson.new(:id => 1, :name => "leon"), ScaffoldPerson.new(:id => 2, :name => "paul")]
  end

  def rescue_action(e)
    raise e
  end
  
  def date_diff(start_date, end_date)
    end_date - start_date
  end
  
  def time_diff(start_time, end_time)
    end_time - start_time
  end
  
  def base64_upcase(data)
    data.upcase
  end
end

class ScaffoldedControllerTest < Test::Unit::TestCase
  def setup
    @controller = ScaffoldedController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  def test_scaffold_invoke
    get :scaffold_invoke
    assert_rendered_file 'methods.rhtml'
  end

  def test_scaffold_invoke_method_params
    get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'Hello'
    assert_rendered_file 'parameters.rhtml'
  end
  
  def test_scaffold_invoke_method_params_with_struct
    get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'HelloStructParam'
    assert_rendered_file 'parameters.rhtml'
    assert_tag :tag => 'input', :attributes => {:name => "method_params[0][name]"}
  end

  def test_scaffold_invoke_submit_hello
    post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Hello', :method_params => {'0' => '5', '1' => 'hello world'}
    assert_rendered_file 'result.rhtml'
    assert_equal false, @controller.instance_eval{ @method_return_value }
  end

  def test_scaffold_invoke_submit_bye
    post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Bye'
    assert_rendered_file 'result.rhtml'
    persons = [ScaffoldPerson.new(:id => 1, :name => "leon"), ScaffoldPerson.new(:id => 2, :name => "paul")]
    assert_equal persons, @controller.instance_eval{ @method_return_value }
  end
  
  def test_scaffold_date_params
    get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'DateDiff'
    (0..1).each do |param|
      (1..3).each do |date_part|
        assert_tag :tag => 'select', :attributes => {:name => "method_params[#{param}][#{date_part}]"}, 
                   :children => {:greater_than => 1, :only => {:tag => 'option'}}
      end
    end
    
    post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'DateDiff', 
         :method_params => {'0' => {'1' => '2006', '2' => '2', '3' => '1'}, '1' => {'1' => '2006', '2' => '2', '3' => '2'}}
    assert_equal 1, @controller.instance_eval{ @method_return_value }
  end
  
  def test_scaffold_struct_date_params
    post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'DateOfBirth', 
         :method_params => {'0' => {'birth' => {'1' => '2006', '2' => '2', '3' => '1'}, 'id' => '1', 'name' => 'person'}}
    assert_equal '2006-02-01', @controller.instance_eval{ @method_return_value }
  end

  def test_scaffold_time_params
    get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'TimeDiff'
    (0..1).each do |param|
      (1..6).each do |date_part|
        assert_tag :tag => 'select', :attributes => {:name => "method_params[#{param}][#{date_part}]"}, 
                   :children => {:greater_than => 1, :only => {:tag => 'option'}}
      end
    end

    post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'TimeDiff', 
         :method_params => {'0' => {'1' => '2006', '2' => '2', '3' => '1', '4' => '1', '5' => '1', '6' => '1'}, 
                            '1' => {'1' => '2006', '2' => '2', '3' => '2', '4' => '1', '5' => '1', '6' => '1'}}
    assert_equal 86400, @controller.instance_eval{ @method_return_value }
  end
  
  def test_scaffold_base64
    get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'Base64Upcase'
    assert_tag :tag => 'textarea', :attributes => {:name => 'method_params[0]'}
    
    post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Base64Upcase', :method_params => {'0' => 'scaffold'}
    assert_equal 'SCAFFOLD', @controller.instance_eval{ @method_return_value }
  end
end
require File.dirname(__FILE__) + '/abstract_unit'

module StructTest
  class Struct < ActionWebService::Struct
    member :id, Integer
    member :name, String
    member :items, [String]
    member :deleted, :bool
    member :emails, [:string]
  end
end

class TC_Struct < Test::Unit::TestCase
  include StructTest

  def setup
    @struct = Struct.new(:id      => 5,
                         :name    => 'hello',
                         :items   => ['one', 'two'],
                         :deleted => true,
                         :emails  => ['test@test.com'])
  end

  def test_members
    assert_equal(5, Struct.members.size)
    assert_equal(Integer, Struct.members[:id].type_class)
    assert_equal(String, Struct.members[:name].type_class)
    assert_equal(String, Struct.members[:items].element_type.type_class)
    assert_equal(TrueClass, Struct.members[:deleted].type_class)
    assert_equal(String, Struct.members[:emails].element_type.type_class)
  end

  def test_initializer_and_lookup
    assert_equal(5, @struct.id)
    assert_equal('hello', @struct.name)
    assert_equal(['one', 'two'], @struct.items)
    assert_equal(true, @struct.deleted)
    assert_equal(['test@test.com'], @struct.emails)
    assert_equal(5, @struct['id'])
    assert_equal('hello', @struct['name'])
    assert_equal(['one', 'two'], @struct['items'])
    assert_equal(true, @struct['deleted'])
    assert_equal(['test@test.com'], @struct['emails'])
  end

  def test_each_pair
    @struct.each_pair do |name, value|
      assert_equal @struct.__send__(name), value
      assert_equal @struct[name], value
    end
  end
end
require File.dirname(__FILE__) + '/abstract_unit'
require 'action_web_service/test_invoke'

class TestInvokeAPI < ActionWebService::API::Base
  api_method :add, :expects => [:int, :int], :returns => [:int]
end

class TestInvokeService < ActionWebService::Base
  web_service_api TestInvokeAPI

  attr :invoked

  def add(a, b)
    @invoked = true
    a + b
  end
end

class TestController < ActionController::Base
  def rescue_action(e); raise e; end
end

class TestInvokeDirectController < TestController
  web_service_api TestInvokeAPI

  attr :invoked

  def add
    @invoked = true
    @method_params[0] + @method_params[1]
  end
end

class TestInvokeDelegatedController < TestController
  web_service_dispatching_mode :delegated
  web_service :service, TestInvokeService.new
end

class TestInvokeLayeredController < TestController
  web_service_dispatching_mode :layered
  web_service(:one) { @service_one ||= TestInvokeService.new }
  web_service(:two) { @service_two ||= TestInvokeService.new }
end

class TestInvokeTest < Test::Unit::TestCase
  def setup
    @request  = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
  end

  def test_direct_add
    @controller = TestInvokeDirectController.new
    assert_equal nil, @controller.invoked
    result = invoke :add, 25, 25
    assert_equal 50, result
    assert_equal true, @controller.invoked
  end

  def test_delegated_add
    @controller = TestInvokeDelegatedController.new
    assert_equal nil, @controller.web_service_object(:service).invoked
    result = invoke_delegated :service, :add, 100, 50
    assert_equal 150, result
    assert_equal true, @controller.web_service_object(:service).invoked
  end

  def test_layered_add
    [:soap, :xmlrpc].each do |protocol|
      @protocol = protocol
      [:one, :two].each do |service|
        @controller = TestInvokeLayeredController.new
        assert_equal nil, @controller.web_service_object(service).invoked
        result = invoke_layered service, :add, 200, -50
        assert_equal 150, result
        assert_equal true, @controller.web_service_object(service).invoked
      end
    end
  end
  
  def test_layered_fail_with_wrong_number_of_arguments
    [:soap, :xmlrpc].each do |protocol|
      @protocol = protocol
      [:one, :two].each do |service|
        @controller = TestInvokeLayeredController.new
        assert_raise(ArgumentError) { invoke_layered service, :add, 1 }
      end
    end
  end

  def test_delegated_fail_with_wrong_number_of_arguments
    @controller = TestInvokeDelegatedController.new
    assert_raise(ArgumentError) { invoke_delegated :service, :add, 1 }
  end
  
  def test_direct_fail_with_wrong_number_of_arguments
    @controller = TestInvokeDirectController.new
    assert_raise(ArgumentError) { invoke :add, 1 }
  end
  
end
require File.dirname(__FILE__) + '/shared_setup'

logger = Logger.new(STDOUT)

# Database setup ---------------

logger.info "\nCreate tables"

[ "DROP TABLE companies", "DROP TABLE people", "DROP TABLE people_companies",
  "CREATE TABLE companies (id int(11) auto_increment, client_of int(11), name varchar(255), type varchar(100), PRIMARY KEY (id))",
  "CREATE TABLE people (id int(11) auto_increment, name varchar(100), PRIMARY KEY (id))",
  "CREATE TABLE people_companies (person_id int(11), company_id int(11), PRIMARY KEY (person_id, company_id))",
].each { |statement|
  # Tables doesn't necessarily already exist
  begin; ActiveRecord::Base.connection.execute(statement); rescue ActiveRecord::StatementInvalid; end
}


# Class setup ---------------

class Company < ActiveRecord::Base
  has_and_belongs_to_many :people, :class_name => "Person", :join_table => "people_companies", :table_name => "people"
end

class Firm < Company
  has_many :clients, :foreign_key => "client_of"

  def people_with_all_clients
    clients.inject([]) { |people, client| people + client.people }
  end
end

class Client < Company
  belongs_to :firm, :foreign_key => "client_of"
end

class Person < ActiveRecord::Base
  has_and_belongs_to_many :companies, :join_table => "people_companies"
  def self.table_name() "people" end
end


# Usage ---------------

logger.info "\nCreate fixtures"

Firm.new("name" => "Next Angle").save
Client.new("name" => "37signals", "client_of" => 1).save
Person.new("name" => "David").save


logger.info "\nUsing Finders"

next_angle = Company.find(1)
next_angle = Firm.find(1)    
next_angle = Company.find_first "name = 'Next Angle'"
next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first

Firm === next_angle


logger.info "\nUsing has_many association"

next_angle.has_clients?
next_angle.clients_count
all_clients = next_angle.clients

thirty_seven_signals = next_angle.find_in_clients(2)


logger.info "\nUsing belongs_to association"

thirty_seven_signals.has_firm?
thirty_seven_signals.firm?(next_angle)


logger.info "\nUsing has_and_belongs_to_many association"

david = Person.find(1)
david.add_companies(thirty_seven_signals, next_angle)
david.companies.include?(next_angle)
david.companies_count == 2

david.remove_companies(next_angle)
david.companies_count == 1

thirty_seven_signals.people.include?(david)# Be sure to change the mysql_connection details and create a database for the example

$: << File.dirname(__FILE__) + '/../lib'

require 'active_record'
require 'logger'; class Logger; def format_message(severity, timestamp, msg, progname) "#{msg}\n" end; end

ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(
  :adapter  => "mysql", 
  :host     => "localhost", 
  :username => "root", 
  :password => "", 
  :database => "activerecord_examples"
)
require File.dirname(__FILE__) + '/shared_setup'

logger = Logger.new(STDOUT)

# Database setup ---------------

logger.info "\nCreate tables"

[ "DROP TABLE people",
  "CREATE TABLE people (id int(11) auto_increment, name varchar(100), pass varchar(100), email varchar(100), PRIMARY KEY (id))"
].each { |statement|
  begin; ActiveRecord::Base.connection.execute(statement); rescue ActiveRecord::StatementInvalid; end # Tables doesn't necessarily already exist
}


# Class setup ---------------

class Person < ActiveRecord::Base
  # Using 
  def self.authenticate(name, pass)
    # find_first "name = '#{name}' AND pass = '#{pass}'" would be open to sql-injection (in a web-app scenario)
    find_first [ "name = '%s' AND pass = '%s'", name, pass ]
  end

  def self.name_exists?(name, id = nil)
    if id.nil?
      condition = [ "name = '%s'", name ]
    else
      # Check if anyone else than the person identified by person_id has that user_name
      condition = [ "name = '%s' AND id <> %d", name, id ]
    end

    !find_first(condition).nil?
  end

  def email_address_with_name
    "\"#{name}\" <#{email}>"
  end
        
  protected
    def validate
      errors.add_on_empty(%w(name pass email))
      errors.add("email", "must be valid") unless email_address_valid?
    end

    def validate_on_create
      if attribute_present?("name") && Person.name_exists?(name)
          errors.add("name", "is already taken by another person")
      end
    end

    def validate_on_update
      if attribute_present?("name") && Person.name_exists?(name, id)
          errors.add("name", "is already taken by another person")
      end
    end
  
  private
    def email_address_valid?() email =~ /\w[-.\w]*\@[-\w]+[-.\w]*\.\w+/ end
end

# Usage ---------------

logger.info "\nCreate fixtures"
david = Person.new("name" => "David Heinemeier Hansson", "pass" => "", "email" => "")
unless david.save
  puts "There was #{david.errors.count} error(s)"
  david.errors.each_full { |error| puts error }
end

david.pass = "something"
david.email = "invalid_address"
unless david.save
  puts "There was #{david.errors.count} error(s)"
  puts "It was email with: " + david.errors.on("email")
end

david.email = "david@loudthinking.com"
if david.save then puts "David finally made it!" end


another_david = Person.new("name" => "David Heinemeier Hansson", "pass" => "xc", "email" => "david@loudthinking")
unless another_david.save
  puts "Error on name: " + another_david.errors.on("name")
endrequire 'rbconfig'
require 'find'
require 'ftools'

include Config

# this was adapted from rdoc's install.rb by ways of Log4r

$sitedir = CONFIG["sitelibdir"]
unless $sitedir
  version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  $libdir = File.join(CONFIG["libdir"], "ruby", version)
  $sitedir = $:.find {|x| x =~ /site_ruby/ }
  if !$sitedir
    $sitedir = File.join($libdir, "site_ruby")
  elsif $sitedir !~ Regexp.quote(version)
    $sitedir = File.join($sitedir, version)
  end
end

# the acual gruntwork
Dir.chdir("lib")

Find.find("active_record", "active_record.rb") { |f|
  if f[-3..-1] == ".rb"
    File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  else
    File::makedirs(File.join($sitedir, *f.split(/\//)))
  end
}
module ActiveRecord
  module Acts #:nodoc:
    module List #:nodoc:
      def self.append_features(base)
        super
        base.extend(ClassMethods)
      end
      
      # This act provides the capabilities for sorting and reordering a number of objects in a list.
      # The class that has this specified needs to have a "position" column defined as an integer on
      # the mapped database table.
      #
      # Todo list example:
      #
      #   class TodoList < ActiveRecord::Base
      #     has_many :todo_items, :order => "position"
      #   end
      #
      #   class TodoItem < ActiveRecord::Base
      #     belongs_to :todo_list
      #     acts_as_list :scope => :todo_list
      #   end
      #
      #   todo_list.first.move_to_bottom
      #   todo_list.last.move_higher
      module ClassMethods
        # Configuration options are:
        #
        # * +column+ - specifies the column name to use for keeping the position integer (default: position)
        # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id" 
        #   (if that hasn't been already) and use that as the foreign key restriction. It's also possible 
        #   to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
        #   Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
        def acts_as_list(options = {})
          configuration = { :column => "position", :scope => "1 = 1" }
          configuration.update(options) if options.is_a?(Hash)

          configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
          
          if configuration[:scope].is_a?(Symbol)
            scope_condition_method = %(
              def scope_condition
                if #{configuration[:scope].to_s}.nil?
                  "#{configuration[:scope].to_s} IS NULL"
                else
                  "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
                end
              end
            )
          else
            scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
          end
          
          class_eval <<-EOV
            include ActiveRecord::Acts::List::InstanceMethods

            def acts_as_list_class
              ::#{self.name}
            end
            
            def position_column
              '#{configuration[:column]}'
            end
            
            #{scope_condition_method}
            
            after_destroy  :remove_from_list
            before_create  :add_to_list_bottom
          EOV
        end
      end
        
      # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
      # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
      # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return true if that chapter is
      # the first in the list of all chapters.
      module InstanceMethods
        def insert_at(position = 1)
          insert_at_position(position)
        end

        def move_lower
          return unless lower_item

          acts_as_list_class.transaction do
            lower_item.decrement_position
            increment_position
          end
        end
        
        def move_higher
          return unless higher_item

          acts_as_list_class.transaction do
            higher_item.increment_position
            decrement_position
          end
        end
        
        def move_to_bottom
          return unless in_list?
          acts_as_list_class.transaction do
            decrement_positions_on_lower_items
            assume_bottom_position
          end
        end
        
        def move_to_top
          return unless in_list?
          acts_as_list_class.transaction do
            increment_positions_on_higher_items
            assume_top_position
          end
        end
  
        def remove_from_list
          decrement_positions_on_lower_items if in_list?
        end

        def increment_position
          return unless in_list?
          update_attribute position_column, self.send(position_column).to_i + 1
        end
  
        def decrement_position
          return unless in_list?
          update_attribute position_column, self.send(position_column).to_i - 1
        end
  
        def first?
          return false unless in_list?
          self.send(position_column) == 1
        end
        
        def last?
          return false unless in_list?
          self.send(position_column) == bottom_position_in_list
        end
        
        def higher_item
          return nil unless in_list?
          acts_as_list_class.find(:first, :conditions =>
            "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
          )
        end

        def lower_item
          return nil unless in_list?
          acts_as_list_class.find(:first, :conditions =>
            "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
          )
        end

        def in_list?
          !send(position_column).nil?
        end

        private
          def add_to_list_top
            increment_positions_on_all_items
          end

          def add_to_list_bottom
            self[position_column] = bottom_position_in_list.to_i + 1
          end

          # Overwrite this method to define the scope of the list changes
          def scope_condition() "1" end

          def bottom_position_in_list(except = nil)
            item = bottom_item(except)
            item ? item.send(position_column) : 0
          end

          def bottom_item(except = nil)
            conditions = scope_condition
            conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
            acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
          end

          def assume_bottom_position
            update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
          end
  
          def assume_top_position
            update_attribute(position_column, 1)
          end

          # This has the effect of moving all the higher items up one.
          def decrement_positions_on_higher_items(position)
            acts_as_list_class.update_all(
              "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
            )
          end

          # This has the effect of moving all the lower items up one.
          def decrement_positions_on_lower_items
            return unless in_list?
            acts_as_list_class.update_all(
              "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
            )
          end

          # This has the effect of moving all the higher items down one.
          def increment_positions_on_higher_items
            return unless in_list?
            acts_as_list_class.update_all(
              "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
            )
          end

          # This has the effect of moving all the lower items down one.
          def increment_positions_on_lower_items(position)
            acts_as_list_class.update_all(
              "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
           )
          end

          def increment_positions_on_all_items
            acts_as_list_class.update_all(
              "#{position_column} = (#{position_column} + 1)",  "#{scope_condition}"
            )
          end

          def insert_at_position(position)
            remove_from_list
            increment_positions_on_lower_items(position)
            self.update_attribute(position_column, position)
          end
      end     
    end
  end
end
module ActiveRecord
  module Acts #:nodoc:
    module NestedSet #:nodoc:
      def self.append_features(base)
        super        
        base.extend(ClassMethods)              
      end  

      # This acts provides Nested Set functionality.  Nested Set is similiar to Tree, but with
      # the added feature that you can select the children and all of their descendents with
      # a single query.  A good use case for this is a threaded post system, where you want
      # to display every reply to a comment without multiple selects.
      #
      # A google search for "Nested Set" should point you in the direction to explain the
      # database theory.  I figured out a bunch of this from
      # http://threebit.net/tutorials/nestedset/tutorial1.html
      #
      # Instead of picturing a leaf node structure with children pointing back to their parent,
      # the best way to imagine how this works is to think of the parent entity surrounding all
      # of its children, and its parent surrounding it, etc.  Assuming that they are lined up
      # horizontally, we store the left and right boundries in the database.
      #
      # Imagine:
      #   root
      #     |_ Child 1
      #       |_ Child 1.1
      #       |_ Child 1.2
      #     |_ Child 2
      #       |_ Child 2.1
      #       |_ Child 2.2
      #
      # If my cirlces in circles description didn't make sense, check out this sweet
      # ASCII art:
      #
      #     ___________________________________________________________________
      #    |  Root                                                             |
      #    |    ____________________________    ____________________________   |
      #    |   |  Child 1                  |   |  Child 2                  |   |
      #    |   |   __________   _________  |   |   __________   _________  |   |
      #    |   |  |  C 1.1  |  |  C 1.2 |  |   |  |  C 2.1  |  |  C 2.2 |  |   |
      #    1   2  3_________4  5________6  7   8  9_________10 11_______12 13  14
      #    |   |___________________________|   |___________________________|   |
      #    |___________________________________________________________________| 
      #
      # The numbers represent the left and right boundries.  The table then might
      # look like this:
      #    ID | PARENT | LEFT | RIGHT | DATA
      #     1 |      0 |    1 |    14 | root
      #     2 |      1 |    2 |     7 | Child 1
      #     3 |      2 |    3 |     4 | Child 1.1
      #     4 |      2 |    5 |     6 | Child 1.2
      #     5 |      1 |    8 |    13 | Child 2
      #     6 |      5 |    9 |    10 | Child 2.1
      #     7 |      5 |   11 |    12 | Child 2.2
      #
      # So, to get all children of an entry, you
      #     SELECT * WHERE CHILD.LEFT IS BETWEEN PARENT.LEFT AND PARENT.RIGHT
      #
      # To get the count, it's (LEFT - RIGHT + 1)/2, etc.
      #
      # To get the direct parent, it falls back to using the PARENT_ID field.   
      #
      # There are instance methods for all of these.
      #
      # The structure is good if you need to group things together; the downside is that
      # keeping data integrity is a pain, and both adding and removing an entry
      # require a full table write.        
      #
      # This sets up a before_destroy trigger to prune the tree correctly if one of its
      # elements gets deleted.
      #
      module ClassMethods                      
        # Configuration options are:
        #
        # * +parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
        # * +left_column+ - column name for left boundry data, default "lft"
        # * +right_column+ - column name for right boundry data, default "rgt"
        # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id" 
        #   (if that hasn't been already) and use that as the foreign key restriction. It's also possible 
        #   to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
        #   Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
        def acts_as_nested_set(options = {})
          configuration = { :parent_column => "parent_id", :left_column => "lft", :right_column => "rgt", :scope => "1 = 1" }
          
          configuration.update(options) if options.is_a?(Hash)
          
          configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
          
          if configuration[:scope].is_a?(Symbol)
            scope_condition_method = %(
              def scope_condition
                if #{configuration[:scope].to_s}.nil?
                  "#{configuration[:scope].to_s} IS NULL"
                else
                  "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
                end
              end
            )
          else
            scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
          end
        
          class_eval <<-EOV
            include ActiveRecord::Acts::NestedSet::InstanceMethods

            #{scope_condition_method}
            
            def left_col_name() "#{configuration[:left_column]}" end

            def right_col_name() "#{configuration[:right_column]}" end
              
            def parent_column() "#{configuration[:parent_column]}" end

          EOV
        end
      end
      
      module InstanceMethods
        # Returns true is this is a root node.  
        def root?
          parent_id = self[parent_column]
          (parent_id == 0 || parent_id.nil?) && (self[left_col_name] == 1) && (self[right_col_name] > self[left_col_name])
        end                                                                                             
                                    
        # Returns true is this is a child node
        def child?                          
          parent_id = self[parent_column]
          !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
        end     
        
        # Returns true if we have no idea what this is
        def unknown?
          !root? && !child?
        end

                     
        # Adds a child to this object in the tree.  If this object hasn't been initialized,
        # it gets set up as a root node.  Otherwise, this method will update all of the
        # other elements in the tree and shift them to the right, keeping everything
        # balanced. 
        def add_child( child )     
          self.reload
          child.reload

          if child.root?
            raise "Adding sub-tree isn\'t currently supported"
          else
            if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
              # Looks like we're now the root node!  Woo
              self[left_col_name] = 1
              self[right_col_name] = 4
              
              # What do to do about validation?
              return nil unless self.save
              
              child[parent_column] = self.id
              child[left_col_name] = 2
              child[right_col_name]= 3
              return child.save
            else
              # OK, we need to add and shift everything else to the right
              child[parent_column] = self.id
              right_bound = self[right_col_name]
              child[left_col_name] = right_bound
              child[right_col_name] = right_bound + 1
              self[right_col_name] += 2
              self.class.transaction {
                self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)",  "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
                self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)",  "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
                self.save
                child.save
              }
            end
          end                                   
        end
                                   
        # Returns the number of nested children of this object.
        def children_count
          return (self[right_col_name] - self[left_col_name] - 1)/2
        end
                                                               
        # Returns a set of itself and all of its nested children
        def full_set
          self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} and #{self[right_col_name]})" )
        end
                  
        # Returns a set of all of its children and nested children
        def all_children
          self.class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} > #{self[left_col_name]}) and (#{right_col_name} < #{self[right_col_name]})" )
        end
                                  
        # Returns a set of only this entry's immediate children
        def direct_children
          self.class.find(:all, :conditions => "#{scope_condition} and #{parent_column} = #{self.id}")
        end
                                      
        # Prunes a branch off of the tree, shifting all of the elements on the right
        # back to the left so the counts still work.
        def before_destroy
          return if self[right_col_name].nil? || self[left_col_name].nil?
          dif = self[right_col_name] - self[left_col_name] + 1

          self.class.transaction {
            self.class.delete_all( "#{scope_condition} and #{left_col_name} > #{self[left_col_name]} and #{right_col_name} < #{self[right_col_name]}" )
            self.class.update_all( "#{left_col_name} = (#{left_col_name} - #{dif})",  "#{scope_condition} AND #{left_col_name} >= #{self[right_col_name]}" )
            self.class.update_all( "#{right_col_name} = (#{right_col_name} - #{dif} )",  "#{scope_condition} AND #{right_col_name} >= #{self[right_col_name]}" )
          }
        end
      end
    end
  end
end
module ActiveRecord
  module Acts #:nodoc:
    module Tree #:nodoc:
      def self.append_features(base)
        super        
        base.extend(ClassMethods)              
      end  

      # Specify this act if you want to model a tree structure by providing a parent association and a children 
      # association. This act requires that you have a foreign key column, which by default is called parent_id.
      # 
      #   class Category < ActiveRecord::Base
      #     acts_as_tree :order => "name"
      #   end
      #   
      #   Example : 
      #   root
      #    \_ child1 
      #         \_ subchild1
      #         \_ subchild2
      #
      #   root      = Category.create("name" => "root")
      #   child1    = root.children.create("name" => "child1")
      #   subchild1 = child1.children.create("name" => "subchild1")
      #
      #   root.parent   # => nil
      #   child1.parent # => root
      #   root.children # => [child1]
      #   root.children.first.children.first # => subchild1
      #
      # In addition to the parent and children associations, the following instance methods are added to the class 
      # after specifying the act:
      # * siblings          : Returns all the children of the parent, excluding the current node ([ subchild2 ] when called from subchild1)
      # * self_and_siblings : Returns all the children of the parent, including the current node ([ subchild1, subchild2 ] when called from subchild1)
      # * ancestors         : Returns all the ancestors of the current node ([child1, root] when called from subchild2)
      # * root              : Returns the root of the current node (root when called from subchild2)
      module ClassMethods
        # Configuration options are:
        #
        # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: parent_id)
        # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
        # * <tt>counter_cache</tt> - keeps a count in a children_count column if set to true (default: false).
        def acts_as_tree(options = {})
          configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil }
          configuration.update(options) if options.is_a?(Hash)

          belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
          has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy

          class_eval <<-EOV
            include ActiveRecord::Acts::Tree::InstanceMethods
            
            def self.roots
              find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
            end

            def self.root
              find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
            end
          EOV
        end
      end

      module InstanceMethods
        # Returns list of ancestors, starting from parent until root.
        #
        #   subchild1.ancestors # => [child1, root]
        def ancestors
          node, nodes = self, []
          nodes << node = node.parent until not node.has_parent?
          nodes
        end

        def root
          node = self
          node = node.parent until not node.has_parent?
          node
        end

        def siblings
          self_and_siblings - [self]
        end

        def self_and_siblings
          has_parent? ? parent.children : self.class.roots
        end
      end
    end
  end
end
module ActiveRecord
  module Aggregations # :nodoc:
    def self.included(base)
      base.extend(ClassMethods)
    end

    def clear_aggregation_cache #:nodoc:
      self.class.reflect_on_all_aggregations.to_a.each do |assoc|
        instance_variable_set "@#{assoc.name}", nil
      end unless self.new_record?
    end

    # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes 
    # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
    # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the 
    # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object) 
    # and how it can be turned back into attributes (when the entity is saved to the database). Example:
    #
    #   class Customer < ActiveRecord::Base
    #     composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
    #     composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
    #   end
    #
    # The customer class now has the following methods to manipulate the value objects:
    # * <tt>Customer#balance, Customer#balance=(money)</tt>
    # * <tt>Customer#address, Customer#address=(address)</tt>
    #
    # These methods will operate with value objects like the ones described below:
    #
    #  class Money
    #    include Comparable
    #    attr_reader :amount, :currency
    #    EXCHANGE_RATES = { "USD_TO_DKK" => 6 }  
    # 
    #    def initialize(amount, currency = "USD") 
    #      @amount, @currency = amount, currency 
    #    end
    #
    #    def exchange_to(other_currency)
    #      exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
    #      Money.new(exchanged_amount, other_currency)
    #    end
    #
    #    def ==(other_money)
    #      amount == other_money.amount && currency == other_money.currency
    #    end
    #
    #    def <=>(other_money)
    #      if currency == other_money.currency
    #        amount <=> amount
    #      else
    #        amount <=> other_money.exchange_to(currency).amount
    #      end
    #    end
    #  end
    #
    #  class Address
    #    attr_reader :street, :city
    #    def initialize(street, city) 
    #      @street, @city = street, city 
    #    end
    #
    #    def close_to?(other_address) 
    #      city == other_address.city 
    #    end
    #
    #    def ==(other_address)
    #      city == other_address.city && street == other_address.street
    #    end
    #  end
    #  
    # Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
    # composition the same as the attributes name, it will be the only way to access that attribute. That's the case with our
    # +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
    #
    #   customer.balance = Money.new(20)     # sets the Money value object and the attribute
    #   customer.balance                     # => Money value object
    #   customer.balance.exchanged_to("DKK") # => Money.new(120, "DKK")
    #   customer.balance > Money.new(10)     # => true
    #   customer.balance == Money.new(20)    # => true
    #   customer.balance < Money.new(5)      # => false
    #
    # Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
    # determine the order of the parameters. Example:
    #
    #   customer.address_street = "Hyancintvej"
    #   customer.address_city   = "Copenhagen"
    #   customer.address        # => Address.new("Hyancintvej", "Copenhagen")
    #   customer.address = Address.new("May Street", "Chicago")
    #   customer.address_street # => "May Street" 
    #   customer.address_city   # => "Chicago" 
    #
    # == Writing value objects
    #
    # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
    # $5. Two Money objects both representing $5 should be equal (through methods such as == and <=> from Comparable if ranking
    # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
    # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
    # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
    #
    # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
    # creation. Create a new money object with the new value instead. This is exemplified by the Money#exchanged_to method that
    # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
    # changed through other means than the writer method.
    #
    # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to 
    # change it afterwards will result in a TypeError.
    # 
    # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
    # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
    module ClassMethods
      # Adds the a reader and writer method for manipulating a value object, so
      # <tt>composed_of :address</tt> would add <tt>address</tt> and <tt>address=(new_address)</tt>.
      #
      # Options are:
      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
      #   from the part id. So <tt>composed_of :address</tt> will by default be linked to the +Address+ class, but
      #   if the real class name is +CompanyAddress+, you'll have to specify it with this option.
      # * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
      #   to a constructor parameter on the value class.
      #
      # Option examples:
      #   composed_of :temperature, :mapping => %w(reading celsius)
      #   composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
      #   composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
      #   composed_of :gps_location
      def composed_of(part_id, options = {})
        options.assert_valid_keys(:class_name, :mapping)

        name        = part_id.id2name
        class_name  = options[:class_name] || name_to_class_name(name)
        mapping     = options[:mapping] || [ name, name ]

        reader_method(name, class_name, mapping)
        writer_method(name, class_name, mapping)
        
        create_reflection(:composed_of, part_id, options, self)
      end

      private
        def name_to_class_name(name)
          name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
        end
        
        def reader_method(name, class_name, mapping)
          module_eval <<-end_eval
            def #{name}(force_reload = false)
              if @#{name}.nil? || force_reload
                @#{name} = #{class_name}.new(#{(Array === mapping.first ? mapping : [ mapping ]).collect{ |pair| "read_attribute(\"#{pair.first}\")"}.join(", ")})
              end
              
              return @#{name}
            end
          end_eval
        end        
        
        def writer_method(name, class_name, mapping)
          module_eval <<-end_eval
            def #{name}=(part)
              @#{name} = part.freeze
              #{(Array === mapping.first ? mapping : [ mapping ]).collect{ |pair| "@attributes[\"#{pair.first}\"] = part.#{pair.last}" }.join("\n")}
            end
          end_eval
        end
    end
  end
end
require 'set'

module ActiveRecord
  module Associations
    class AssociationCollection < AssociationProxy #:nodoc:
      def to_ary
        load_target
        @target.to_ary
      end
  
      def reset
        @target = []
        @loaded = false
      end

      # Add +records+ to this association.  Returns +self+ so method calls may be chained.  
      # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
      def <<(*records)
        result = true
        load_target

        @owner.transaction do
          flatten_deeper(records).each do |record|
            raise_on_type_mismatch(record)
            callback(:before_add, record)
            result &&= insert_record(record) unless @owner.new_record?
            @target << record
            callback(:after_add, record)
          end
        end
				
        result && self
      end

      alias_method :push, :<<
      alias_method :concat, :<<
                      
      # Remove all records from this association
      def delete_all
        load_target
        delete(@target)
        @target = []
      end

      # Remove +records+ from this association.  Does not destroy +records+.
      def delete(*records)
        records = flatten_deeper(records)
        records.each { |record| raise_on_type_mismatch(record) }
        records.reject! { |record| @target.delete(record) if record.new_record? }
        return if records.empty?
        
        @owner.transaction do
          records.each { |record| callback(:before_remove, record) }
          delete_records(records)
          records.each do |record|
            @target.delete(record)
            callback(:after_remove, record)
          end
        end
      end

      # Removes all records from this association.  Returns +self+ so method calls may be chained.
      def clear
        return self if length.zero? # forces load_target if hasn't happened already

        if @reflection.options[:dependent] && @reflection.options[:dependent] == :delete_all
          destroy_all
        else          
          delete_all
        end

        self
      end
      
      def destroy_all
        @owner.transaction do
          each { |record| record.destroy }
        end

        @target = []
      end
      
      def create(attributes = {})
        # Can't use Base.create since the foreign key may be a protected attribute.
        if attributes.is_a?(Array)
          attributes.collect { |attr| create(attr) }
        else
          record = build(attributes)
          record.save unless @owner.new_record?
          record
        end
      end

      # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
      # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
      # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
      def size
        if loaded? then @target.size else count_records end
      end
      
      # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
      # whether the collection is empty, use collection.length.zero? instead of collection.empty?
      def length
        load_target.size
      end
      
      def empty?
        size.zero?
      end
      
      def uniq(collection = self)
        collection.inject([]) { |uniq_records, record| uniq_records << record unless uniq_records.include?(record); uniq_records }
      end

      # Replace this collection with +other_array+
      # This will perform a diff and delete/add only records that have changed.
      def replace(other_array)
        other_array.each { |val| raise_on_type_mismatch(val) }

        load_target
        other   = other_array.size < 100 ? other_array : other_array.to_set
        current = @target.size < 100 ? @target : @target.to_set

        @owner.transaction do
          delete(@target.select { |v| !other.include?(v) })
          concat(other_array.select { |v| !current.include?(v) })
        end
      end

      private
        # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
        def flatten_deeper(array)
          array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
        end
        
        def callback(method, record)
          callbacks_for(method).each do |callback|
            case callback
              when Symbol
                @owner.send(callback, record)
              when Proc, Method
                callback.call(@owner, record)
              else
                if callback.respond_to?(method)
                  callback.send(method, @owner, record)
                else
                  raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
                end
            end
          end
        end
        
        def callbacks_for(callback_name)
          full_callback_name = "#{callback_name}_for_#{@reflection.name}"
          @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
        end
        
    end
  end
end
module ActiveRecord
  module Associations
    class AssociationProxy #:nodoc:
      attr_reader :reflection
      alias_method :proxy_respond_to?, :respond_to?
      alias_method :proxy_extend, :extend
      instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }

      def initialize(owner, reflection)
        @owner, @reflection = owner, reflection
        proxy_extend(reflection.options[:extend]) if reflection.options[:extend]
        reset
      end
      
      def respond_to?(symbol, include_priv = false)
        proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
      end
      
      # Explicitly proxy === because the instance method removal above
      # doesn't catch it.
      def ===(other)
        load_target
        other === @target
      end
      
      def aliased_table_name
        @reflection.klass.table_name
      end
      
      def conditions
        @conditions ||= eval("%(#{@reflection.active_record.send :sanitize_sql, @reflection.options[:conditions]})") if @reflection.options[:conditions]
      end
      alias :sql_conditions :conditions
      
      def reset
        @target = nil
        @loaded = false
      end

      def reload
        reset
        load_target
      end

      def loaded?
        @loaded
      end
      
      def loaded
        @loaded = true
      end
      
      def target
        @target
      end
      
      def target=(target)
        @target = target
        loaded
      end
      
      protected
        def dependent?
          @reflection.options[:dependent] || false
        end
        
        def quoted_record_ids(records)
          records.map { |record| record.quoted_id }.join(',')
        end

        def interpolate_sql_options!(options, *keys)
          keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
        end

        def interpolate_sql(sql, record = nil)
          @owner.send(:interpolate_sql, sql, record)
        end

        def sanitize_sql(sql)
          @reflection.klass.send(:sanitize_sql, sql)
        end

        def extract_options_from_args!(args)
          @owner.send(:extract_options_from_args!, args)
        end

        def set_belongs_to_association_for(record)
          if @reflection.options[:as]
            record["#{@reflection.options[:as]}_id"]   = @owner.id unless @owner.new_record?
            record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
          else
            record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
          end
        end

        def merge_options_from_reflection!(options)
          options.reverse_merge!(
            :group   => @reflection.options[:group],
            :limit   => @reflection.options[:limit],
            :offset  => @reflection.options[:offset],
            :joins   => @reflection.options[:joins],
            :include => @reflection.options[:include],
            :select  => @reflection.options[:select]
          )
        end
        
      private
        def method_missing(method, *args, &block)
          load_target
          @target.send(method, *args, &block)
        end

        def load_target
          if !@owner.new_record? || foreign_key_present
            begin
              @target = find_target if !loaded?
            rescue ActiveRecord::RecordNotFound
              reset
            end
          end

          loaded if target
          target
        end

        # Can be overwritten by associations that might have the foreign key available for an association without
        # having the object itself (and still being a new record). Currently, only belongs_to present this scenario.
        def foreign_key_present
          false
        end

        def raise_on_type_mismatch(record)
          unless record.is_a?(@reflection.klass)
            raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
          end
        end
    end
  end
end
module ActiveRecord
  module Associations
    class BelongsToAssociation < AssociationProxy #:nodoc:
      def create(attributes = {})
        replace(@reflection.klass.create(attributes))
      end

      def build(attributes = {})
        replace(@reflection.klass.new(attributes))
      end

      def replace(record)
        counter_cache_name = @reflection.counter_cache_column

        if record.nil?
          if counter_cache_name && @owner[counter_cache_name] && !@owner.new_record?
            @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
          end

          @target = @owner[@reflection.primary_key_name] = nil
        else
          raise_on_type_mismatch(record)

          if counter_cache_name && !@owner.new_record?
            @reflection.klass.increment_counter(counter_cache_name, record.id)
            @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
          end

          @target = (AssociationProxy === record ? record.target : record)
          @owner[@reflection.primary_key_name] = record.id unless record.new_record?
          @updated = true
        end

        loaded
        record
      end
      
      def updated?
        @updated
      end
      
      private
        def find_target
          @reflection.klass.find(
            @owner[@reflection.primary_key_name], 
            :conditions => conditions,
            :include    => @reflection.options[:include]
          )
        end

        def foreign_key_present
          !@owner[@reflection.primary_key_name].nil?
        end
    end
  end
end
module ActiveRecord
  module Associations
    class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
      def replace(record)
        if record.nil?
          @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
        else
          @target = (AssociationProxy === record ? record.target : record)

          unless record.new_record?
            @owner[@reflection.primary_key_name] = record.id
            @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
          end

          @updated = true
        end

        loaded
        record
      end

      def updated?
        @updated
      end

      private
        def find_target
          return nil if association_class.nil?

          if @reflection.options[:conditions]
            association_class.find(
              @owner[@reflection.primary_key_name], 
              :conditions => conditions,
              :include    => @reflection.options[:include]
            )
          else
            association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
          end
        end

        def foreign_key_present
          !@owner[@reflection.primary_key_name].nil?
        end

        def association_class
          @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
        end
    end
  end
end
module ActiveRecord
  module Associations
    class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
      def initialize(owner, reflection)
        super
        construct_sql
      end
 
      def build(attributes = {})
        load_target
        record = @reflection.klass.new(attributes)
        @target << record
        record
      end

      def find_first
        load_target.first
      end
      
      def find(*args)
        options = Base.send(:extract_options_from_args!, args)

        # If using a custom finder_sql, scan the entire collection.
        if @reflection.options[:finder_sql]
          expects_array = args.first.kind_of?(Array)
          ids = args.flatten.compact.uniq

          if ids.size == 1
            id = ids.first.to_i
            record = load_target.detect { |record| id == record.id }
            expects_array ? [record] : record
          else
            load_target.select { |record| ids.include?(record.id) }
          end
        else
          conditions = "#{@finder_sql}"

          if sanitized_conditions = sanitize_sql(options[:conditions])
            conditions << " AND (#{sanitized_conditions})"
          end

          options[:conditions] = conditions
          options[:joins]      = @join_sql
          options[:readonly]   = finding_with_ambigious_select?(options[:select])

          if options[:order] && @reflection.options[:order]
            options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
          elsif @reflection.options[:order]
            options[:order] = @reflection.options[:order]
          end

          merge_options_from_reflection!(options)

          # Pass through args exactly as we received them.
          args << options
          @reflection.klass.find(*args)
        end
      end      

      def push_with_attributes(record, join_attributes = {})
        raise_on_type_mismatch(record)
        join_attributes.each { |key, value| record[key.to_s] = value }

        callback(:before_add, record)
        insert_record(record) unless @owner.new_record?
        @target << record
        callback(:after_add, record)

        self
      end
      
      alias :concat_with_attributes :push_with_attributes

      def size
        @reflection.options[:uniq] ? count_records : super
      end
      
      protected
        def method_missing(method, *args, &block)
          if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
            super
          else
            @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
              @reflection.klass.send(method, *args, &block)
            end
          end
        end
            
        def find_target
          if @reflection.options[:finder_sql]
            records = @reflection.klass.find_by_sql(@finder_sql)
          else
            records = find(:all)
          end
          
          @reflection.options[:uniq] ? uniq(records) : records
        end
        
        def count_records
          load_target.size
        end

        def insert_record(record)
          if record.new_record?
            return false unless record.save
          end

          if @reflection.options[:insert_sql]
            @owner.connection.execute(interpolate_sql(@reflection.options[:insert_sql], record))
          else
            columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")

            attributes = columns.inject({}) do |attributes, column|
              case column.name
                when @reflection.primary_key_name
                  attributes[column.name] = @owner.quoted_id
                when @reflection.association_foreign_key
                  attributes[column.name] = record.quoted_id
                else
                  if record.attributes.has_key?(column.name)
                    value = @owner.send(:quote, record[column.name], column)
                    attributes[column.name] = value unless value.nil?
                  end
              end
              attributes
            end

            sql =
              "INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
              "VALUES (#{attributes.values.join(', ')})"

            @owner.connection.execute(sql)
          end
          
          return true
        end
        
        def delete_records(records)
          if sql = @reflection.options[:delete_sql]
            records.each { |record| @owner.connection.execute(interpolate_sql(sql, record)) }
          else
            ids = quoted_record_ids(records)
            sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
            @owner.connection.execute(sql)
          end
        end
        
        def construct_sql
          interpolate_sql_options!(@reflection.options, :finder_sql)

          if @reflection.options[:finder_sql]
            @finder_sql = @reflection.options[:finder_sql]
          else
            @finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
            @finder_sql << " AND (#{conditions})" if conditions
          end

          @join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
        end

        # Join tables with additional columns on top of the two foreign keys must be considered ambigious unless a select
        # clause has been explicitly defined. Otherwise you can get broken records back, if, say, the join column also has
        # and id column, which will then overwrite the id column of the records coming back.
        def finding_with_ambigious_select?(select_clause)
          !select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
        end
    end
  end
end
module ActiveRecord
  module Associations
    class HasManyAssociation < AssociationCollection #:nodoc:
      def initialize(owner, reflection)
        super
        construct_sql
      end

      def build(attributes = {})
        if attributes.is_a?(Array)
          attributes.collect { |attr| build(attr) }
        else
          load_target
          record = @reflection.klass.new(attributes)
          set_belongs_to_association_for(record)
          @target << record
          record
        end
      end

      # DEPRECATED.
      def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
        if @reflection.options[:finder_sql]
          @reflection.klass.find_by_sql(@finder_sql)
        else
          conditions = @finder_sql
          conditions += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
          orderings ||= @reflection.options[:order]
          @reflection.klass.find_all(conditions, orderings, limit, joins)
        end
      end

      # DEPRECATED. Find the first associated record.  All arguments are optional.
      def find_first(conditions = nil, orderings = nil)
        find_all(conditions, orderings, 1).first
      end

      # Count the number of associated records. All arguments are optional.
      def count(runtime_conditions = nil)
        if @reflection.options[:counter_sql]
          @reflection.klass.count_by_sql(@counter_sql)
        elsif @reflection.options[:finder_sql]
          @reflection.klass.count_by_sql(@finder_sql)
        else
          sql = @finder_sql
          sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
          @reflection.klass.count(sql)
        end
      end

      def find(*args)
        options = Base.send(:extract_options_from_args!, args)

        # If using a custom finder_sql, scan the entire collection.
        if @reflection.options[:finder_sql]
          expects_array = args.first.kind_of?(Array)
          ids = args.flatten.compact.uniq

          if ids.size == 1
            id = ids.first
            record = load_target.detect { |record| id == record.id }
            expects_array ? [ record ] : record
          else
            load_target.select { |record| ids.include?(record.id) }
          end
        else
          conditions = "#{@finder_sql}"
          if sanitized_conditions = sanitize_sql(options[:conditions])
            conditions << " AND (#{sanitized_conditions})"
          end
          options[:conditions] = conditions

          if options[:order] && @reflection.options[:order]
            options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
          elsif @reflection.options[:order]
            options[:order] = @reflection.options[:order]
          end

          merge_options_from_reflection!(options)

          # Pass through args exactly as we received them.
          args << options
          @reflection.klass.find(*args)
        end
      end
      
      protected
        def method_missing(method, *args, &block)
          if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
            super
          else
            @reflection.klass.with_scope(
              :find => {
                :conditions => @finder_sql, 
                :joins      => @join_sql, 
                :readonly   => false
              },
              :create => {
                @reflection.primary_key_name => @owner.id
              }
            ) do
              @reflection.klass.send(method, *args, &block)
            end
          end
        end
            
        def find_target
          if @reflection.options[:finder_sql]
            @reflection.klass.find_by_sql(@finder_sql)
          else
            find(:all)
          end
        end

        def count_records
          count = if has_cached_counter?
            @owner.send(:read_attribute, cached_counter_attribute_name)
          elsif @reflection.options[:counter_sql]
            @reflection.klass.count_by_sql(@counter_sql)
          else
            @reflection.klass.count(@counter_sql)
          end
          
          @target = [] and loaded if count == 0
          
          if @reflection.options[:limit]
            count = [ @reflection.options[:limit], count ].min
          end
          
          return count
        end

        def has_cached_counter?
          @owner.attribute_present?(cached_counter_attribute_name)
        end

        def cached_counter_attribute_name
          "#{@reflection.name}_count"
        end

        def insert_record(record)
          set_belongs_to_association_for(record)
          record.save
        end

        def delete_records(records)
          if @reflection.options[:dependent]
            records.each { |r| r.destroy }
          else
            ids = quoted_record_ids(records)
            @reflection.klass.update_all(
              "#{@reflection.primary_key_name} = NULL", 
              "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
            )
          end
        end

        def target_obsolete?
          false
        end

        def construct_sql
          case
            when @reflection.options[:finder_sql]
              @finder_sql = interpolate_sql(@reflection.options[:finder_sql])

            when @reflection.options[:as]
              @finder_sql = 
                "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " + 
                "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
              @finder_sql << " AND (#{conditions})" if conditions
            
            else
              @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
              @finder_sql << " AND (#{conditions})" if conditions
          end

          if @reflection.options[:counter_sql]
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          elsif @reflection.options[:finder_sql]
            # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
            @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          else
            @counter_sql = @finder_sql
          end
        end
    end
  end
end
module ActiveRecord
  module Associations
    class HasManyThroughAssociation < AssociationProxy #:nodoc:
      def initialize(owner, reflection)
        super
        reflection.check_validity!
        @finder_sql = construct_conditions
        construct_sql
      end


      def find(*args)
        options = Base.send(:extract_options_from_args!, args)

        conditions = "#{@finder_sql}"
        if sanitized_conditions = sanitize_sql(options[:conditions])
          conditions << " AND (#{sanitized_conditions})"
        end
        options[:conditions] = conditions

        if options[:order] && @reflection.options[:order]
          options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
        elsif @reflection.options[:order]
          options[:order] = @reflection.options[:order]
        end
        
        options[:select]  = construct_select(options[:select])
        options[:from]  ||= construct_from
        options[:joins]   = construct_joins(options[:joins])
        options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
        
        merge_options_from_reflection!(options)

        # Pass through args exactly as we received them.
        args << options
        @reflection.klass.find(*args)
      end

      def reset
        @target = []
        @loaded = false
      end

      protected
        def method_missing(method, *args, &block)
          if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
            super
          else
            @reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
          end
        end
            
        def find_target
          @reflection.klass.find(:all, 
            :select     => construct_select,
            :conditions => construct_conditions,
            :from       => construct_from,
            :joins      => construct_joins,
            :order      => @reflection.options[:order], 
            :limit      => @reflection.options[:limit],
            :group      => @reflection.options[:group],
            :include    => @reflection.options[:include] || @reflection.source_reflection.options[:include]
          )
        end

        def construct_conditions
          conditions = if @reflection.through_reflection.options[:as]
              "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_id = #{@owner.quoted_id} " + 
              "AND #{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"
          else
            "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.primary_key_name} = #{@owner.quoted_id}"
          end
          conditions << " AND (#{sql_conditions})" if sql_conditions
          
          return conditions
        end

        def construct_from
          @reflection.table_name
        end
        
        def construct_select(custom_select = nil)
          selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"          
        end
        
        def construct_joins(custom_joins = nil)          
          polymorphic_join = nil
          if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
            reflection_primary_key = @reflection.klass.primary_key
            source_primary_key     = @reflection.source_reflection.primary_key_name
          else
            reflection_primary_key = @reflection.source_reflection.primary_key_name
            source_primary_key     = @reflection.klass.primary_key
            if @reflection.source_reflection.options[:as]
              polymorphic_join = "AND %s.%s = %s" % [
                @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type",
                @owner.class.quote(@reflection.through_reflection.klass.name)
              ]
            end
          end

          "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
            @reflection.through_reflection.table_name,
            @reflection.table_name, reflection_primary_key,
            @reflection.through_reflection.table_name, source_primary_key,
            polymorphic_join
          ]
        end
        
        def construct_scope
          {
            :find   => { :from => construct_from, :conditions => construct_conditions, :joins => construct_joins, :select => construct_select },
            :create => { @reflection.primary_key_name => @owner.id }
          }
        end
        
        def construct_sql
          case
            when @reflection.options[:finder_sql]
              @finder_sql = interpolate_sql(@reflection.options[:finder_sql])

              @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
              @finder_sql << " AND (#{conditions})" if conditions
          end

          if @reflection.options[:counter_sql]
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          elsif @reflection.options[:finder_sql]
            # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
            @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
            @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
          else
            @counter_sql = @finder_sql
          end
        end
        
        def conditions
          @conditions ||= [
            (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
            (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions])
          ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions])
        end
        
        alias_method :sql_conditions, :conditions
    end
  end
end
module ActiveRecord
  module Associations
    class HasOneAssociation < BelongsToAssociation #:nodoc:
      def initialize(owner, reflection)
        super
        construct_sql
      end

      def create(attributes = {}, replace_existing = true)
        record = build(attributes, replace_existing)
        record.save
        record
      end

      def build(attributes = {}, replace_existing = true)
        record = @reflection.klass.new(attributes)

        if replace_existing
          replace(record, true) 
        else
          record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
          self.target = record
        end

        record
      end

      def replace(obj, dont_save = false)
        load_target

        unless @target.nil?
          if dependent? && !dont_save && @target != obj
            @target.destroy unless @target.new_record?
            @owner.clear_association_cache
          else
            @target[@reflection.primary_key_name] = nil
            @target.save unless @owner.new_record? || @target.new_record?
          end
        end

        if obj.nil?
          @target = nil
        else
          raise_on_type_mismatch(obj)
          set_belongs_to_association_for(obj)
          @target = (AssociationProxy === obj ? obj.target : obj)
        end

        @loaded = true

        unless @owner.new_record? or obj.nil? or dont_save
          return (obj.save ? self : false)
        else
          return (obj.nil? ? nil : self)
        end
      end
            
      private
        def find_target
          @reflection.klass.find(:first, 
            :conditions => @finder_sql, 
            :order      => @reflection.options[:order], 
            :include    => @reflection.options[:include]
          )
        end

        def construct_sql
          case
            when @reflection.options[:as]
              @finder_sql = 
                "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " + 
                "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote @owner.class.base_class.name.to_s}"          
            else
              @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
          end
          @finder_sql << " AND (#{conditions})" if conditions
        end
    end
  end
end
require 'active_record/associations/association_proxy'
require 'active_record/associations/association_collection'
require 'active_record/associations/belongs_to_association'
require 'active_record/associations/belongs_to_polymorphic_association'
require 'active_record/associations/has_one_association'
require 'active_record/associations/has_many_association'
require 'active_record/associations/has_many_through_association'
require 'active_record/associations/has_and_belongs_to_many_association'
require 'active_record/deprecated_associations'

module ActiveRecord
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
    def initialize(reflection)
      @reflection = reflection
    end
    
    def message
      "Could not find the association #{@reflection.options[:through].inspect} in model #{@reflection.klass}"
    end
  end

  class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
    def initialize(owner_class_name, reflection, source_reflection)
      @owner_class_name  = owner_class_name
      @reflection        = reflection
      @source_reflection = source_reflection
    end
    
    def message
      "Cannot have a has_many :through association '#{@owner_class_name}##{@reflection.name}' on the polymorphic object '#{@source_reflection.class_name}##{@source_reflection.name}'."
    end
  end

  class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
    def initialize(reflection)
      @reflection              = reflection
      @through_reflection      = reflection.through_reflection
      @source_reflection_names = reflection.source_reflection_names
      @source_associations     = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
    end
    
    def message
      "Could not find the source association(s) #{@source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{@through_reflection.klass}.  Try 'has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}, :source => <name>'.  Is it one of #{@source_associations.to_sentence :connector => 'or'}?"
    end
  end

  class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc
    def initialize(reflection)
      @reflection         = reflection
      @through_reflection = reflection.through_reflection
      @source_reflection  = reflection.source_reflection
    end
    
    def message
      "Invalid source reflection macro :#{@source_reflection.macro}#{" :through" if @source_reflection.options[:through]} for has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}.  Use :source to specify the source reflection."
    end
  end

  class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
    def initialize(reflection)
      @reflection = reflection
    end
    
    def message
      "Can not eagerly load the polymorphic association #{@reflection.name.inspect}"
    end
  end

  module Associations # :nodoc:
    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    # Clears out the association cache 
    def clear_association_cache #:nodoc:
      self.class.reflect_on_all_associations.to_a.each do |assoc|
        instance_variable_set "@#{assoc.name}", nil
      end unless self.new_record?
    end
    
    # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like 
    # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are 
    # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr* 
    # methods. Example:
    #
    #   class Project < ActiveRecord::Base
    #     belongs_to              :portfolio
    #     has_one                 :project_manager 
    #     has_many                :milestones
    #     has_and_belongs_to_many :categories
    #   end
    #
    # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
    # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
    # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
    # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
    #   <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions),</tt>
    #   <tt>Project#milestones.build, Project#milestones.create</tt>
    # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
    #   <tt>Project#categories.delete(category1)</tt>
    #
    # == Example
    #
    # link:files/examples/associations.png
    #
    # == Is it belongs_to or has_one?
    #
    # Both express a 1-1 relationship, the difference is mostly where to place the foreign key, which goes on the table for the class
    # saying belongs_to. Example:
    #
    #   class Post < ActiveRecord::Base
    #     has_one :author
    #   end
    #
    #   class Author < ActiveRecord::Base
    #     belongs_to :post
    #   end
    #
    # The tables for these classes could look something like:
    #
    #   CREATE TABLE posts (
    #     id int(11) NOT NULL auto_increment,
    #     title varchar default NULL,
    #     PRIMARY KEY  (id)
    #   )
    #
    #   CREATE TABLE authors (
    #     id int(11) NOT NULL auto_increment,
    #     post_id int(11) default NULL,
    #     name varchar default NULL,
    #     PRIMARY KEY  (id)
    #   )
    #
    # == Unsaved objects and associations
    #
    # You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
    # aware of, mostly involving the saving of associated objects.
    #
    # === One-to-one associations
    #
    # * Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in
    #   order to update their primary keys - except if the parent object is unsaved (new_record? == true).
    # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
    #   is cancelled.
    # * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
    # * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
    #   not save the parent either.
    #
    # === Collections
    #
    # * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
    #   (the owner of the collection) is not yet stored in the database.
    # * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
    # * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
    # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
    #
    # === Association callbacks
    #
    # Similiar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
    # trigged when you add an object to or removing an object from a association collection. Example:
    #
    #   class Project
    #     has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
    #
    #     def evaluate_velocity(developer)
    #       ...
    #     end
    #   end 
    #
    # It's possible to stack callbacks by passing them as an array. Example:
    # 
    #   class Project
    #     has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
    #   end
    #
    # Possible callbacks are: before_add, after_add, before_remove and after_remove.
    #
    # Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
    # the before_remove callbacks, if an exception is thrown the object doesn't get removed.
    #
    # === Association extensions
    #
    # The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
    # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
    # Example:
    #
    #   class Account < ActiveRecord::Base
    #     has_many :people do
    #       def find_or_create_by_name(name)
    #         first_name, last_name = name.split(" ", 2)
    #         find_or_create_by_first_name_and_last_name(first_name, last_name)
    #       end
    #     end
    #   end
    #
    #   person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
    #   person.first_name # => "David"
    #   person.last_name  # => "Heinemeier Hansson"
    #
    # If you need to share the same extensions between many associations, you can use a named extension module. Example:
    #
    #   module FindOrCreateByNameExtension
    #     def find_or_create_by_name(name)
    #       first_name, last_name = name.split(" ", 2)
    #       find_or_create_by_first_name_and_last_name(first_name, last_name)
    #     end
    #   end
    #
    #   class Account < ActiveRecord::Base
    #     has_many :people, :extend => FindOrCreateByNameExtension
    #   end
    #
    #   class Company < ActiveRecord::Base
    #     has_many :people, :extend => FindOrCreateByNameExtension
    #   end
    #
    # === Association Join Models
    # 
    # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data.  This
    # operates similarly to a <tt>has_and_belongs_to_many</tt> association.  The advantage is that you're able to add validations,
    # callbacks, and extra attributes on the join model.  Consider the following schema:
    # 
    #   class Author < ActiveRecord::Base
    #     has_many :authorships
    #     has_many :books, :through => :authorships
    #   end
    # 
    #   class Authorship < ActiveRecord::Base
    #     belongs_to :author
    #     belongs_to :book
    #   end
    # 
    #   @author = Author.find :first
    #   @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
    #   @author.books                              # selects all books by using the Authorship join model
    # 
    # You can also go through a has_many association on the join model:
    # 
    #   class Firm < ActiveRecord::Base
    #     has_many   :clients
    #     has_many   :invoices, :through => :clients
    #   end
    #   
    #   class Client < ActiveRecord::Base
    #     belongs_to :firm
    #     has_many   :invoices
    #   end
    #   
    #   class Invoice < ActiveRecord::Base
    #     belongs_to :client
    #   end
    #
    #   @firm = Firm.find :first
    #   @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
    #   @firm.invoices                                   # selects all invoices by going through the Client join model.
    #
    # === Polymorphic Associations
    # 
    # Polymorphic associations on models are not restricted on what types of models they can be associated with.  Rather, they 
    # specify an interface that a has_many association must adhere to.
    # 
    #   class Asset < ActiveRecord::Base
    #     belongs_to :attachable, :polymorphic => true
    #   end
    # 
    #   class Post < ActiveRecord::Base
    #     has_many :assets, :as => :attachable         # The <tt>:as</tt> option specifies the polymorphic interface to use.
    #   end
    #
    #   @asset.attachable = @post
    # 
    # This works by using a type column in addition to a foreign key to specify the associated record.  In the Asset example, you'd need
    # an attachable_id integer column and an attachable_type string column.
    #
    # == Caching
    #
    # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
    # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without 
    # worrying too much about performance at the first go. Example:
    #
    #   project.milestones             # fetches milestones from the database
    #   project.milestones.size        # uses the milestone cache
    #   project.milestones.empty?      # uses the milestone cache
    #   project.milestones(true).size  # fetches milestones from the database
    #   project.milestones             # uses the milestone cache
    #
    # == Eager loading of associations
    #
    # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
    # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each needs to display their author
    # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
    #
    #   class Post < ActiveRecord::Base
    #     belongs_to :author
    #     has_many   :comments
    #   end
    #
    # Consider the following loop using the class above:
    #
    #   for post in Post.find(:all)
    #     puts "Post:            " + post.title
    #     puts "Written by:      " + post.author.name
    #     puts "Last comment on: " + post.comments.first.created_on
    #   end 
    #
    # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
    #
    #   for post in Post.find(:all, :include => :author)
    #
    # This references the name of the belongs_to association that also used the :author symbol, so the find will now weave in a join something
    # like this: LEFT OUTER JOIN authors ON authors.id = posts.author_id. Doing so will cut down the number of queries from 201 to 101.
    #
    # We can improve upon the situation further by referencing both associations in the finder with:
    #
    #   for post in Post.find(:all, :include => [ :author, :comments ])
    #
    # That'll add another join along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id. And we'll be down to 1 query.
    # But that shouldn't fool you to think that you can pull out huge amounts of data with no performance penalty just because you've reduced
    # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
    # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
    #
    # Please note that limited eager loading with has_many and has_and_belongs_to_many associations is not compatible with describing conditions
    # on these eager tables. This will work:
    #
    #   Post.find(:all, :include => :comments, :conditions => "posts.title = 'magic forest'", :limit => 2)
    #
    # ...but this will not (and an ArgumentError will be raised):
    #
    #   Post.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%'", :limit => 2)
    #
    # Also have in mind that since the eager loading is pulling from multiple tables, you'll have to disambiguate any column references
    # in both conditions and orders. So :order => "posts.id DESC" will work while :order => "id DESC" will not. This may require that
    # you alter the :order and :conditions on the association definitions themselves.
    #
    # It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will not pull
    # additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading.
    # 
    # == Table Aliasing
    #
    # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join.  If a table is referenced only once,
    # the standard table name is used.  The second time, the table is aliased as #{reflection_name}_#{parent_table_name}.  Indexes are appended
    # for any more successive uses of the table name.
    # 
    #   Post.find :all, :include => :comments
    #   # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
    #   Post.find :all, :include => :special_comments # STI
    #   # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
    #   Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
    #   # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
    # 
    # Acts as tree example:
    # 
    #   TreeMixin.find :all, :include => :children
    #   # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
    #   TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
    #   # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... 
    #                               LEFT OUTER JOIN parents_mixins ...
    #   TreeMixin.find :all, :include => {:children => {:parent => :children}} 
    #   # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ... 
    #                               LEFT OUTER JOIN parents_mixins ... 
    # LEFT OUTER JOIN mixins childrens_mixins_2
    # 
    # Has and Belongs to Many join tables use the same idea, but add a _join suffix:
    # 
    #   Post.find :all, :include => :categories
    #   # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
    #   Post.find :all, :include => {:categories => :posts}
    #   # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
    #                              LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
    #   Post.find :all, :include => {:categories => {:posts => :categories}}
    #   # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
    #                              LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
    #                              LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
    # 
    # If you wish to specify your own custom joins using a :joins option, those table names will take precedence over the eager associations..
    # 
    #   Post.find :all, :include => :comments, :joins => "inner join comments ..."
    #   # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
    #   Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
    #   # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ... 
    #                              LEFT OUTER JOIN comments special_comments_posts ...
    #                              INNER JOIN comments ...
    # 
    # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
    # 
    # == Modules
    #
    # By default, associations will look for objects within the current module scope. Consider:
    #
    #   module MyApplication
    #     module Business
    #       class Firm < ActiveRecord::Base
    #          has_many :clients
    #        end
    #
    #       class Company < ActiveRecord::Base; end
    #     end
    #   end
    #
    # When Firm#clients is called, it'll in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
    # with a class in another module scope this can be done by specifying the complete class name, such as:
    #
    #   module MyApplication
    #     module Business
    #       class Firm < ActiveRecord::Base; end
    #     end
    #
    #     module Billing
    #       class Account < ActiveRecord::Base
    #         belongs_to :firm, :class_name => "MyApplication::Business::Firm"
    #       end
    #     end
    #   end
    #
    # == Type safety with ActiveRecord::AssociationTypeMismatch
    #
    # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
    # get a ActiveRecord::AssociationTypeMismatch.
    #
    # == Options
    #
    # All of the association macros can be specialized through options which makes more complex cases than the simple and guessable ones
    # possible.
    module ClassMethods
      # Adds the following methods for retrieval and query of collections of associated objects.
      # +collection+ is replaced with the symbol passed as the first argument, so 
      # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
      # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
      #   An empty array is returned if none are found.
      # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
      # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.  
      #   This will also destroy the objects if they're declared as belongs_to and dependent on this model.
      # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
      # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
      # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
      #   are <tt>:dependent</tt>, deletes them directly from the database if they are <tt>:dependent => :delete_all</tt>,
      #   and sets their foreign keys to NULL otherwise.
      # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
      # * <tt>collection.size</tt> - returns the number of associated objects.
      # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
      # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key but has not yet been saved. *Note:* This only works if an 
      #   associated object already exists, not if it's nil!
      # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
      #   *Note:* This only works if an associated object already exists, not if it's nil!
      #
      # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
      # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
      # * <tt>Firm#clients<<</tt>
      # * <tt>Firm#clients.delete</tt>
      # * <tt>Firm#clients=</tt>
      # * <tt>Firm#client_ids=</tt>
      # * <tt>Firm#clients.clear</tt>
      # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
      # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
      # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
      # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
      # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
      # The declaration can also include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
      #   if the real class name is +SpecialProduct+, you'll have to specify it with this option.
      # * <tt>:conditions</tt>  - specify the conditions that the associated objects must meet in order to be included as a "WHERE"
      #   sql fragment, such as "price > 5 AND name LIKE 'B%'".
      # * <tt>:order</tt>       - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment,
      #   such as "last_name, first_name DESC"
      # * <tt>:group</tt>       - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment,
      #   such as "category"      
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_many association will use "person_id"
      #   as the default foreign_key.
      # * <tt>:dependent</tt>   - if set to :destroy all the associated objects are destroyed
      #   alongside this object by calling their destroy method.  If set to :delete_all all associated
      #   objects are deleted *without* calling their destroy method.  If set to :nullify all associated
      #   objects' foreign keys are set to NULL *without* calling their save callbacks.
      #   NOTE: :dependent => true is deprecated and has been replaced with :dependent => :destroy. 
      #   May not be set if :exclusively_dependent is also set.
      # * <tt>:exclusively_dependent</tt>   - Deprecated; equivalent to :dependent => :delete_all. If set to true all
      #   the associated object are deleted in one SQL statement without having their
      #   before_destroy callback run. This should only be used on associations that depend solely on this class and don't need to do any
      #   clean-up in before_destroy. The upside is that it's much faster, especially if there's a counter_cache involved.
      #   May not be set if :dependent is also set.
      # * <tt>:finder_sql</tt>  - specify a complete SQL statement to fetch the association. This is a good way to go for complex
      #   associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
      # * <tt>:counter_sql</tt>  - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
      #   specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
      # * <tt>:extend</tt>  - specify a named module for extending the proxy, see "Association extensions".
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when the collection is loaded.
      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
      # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
      # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
      # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
      #   include the joined columns.
      # * <tt>:as</tt>: Specifies a polymorphic interface (See #belongs_to).
      # * <tt>:through</tt>: Specifies a Join Model to perform the query through.  Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> 
      #   are ignored, as the association uses the source reflection.  You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
      #   or <tt>has_many</tt> association.
      # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries.  Only use it if the name cannot be 
      #   inferred from the association.  <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
      #   +:subscriber+ on +Subscription+, unless a +:source+ is given.
      #
      # Option examples:
      #   has_many :comments, :order => "posted_on"
      #   has_many :comments, :include => :author
      #   has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
      #   has_many :tracks, :order => "position", :dependent => :destroy
      #   has_many :comments, :dependent => :nullify
      #   has_many :tags, :as => :taggable
      #   has_many :subscribers, :through => :subscriptions, :source => :user
      #   has_many :subscribers, :class_name => "Person", :finder_sql =>
      #       'SELECT DISTINCT people.* ' +
      #       'FROM people p, post_subscriptions ps ' +
      #       'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
      #       'ORDER BY p.first_name'
      def has_many(association_id, options = {}, &extension)
        reflection = create_has_many_reflection(association_id, options, &extension)

        configure_dependency_for_has_many(reflection)

        if options[:through]
          collection_reader_method(reflection, HasManyThroughAssociation)
        else
          add_multiple_associated_save_callbacks(reflection.name)
          add_association_callbacks(reflection.name, reflection.options)
          collection_accessor_methods(reflection, HasManyAssociation)
        end

        add_deprecated_api_for_has_many(reflection.name)
      end

      # Adds the following methods for retrieval and query of a single associated object.
      # +association+ is replaced with the symbol passed as the first argument, so 
      # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
      # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
      # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key, 
      #   and saves the associate object.
      # * <tt>association.nil?</tt> - returns true if there is no associated object.
      # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key but has not yet been saved. Note: This ONLY works if
      #   an association already exists. It will NOT work if the association is nil.
      # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
      #
      # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
      # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
      # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
      # * <tt>Account#beneficiary.nil?</tt>
      # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
      # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
      #
      # The declaration can also include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
      #   if the real class name is +Person+, you'll have to specify it with this option.
      # * <tt>:conditions</tt>  - specify the conditions that the associated object must meet in order to be included as a "WHERE"
      #   sql fragment, such as "rank = 5".
      # * <tt>:order</tt>       - specify the order from which the associated object will be picked at the top. Specified as
      #    an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
      # * <tt>:dependent</tt>   - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also,
      #   association is assigned.
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
      #   as the default foreign_key.
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when this object is loaded.
      #
      # Option examples:
      #   has_one :credit_card, :dependent => :destroy  # destroys the associated credit card
      #   has_one :credit_card, :dependent => :nullify  # updates the associated records foriegn key value to null rather than destroying it
      #   has_one :last_comment, :class_name => "Comment", :order => "posted_on"
      #   has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
      def has_one(association_id, options = {})
        reflection = create_has_one_reflection(association_id, options)

        module_eval do
          after_save <<-EOF
            association = instance_variable_get("@#{reflection.name}")
            unless association.nil?
              association["#{reflection.primary_key_name}"] = id
              association.save(true)
            end
          EOF
        end
      
        association_accessor_methods(reflection, HasOneAssociation)
        association_constructor_method(:build,  reflection, HasOneAssociation)
        association_constructor_method(:create, reflection, HasOneAssociation)
        
        configure_dependency_for_has_one(reflection)

        # deprecated api
        deprecated_has_association_method(reflection.name)
        deprecated_association_comparison_method(reflection.name, reflection.class_name)
      end

      # Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
      # +association+ is replaced with the symbol passed as the first argument, so 
      # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
      # * <tt>association(force_reload = false)</tt> - returns the associated object. Nil is returned if none is found.
      # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
      # * <tt>association.nil?</tt> - returns true if there is no associated object.
      # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key but has not yet been saved.
      # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
      #   with +attributes+ and linked to this object through a foreign key and that has already been saved (if it passed the validation).
      #
      # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
      # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
      # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
      # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
      # * <tt>Post#author.nil?</tt>
      # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
      # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
      # The declaration can also include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt>  - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
      #   if the real class name is +Person+, you'll have to specify it with this option.
      # * <tt>:conditions</tt>  - specify the conditions that the associated object must meet in order to be included as a "WHERE"
      #   sql fragment, such as "authorized = 1".
      # * <tt>:order</tt>       - specify the order from which the associated object will be picked at the top. Specified as
      #   an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of the associated class in lower-case and "_id" suffixed. So a +Person+ class that makes a belongs_to association to a
      #   +Boss+ class will use "boss_id" as the default foreign_key.
      # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through use of increment_counter 
      #   and decrement_counter. The counter cache is incremented when an object of this class is created and decremented when it's
      #   destroyed. This requires that a column named "#{table_name}_count" (such as comments_count for a belonging Comment class)
      #   is used on the associate class (such as a Post class). You can also specify a custom counter cache column by given that
      #   name instead of a true/false value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when this object is loaded.
      # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing true.
      #
      # Option examples:
      #   belongs_to :firm, :foreign_key => "client_of"
      #   belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
      #   belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id", 
      #              :conditions => 'discounts > #{payments_count}'
      #   belongs_to :attachable, :polymorphic => true
      def belongs_to(association_id, options = {})
        reflection = create_belongs_to_reflection(association_id, options)
        
        if reflection.options[:polymorphic]
          association_accessor_methods(reflection, BelongsToPolymorphicAssociation)

          module_eval do
            before_save <<-EOF
              association = instance_variable_get("@#{reflection.name}")
              if !association.nil? 
                if association.new_record?
                  association.save(true)
                end
                
                if association.updated?
                  self["#{reflection.primary_key_name}"] = association.id
                  self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
                end
              end
            EOF
          end
        else
          association_accessor_methods(reflection, BelongsToAssociation)
          association_constructor_method(:build,  reflection, BelongsToAssociation)
          association_constructor_method(:create, reflection, BelongsToAssociation)

          module_eval do
            before_save <<-EOF
              association = instance_variable_get("@#{reflection.name}")
              if !association.nil? 
                if association.new_record?
                  association.save(true)
                end
                
                if association.updated?
                  self["#{reflection.primary_key_name}"] = association.id
                end
              end            
            EOF
          end
      
          # deprecated api
          deprecated_has_association_method(reflection.name)
          deprecated_association_comparison_method(reflection.name, reflection.class_name)
        end

        if options[:counter_cache]
          cache_column = options[:counter_cache] == true ?
            "#{self.to_s.underscore.pluralize}_count" :
            options[:counter_cache]

          module_eval(
            "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
            " unless #{reflection.name}.nil?'"
          )

          module_eval(
            "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
            " unless #{reflection.name}.nil?'"
          )          
        end
      end

      # Associates two classes via an intermediate join table.  Unless the join table is explicitly specified as
      # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
      # will give the default join table name of "developers_projects" because "D" outranks "P".
      #
      # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
      # has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as
      # ReadOnly (because we can't save changes to the additional attrbutes). It's strongly recommended that you upgrade any
      # associations with attributes to a real join model (see introduction).
      #
      # Adds the following methods for retrieval and query.
      # +collection+ is replaced with the symbol passed as the first argument, so 
      # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
      # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
      #   An empty array is returned if none is found.
      # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table 
      #   (collection.push and collection.concat are aliases to this method).
      # * <tt>collection.push_with_attributes(object, join_attributes)</tt> - adds one to the collection by creating an association in the join table that
      #   also holds the attributes from <tt>join_attributes</tt> (should be a hash with the column names as keys). This can be used to have additional
      #   attributes on the join, which will be injected into the associated objects when they are retrieved through the collection.
      #   (collection.concat_with_attributes is an alias to this method). This method is now deprecated.
      # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.  
      #   This does not destroy the objects.
      # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
      # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
      # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
      # * <tt>collection.empty?</tt> - returns true if there are no associated objects.
      # * <tt>collection.size</tt> - returns the number of associated objects.
      # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
      #   meets the condition that it has to be associated with this object.
      #
      # Example: An Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
      # * <tt>Developer#projects</tt>
      # * <tt>Developer#projects<<</tt>
      # * <tt>Developer#projects.push_with_attributes</tt>
      # * <tt>Developer#projects.delete</tt>
      # * <tt>Developer#projects=</tt>
      # * <tt>Developer#project_ids=</tt>
      # * <tt>Developer#projects.clear</tt>
      # * <tt>Developer#projects.empty?</tt>
      # * <tt>Developer#projects.size</tt>
      # * <tt>Developer#projects.find(id)</tt>
      # The declaration may include an options hash to specialize the behavior of the association.
      # 
      # Options are:
      # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
      #   from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the 
      #   +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
      # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
      #   WARNING: If you're overwriting the table name of either class, the table_name method MUST be declared underneath any
      #   has_and_belongs_to_many declaration in order to work.
      # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
      #   of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_and_belongs_to_many association
      #   will use "person_id" as the default foreign_key.
      # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
      #   guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is +Project+,
      #   the has_and_belongs_to_many association will use "project_id" as the default association foreign_key.
      # * <tt>:conditions</tt>  - specify the conditions that the associated object must meet in order to be included as a "WHERE"
      #   sql fragment, such as "authorized = 1".
      # * <tt>:order</tt> - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC"
      # * <tt>:uniq</tt> - if set to true, duplicate associated objects will be ignored by accessors and query methods
      # * <tt>:finder_sql</tt> - overwrite the default generated SQL used to fetch the association with a manual one
      # * <tt>:delete_sql</tt> - overwrite the default generated SQL used to remove links between the associated 
      #   classes with a manual one
      # * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
      #   with a manual one
      # * <tt>:extend</tt>  - anonymous module for extending the proxy, see "Association extensions".
      # * <tt>:include</tt>  - specify second-order associations that should be eager loaded when the collection is loaded.
      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
      # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
      # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
      # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
      #   include the joined columns.
      #
      # Option examples:
      #   has_and_belongs_to_many :projects
      #   has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
      #   has_and_belongs_to_many :nations, :class_name => "Country"
      #   has_and_belongs_to_many :categories, :join_table => "prods_cats"
      #   has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => 
      #   'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
      def has_and_belongs_to_many(association_id, options = {}, &extension)
        reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
        
        add_multiple_associated_save_callbacks(reflection.name)
        collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)

        # Don't use a before_destroy callback since users' before_destroy
        # callbacks will be executed after the association is wiped out.
        old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
        class_eval <<-end_eval
          alias_method :#{old_method}, :destroy_without_callbacks
          def destroy_without_callbacks
            #{reflection.name}.clear
            #{old_method}
          end
        end_eval

        add_association_callbacks(reflection.name, options)
        
        # deprecated api
        deprecated_collection_count_method(reflection.name)
        deprecated_add_association_relation(reflection.name)
        deprecated_remove_association_relation(reflection.name)
        deprecated_has_collection_method(reflection.name)
      end

      private
        def join_table_name(first_table_name, second_table_name)
          if first_table_name < second_table_name
            join_table = "#{first_table_name}_#{second_table_name}"
          else
            join_table = "#{second_table_name}_#{first_table_name}"
          end

          table_name_prefix + join_table + table_name_suffix
        end
        
        def association_accessor_methods(reflection, association_proxy_class)
          define_method(reflection.name) do |*params|
            force_reload = params.first unless params.empty?
            association = instance_variable_get("@#{reflection.name}")

            if association.nil? || force_reload
              association = association_proxy_class.new(self, reflection)
              retval = association.reload
              unless retval.nil?
                instance_variable_set("@#{reflection.name}", association)
              else
                instance_variable_set("@#{reflection.name}", nil)
                return nil
              end
            end
            association
          end

          define_method("#{reflection.name}=") do |new_value|
            association = instance_variable_get("@#{reflection.name}")
            if association.nil?
              association = association_proxy_class.new(self, reflection)
            end

            association.replace(new_value)

            unless new_value.nil?
              instance_variable_set("@#{reflection.name}", association)
            else
              instance_variable_set("@#{reflection.name}", nil)
              return nil
            end

            association
          end

          define_method("set_#{reflection.name}_target") do |target|
            return if target.nil?
            association = association_proxy_class.new(self, reflection)
            association.target = target
            instance_variable_set("@#{reflection.name}", association)
          end
        end

        def collection_reader_method(reflection, association_proxy_class)
          define_method(reflection.name) do |*params|
            force_reload = params.first unless params.empty?
            association = instance_variable_get("@#{reflection.name}")

            unless association.respond_to?(:loaded?)
              association = association_proxy_class.new(self, reflection)
              instance_variable_set("@#{reflection.name}", association)
            end

            association.reload if force_reload

            association
          end
        end

        def collection_accessor_methods(reflection, association_proxy_class)
          collection_reader_method(reflection, association_proxy_class)

          define_method("#{reflection.name}=") do |new_value|
            association = instance_variable_get("@#{reflection.name}")
            unless association.respond_to?(:loaded?)
              association = association_proxy_class.new(self, reflection)
              instance_variable_set("@#{reflection.name}", association)
            end
            association.replace(new_value)
            association
          end

          define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
            send("#{reflection.name}=", reflection.class_name.constantize.find(new_value))
          end
        end

        def require_association_class(class_name)
          require_association(Inflector.underscore(class_name)) if class_name
        end

        def add_multiple_associated_save_callbacks(association_name)
          method_name = "validate_associated_records_for_#{association_name}".to_sym
          define_method(method_name) do
            association = instance_variable_get("@#{association_name}")
            if association.respond_to?(:loaded?)
              if new_record?
                association
              else
                association.select { |record| record.new_record? }
              end.each do |record|
                errors.add "#{association_name}" unless record.valid?
              end
            end
          end

          validate method_name
          before_save("@new_record_before_save = new_record?; true")

          after_callback = <<-end_eval
            association = instance_variable_get("@#{association_name}")
            
            if association.respond_to?(:loaded?)
              if @new_record_before_save
                records_to_save = association
              else
                records_to_save = association.select { |record| record.new_record? }
              end
              records_to_save.each { |record| association.send(:insert_record, record) }
              association.send(:construct_sql)   # reconstruct the SQL queries now that we know the owner's id
            end
          end_eval
                
          # Doesn't use after_save as that would save associations added in after_create/after_update twice
          after_create(after_callback)
          after_update(after_callback)
        end

        def association_constructor_method(constructor, reflection, association_proxy_class)
          define_method("#{constructor}_#{reflection.name}") do |*params|
            attributees      = params.first unless params.empty?
            replace_existing = params[1].nil? ? true : params[1]
            association      = instance_variable_get("@#{reflection.name}")

            if association.nil?
              association = association_proxy_class.new(self, reflection)
              instance_variable_set("@#{reflection.name}", association)
            end

            if association_proxy_class == HasOneAssociation
              association.send(constructor, attributees, replace_existing)
            else
              association.send(constructor, attributees)
            end
          end
        end
        
        def count_with_associations(options = {})
          catch :invalid_query do
            join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
            return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
          end
          0
        end

        def find_with_associations(options = {})
          catch :invalid_query do
            join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
            rows = select_all_rows(options, join_dependency)
            return join_dependency.instantiate(rows)
          end
          []
        end

        def configure_dependency_for_has_many(reflection)
          if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
            raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options.  You may specify one or the other.'
          end

          if reflection.options[:exclusively_dependent]
            reflection.options[:dependent] = :delete_all
            #warn "The :exclusively_dependent option is deprecated.  Please use :dependent => :delete_all instead.")
          end

          # See HasManyAssociation#delete_records.  Dependent associations
          # delete children, otherwise foreign key is set to NULL.

          # Add polymorphic type if the :as option is present
          dependent_conditions = %(#{reflection.primary_key_name} = \#{record.quoted_id})
          if reflection.options[:as]
            dependent_conditions += " AND #{reflection.options[:as]}_type = '#{base_class.name}'"
          end

          case reflection.options[:dependent]
            when :destroy, true  
              module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
            when :delete_all
              module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
            when :nullify
              module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL),  %(#{dependent_conditions})) }"
            when nil, false
              # pass
            else
              raise ArgumentError, 'The :dependent option expects either :destroy, :delete_all, or :nullify' 
          end
        end
        
        def configure_dependency_for_has_one(reflection)
          case reflection.options[:dependent]
            when :destroy, true
              module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
            when :nullify
              module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
            when nil, false
              # pass
            else
              raise ArgumentError, "The :dependent option expects either :destroy or :nullify."
          end
        end
        
        
        def add_deprecated_api_for_has_many(association_name)
          deprecated_collection_count_method(association_name)
          deprecated_add_association_relation(association_name)
          deprecated_remove_association_relation(association_name)
          deprecated_has_collection_method(association_name)
          deprecated_find_in_collection_method(association_name)
          deprecated_find_all_in_collection_method(association_name)
          deprecated_collection_create_method(association_name)
          deprecated_collection_build_method(association_name)
        end

        def create_has_many_reflection(association_id, options, &extension)
          options.assert_valid_keys(
            :class_name, :table_name, :foreign_key,
            :exclusively_dependent, :dependent,
            :select, :conditions, :include, :order, :group, :limit, :offset,
            :as, :through, :source,
            :finder_sql, :counter_sql, 
            :before_add, :after_add, :before_remove, :after_remove, 
            :extend
          )

          options[:extend] = create_extension_module(association_id, extension) if block_given?

          create_reflection(:has_many, association_id, options, self)
        end

        def create_has_one_reflection(association_id, options)
          options.assert_valid_keys(
            :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
          )

          create_reflection(:has_one, association_id, options, self)
        end

        def create_belongs_to_reflection(association_id, options)
          options.assert_valid_keys(
            :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, 
            :counter_cache, :extend, :polymorphic
          )
          
          reflection = create_reflection(:belongs_to, association_id, options, self)

          if options[:polymorphic]
            reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
          end

          reflection
        end
        
        def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
          options.assert_valid_keys(
            :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, 
            :select, :conditions, :include, :order, :group, :limit, :offset,
            :finder_sql, :delete_sql, :insert_sql, :uniq, 
            :before_add, :after_add, :before_remove, :after_remove, 
            :extend
          )

          options[:extend] = create_extension_module(association_id, extension) if block_given?

          reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)

          reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
          
          reflection
        end

        def reflect_on_included_associations(associations)
          [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
        end

        def guard_against_unlimitable_reflections(reflections, options)
          if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
            raise(
              ConfigurationError, 
              "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
            )
          end
        end

        def select_all_rows(options, join_dependency)
          connection.select_all(
            construct_finder_sql_with_included_associations(options, join_dependency),
            "#{name} Load Including Associations"
          )
        end
        
        def construct_counter_sql_with_included_associations(options, join_dependency)
          scope = scope(:find)
          sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
          
          # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
          if !Base.connection.supports_count_distinct?
            sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"
          end
          
          sql << " FROM #{table_name} "
          sql << join_dependency.join_associations.collect{|join| join.association_join }.join
          
          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)
          add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])

          add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)

          if !Base.connection.supports_count_distinct?
            sql << ")"
          end

          return sanitize_sql(sql)          
        end

        def construct_finder_sql_with_included_associations(options, join_dependency)
          scope = scope(:find)
          sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
          sql << join_dependency.join_associations.collect{|join| join.association_join }.join
 
          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)
          add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]

          sql << "ORDER BY #{options[:order]} " if options[:order]
 
          add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
 
          return sanitize_sql(sql)
        end
 
        def add_limited_ids_condition!(sql, options, join_dependency)
          unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
            sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
          else
            throw :invalid_query
          end
        end
 
        def select_limited_ids_list(options, join_dependency)
          connection.select_all(
            construct_finder_sql_for_association_limiting(options, join_dependency),
            "#{name} Load IDs For Limited Eager Loading"
          ).collect { |row| connection.quote(row[primary_key]) }.join(", ")
        end
 
        def construct_finder_sql_for_association_limiting(options, join_dependency)
          scope = scope(:find)
          sql = "SELECT "
          sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options)
          sql << primary_key
          sql << ", #{options[:order].split(',').collect { |s| s.split.first } * ', '}" if options[:order] && (include_eager_conditions?(options) || include_eager_order?(options))
          sql << " FROM #{table_name} "
          
          if include_eager_conditions?(options) || include_eager_order?(options)
            sql << join_dependency.join_associations.collect{|join| join.association_join }.join
            add_joins!(sql, options, scope)
          end
          
          add_conditions!(sql, options[:conditions], scope)
          sql << "ORDER BY #{options[:order]} " if options[:order]
          add_limit!(sql, options, scope)
          return sanitize_sql(sql)
        end
        
        # Checks if the conditions reference a table other than the current model table
        def include_eager_conditions?(options)
          # look in both sets of conditions
          conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
            case cond
              when nil   then all
              when Array then all << cond.first
              else            all << cond
            end
          end
          return false unless conditions.any?
          conditions.join(' ').scan(/(\w+)\.\w+/).flatten.any? do |condition_table_name|
            condition_table_name != table_name
          end
        end
        
        # Checks if the query order references a table other than the current model's table.
        def include_eager_order?(options)
          order = options[:order]
          return false unless order
          order.scan(/(\w+)\.\w+/).flatten.any? do |order_table_name|
            order_table_name != table_name
          end
        end

        def using_limitable_reflections?(reflections)
          reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
        end

        def column_aliases(join_dependency)
          join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
              "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
        end

        def add_association_callbacks(association_name, options)
          callbacks = %w(before_add after_add before_remove after_remove)
          callbacks.each do |callback_name|
            full_callback_name = "#{callback_name.to_s}_for_#{association_name.to_s}"
            defined_callbacks = options[callback_name.to_sym]
            if options.has_key?(callback_name.to_sym)
              class_inheritable_reader full_callback_name.to_sym
              write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten)
            end
          end
        end

        def condition_word(sql)
          sql =~ /where/i ? " AND " : "WHERE "
        end

        def create_extension_module(association_id, extension)
          extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"

          silence_warnings do
            Object.const_set(extension_module_name, Module.new(&extension))
          end
          
          extension_module_name.constantize
        end

        class JoinDependency
          attr_reader :joins, :reflections, :table_aliases

          def initialize(base, associations, joins)
            @joins                 = [JoinBase.new(base, joins)]
            @associations          = associations
            @reflections           = []
            @base_records_hash     = {}
            @base_records_in_order = []
            @table_aliases         = Hash.new { |aliases, table| aliases[table] = 0 }
            @table_aliases[base.table_name] = 1
            build(associations)
          end

          def join_associations
            @joins[1..-1].to_a
          end

          def join_base
            @joins[0]
          end

          def instantiate(rows)
            rows.each_with_index do |row, i|
              primary_id = join_base.record_id(row)
              unless @base_records_hash[primary_id]
                @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
              end
              construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
            end
            return @base_records_in_order
          end

          def aliased_table_names_for(table_name)
            joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
          end

          protected
            def build(associations, parent = nil)
              parent ||= @joins.last
              case associations
                when Symbol, String
                  reflection = parent.reflections[associations.to_s.intern] or
                  raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
                  @reflections << reflection
                  @joins << JoinAssociation.new(reflection, self, parent)
                when Array
                  associations.each do |association|
                    build(association, parent)
                  end
                when Hash
                  associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
                    build(name, parent)
                    build(associations[name])
                  end
                else
                  raise ConfigurationError, associations.inspect
              end
            end

            def construct(parent, associations, joins, row)
              case associations
                when Symbol, String
                  while (join = joins.shift).reflection.name.to_s != associations.to_s
                    raise ConfigurationError, "Not Enough Associations" if joins.empty?
                  end
                  construct_association(parent, join, row)
                when Array
                  associations.each do |association|
                    construct(parent, association, joins, row)
                  end
                when Hash
                  associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
                    association = construct_association(parent, joins.shift, row)
                    construct(association, associations[name], joins, row) if association
                  end
                else
                  raise ConfigurationError, associations.inspect
              end
            end

            def construct_association(record, join, row)
              case join.reflection.macro
                when :has_many, :has_and_belongs_to_many
                  collection = record.send(join.reflection.name)
                  collection.loaded
    
                  return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
                  association = join.instantiate(row)
                  collection.target.push(association) unless collection.target.include?(association)
                when :has_one, :belongs_to
                  return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
                  association = join.instantiate(row)
                  record.send("set_#{join.reflection.name}_target", association)
                else
                  raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
              end
              return association
            end

          class JoinBase
            attr_reader :active_record, :table_joins
            delegate    :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record

            def initialize(active_record, joins = nil)
              @active_record = active_record
              @cached_record = {}
              @table_joins   = joins
            end

            def aliased_prefix
              "t0"
            end

            def aliased_primary_key
              "#{ aliased_prefix }_r0"
            end

            def aliased_table_name
              active_record.table_name
            end

            def column_names_with_alias
              unless @column_names_with_alias
                @column_names_with_alias = []
                ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
                  @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
                end
              end
              return @column_names_with_alias
            end

            def extract_record(row)
              column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
            end

            def record_id(row)
              row[aliased_primary_key]
            end

            def instantiate(row)
              @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
            end
          end

          class JoinAssociation < JoinBase
            attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
            delegate    :options, :klass, :through_reflection, :source_reflection, :to => :reflection

            def initialize(reflection, join_dependency, parent = nil)
              reflection.check_validity!
              if reflection.options[:polymorphic]
                raise EagerLoadPolymorphicError.new(reflection)
              end

              super(reflection.klass)
              @parent             = parent
              @reflection         = reflection
              @aliased_prefix     = "t#{ join_dependency.joins.size }"
              @aliased_table_name = table_name # start with the table name
              @parent_table_name  = parent.active_record.table_name

              if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
                join_dependency.table_aliases[aliased_table_name] += 1
              end
              
              unless join_dependency.table_aliases[aliased_table_name].zero?
                # if the table name has been used, then use an alias
                @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
                table_index = join_dependency.table_aliases[aliased_table_name]
                @aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
              end
              join_dependency.table_aliases[aliased_table_name] += 1
              
              if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
                @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
                unless join_dependency.table_aliases[aliased_join_table_name].zero?
                  @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
                  table_index = join_dependency.table_aliases[aliased_join_table_name]
                  @aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
                end
                join_dependency.table_aliases[aliased_join_table_name] += 1
              end
            end

            def association_join
              join = case reflection.macro
                when :has_and_belongs_to_many
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_alias_for(options[:join_table], aliased_join_table_name),
                     aliased_join_table_name,
                     options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
                     reflection.active_record.table_name, reflection.active_record.primary_key] +
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_name_and_alias, aliased_table_name, klass.primary_key,
                     aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
                     ]
                when :has_many, :has_one
                  case
                    when reflection.macro == :has_many && reflection.options[:through]
                      through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
                      if through_reflection.options[:as] # has_many :through against a polymorphic join
                        polymorphic_foreign_key  = through_reflection.options[:as].to_s + '_id'
                        polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'

                        " LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) "  % [
                          table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
                          aliased_join_table_name, polymorphic_foreign_key,
                          parent.aliased_table_name, parent.primary_key,
                          aliased_join_table_name, polymorphic_foreign_type, klass.quote(parent.active_record.base_class.name)] +
                        " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
                          aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
                        ]
                      else
                        if source_reflection.macro == :has_many && source_reflection.options[:as]
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s "  % [
                            table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
                            through_reflection.primary_key_name,
                            parent.aliased_table_name, parent.primary_key] +
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
                            table_name_and_alias,
                            aliased_table_name, "#{source_reflection.options[:as]}_id", 
                            aliased_join_table_name, options[:foreign_key] || primary_key,
                            aliased_table_name, "#{source_reflection.options[:as]}_type", 
                            klass.quote(source_reflection.active_record.base_class.name)
                          ]
                        else
                          case source_reflection.macro
                            when :belongs_to
                              first_key  = primary_key
                              second_key = options[:foreign_key] || klass.to_s.classify.foreign_key
                            when :has_many
                              first_key  = through_reflection.klass.to_s.classify.foreign_key
                              second_key = options[:foreign_key] || primary_key
                          end
                          
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s "  % [
                            table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
                            through_reflection.primary_key_name,
                            parent.aliased_table_name, parent.primary_key] +
                          " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                            table_name_and_alias,
                            aliased_table_name, first_key, 
                            aliased_join_table_name, second_key
                          ]
                        end
                      end
                    
                    when reflection.macro == :has_many && reflection.options[:as]
                      " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
                        table_name_and_alias,
                        aliased_table_name, "#{reflection.options[:as]}_id",
                        parent.aliased_table_name, parent.primary_key,
                        aliased_table_name, "#{reflection.options[:as]}_type",
                        klass.quote(parent.active_record.base_class.name)
                      ]
                    when reflection.macro == :has_one && reflection.options[:as]
                      " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
                        table_name_and_alias,
                        aliased_table_name, "#{reflection.options[:as]}_id",
                        parent.aliased_table_name, parent.primary_key,
                        aliased_table_name, "#{reflection.options[:as]}_type",
                        klass.quote(reflection.active_record.base_class.name)
                      ]
                    else
                      foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
                      " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                        table_name_and_alias,
                        aliased_table_name, foreign_key,
                        parent.aliased_table_name, parent.primary_key
                      ]
                  end
                when :belongs_to
                  " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
                     table_name_and_alias, aliased_table_name, reflection.klass.primary_key,
                     parent.aliased_table_name, options[:foreign_key] || klass.to_s.foreign_key
                    ]
                else
                  ""
              end || ''
              join << %(AND %s.%s = %s ) % [
                aliased_table_name, 
                reflection.active_record.connection.quote_column_name(reflection.active_record.inheritance_column), 
                klass.quote(klass.name)] unless klass.descends_from_active_record?
              join << "AND #{interpolate_sql(sanitize_sql(reflection.options[:conditions]))} " if reflection.options[:conditions]
              join
            end
            
            protected
              def pluralize(table_name)
                ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
              end
              
              def table_alias_for(table_name, table_alias)
                "#{table_name} #{table_alias if table_name != table_alias}".strip
              end

              def table_name_and_alias
                table_alias_for table_name, @aliased_table_name
              end

              def interpolate_sql(sql)
                instance_eval("%@#{sql.gsub('@', '\@')}@")
              end
          end
        end
    end
  end
end
require 'yaml'
require 'set'
require 'active_record/deprecated_finders'

module ActiveRecord #:nodoc:
  class ActiveRecordError < StandardError #:nodoc:
  end
  class SubclassNotFound < ActiveRecordError #:nodoc:
  end
  class AssociationTypeMismatch < ActiveRecordError #:nodoc:
  end
  class SerializationTypeMismatch < ActiveRecordError #:nodoc:
  end
  class AdapterNotSpecified < ActiveRecordError # :nodoc:
  end
  class AdapterNotFound < ActiveRecordError # :nodoc:
  end
  class ConnectionNotEstablished < ActiveRecordError #:nodoc:
  end
  class ConnectionFailed < ActiveRecordError #:nodoc:
  end
  class RecordNotFound < ActiveRecordError #:nodoc:
  end
  class RecordNotSaved < ActiveRecordError #:nodoc:
  end
  class StatementInvalid < ActiveRecordError #:nodoc:
  end
  class PreparedStatementInvalid < ActiveRecordError #:nodoc:
  end
  class StaleObjectError < ActiveRecordError #:nodoc:
  end
  class ConfigurationError < StandardError #:nodoc:
  end
  class ReadOnlyRecord < StandardError #:nodoc:
  end
  
  class AttributeAssignmentError < ActiveRecordError #:nodoc:
    attr_reader :exception, :attribute
    def initialize(message, exception, attribute)
      @exception = exception
      @attribute = attribute
      @message = message
    end
  end

  class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
    attr_reader :errors
    def initialize(errors)
      @errors = errors
    end
  end

  # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
  # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
  # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
  # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
  #
  # See the mapping rules in table_name and the full example in link:files/README.html for more insight.
  #
  # == Creation
  #
  # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
  # you're receiving the data from somewhere else, like a HTTP request. It works like this:
  #
  #   user = User.new(:name => "David", :occupation => "Code Artist")
  #   user.name # => "David"
  #
  # You can also use block initialization:
  #
  #   user = User.new do |u|
  #     u.name = "David"
  #     u.occupation = "Code Artist"
  #   end
  #
  # And of course you can just create a bare object and specify the attributes after the fact:
  #
  #   user = User.new
  #   user.name = "David"
  #   user.occupation = "Code Artist"
  #
  # == Conditions
  #
  # Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
  # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
  # be used for statements that don't involve tainted data. Examples:
  #
  #   User < ActiveRecord::Base
  #     def self.authenticate_unsafely(user_name, password)
  #       find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
  #     end
  #
  #     def self.authenticate_safely(user_name, password)
  #       find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
  #     end
  #   end
  #
  # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
  # attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
  # on the other hand, will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query, which will ensure that
  # an attacker can't escape the query and fake the login (or worse).
  #
  # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
  # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
  # the question marks with symbols and supplying a hash with values for the matching symbol keys:
  #
  #   Company.find(:first, [
  #     "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
  #     { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
  #   ])
  #
  # == Overwriting default accessors
  #
  # All column values are automatically available through basic accessors on the Active Record object, but some times you
  # want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
  # name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
  # Example:
  #
  #   class Song < ActiveRecord::Base
  #     # Uses an integer of seconds to hold the length of the song
  #
  #     def length=(minutes)
  #       write_attribute(:length, minutes * 60)
  #     end
  #
  #     def length
  #       read_attribute(:length) / 60
  #     end
  #   end
  #
  # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, vaule) and
  # read_attribute(:attribute) as a shorter form.
  #
  # == Accessing attributes before they have been typecasted
  #
  # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
  # That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
  # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
  #
  # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
  # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
  # want.
  #
  # == Dynamic attribute-based finders
  #
  # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
  # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
  # Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
  # <tt>Person.find(:first, ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
  # And instead of writing <tt>Person.find(:all, ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
  #
  # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
  # <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
  # <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
  # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
  #
  # It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
  # is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
  # actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
  #
  # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
  # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Example:
  #
  #   # No 'Summer' tag exists
  #   Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
  #   
  #   # Now the 'Summer' tag does exist
  #   Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
  #
  # == Saving arrays, hashes, and other non-mappable objects in text columns
  #
  # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
  # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
  #
  #   class User < ActiveRecord::Base
  #     serialize :preferences
  #   end
  #
  #   user = User.create(:preferences => { "background" => "black", "display" => large })
  #   User.find(user.id).preferences # => { "background" => "black", "display" => large }
  #
  # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
  # descendent of a class not in the hierarchy. Example:
  #
  #   class User < ActiveRecord::Base
  #     serialize :preferences, Hash
  #   end
  #
  #   user = User.create(:preferences => %w( one two three ))
  #   User.find(user.id).preferences    # raises SerializationTypeMismatch
  #
  # == Single table inheritance
  #
  # Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
  # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
  #
  #   class Company < ActiveRecord::Base; end
  #   class Firm < Company; end
  #   class Client < Company; end
  #   class PriorityClient < Client; end
  #
  # When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
  # fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
  #
  # If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
  # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
  #
  # Note, all the attributes for all the cases are kept in the same table. Read more:
  # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
  #
  # == Connection to multiple databases in different models
  #
  # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
  # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
  # For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
  # and Course *and all its subclasses* will use this connection instead.
  #
  # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
  # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
  #
  # == Exceptions
  #
  # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
  # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
  #   <tt>:adapter</tt> key.
  # * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
  #   (or a bad spelling of an existing one).
  # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
  # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
  # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
  # * +RecordNotFound+ -- no record responded to the find* method.
  #   Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
  # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the  message.
  #   Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
  # * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
  #   +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
  #   objects that should be inspected to determine which attributes triggered the errors.
  # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
  #   You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
  #
  # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
  # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
  # instances in the current object space.
  class Base
    # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
    # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
    cattr_accessor :logger
    
    include Reloadable::Subclasses
    
    def self.inherited(child) #:nodoc:
      @@subclasses[self] ||= []
      @@subclasses[self] << child
      super
    end
    
    def self.reset_subclasses #:nodoc:
      nonreloadables = []
      subclasses.each do |klass|
        unless klass.reloadable?
          nonreloadables << klass
          next
        end
        klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
        klass.instance_methods(false).each { |m| klass.send :undef_method, m }
      end
      @@subclasses = {}
      nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
    end

    @@subclasses = {}

    cattr_accessor :configurations
    @@configurations = {}

    # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
    # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
    # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
    # that this is a global setting for all Active Records.
    cattr_accessor :primary_key_prefix_type
    @@primary_key_prefix_type = nil

    # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
    # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
    # for tables in a shared database. By default, the prefix is the empty string.
    cattr_accessor :table_name_prefix
    @@table_name_prefix = ""

    # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
    # "people_basecamp"). By default, the suffix is the empty string.
    cattr_accessor :table_name_suffix
    @@table_name_suffix = ""

    # Indicates whether or not table names should be the pluralized versions of the corresponding class names.
    # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
    # See table_name for the full rules on table/class naming. This is true, by default.
    cattr_accessor :pluralize_table_names
    @@pluralize_table_names = true

    # Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
    # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
    # may complicate matters if you use software like syslog. This is true, by default.
    cattr_accessor :colorize_logging
    @@colorize_logging = true

    # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
    # This is set to :local by default.
    cattr_accessor :default_timezone
    @@default_timezone = :local

    # Determines whether or not to use a connection for each thread, or a single shared connection for all threads.
    # Defaults to false. Set to true if you're writing a threaded application.
    cattr_accessor :allow_concurrency
    @@allow_concurrency = false

    # Determines whether to speed up access by generating optimized reader
    # methods to avoid expensive calls to method_missing when accessing
    # attributes by name. You might want to set this to false in development
    # mode, because the methods would be regenerated on each request.
    cattr_accessor :generate_read_methods
    @@generate_read_methods = true
    
    # Specifies the format to use when dumping the database schema with Rails'
    # Rakefile.  If :sql, the schema is dumped as (potentially database-
    # specific) SQL statements.  If :ruby, the schema is dumped as an 
    # ActiveRecord::Schema file which can be loaded into any database that
    # supports migrations.  Use :ruby if you want to have different database
    # adapters for, e.g., your development and test environments.
    cattr_accessor :schema_format 
    @@schema_format = :ruby

    class << self # Class methods
      # Find operates with three different retrieval approaches:
      #
      # * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
      #   If no record can be found for all of the listed ids, then RecordNotFound will be raised.
      # * Find first: This will return the first record matched by the options used. These options can either be specific
      #   conditions or merely an order. If no record can matched, nil is returned.
      # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
      #
      # All approaches accept an option hash as their last parameter. The options are:
      #
      # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
      # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
      # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
      # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
      # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
      #   The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
      #   Pass :readonly => false to override.
      # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
      #   to already defined associations. See eager loading under Associations.
      # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
      #   include the joined columns.
      # * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
      #
      # Examples for find by id:
      #   Person.find(1)       # returns the object for ID = 1
      #   Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
      #   Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
      #   Person.find([1])     # returns an array for objects the object with ID = 1
      #   Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
      #
      # Examples for find first:
      #   Person.find(:first) # returns the first object fetched by SELECT * FROM people
      #   Person.find(:first, :conditions => [ "user_name = ?", user_name])
      #   Person.find(:first, :order => "created_on DESC", :offset => 5)
      #
      # Examples for find all:
      #   Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
      #   Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
      #   Person.find(:all, :offset => 10, :limit => 10)
      #   Person.find(:all, :include => [ :account, :friends ])
      #   Person.find(:all, :group => "category")
      def find(*args)
        options = extract_options_from_args!(args)
        validate_find_options(options)
        set_readonly_option!(options)

        case args.first
          when :first then find_initial(options)
          when :all   then find_every(options)
          else             find_from_ids(args, options)
        end
      end
      
      # Works like find(:all), but requires a complete SQL string. Examples:
      #   Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
      #   Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
      def find_by_sql(sql)
        connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
      end

      # Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
      # Example:
      #   Person.exists?(5)
      def exists?(id)
        !find(:first, :conditions => ["#{primary_key} = ?", id]).nil? rescue false
      end

      # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
      # fails under validations, the unsaved object is still returned.
      def create(attributes = nil)
        if attributes.is_a?(Array)
          attributes.collect { |attr| create(attr) }
        else
          object = new(attributes)
          scope(:create).each { |att,value| object.send("#{att}=", value) } if scoped?(:create)
          object.save
          object
        end
      end

      # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
      # and returns it. If the save fails under validations, the unsaved object is still returned.
      #
      # The arguments may also be given as arrays in which case the update method is called for each pair of +id+ and 
      # +attributes+ and an array of objects is returned.
      #
      # Example of updating one record:
      #   Person.update(15, {:user_name => 'Samuel', :group => 'expert'})
      # 
      # Example of updating multiple records:
      #   people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} } 	
      #   Person.update(people.keys, people.values)
      def update(id, attributes)
        if id.is_a?(Array)
          idx = -1
          id.collect { |id| idx += 1; update(id, attributes[idx]) }
        else
          object = find(id)
          object.update_attributes(attributes)
          object
        end
      end

      # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
      # are deleted.
      def delete(id)
        delete_all([ "#{primary_key} IN (?)", id ])
      end

      # Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
      # If an array of ids is provided, all of them are destroyed.
      def destroy(id)
        id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
      end

      # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updated.
      # A subset of the records can be selected by specifying +conditions+. Example:
      #   Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
      def update_all(updates, conditions = nil)
        sql  = "UPDATE #{table_name} SET #{sanitize_sql(updates)} "
        add_conditions!(sql, conditions, scope(:find))
        connection.update(sql, "#{name} Update")
      end

      # Destroys the objects for all the records that match the +condition+ by instantiating each object and calling
      # the destroy method. Example:
      #   Person.destroy_all "last_login < '2004-04-04'"
      def destroy_all(conditions = nil)
        find(:all, :conditions => conditions).each { |object| object.destroy }
      end

      # Deletes all the records that match the +condition+ without instantiating the objects first (and hence not
      # calling the destroy method). Example:
      #   Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
      def delete_all(conditions = nil)
        sql = "DELETE FROM #{table_name} "
        add_conditions!(sql, conditions, scope(:find))
        connection.delete(sql, "#{name} Delete all")
      end

      # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
      #   Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
      def count_by_sql(sql)
        sql = sanitize_conditions(sql)
        connection.select_value(sql, "#{name} Count").to_i
      end

      # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
      # discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
      # This is used for caching aggregate values, so that they don't need to be computed every time. Especially important
      # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
      # that needs to list both the number of posts and comments.
      def increment_counter(counter_name, id)
        update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{quote(id)}"
      end

      # Works like increment_counter, but decrements instead.
      def decrement_counter(counter_name, id)
        update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote(id)}"
      end


      # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
      # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
      # methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
      #
      #   class Customer < ActiveRecord::Base
      #     attr_protected :credit_rating
      #   end
      #
      #   customer = Customer.new("name" => David, "credit_rating" => "Excellent")
      #   customer.credit_rating # => nil
      #   customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
      #   customer.credit_rating # => nil
      #
      #   customer.credit_rating = "Average"
      #   customer.credit_rating # => "Average"
      def attr_protected(*attributes)
        write_inheritable_array("attr_protected", attributes - (protected_attributes || []))
      end

      # Returns an array of all the attributes that have been protected from mass-assignment.
      def protected_attributes # :nodoc:
        read_inheritable_attribute("attr_protected")
      end

      # If this macro is used, only those attributes named in it will be accessible for mass-assignment, such as
      # <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
      # protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
      # attr_protected.
      def attr_accessible(*attributes)
        write_inheritable_array("attr_accessible", attributes - (accessible_attributes || []))
      end

      # Returns an array of all the attributes that have been made accessible to mass-assignment.
      def accessible_attributes # :nodoc:
        read_inheritable_attribute("attr_accessible")
      end


      # Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
      # after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
      # object must be of that class on retrieval or +SerializationTypeMismatch+ will be raised.
      def serialize(attr_name, class_name = Object)
        serialized_attributes[attr_name.to_s] = class_name
      end

      # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
      def serialized_attributes
        read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
      end


      # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
      # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
      # to guess the table name from even when called on Reply. The rules used to do the guess are handled by the Inflector class
      # in Active Support, which knows almost all common English inflections (report a bug if your inflection isn't covered).
      #
      # Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
      # So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
      #
      # You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
      # "mice" table. Example:
      #
      #   class Mouse < ActiveRecord::Base
      #      set_table_name "mice"
      #   end
      def table_name
        reset_table_name
      end

      def reset_table_name #:nodoc:
        name = "#{table_name_prefix}#{undecorated_table_name(base_class.name)}#{table_name_suffix}"
        set_table_name(name)
        name
      end

      # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
      # primary_key_prefix_type setting, though.
      def primary_key
        reset_primary_key
      end

      def reset_primary_key #:nodoc:
        key = 'id'
        case primary_key_prefix_type
          when :table_name
            key = Inflector.foreign_key(base_class.name, false)
          when :table_name_with_underscore
            key = Inflector.foreign_key(base_class.name)
        end
        set_primary_key(key)
        key
      end

      # Defines the column name for use with single table inheritance -- can be overridden in subclasses.
      def inheritance_column
        "type"
      end

      # Lazy-set the sequence name to the connection's default.  This method
      # is only ever called once since set_sequence_name overrides it.
      def sequence_name #:nodoc:
        reset_sequence_name
      end

      def reset_sequence_name #:nodoc:
        default = connection.default_sequence_name(table_name, primary_key)
        set_sequence_name(default)
        default
      end

      # Sets the table name to use to the given value, or (if the value
      # is nil or false) to the value returned by the given block.
      #
      # Example:
      #
      #   class Project < ActiveRecord::Base
      #     set_table_name "project"
      #   end
      def set_table_name(value = nil, &block)
        define_attr_method :table_name, value, &block
      end
      alias :table_name= :set_table_name

      # Sets the name of the primary key column to use to the given value,
      # or (if the value is nil or false) to the value returned by the given
      # block.
      #
      # Example:
      #
      #   class Project < ActiveRecord::Base
      #     set_primary_key "sysid"
      #   end
      def set_primary_key(value = nil, &block)
        define_attr_method :primary_key, value, &block
      end
      alias :primary_key= :set_primary_key

      # Sets the name of the inheritance column to use to the given value,
      # or (if the value # is nil or false) to the value returned by the
      # given block.
      #
      # Example:
      #
      #   class Project < ActiveRecord::Base
      #     set_inheritance_column do
      #       original_inheritance_column + "_id"
      #     end
      #   end
      def set_inheritance_column(value = nil, &block)
        define_attr_method :inheritance_column, value, &block
      end
      alias :inheritance_column= :set_inheritance_column

      # Sets the name of the sequence to use when generating ids to the given
      # value, or (if the value is nil or false) to the value returned by the
      # given block. This is required for Oracle and is useful for any
      # database which relies on sequences for primary key generation.
      #
      # If a sequence name is not explicitly set when using Oracle or Firebird,
      # it will default to the commonly used pattern of: #{table_name}_seq
      #
      # If a sequence name is not explicitly set when using PostgreSQL, it
      # will discover the sequence corresponding to your primary key for you.
      #
      # Example:
      #
      #   class Project < ActiveRecord::Base
      #     set_sequence_name "projectseq"   # default would have been "project_seq"
      #   end
      def set_sequence_name(value = nil, &block)
        define_attr_method :sequence_name, value, &block
      end
      alias :sequence_name= :set_sequence_name

      # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
      def class_name(table_name = table_name) # :nodoc:
        # remove any prefix and/or suffix from the table name
        class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
        class_name = class_name.singularize if pluralize_table_names
        class_name
      end

      # Indicates whether the table associated with this class exists
      def table_exists?
        if connection.respond_to?(:tables)
          connection.tables.include? table_name
        else
          # if the connection adapter hasn't implemented tables, there are two crude tests that can be
          # used - see if getting column info raises an error, or if the number of columns returned is zero
          begin
            reset_column_information
            columns.size > 0
          rescue ActiveRecord::StatementInvalid
            false
          end          
        end
      end

      # Returns an array of column objects for the table associated with this class.
      def columns
        unless @columns
          @columns = connection.columns(table_name, "#{name} Columns")
          @columns.each {|column| column.primary = column.name == primary_key}
        end
        @columns
      end

      # Returns an array of column objects for the table associated with this class.
      def columns_hash
        @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
      end

      # Returns an array of column names as strings.
      def column_names
        @column_names ||= columns.map { |column| column.name }
      end

      # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
      # and columns used for single table inheritance have been removed.
      def content_columns
        @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
      end

      # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
      # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
      # is available.
      def column_methods_hash #:nodoc:
        @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
          attr_name = attr.to_s
          methods[attr.to_sym]       = attr_name
          methods["#{attr}=".to_sym] = attr_name
          methods["#{attr}?".to_sym] = attr_name
          methods["#{attr}_before_type_cast".to_sym] = attr_name
          methods
        end
      end

      # Contains the names of the generated reader methods.
      def read_methods #:nodoc:
        @read_methods ||= Set.new
      end

      # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
      def reset_column_information
        read_methods.each { |name| undef_method(name) }
        @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @read_methods = nil
      end

      def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
        subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
      end

      # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
      #   Person.human_attribute_name("first_name") # => "First name"
      # Deprecated in favor of just calling "first_name".humanize
      def human_attribute_name(attribute_key_name) #:nodoc:
        attribute_key_name.humanize
      end

      def descends_from_active_record? # :nodoc:
        superclass == Base || !columns_hash.include?(inheritance_column)
      end

      def quote(object) #:nodoc:
        connection.quote(object)
      end

      # Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
      def sanitize(object) #:nodoc:
        connection.quote(object)
      end

      # Log and benchmark multiple statements in a single block. Example:
      #
      #   Project.benchmark("Creating project") do
      #     project = Project.create("name" => "stuff")
      #     project.create_manager("name" => "David")
      #     project.milestones << Milestone.find(:all)
      #   end
      #
      # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
      # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
      # will only be conducted if the log level is low enough.
      #
      # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
      def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
        if logger && logger.level == log_level
          result = nil
          seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
          logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
          result
        else
          yield
        end
      end

      # Silences the logger for the duration of the block.
      def silence
        old_logger_level, logger.level = logger.level, Logger::ERROR if logger
        yield
      ensure
        logger.level = old_logger_level if logger
      end

      # Scope parameters to method calls within the block.  Takes a hash of method_name => parameters hash.
      # method_name may be :find or :create. :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
      # <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. :create parameters are an attributes hash.
      #
      #   Article.with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
      #     Article.find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
      #     a = Article.create(1)
      #     a.blog_id # => 1
      #   end
      #
      # In nested scopings, all previous parameters are overwritten by inner rule
      # except :conditions in :find, that are merged as hash.
      #
      #   Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
      #     Article.with_scope(:find => { :limit => 10})
      #       Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
      #     end
      #     Article.with_scope(:find => { :conditions => "author_id = 3" })
      #       Article.find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
      #     end
      #   end
      #
      # You can ignore any previous scopings by using <tt>with_exclusive_scope</tt> method.
      #
      #   Article.with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
      #     Article.with_exclusive_scope(:find => { :limit => 10 })
      #       Article.find(:all) # => SELECT * from articles LIMIT 10
      #     end
      #   end
      def with_scope(method_scoping = {}, action = :merge, &block)
        method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)

        # Dup first and second level of hash (method and params).
        method_scoping = method_scoping.inject({}) do |hash, (method, params)|
          hash[method] = (params == true) ? params : params.dup
          hash
        end

        method_scoping.assert_valid_keys([ :find, :create ])

        if f = method_scoping[:find]
          f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :readonly ])
          f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
        end

        # Merge scopings
        if action == :merge && current_scoped_methods
          method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
            case hash[method]
              when Hash
                if method == :find
                  (hash[method].keys + params.keys).uniq.each do |key|
                    merge = hash[method][key] && params[key] # merge if both scopes have the same key
                    if key == :conditions && merge
                      hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
                    elsif key == :include && merge
                      hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
                    else
                      hash[method][key] = hash[method][key] || params[key]
                    end
                  end
                else
                  hash[method] = params.merge(hash[method])
                end
              else
                hash[method] = params
            end
            hash
          end
        end

        self.scoped_methods << method_scoping

        begin
          yield
        ensure
          self.scoped_methods.pop
        end
      end

      # Works like with_scope, but discards any nested properties.
      def with_exclusive_scope(method_scoping = {}, &block)
        with_scope(method_scoping, :overwrite, &block)
      end

      # Overwrite the default class equality method to provide support for association proxies.
      def ===(object)
        object.is_a?(self)
      end      

      # Deprecated 
      def threaded_connections #:nodoc:
        allow_concurrency
      end

      # Deprecated 
      def threaded_connections=(value) #:nodoc:
        self.allow_concurrency = value
      end

      # Returns the base AR subclass that this class descends from. If A
      # extends AR::Base, A.base_class will return A. If B descends from A
      # through some arbitrarily deep hierarchy, B.base_class will return A.
      def base_class
        class_of_active_record_descendant(self)
      end

      # Set this to true if this is an abstract class (see #abstract_class?).
      attr_accessor :abstract_class

      # Returns whether this class is a base AR class.  If A is a base class and
      # B descends from A, then B.base_class will return B.
      def abstract_class?
        abstract_class == true
      end

      private
        def find_initial(options)
          options.update(:limit => 1) unless options[:include]
          find_every(options).first
        end
           
        def find_every(options)
          records = scoped?(:find, :include) || options[:include] ?
            find_with_associations(options) : 
            find_by_sql(construct_finder_sql(options))

          records.each { |record| record.readonly! } if options[:readonly]

          records
        end
 
        def find_from_ids(ids, options)
          expects_array = ids.first.kind_of?(Array)       
          return ids.first if expects_array && ids.first.empty?
        
          ids = ids.flatten.compact.uniq

          case ids.size
            when 0
              raise RecordNotFound, "Couldn't find #{name} without an ID"
            when 1
              result = find_one(ids.first, options)
              expects_array ? [ result ] : result
            else
              find_some(ids, options)
          end
        end
      
        def find_one(id, options)
          conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
          options.update :conditions => "#{table_name}.#{primary_key} = #{sanitize(id)}#{conditions}"

          if result = find_initial(options)
            result
          else
            raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
          end
        end
      
        def find_some(ids, options)
          conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
          ids_list   = ids.map { |id| sanitize(id) }.join(',')
          options.update :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"

          result = find_every(options)

          if result.size == ids.size
            result
          else
            raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
          end
        end

        # Finder methods must instantiate through this method to work with the single-table inheritance model
        # that makes it possible to create objects of different types from the same table.
        def instantiate(record)
          object = 
            if subclass_name = record[inheritance_column]
              if subclass_name.empty?
                allocate
              else
                require_association_class(subclass_name)
                begin
                  compute_type(subclass_name).allocate
                rescue NameError
                  raise SubclassNotFound,
                    "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
                    "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
                    "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
                    "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
                end
              end
            else
              allocate
            end

          object.instance_variable_set("@attributes", record)
          object
        end

        # Nest the type name in the same module as this class.
        # Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
        def type_name_with_module(type_name)
          (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
        end

        def construct_finder_sql(options)
          scope = scope(:find)
          sql  = "SELECT #{(scope && scope[:select]) || options[:select] || '*'} "
          sql << "FROM #{(scope && scope[:from]) || options[:from] || table_name} "

          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)

          sql << " GROUP BY #{options[:group]} " if options[:group]
          sql << " ORDER BY #{options[:order]} " if options[:order]

          add_limit!(sql, options, scope)

          sql
        end

        # Merges includes so that the result is a valid +include+
        def merge_includes(first, second)
         safe_to_array(first) + safe_to_array(second)
        end

        # Object#to_a is deprecated, though it does have the desired behaviour
        def safe_to_array(o)
          case o
          when NilClass
            []
          when Array
            o
          else
            [o]
          end
        end

        # The optional scope argument is for the current :find scope.
        def add_limit!(sql, options, scope = :auto)
          scope = scope(:find) if :auto == scope
          if scope
            options[:limit]  ||= scope[:limit]
            options[:offset] ||= scope[:offset]
          end
          connection.add_limit_offset!(sql, options)
        end

        # The optional scope argument is for the current :find scope.
        def add_joins!(sql, options, scope = :auto)
          scope = scope(:find) if :auto == scope
          join = (scope && scope[:joins]) || options[:joins]
          sql << " #{join} " if join
        end

        # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
        # The optional scope argument is for the current :find scope.
        def add_conditions!(sql, conditions, scope = :auto)
          scope = scope(:find) if :auto == scope
          segments = []
          segments << sanitize_sql(scope[:conditions]) if scope && scope[:conditions]
          segments << sanitize_sql(conditions) unless conditions.nil?
          segments << type_condition unless descends_from_active_record?        
          segments.compact!
          sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
        end

        def type_condition
          quoted_inheritance_column = connection.quote_column_name(inheritance_column)
          type_condition = subclasses.inject("#{table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
            condition << "OR #{table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
          end

          " (#{type_condition}) "
        end

        # Guesses the table name, but does not decorate it with prefix and suffix information.
        def undecorated_table_name(class_name = base_class.name)
          table_name = Inflector.underscore(Inflector.demodulize(class_name))
          table_name = Inflector.pluralize(table_name) if pluralize_table_names
          table_name
        end

        # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
        # find(:first, :conditions => ["user_name = ?", user_name]) and  find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
        # respectively. Also works for find(:all), but using find_all_by_amount(50) that are turned into find(:all, :conditions => ["amount = ?", 50]).
        #
        # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
        # is actually find_all_by_amount(amount, options).
        def method_missing(method_id, *arguments)
          if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
            finder, deprecated_finder = determine_finder(match), determine_deprecated_finder(match)

            attribute_names = extract_attribute_names_from_match(match)
            super unless all_attributes_exists?(attribute_names)

            conditions = construct_conditions_from_arguments(attribute_names, arguments)

            case extra_options = arguments[attribute_names.size]
              when nil
                options = { :conditions => conditions }
                set_readonly_option!(options)
                send(finder, options)

              when Hash
                finder_options = extra_options.merge(:conditions => conditions)
                validate_find_options(finder_options)
                set_readonly_option!(finder_options)

                if extra_options[:conditions]
                  with_scope(:find => { :conditions => extra_options[:conditions] }) do
                    send(finder, finder_options)
                  end
                else
                  send(finder, finder_options)
                end

              else
                send(deprecated_finder, conditions, *arguments[attribute_names.length..-1]) # deprecated API
            end
          elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s)
            attribute_names = extract_attribute_names_from_match(match)
            super unless all_attributes_exists?(attribute_names)

            options = { :conditions => construct_conditions_from_arguments(attribute_names, arguments) }
            set_readonly_option!(options)
            find_initial(options) || create(construct_attributes_from_arguments(attribute_names, arguments))
          else
            super
          end
        end

        def determine_finder(match)
          match.captures.first == 'all_by' ? :find_every : :find_initial
        end

        def determine_deprecated_finder(match)
          match.captures.first == 'all_by' ? :find_all : :find_first
        end

        def extract_attribute_names_from_match(match)
          match.captures.last.split('_and_')
        end

        def construct_conditions_from_arguments(attribute_names, arguments)
          conditions = []
          attribute_names.each_with_index { |name, idx| conditions << "#{table_name}.#{connection.quote_column_name(name)} #{attribute_condition(arguments[idx])} " }
          [ conditions.join(" AND "), *arguments[0...attribute_names.length] ]
        end
        
        def construct_attributes_from_arguments(attribute_names, arguments)
          attributes = {}
          attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
          attributes
        end

        def all_attributes_exists?(attribute_names)
          attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
        end

        def attribute_condition(argument)
          case argument
            when nil   then "IS ?"
            when Array then "IN (?)"
            else            "= ?"
          end
        end

        # Defines an "attribute" method (like #inheritance_column or
        # #table_name). A new (class) method will be created with the
        # given name. If a value is specified, the new method will
        # return that value (as a string). Otherwise, the given block
        # will be used to compute the value of the method.
        #
        # The original method will be aliased, with the new name being
        # prefixed with "original_". This allows the new method to
        # access the original value.
        #
        # Example:
        #
        #   class A < ActiveRecord::Base
        #     define_attr_method :primary_key, "sysid"
        #     define_attr_method( :inheritance_column ) do
        #       original_inheritance_column + "_id"
        #     end
        #   end
        def define_attr_method(name, value=nil, &block)
          sing = class << self; self; end
          sing.send :alias_method, "original_#{name}", name
          if block_given?
            sing.send :define_method, name, &block
          else
            # use eval instead of a block to work around a memory leak in dev
            # mode in fcgi
            sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
          end
        end

      protected
        def subclasses #:nodoc:
          @@subclasses[self] ||= []
          @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
        end

        # Test whether the given method and optional key are scoped.
        def scoped?(method, key = nil) #:nodoc:
          if current_scoped_methods && (scope = current_scoped_methods[method])
            !key || scope.has_key?(key)
          end
        end

        # Retrieve the scope for the given method and optional key.
        def scope(method, key = nil) #:nodoc:
          if current_scoped_methods && (scope = current_scoped_methods[method])
            key ? scope[key] : scope
          end
        end

        def thread_safe_scoped_methods #:nodoc:
          scoped_methods = (Thread.current[:scoped_methods] ||= {})
          scoped_methods[self] ||= []
        end
        
        def single_threaded_scoped_methods #:nodoc:
          @scoped_methods ||= []
        end
        
        # pick up the correct scoped_methods version from @@allow_concurrency
        if @@allow_concurrency
          alias_method :scoped_methods, :thread_safe_scoped_methods
        else
          alias_method :scoped_methods, :single_threaded_scoped_methods
        end
        
        def current_scoped_methods #:nodoc:
          scoped_methods.last
        end

        # Returns the class type of the record using the current module as a prefix. So descendents of
        # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
        def compute_type(type_name)
          modularized_name = type_name_with_module(type_name)
          begin
            instance_eval(modularized_name)
          rescue NameError => e
            instance_eval(type_name)
          end
        end

        # Returns the class descending directly from ActiveRecord in the inheritance hierarchy.
        def class_of_active_record_descendant(klass)
          if klass.superclass == Base || klass.superclass.abstract_class?
            klass
          elsif klass.superclass.nil?
            raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
          else
            class_of_active_record_descendant(klass.superclass)
          end
        end

        # Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
        def class_name_of_active_record_descendant(klass) #:nodoc:
          klass.base_class.name
        end

        # Accepts an array or string.  The string is returned untouched, but the array has each value
        # sanitized and interpolated into the sql statement.
        #   ["name='%s' and group_id='%s'", "foo'bar", 4]  returns  "name='foo''bar' and group_id='4'"
        def sanitize_sql(ary)
          return ary unless ary.is_a?(Array)

          statement, *values = ary
          if values.first.is_a?(Hash) and statement =~ /:\w+/
            replace_named_bind_variables(statement, values.first)
          elsif statement.include?('?')
            replace_bind_variables(statement, values)
          else
            statement % values.collect { |value| connection.quote_string(value.to_s) }
          end
        end

        alias_method :sanitize_conditions, :sanitize_sql

        def replace_bind_variables(statement, values) #:nodoc:
          raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
          bound = values.dup
          statement.gsub('?') { quote_bound_value(bound.shift) }
        end

        def replace_named_bind_variables(statement, bind_vars) #:nodoc:
          statement.gsub(/:(\w+)/) do
            match = $1.to_sym
            if bind_vars.include?(match)
              quote_bound_value(bind_vars[match])
            else
              raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
            end
          end
        end

        def quote_bound_value(value) #:nodoc:
          if (value.respond_to?(:map) && !value.is_a?(String))
            value.map { |v| connection.quote(v) }.join(',')
          else
            connection.quote(value)
          end
        end

        def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
          unless expected == provided
            raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
          end
        end

        def extract_options_from_args!(args) #:nodoc:
          args.last.is_a?(Hash) ? args.pop : {}
        end

        VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
                               :order, :select, :readonly, :group, :from      ]
        
        def validate_find_options(options) #:nodoc:
          options.assert_valid_keys(VALID_FIND_OPTIONS)
        end
        
        def set_readonly_option!(options) #:nodoc:
          # Inherit :readonly from finder scope if set.  Otherwise,
          # if :joins is not blank then :readonly defaults to true.
          unless options.has_key?(:readonly)
            if scoped?(:find, :readonly)
              options[:readonly] = scope(:find, :readonly)
            elsif !options[:joins].blank? && !options[:select]
              options[:readonly] = true
            end
          end
        end

        def encode_quoted_value(value) #:nodoc:
          quoted_value = connection.quote(value)
          quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) " 
          quoted_value 
        end
    end

    public
      # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
      # attributes but not yet saved (pass a hash with key names matching the associated table column names).
      # In both instances, valid attribute keys are determined by the column names of the associated table --
      # hence you can't have attributes that aren't part of the table columns.
      def initialize(attributes = nil)
        @attributes = attributes_from_column_definition
        @new_record = true
        ensure_proper_type
        self.attributes = attributes unless attributes.nil?
        yield self if block_given?
      end

      # A model instance's primary key is always available as model.id
      # whether you name it the default 'id' or set it to something else.
      def id
        attr_name = self.class.primary_key
        column = column_for_attribute(attr_name)
        define_read_method(:id, attr_name, column) if self.class.generate_read_methods
        read_attribute(attr_name)
      end

      # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
      alias_method :to_param, :id

      def id_before_type_cast #:nodoc:
        read_attribute_before_type_cast(self.class.primary_key)
      end

      def quoted_id #:nodoc:
        quote(id, column_for_attribute(self.class.primary_key))
      end

      # Sets the primary ID.
      def id=(value)
        write_attribute(self.class.primary_key, value)
      end

      # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
      def new_record?
        @new_record
      end

      # * No record exists: Creates a new record with values matching those of the object attributes.
      # * A record does exist: Updates the record with values matching those of the object attributes.
      def save
        raise ReadOnlyRecord if readonly?
        create_or_update
      end
      
      # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a 
      # RecordNotSaved exception
      def save!
        save || raise(RecordNotSaved)
      end

      # Deletes the record in the database and freezes this instance to reflect that no changes should
      # be made (since they can't be persisted).
      def destroy
        unless new_record?
          connection.delete <<-end_sql, "#{self.class.name} Destroy"
            DELETE FROM #{self.class.table_name}
            WHERE #{self.class.primary_key} = #{quoted_id}
          end_sql
        end

        freeze
      end

      # Returns a clone of the record that hasn't been assigned an id yet and
      # is treated as a new record.  Note that this is a "shallow" clone:
      # it copies the object's attributes only, not its associations.
      # The extent of a "deep" clone is application-specific and is therefore
      # left to the application to implement according to its need.
      def clone
        attrs = self.attributes_before_type_cast
        attrs.delete(self.class.primary_key)
        self.class.new do |record|
          record.send :instance_variable_set, '@attributes', attrs
        end
      end

      # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
      # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
      # doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
      def update_attribute(name, value)
        send(name.to_s + '=', value)
        save
      end

      # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
      # fail and false will be returned.
      def update_attributes(attributes)
        self.attributes = attributes
        save
      end

      # Initializes the +attribute+ to zero if nil and adds one. Only makes sense for number-based attributes. Returns self.
      def increment(attribute)
        self[attribute] ||= 0
        self[attribute] += 1
        self
      end

      # Increments the +attribute+ and saves the record.
      def increment!(attribute)
        increment(attribute).update_attribute(attribute, self[attribute])
      end

      # Initializes the +attribute+ to zero if nil and subtracts one. Only makes sense for number-based attributes. Returns self.
      def decrement(attribute)
        self[attribute] ||= 0
        self[attribute] -= 1
        self
      end

      # Decrements the +attribute+ and saves the record.
      def decrement!(attribute)
        decrement(attribute).update_attribute(attribute, self[attribute])
      end

      # Turns an +attribute+ that's currently true into false and vice versa. Returns self.
      def toggle(attribute)
        self[attribute] = !send("#{attribute}?")
        self
      end

      # Toggles the +attribute+ and saves the record.
      def toggle!(attribute)
        toggle(attribute).update_attribute(attribute, self[attribute])
      end

      # Reloads the attributes of this object from the database.
      def reload
        clear_aggregation_cache
        clear_association_cache
        @attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
        self
      end

      # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
      # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
      # (Alias for the protected read_attribute method).
      def [](attr_name)
        read_attribute(attr_name)
      end

      # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
      # (Alias for the protected write_attribute method).
      def []=(attr_name, value)
        write_attribute(attr_name, value)
      end

      # Allows you to set all the attributes at once by passing in a hash with keys
      # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
      # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
      # specify which attributes *can* be accessed in with the +attr_accessible+ macro. Then all the
      # attributes not included in that won't be allowed to be mass-assigned.
      def attributes=(new_attributes)
        return if new_attributes.nil?
        attributes = new_attributes.dup
        attributes.stringify_keys!

        multi_parameter_attributes = []
        remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
          k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
        end

        assign_multiparameter_attributes(multi_parameter_attributes)
      end


      # Returns a hash of all the attributes with their names as keys and clones of their objects as values.
      def attributes(options = nil)
        attributes = clone_attributes :read_attribute
        
        if options.nil?
          attributes
        else
          if except = options[:except]
            except = Array(except).collect { |attribute| attribute.to_s }
            except.each { |attribute_name| attributes.delete(attribute_name) }
            attributes
          elsif only = options[:only]
            only = Array(only).collect { |attribute| attribute.to_s }
            attributes.delete_if { |key, value| !only.include?(key) }
            attributes
          else
            raise ArgumentError, "Options does not specify :except or :only (#{options.keys.inspect})"
          end
        end
      end

      # Returns a hash of cloned attributes before typecasting and deserialization.
      def attributes_before_type_cast
        clone_attributes :read_attribute_before_type_cast
      end

      # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
      # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
      def attribute_present?(attribute)
        value = read_attribute(attribute)
        !value.blank? or value == 0
      end

      # Returns true if the given attribute is in the attributes hash
      def has_attribute?(attr_name)
        @attributes.has_key?(attr_name.to_s)
      end

      # Returns an array of names for the attributes available on this object sorted alphabetically.
      def attribute_names
        @attributes.keys.sort
      end

      # Returns the column object for the named attribute.
      def column_for_attribute(name)
        self.class.columns_hash[name.to_s]
      end

      # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
      def ==(comparison_object)
        comparison_object.equal?(self) ||
          (comparison_object.instance_of?(self.class) && 
            comparison_object.id == id && 
            !comparison_object.new_record?)
      end

      # Delegates to ==
      def eql?(comparison_object)
        self == (comparison_object)
      end

      # Delegates to id in order to allow two records of the same type and id to work with something like:
      #   [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
      def hash
        id.hash
      end

      # For checking respond_to? without searching the attributes (which is faster).
      alias_method :respond_to_without_attributes?, :respond_to?

      # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
      # person.respond_to?("name?") which will all return true.
      def respond_to?(method, include_priv = false)
        if @attributes.nil?
          return super 
        elsif attr_name = self.class.column_methods_hash[method.to_sym]
          return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
          return false if self.class.read_methods.include?(attr_name)
        elsif @attributes.include?(method_name = method.to_s)
          return true
        elsif md = /(=|\?|_before_type_cast)$/.match(method_name)
          return true if @attributes.include?(md.pre_match)
        end
        # super must be called at the end of the method, because the inherited respond_to?
        # would return true for generated readers, even if the attribute wasn't present
        super
      end

      # Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
      def freeze
        @attributes.freeze; self
      end

      def frozen?
        @attributes.frozen?
      end

      # Records loaded through joins with piggy-back attributes will be marked as read only as they cannot be saved and return true to this query.
      def readonly?
        @readonly == true
      end

      def readonly! #:nodoc:
        @readonly = true
      end

      # Builds an XML document to represent the model.   Some configuration is
      # availble through +options+, however more complicated cases should use 
      # Builder.
      #
      # By default the generated XML document will include the processing 
      # instruction and all object's attributes.  For example:
      #    
      #   <?xml version="1.0" encoding="UTF-8"?>
      #   <topic>
      #     <title>The First Topic</title>
      #     <author-name>David</author-name>
      #     <id type="integer">1</id>
      #     <approved type="boolean">false</approved>
      #     <replies-count type="integer">0</replies-count>
      #     <bonus-time type="datetime">2000-01-01T08:28:00+12:00</bonus-time>
      #     <written-on type="datetime">2003-07-16T09:28:00+1200</written-on>
      #     <content>Have a nice day</content>
      #     <author-email-address>david@loudthinking.com</author-email-address>
      #     <parent-id></parent-id>
      #     <last-read type="date">2004-04-15</last-read>
      #   </topic>
      #
      # This behaviour can be controlled with :only, :except, and :skip_instruct 
      # for instance:
      #
      #   topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
      #
      #   <topic>
      #     <title>The First Topic</title>
      #     <author-name>David</author-name>
      #     <approved type="boolean">false</approved>
      #     <content>Have a nice day</content>
      #     <author-email-address>david@loudthinking.com</author-email-address>
      #     <parent-id></parent-id>
      #     <last-read type="date">2004-04-15</last-read>
      #   </topic>
      # 
      # To include first level associations use :include
      #
      #   firm.to_xml :include => [ :account, :clients ]
      #
      #   <?xml version="1.0" encoding="UTF-8"?>
      #   <firm>
      #     <id type="integer">1</id>
      #     <rating type="integer">1</rating>
      #     <name>37signals</name>
      #     <clients>
      #       <client>
      #         <rating type="integer">1</rating>
      #         <name>Summit</name>
      #       </client>
      #       <client>
      #         <rating type="integer">1</rating>
      #         <name>Microsoft</name>
      #       </client>
      #     </clients>
      #     <account>
      #       <id type="integer">1</id>
      #       <credit-limit type="integer">50</credit-limit>
      #     </account>
      #   </firm>
      def to_xml(options = {})
        options[:root]    ||= self.class.to_s.underscore
        options[:except]    = Array(options[:except]) << self.class.inheritance_column unless options[:only] # skip type column
        root_only_or_except = { :only => options[:only], :except => options[:except] }

        attributes_for_xml = attributes(root_only_or_except)
        
        if include_associations = options.delete(:include)
          include_has_options = include_associations.is_a?(Hash)
          
          for association in include_has_options ? include_associations.keys : Array(include_associations)
            association_options = include_has_options ? include_associations[association] : root_only_or_except

            case self.class.reflect_on_association(association).macro
              when :has_many, :has_and_belongs_to_many
                records = send(association).to_a
                unless records.empty?
                  attributes_for_xml[association] = records.collect do |record| 
                    record.attributes(association_options)
                  end
                end
              when :has_one, :belongs_to
                if record = send(association)
                  attributes_for_xml[association] = record.attributes(association_options)
                end
            end
          end
        end

        attributes_for_xml.to_xml(options)
      end

    private
      def create_or_update
        if new_record? then create else update end
      end

      # Updates the associated record with values matching those of the instance attributes.
      def update
        connection.update(
          "UPDATE #{self.class.table_name} " +
          "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
          "WHERE #{self.class.primary_key} = #{quote(id)}",
          "#{self.class.name} Update"
        )
        
        return true
      end

      # Creates a new record with values matching those of the instance attributes.
      def create
        if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
          self.id = connection.next_sequence_value(self.class.sequence_name)
        end

        self.id = connection.insert(
          "INSERT INTO #{self.class.table_name} " +
          "(#{quoted_column_names.join(', ')}) " +
          "VALUES(#{attributes_with_quotes.values.join(', ')})",
          "#{self.class.name} Create",
          self.class.primary_key, self.id, self.class.sequence_name
        )

        @new_record = false
        
        return true
      end

      # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
      # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
      # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
      # Message class in that example.
      def ensure_proper_type
        unless self.class.descends_from_active_record?
          write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name))
        end
      end

      # Allows access to the object attributes, which are held in the @attributes hash, as were
      # they first-class methods. So a Person class with a name attribute can use Person#name and
      # Person#name= and never directly use the attributes hash -- except for multiple assigns with
      # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
      # the completed attribute is not nil or 0.
      #
      # It's also possible to instantiate related objects, so a Client class belonging to the clients
      # table with a master_id foreign key can instantiate master through Client#master.
      def method_missing(method_id, *args, &block)
        method_name = method_id.to_s
        if @attributes.include?(method_name) or
            (md = /\?$/.match(method_name) and
            @attributes.include?(method_name = md.pre_match))
          define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
          md ? query_attribute(method_name) : read_attribute(method_name)
        elsif self.class.primary_key.to_s == method_name
          id
        elsif md = /(=|_before_type_cast)$/.match(method_name)
          attribute_name, method_type = md.pre_match, md.to_s
          if @attributes.include?(attribute_name)
            case method_type
              when '='
                write_attribute(attribute_name, args.first)
              when '_before_type_cast'
                read_attribute_before_type_cast(attribute_name)
            end
          else
            super
          end
        else
          super
        end
      end

      # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
      # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
      def read_attribute(attr_name)
        attr_name = attr_name.to_s
        if !(value = @attributes[attr_name]).nil?
          if column = column_for_attribute(attr_name)
            if unserializable_attribute?(attr_name, column)
              unserialize_attribute(attr_name)
            else
              column.type_cast(value)
            end
          else
            value
          end
        else
          nil
        end
      end

      def read_attribute_before_type_cast(attr_name)
        @attributes[attr_name]
      end

      # Called on first read access to any given column and generates reader
      # methods for all columns in the columns_hash if
      # ActiveRecord::Base.generate_read_methods is set to true.
      def define_read_methods
        self.class.columns_hash.each do |name, column|
          unless self.class.serialized_attributes[name]
            define_read_method(name.to_sym, name, column) unless respond_to_without_attributes?(name)
            define_question_method(name)     unless respond_to_without_attributes?("#{name}?")
          end
        end
      end

      # Define an attribute reader method.  Cope with nil column.
      def define_read_method(symbol, attr_name, column)
        cast_code = column.type_cast_code('v') if column
        access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
        
        unless attr_name.to_s == self.class.primary_key.to_s
          access_code = access_code.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ")
          self.class.read_methods << attr_name
        end
        
        evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end"
      end
      
      # Define an attribute ? method.
      def define_question_method(attr_name)
        unless attr_name.to_s == self.class.primary_key.to_s
          self.class.read_methods << "#{attr_name}?"
        end
        
        evaluate_read_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end"
      end
      
      # Evaluate the definition for an attribute reader or ? method
      def evaluate_read_method(attr_name, method_definition)
        begin
          self.class.class_eval(method_definition)
        rescue SyntaxError => err
          self.class.read_methods.delete(attr_name)
          if logger
            logger.warn "Exception occured during reader method compilation."
            logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
            logger.warn "#{err.message}"
          end
        end
      end

      # Returns true if the attribute is of a text column and marked for serialization.
      def unserializable_attribute?(attr_name, column)
        column.text? && self.class.serialized_attributes[attr_name]
      end

      # Returns the unserialized object of the attribute.
      def unserialize_attribute(attr_name)
        unserialized_object = object_from_yaml(@attributes[attr_name])

        if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
          @attributes[attr_name] = unserialized_object
        else
          raise SerializationTypeMismatch,
            "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
        end
      end

      # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
      # columns are turned into nil.
      def write_attribute(attr_name, value)
        attr_name = attr_name.to_s
        if (column = column_for_attribute(attr_name)) && column.number?
          @attributes[attr_name] = convert_number_column_value(value)
        else
          @attributes[attr_name] = value
        end
      end

      def convert_number_column_value(value)
        case value
          when FalseClass: 0
          when TrueClass:  1
          when '':         nil
          else value
        end
      end

      def query_attribute(attr_name)
        attribute = @attributes[attr_name]
        if attribute.kind_of?(Fixnum) && attribute == 0
          false
        elsif attribute.kind_of?(String) && attribute == "0"
          false
        elsif attribute.kind_of?(String) && attribute.empty?
          false
        elsif attribute.nil?
          false
        elsif attribute == false
          false
        elsif attribute == "f"
          false
        elsif attribute == "false"
          false
        else
          true
        end
      end

      def remove_attributes_protected_from_mass_assignment(attributes)
        if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
          attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
        elsif self.class.protected_attributes.nil?
          attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
        elsif self.class.accessible_attributes.nil?
          attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"").intern) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
        end
      end

      # The primary key and inheritance column can never be set by mass-assignment for security reasons.
      def attributes_protected_by_default
        default = [ self.class.primary_key, self.class.inheritance_column ]
        default << 'id' unless self.class.primary_key.eql? 'id'
        default
      end

      # Returns copy of the attributes hash where all the values have been safely quoted for use in
      # an SQL statement.
      def attributes_with_quotes(include_primary_key = true)
        attributes.inject({}) do |quoted, (name, value)|
          if column = column_for_attribute(name)
            quoted[name] = quote(value, column) unless !include_primary_key && column.primary
          end
          quoted
        end
      end

      # Quote strings appropriately for SQL statements.
      def quote(value, column = nil)
        self.class.connection.quote(value, column)
      end

      # Interpolate custom sql string in instance context.
      # Optional record argument is meant for custom insert_sql.
      def interpolate_sql(sql, record = nil)
        instance_eval("%@#{sql.gsub('@', '\@')}@")
      end

      # Initializes the attributes array with keys matching the columns from the linked table and
      # the values matching the corresponding default value of that column, so
      # that a new instance, or one populated from a passed-in Hash, still has all the attributes
      # that instances loaded from the database would.
      def attributes_from_column_definition
        self.class.columns.inject({}) do |attributes, column|
          attributes[column.name] = column.default unless column.name == self.class.primary_key
          attributes
        end
      end

      # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
      # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
      # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
      # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
      # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
      # s for String, and a for Array. If all the values for a given attribute is empty, the attribute will be set to nil.
      def assign_multiparameter_attributes(pairs)
        execute_callstack_for_multiparameter_attributes(
          extract_callstack_for_multiparameter_attributes(pairs)
        )
      end

      # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
      def execute_callstack_for_multiparameter_attributes(callstack)
        errors = []
        callstack.each do |name, values|
          klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
          if values.empty?
            send(name + "=", nil)
          else
            begin
              send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values))
            rescue => ex
              errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
            end
          end
        end
        unless errors.empty?
          raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
        end
      end

      def extract_callstack_for_multiparameter_attributes(pairs)
        attributes = { }

        for pair in pairs
          multiparameter_name, value = pair
          attribute_name = multiparameter_name.split("(").first
          attributes[attribute_name] = [] unless attributes.include?(attribute_name)

          unless value.empty?
            attributes[attribute_name] <<
              [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
          end
        end

        attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
      end

      def type_cast_attribute_value(multiparameter_name, value)
        multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
      end

      def find_parameter_position(multiparameter_name)
        multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
      end

      # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
      def comma_pair_list(hash)
        hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
      end

      def quoted_column_names(attributes = attributes_with_quotes)
        attributes.keys.collect do |column_name|
          self.class.connection.quote_column_name(column_name)
        end
      end

      def quote_columns(quoter, hash)
        hash.inject({}) do |quoted, (name, value)|
          quoted[quoter.quote_column_name(name)] = value
          quoted
        end
      end

      def quoted_comma_pair_list(quoter, hash)
        comma_pair_list(quote_columns(quoter, hash))
      end

      def object_from_yaml(string)
        return string unless string.is_a?(String)
        YAML::load(string) rescue string
      end

      def clone_attributes(reader_method = :read_attribute, attributes = {})
        self.attribute_names.inject(attributes) do |attributes, name|
          attributes[name] = clone_attribute_value(reader_method, name)
          attributes
        end
      end

      def clone_attribute_value(reader_method, attribute_name)
        value = send(reader_method, attribute_name)
        value.clone
      rescue TypeError, NoMethodError
        value
      end
  end
end
module ActiveRecord
  module Calculations #:nodoc:
    CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset]
    def self.included(base)
      base.extend(ClassMethods)
    end

    module ClassMethods
      # Count operates using three different approaches. 
      #
      # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
      # * Count by conditions or joins: For backwards compatibility, you can pass in +conditions+ and +joins+ as individual parameters.
      # * Count using options will find the row count matched by the options used.
      #
      # The last approach, count using options, accepts an option hash as the only parameter. The options are:
      #
      # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
      # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
      #   The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
      # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
      #   to already defined associations. When using named associations count returns the number DISTINCT items for the model you're counting.
      #   See eager loading under Associations.
      # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
      # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
      #   include the joined columns.
      # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
      #
      # Examples for counting all:
      #   Person.count         # returns the total count of all people
      #
      # Examples for count by +conditions+ and +joins+ (for backwards compatibility):
      #   Person.count("age > 26")  # returns the number of people older than 26
      #   Person.find("age > 26 AND job.salary > 60000", "LEFT JOIN jobs on jobs.person_id = person.id") # returns the total number of rows matching the conditions and joins fetched by SELECT COUNT(*).
      #
      # Examples for count with options:
      #   Person.count(:conditions => "age > 26")
      #   Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
      #   Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins. 
      #   Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
      #   Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
      #
      # Note: Person.count(:all) will not work because it will use :all as the condition.  Use Person.count instead.
      def count(*args)
        options     = {}
        column_name = :all
        # For backwards compatibility, we need to handle both count(conditions=nil, joins=nil) or count(options={}) or count(column_name=:all, options={}).
        if args.size >= 0 && args.size <= 2
          if args.first.is_a?(Hash)
            options     = args.first
          elsif args[1].is_a?(Hash)
            options     = args[1]
            column_name = args.first
          else
            # Handle legacy paramter options: def count(conditions=nil, joins=nil)
            options.merge!(:conditions => args[0]) if args.length > 0
            options.merge!(:joins => args[1])      if args.length > 1
          end
        else
          raise(ArgumentError, "Unexpected parameters passed to count(*args): expected either count(conditions=nil, joins=nil) or count(options={})")
        end

        if options[:include] || scope(:find, :include)
          count_with_associations(options)
        else
          calculate(:count, column_name, options)
        end
      end

      # Calculates average value on a given column.  The value is returned as a float.  See #calculate for examples with options.
      #
      #   Person.average('age')
      def average(column_name, options = {})
        calculate(:avg, column_name, options)
      end

      # Calculates the minimum value on a given column.  The value is returned with the same data type of the column..  See #calculate for examples with options.
      #
      #   Person.minimum('age')
      def minimum(column_name, options = {})
        calculate(:min, column_name, options)
      end

      # Calculates the maximum value on a given column.  The value is returned with the same data type of the column..  See #calculate for examples with options.
      #
      #   Person.maximum('age')
      def maximum(column_name, options = {})
        calculate(:max, column_name, options)
      end

      # Calculates the sum value on a given column.  The value is returned with the same data type of the column..  See #calculate for examples with options.
      #
      #   Person.sum('age')
      def sum(column_name, options = {})
        calculate(:sum, column_name, options)
      end

      # This calculates aggregate values in the given column:  Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
      # Options such as :conditions, :order, :group, :having, and :joins can be passed to customize the query.  
      #
      # There are two basic forms of output:
      #   * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
      #   * Grouped values: This returns an ordered hash of the values and groups them by the :group option.  It takes either a column name, or the name 
      #     of a belongs_to association.
      #
      #       values = Person.maximum(:age, :group => 'last_name')
      #       puts values["Drake"]
      #       => 43
      #
      #       drake  = Family.find_by_last_name('Drake')
      #       values = Person.maximum(:age, :group => :family) # Person belongs_to :family
      #       puts values[drake]
      #       => 43
      #
      #       values.each do |family, max_age|
      #       ...
      #       end
      #
      # Options:
      # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
      # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
      #   The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
      # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
      # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
      # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
      #   include the joined columns.
      # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
      #
      # Examples:
      #   Person.calculate(:count, :all) # The same as Person.count
      #   Person.average(:age) # SELECT AVG(age) FROM people...
      #   Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
      #   Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
      def calculate(operation, column_name, options = {})
        validate_calculation_options(operation, options)
        column_name     = options[:select] if options[:select]
        column_name     = '*' if column_name == :all
        column          = column_for column_name
        aggregate       = select_aggregate(operation, column_name, options)
        aggregate_alias = column_alias_for(operation, column_name)
        if options[:group]
          execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
        else
          execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
        end
      end

      protected
        def construct_calculation_sql(aggregate, aggregate_alias, options) #:nodoc:
          scope = scope(:find)
          sql  = "SELECT #{aggregate} AS #{aggregate_alias}"
          sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
          sql << " FROM #{table_name} "
          add_joins!(sql, options, scope)
          add_conditions!(sql, options[:conditions], scope)
          sql << " GROUP BY #{options[:group_field]}" if options[:group]
          sql << " HAVING #{options[:having]}" if options[:group] && options[:having]
          sql << " ORDER BY #{options[:order]}" if options[:order]
          add_limit!(sql, options)
          sql
        end

        def execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
          value     = connection.select_value(construct_calculation_sql(aggregate, aggregate_alias, options))
          type_cast_calculated_value(value, column, operation)
        end

        def execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
          group_attr      = options[:group].to_s
          association     = reflect_on_association(group_attr.to_sym)
          associated      = association && association.macro == :belongs_to # only count belongs_to associations
          group_field     = (associated ? "#{options[:group]}_id" : options[:group]).to_s
          group_alias     = column_alias_for(group_field)
          group_column    = column_for group_field
          sql             = construct_calculation_sql(aggregate, aggregate_alias, options.merge(:group_field => group_field, :group_alias => group_alias))
          calculated_data = connection.select_all(sql)

          if association
            key_ids     = calculated_data.collect { |row| row[group_alias] }
            key_records = association.klass.base_class.find(key_ids)
            key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
          end

          calculated_data.inject(OrderedHash.new) do |all, row|
            key   = associated ? key_records[row[group_alias].to_i] : type_cast_calculated_value(row[group_alias], group_column)
            value = row[aggregate_alias]
            all << [key, type_cast_calculated_value(value, column, operation)]
          end
        end

      private
        def validate_calculation_options(operation, options = {})
          if operation.to_s == 'count'
            options.assert_valid_keys(CALCULATIONS_OPTIONS + [:include])
          else
            options.assert_valid_keys(CALCULATIONS_OPTIONS)
          end
        end

        def select_aggregate(operation, column_name, options)
          "#{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name})"
        end

        # converts a given key to the value that the database adapter returns as
        #
        #   users.id #=> users_id
        #   sum(id) #=> sum_id
        #   count(distinct users.id) #=> count_distinct_users_id
        #   count(*) #=> count_all
        def column_alias_for(*keys)
          keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_')
        end

        def column_for(field)
          field_name = field.to_s.split('.').last
          columns.detect { |c| c.name.to_s == field_name }
        end

        def type_cast_calculated_value(value, column, operation = nil)
          operation = operation.to_s.downcase
          case operation
            when 'count' then value.to_i
            when 'avg'   then value.to_f
            else column ? column.type_cast(value) : value
          end
        end
    end
  end
end
require 'observer'

module ActiveRecord
  # Callbacks are hooks into the lifecycle of an Active Record object that allows you to trigger logic
  # before or after an alteration of the object state. This can be used to make sure that associated and
  # dependent objects are deleted when destroy is called (by overwriting before_destroy) or to massage attributes
  # before they're validated (by overwriting before_validation). As an example of the callbacks initiated, consider
  # the Base#save call:
  #
  # * (-) save
  # * (-) valid?
  # * (1) before_validation
  # * (2) before_validation_on_create
  # * (-) validate
  # * (-) validate_on_create
  # * (3) after_validation
  # * (4) after_validation_on_create
  # * (5) before_save
  # * (6) before_create
  # * (-) create
  # * (7) after_create
  # * (8) after_save
  #
  # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
  # Active Record lifecycle.
  #
  # Examples:
  #   class CreditCard < ActiveRecord::Base
  #     # Strip everything but digits, so the user can specify "555 234 34" or
  #     # "5552-3434" or both will mean "55523434"
  #     def before_validation_on_create
  #       self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
  #     end
  #   end
  #
  #   class Subscription < ActiveRecord::Base
  #     before_create :record_signup
  #
  #     private
  #       def record_signup
  #         self.signed_up_on = Date.today
  #       end
  #   end
  #
  #   class Firm < ActiveRecord::Base
  #     # Destroys the associated clients and people when the firm is destroyed
  #     before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  #     before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
  #   end
  #
  # == Inheritable callback queues
  #
  # Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
  # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
  # hierarchy. Example:
  #
  #   class Topic < ActiveRecord::Base
  #     before_destroy :destroy_author
  #   end
  #
  #   class Reply < Topic
  #     before_destroy :destroy_readers
  #   end
  #
  # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is run both +destroy_author+ and
  # +destroy_readers+ is called. Contrast this to the situation where we've implemented the save behavior through overwriteable
  # methods:
  #
  #   class Topic < ActiveRecord::Base
  #     def before_destroy() destroy_author end
  #   end
  #
  #   class Reply < Topic
  #     def before_destroy() destroy_readers end
  #   end
  #
  # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So use the callback macros when
  # you want to ensure that a certain callback is called for the entire hierarchy and the regular overwriteable methods when you
  # want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
  #
  # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
  # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
  # be inherited.
  #
  # == Types of callbacks
  #
  # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
  # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
  # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
  # eval methods are deprecated.
  #
  # The method reference callbacks work by specifying a protected or private method available in the object, like this:
  #
  #   class Topic < ActiveRecord::Base
  #     before_destroy :delete_parents
  #
  #     private
  #       def delete_parents
  #         self.class.delete_all "parent_id = #{id}"
  #       end
  #   end
  #
  # The callback objects have methods named after the callback called with the record as the only parameter, such as:
  #
  #   class BankAccount < ActiveRecord::Base
  #     before_save      EncryptionWrapper.new("credit_card_number")
  #     after_save       EncryptionWrapper.new("credit_card_number")
  #     after_initialize EncryptionWrapper.new("credit_card_number")
  #   end
  #
  #   class EncryptionWrapper
  #     def initialize(attribute)
  #       @attribute = attribute
  #     end
  #
  #     def before_save(record)
  #       record.credit_card_number = encrypt(record.credit_card_number)
  #     end
  #
  #     def after_save(record)
  #       record.credit_card_number = decrypt(record.credit_card_number)
  #     end
  #
  #     alias_method :after_find, :after_save
  #
  #     private
  #       def encrypt(value)
  #         # Secrecy is committed
  #       end
  #
  #       def decrypt(value)
  #         # Secrecy is unveiled
  #       end
  #   end
  #
  # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
  # a method by the name of the callback messaged.
  #
  # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
  # which will then be evaluated within the binding of the callback. Example:
  #
  #   class Topic < ActiveRecord::Base
  #     before_destroy 'self.class.delete_all "parent_id = #{id}"'
  #   end
  #
  # Notice that single plings (') are used so the #{id} part isn't evaluated until the callback is triggered. Also note that these
  # inline callbacks can be stacked just like the regular ones:
  #
  #   class Topic < ActiveRecord::Base
  #     before_destroy 'self.class.delete_all "parent_id = #{id}"',
  #                    'puts "Evaluated after parents are destroyed"'
  #   end
  #
  # == The after_find and after_initialize exceptions
  #
  # Because after_find and after_initialize are called for each object found and instantiated by a finder, such as Base.find(:all), we've had
  # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, after_find and
  # after_initialize will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
  # callback types will be called.
  #
  # == Cancelling callbacks
  #
  # If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns
  # false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
  # defined as methods on the model, which are called last.
  module Callbacks
    CALLBACKS = %w(
      after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
      after_validation before_validation_on_create after_validation_on_create before_validation_on_update
      after_validation_on_update before_destroy after_destroy
    )

    def self.append_features(base) #:nodoc:
      super

      base.extend(ClassMethods)
      base.class_eval do
        class << self
          include Observable
          alias_method :instantiate_without_callbacks, :instantiate
          alias_method :instantiate, :instantiate_with_callbacks
        end

        alias_method :initialize_without_callbacks, :initialize
        alias_method :initialize, :initialize_with_callbacks

        alias_method :create_or_update_without_callbacks, :create_or_update
        alias_method :create_or_update, :create_or_update_with_callbacks

        alias_method :valid_without_callbacks, :valid?
        alias_method :valid?, :valid_with_callbacks

        alias_method :create_without_callbacks, :create
        alias_method :create, :create_with_callbacks

        alias_method :update_without_callbacks, :update
        alias_method :update, :update_with_callbacks

        alias_method :destroy_without_callbacks, :destroy
        alias_method :destroy, :destroy_with_callbacks
      end

      CALLBACKS.each do |method|
        base.class_eval <<-"end_eval"
          def self.#{method}(*callbacks, &block)
            callbacks << block if block_given?
            write_inheritable_array(#{method.to_sym.inspect}, callbacks)
          end
        end_eval
      end
    end

    module ClassMethods #:nodoc:
      def instantiate_with_callbacks(record)
        object = instantiate_without_callbacks(record)

        if object.respond_to_without_attributes?(:after_find)
          object.send(:callback, :after_find)
        end

        if object.respond_to_without_attributes?(:after_initialize)
          object.send(:callback, :after_initialize)
        end

        object
      end
    end

    # Is called when the object was instantiated by one of the finders, like Base.find.
    #def after_find() end

    # Is called after the object has been instantiated by a call to Base.new.
    #def after_initialize() end

    def initialize_with_callbacks(attributes = nil) #:nodoc:
      initialize_without_callbacks(attributes)
      result = yield self if block_given?
      callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
      result
    end

    # Is called _before_ Base.save (regardless of whether it's a create or update save).
    def before_save() end

    # Is called _after_ Base.save (regardless of whether it's a create or update save).
    #
    #  class Contact < ActiveRecord::Base
    #    after_save { logger.info( 'New contact saved!' ) }
    #  end
    def after_save()  end
    def create_or_update_with_callbacks #:nodoc:
      return false if callback(:before_save) == false
      result = create_or_update_without_callbacks
      callback(:after_save)
      result
    end

    # Is called _before_ Base.save on new objects that haven't been saved yet (no record exists).
    def before_create() end

    # Is called _after_ Base.save on new objects that haven't been saved yet (no record exists).
    def after_create() end
    def create_with_callbacks #:nodoc:
      return false if callback(:before_create) == false
      result = create_without_callbacks
      callback(:after_create)
      result
    end

    # Is called _before_ Base.save on existing objects that have a record.
    def before_update() end

    # Is called _after_ Base.save on existing objects that have a record.
    def after_update() end

    def update_with_callbacks #:nodoc:
      return false if callback(:before_update) == false
      result = update_without_callbacks
      callback(:after_update)
      result
    end

    # Is called _before_ Validations.validate (which is part of the Base.save call).
    def before_validation() end

    # Is called _after_ Validations.validate (which is part of the Base.save call).
    def after_validation() end

    # Is called _before_ Validations.validate (which is part of the Base.save call) on new objects
    # that haven't been saved yet (no record exists).
    def before_validation_on_create() end

    # Is called _after_ Validations.validate (which is part of the Base.save call) on new objects
    # that haven't been saved yet (no record exists).
    def after_validation_on_create()  end

    # Is called _before_ Validations.validate (which is part of the Base.save call) on
    # existing objects that have a record.
    def before_validation_on_update() end

    # Is called _after_ Validations.validate (which is part of the Base.save call) on
    # existing objects that have a record.
    def after_validation_on_update()  end

    def valid_with_callbacks #:nodoc:
      return false if callback(:before_validation) == false
      if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
      return false if result == false

      result = valid_without_callbacks

      callback(:after_validation)
      if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end

      return result
    end

    # Is called _before_ Base.destroy.
    #
    # Note: If you need to _destroy_ or _nullify_ associated records first,
    # use the _:dependent_ option on your associations.
    def before_destroy() end

    # Is called _after_ Base.destroy (and all the attributes have been frozen).
    #
    #  class Contact < ActiveRecord::Base
    #    after_destroy { |record| logger.info( "Contact #{record.id} was destroyed." ) }
    #  end
    def after_destroy()  end
    def destroy_with_callbacks #:nodoc:
      return false if callback(:before_destroy) == false
      result = destroy_without_callbacks
      callback(:after_destroy)
      result
    end

    private
      def callback(method)
        notify(method)

        callbacks_for(method).each do |callback|
          result = case callback
            when Symbol
              self.send(callback)
            when String
              eval(callback, binding)
            when Proc, Method
              callback.call(self)
            else
              if callback.respond_to?(method)
                callback.send(method, self)
              else
                raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
              end
          end
          return false if result == false
        end

        result = send(method) if respond_to_without_attributes?(method)

        return result
      end

      def callbacks_for(method)
        self.class.read_inheritable_attribute(method.to_sym) or []
      end

      def invoke_and_notify(method)
        notify(method)
        send(method) if respond_to_without_attributes?(method)
      end

      def notify(method) #:nodoc:
        self.class.changed
        self.class.notify_observers(method, self)
      end
  end
end
require 'set'

module ActiveRecord
  class Base
    class ConnectionSpecification #:nodoc:
      attr_reader :config, :adapter_method
      def initialize (config, adapter_method)
        @config, @adapter_method = config, adapter_method
      end
    end

    # Check for activity after at least +verification_timeout+ seconds.
    # Defaults to 0 (always check.)
    cattr_accessor :verification_timeout
    @@verification_timeout = 0

    # The class -> [adapter_method, config] map
    @@defined_connections = {}

    # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
    @@active_connections = {}

    class << self
      # Retrieve the connection cache.
      def thread_safe_active_connections #:nodoc:
        @@active_connections[Thread.current.object_id] ||= {}
      end
     
      def single_threaded_active_connections #:nodoc:
        @@active_connections
      end
     
      # pick up the right active_connection method from @@allow_concurrency
      if @@allow_concurrency
        alias_method :active_connections, :thread_safe_active_connections
      else
        alias_method :active_connections, :single_threaded_active_connections
      end
     
      # set concurrency support flag (not thread safe, like most of the methods in this file)
      def allow_concurrency=(threaded) #:nodoc:
        logger.debug "allow_concurrency=#{threaded}" if logger
        return if @@allow_concurrency == threaded
        clear_all_cached_connections!
        @@allow_concurrency = threaded
        method_prefix = threaded ? "thread_safe" : "single_threaded"
        sing = (class << self; self; end)
        [:active_connections, :scoped_methods].each do |method|
          sing.send(:alias_method, method, "#{method_prefix}_#{method}")
        end
        log_connections if logger
      end
      
      def active_connection_name #:nodoc:
        @active_connection_name ||=
           if active_connections[name] || @@defined_connections[name]
             name
           elsif self == ActiveRecord::Base
             nil
           else
             superclass.active_connection_name
           end
      end

      def clear_active_connection_name #:nodoc:
        @active_connection_name = nil
        subclasses.each { |klass| klass.clear_active_connection_name }
      end

      # Returns the connection currently associated with the class. This can
      # also be used to "borrow" the connection to do database work unrelated
      # to any of the specific Active Records.
      def connection
        if @active_connection_name && (conn = active_connections[@active_connection_name])
          conn
        else
          # retrieve_connection sets the cache key.
          conn = retrieve_connection
          active_connections[@active_connection_name] = conn
        end
      end

      # Clears the cache which maps classes to connections.
      def clear_active_connections!
        clear_cache!(@@active_connections) do |name, conn|
          conn.disconnect!
        end
      end

      # Verify active connections.
      def verify_active_connections! #:nodoc:
        if @@allow_concurrency
          remove_stale_cached_threads!(@@active_connections) do |name, conn|
            conn.disconnect!
          end
        end
        
        active_connections.each_value do |connection|
          connection.verify!(@@verification_timeout)
        end
      end

      private
        def clear_cache!(cache, thread_id = nil, &block)
          if cache
            if @@allow_concurrency
              thread_id ||= Thread.current.object_id
              thread_cache, cache = cache, cache[thread_id]
              return unless cache
            end

            cache.each(&block) if block_given?
            cache.clear
          end
        ensure
          if thread_cache && @@allow_concurrency
            thread_cache.delete(thread_id)
          end
        end

        # Remove stale threads from the cache.
        def remove_stale_cached_threads!(cache, &block)
          stale = Set.new(cache.keys)

          Thread.list.each do |thread|
            stale.delete(thread.object_id) if thread.alive?
          end

          stale.each do |thread_id|
            clear_cache!(cache, thread_id, &block)
          end
        end
        
        def clear_all_cached_connections!
          if @@allow_concurrency
            @@active_connections.each_value do |connection_hash_for_thread|
              connection_hash_for_thread.each_value {|conn| conn.disconnect! }
              connection_hash_for_thread.clear
            end
          else
            @@active_connections.each_value {|conn| conn.disconnect! }
          end
          @@active_connections.clear          
        end
    end

    # Returns the connection currently associated with the class. This can
    # also be used to "borrow" the connection to do database work that isn't
    # easily done without going straight to SQL.
    def connection
      self.class.connection
    end

    # Establishes the connection to the database. Accepts a hash as input where
    # the :adapter key must be specified with the name of a database adapter (in lower-case)
    # example for regular databases (MySQL, Postgresql, etc):
    #
    #   ActiveRecord::Base.establish_connection(
    #     :adapter  => "mysql",
    #     :host     => "localhost",
    #     :username => "myuser",
    #     :password => "mypass",
    #     :database => "somedatabase"
    #   )
    #
    # Example for SQLite database:
    #
    #   ActiveRecord::Base.establish_connection(
    #     :adapter => "sqlite",
    #     :database  => "path/to/dbfile"
    #   )
    #
    # Also accepts keys as strings (for parsing from yaml for example):
    #   ActiveRecord::Base.establish_connection(
    #     "adapter" => "sqlite",
    #     "database"  => "path/to/dbfile"
    #   )
    #
    # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
    # may be returned on an error.
    def self.establish_connection(spec = nil)
      case spec
        when nil
          raise AdapterNotSpecified unless defined? RAILS_ENV
          establish_connection(RAILS_ENV)
        when ConnectionSpecification
          clear_active_connection_name
          @active_connection_name = name
          @@defined_connections[name] = spec
        when Symbol, String
          if configuration = configurations[spec.to_s]
            establish_connection(configuration)
          else
            raise AdapterNotSpecified, "#{spec} database is not configured"
          end
        else
          spec = spec.symbolize_keys
          unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
          adapter_method = "#{spec[:adapter]}_connection"
          unless respond_to?(adapter_method) then raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" end
          remove_connection
          establish_connection(ConnectionSpecification.new(spec, adapter_method))
      end
    end

    # Locate the connection of the nearest super class. This can be an
    # active or defined connections: if it is the latter, it will be
    # opened and set as the active connection for the class it was defined
    # for (not necessarily the current class).
    def self.retrieve_connection #:nodoc:
      # Name is nil if establish_connection hasn't been called for
      # some class along the inheritance chain up to AR::Base yet.
      if name = active_connection_name
        if conn = active_connections[name]
          # Verify the connection.
          conn.verify!(@@verification_timeout)
        elsif spec = @@defined_connections[name]
          # Activate this connection specification.
          klass = name.constantize
          klass.connection = spec
          conn = active_connections[name]
        end
      end

      conn or raise ConnectionNotEstablished
    end

    # Returns true if a connection that's accessible to this class have already been opened.
    def self.connected?
      active_connections[active_connection_name] ? true : false
    end

    # Remove the connection for this class. This will close the active
    # connection and the defined connection (if they exist). The result
    # can be used as argument for establish_connection, for easy
    # re-establishing of the connection.
    def self.remove_connection(klass=self)
      spec = @@defined_connections[klass.name]
      konn = active_connections[klass.name]
      @@defined_connections.delete_if { |key, value| value == spec }
      active_connections.delete_if { |key, value| value == konn }
      konn.disconnect! if konn
      spec.config if spec
    end

    # Set the connection for the class.
    def self.connection=(spec) #:nodoc:
      if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
        active_connections[name] = spec
      elsif spec.kind_of?(ConnectionSpecification)
        self.connection = self.send(spec.adapter_method, spec.config)
      elsif spec.nil?
        raise ConnectionNotEstablished
      else
        establish_connection spec
      end
    end

    # connection state logging
    def self.log_connections #:nodoc:
      if logger
        logger.info "Defined connections: #{@@defined_connections.inspect}"
        logger.info "Active connections: #{active_connections.inspect}"
        logger.info "Active connection name: #{@active_connection_name}"
      end
    end
  end
end
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module DatabaseStatements
      # Returns an array of record hashes with the column names as keys and
      # column values as values.
      def select_all(sql, name = nil)
      end

      # Returns a record hash with the column names as keys and column values
      # as values.
      def select_one(sql, name = nil)
      end

      # Returns a single value from a record
      def select_value(sql, name = nil)
        result = select_one(sql, name)
        result.nil? ? nil : result.values.first
      end

      # Returns an array of the values of the first column in a select:
      #   select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
      def select_values(sql, name = nil)
        result = select_all(sql, name)
        result.map{ |v| v.values.first }
      end

      # Executes the SQL statement in the context of this connection.
      # This abstract method raises a NotImplementedError.
      def execute(sql, name = nil)
        raise NotImplementedError, "execute is an abstract method"
      end

      # Returns the last auto-generated ID from the affected table.
      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) end

      # Executes the update statement and returns the number of rows affected.
      def update(sql, name = nil) end

      # Executes the delete statement and returns the number of rows affected.
      def delete(sql, name = nil) end

      # Wrap a block in a transaction.  Returns result of block.
      def transaction(start_db_transaction = true)
        transaction_open = false
        begin
          if block_given?
            if start_db_transaction
              begin_db_transaction 
              transaction_open = true
            end
            yield
          end
        rescue Exception => database_transaction_rollback
          if transaction_open
            transaction_open = false
            rollback_db_transaction
          end
          raise
        end
      ensure
        commit_db_transaction if transaction_open
      end

      # Begins the transaction (and turns off auto-committing).
      def begin_db_transaction()    end

      # Commits the transaction (and turns on auto-committing).
      def commit_db_transaction()   end

      # Rolls back the transaction (and turns on auto-committing). Must be
      # done if the transaction block raises an exception or returns false.
      def rollback_db_transaction() end

      # Alias for #add_limit_offset!.
      def add_limit!(sql, options)
        add_limit_offset!(sql, options) if options
      end

      # Appends +LIMIT+ and +OFFSET+ options to a SQL statement.
      # This method *modifies* the +sql+ parameter.
      # ===== Examples
      #  add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
      # generates
      #  SELECT * FROM suppliers LIMIT 10 OFFSET 50
      def add_limit_offset!(sql, options)
        if limit = options[:limit]
          sql << " LIMIT #{limit}"
          if offset = options[:offset]
            sql << " OFFSET #{offset}"
          end
        end
      end

      def default_sequence_name(table, column)
        nil
      end

      # Set the sequence to the max value of the table's column.
      def reset_sequence!(table, column, sequence = nil)
        # Do nothing by default.  Implement for PostgreSQL, Oracle, ...
      end
    end
  end
end
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module Quoting
      # Quotes the column value to help prevent
      # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
      def quote(value, column = nil)
        case value
          when String
            if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
              "'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
            elsif column && [:integer, :float].include?(column.type) 
              value.to_s
            else
              "'#{quote_string(value)}'" # ' (for ruby-mode)
            end
          when NilClass              then "NULL"
          when TrueClass             then (column && column.type == :integer ? '1' : quoted_true)
          when FalseClass            then (column && column.type == :integer ? '0' : quoted_false)
          when Float, Fixnum, Bignum then value.to_s
          when Date                  then "'#{value.to_s}'"
          when Time, DateTime        then "'#{quoted_date(value)}'"
          else                            "'#{quote_string(value.to_yaml)}'"
        end
      end

      # Quotes a string, escaping any ' (single quote) and \ (backslash)
      # characters.
      def quote_string(s)
        s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
      end

      # Returns a quoted form of the column name.  This is highly adapter
      # specific.
      def quote_column_name(name)
        name
      end

      def quoted_true
        "'t'"
      end
      
      def quoted_false
        "'f'"
      end
      
      def quoted_date(value)
        value.strftime("%Y-%m-%d %H:%M:%S")
      end
    end
  end
end
require 'parsedate'

module ActiveRecord
  module ConnectionAdapters #:nodoc:
    # An abstract definition of a column in a table.
    class Column
      attr_reader :name, :default, :type, :limit, :null, :sql_type
      attr_accessor :primary

      # Instantiates a new column in the table.
      #
      # +name+ is the column's name, as in <tt><b>supplier_id</b> int(11)</tt>.
      # +default+ is the type-casted default value, such as <tt>sales_stage varchar(20) default <b>'new'</b></tt>.
      # +sql_type+ is only used to extract the column's length, if necessary.  For example, <tt>company_name varchar(<b>60</b>)</tt>.
      # +null+ determines if this column allows +NULL+ values.
      def initialize(name, default, sql_type = nil, null = true)
        @name, @type, @null = name, simplified_type(sql_type), null
        @sql_type = sql_type
        # have to do this one separately because type_cast depends on #type
        @default = type_cast(default)
        @limit   = extract_limit(sql_type) unless sql_type.nil?
        @primary = nil
        @text    = [:string, :text].include? @type
        @number  = [:float, :integer].include? @type
      end

      def text?
        @text
      end

      def number?
        @number
      end

      # Returns the Ruby class that corresponds to the abstract data type.
      def klass
        case type
          when :integer       then Fixnum
          when :float         then Float
          when :datetime      then Time
          when :date          then Date
          when :timestamp     then Time
          when :time          then Time
          when :text, :string then String
          when :binary        then String
          when :boolean       then Object
        end
      end

      # Casts value (which is a String) to an appropriate instance.
      def type_cast(value)
        return nil if value.nil?
        case type
          when :string    then value
          when :text      then value
          when :integer   then value.to_i rescue value ? 1 : 0
          when :float     then value.to_f
          when :datetime  then self.class.string_to_time(value)
          when :timestamp then self.class.string_to_time(value)
          when :time      then self.class.string_to_dummy_time(value)
          when :date      then self.class.string_to_date(value)
          when :binary    then self.class.binary_to_string(value)
          when :boolean   then self.class.value_to_boolean(value)
          else value
        end
      end

      def type_cast_code(var_name)
        case type
          when :string    then nil
          when :text      then nil
          when :integer   then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
          when :float     then "#{var_name}.to_f"
          when :datetime  then "#{self.class.name}.string_to_time(#{var_name})"
          when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
          when :time      then "#{self.class.name}.string_to_dummy_time(#{var_name})"
          when :date      then "#{self.class.name}.string_to_date(#{var_name})"
          when :binary    then "#{self.class.name}.binary_to_string(#{var_name})"
          when :boolean   then "#{self.class.name}.value_to_boolean(#{var_name})"
          else nil
        end
      end

      # Returns the human name of the column name.
      #
      # ===== Examples
      #  Column.new('sales_stage', ...).human_name #=> 'Sales stage'
      def human_name
        Base.human_attribute_name(@name)
      end

      # Used to convert from Strings to BLOBs
      def self.string_to_binary(value)
        value
      end

      # Used to convert from BLOBs to Strings
      def self.binary_to_string(value)
        value
      end

      def self.string_to_date(string)
        return string unless string.is_a?(String)
        date_array = ParseDate.parsedate(string)
        # treat 0000-00-00 as nil
        Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
      end

      def self.string_to_time(string)
        return string unless string.is_a?(String)
        time_array = ParseDate.parsedate(string)[0..5]
        # treat 0000-00-00 00:00:00 as nil
        Time.send(Base.default_timezone, *time_array) rescue nil
      end

      def self.string_to_dummy_time(string)
        return string unless string.is_a?(String)
        time_array = ParseDate.parsedate(string)
        # pad the resulting array with dummy date information
        time_array[0] = 2000; time_array[1] = 1; time_array[2] = 1;
        Time.send(Base.default_timezone, *time_array) rescue nil
      end

      # convert something to a boolean
      def self.value_to_boolean(value)
        return value if value==true || value==false
        case value.to_s.downcase
        when "true", "t", "1" then true
        else false
        end
      end

    private
        def extract_limit(sql_type)
          $1.to_i if sql_type =~ /\((.*)\)/
        end

        def simplified_type(field_type)
          case field_type
            when /int/i
              :integer
            when /float|double|decimal|numeric/i
              :float
            when /datetime/i
              :datetime
            when /timestamp/i
              :timestamp
            when /time/i
              :time
            when /date/i
              :date
            when /clob/i, /text/i
              :text
            when /blob/i, /binary/i
              :binary
            when /char/i, /string/i
              :string
            when /boolean/i
              :boolean
          end
        end
    end

    class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
    end

    class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :default, :null) #:nodoc:
      def to_sql
        column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit)}"
        add_column_options!(column_sql, :null => null, :default => default)
        column_sql
      end
      alias to_s :to_sql

      private
        def type_to_sql(name, limit)
          base.type_to_sql(name, limit) rescue name
        end   

        def add_column_options!(sql, options)
          base.add_column_options!(sql, options.merge(:column => self))
        end
    end

    # Represents a SQL table in an abstract way.
    # Columns are stored as ColumnDefinition in the #columns attribute.
    class TableDefinition
      attr_accessor :columns

      def initialize(base)
        @columns = []
        @base = base
      end

      # Appends a primary key definition to the table definition.
      # Can be called multiple times, but this is probably not a good idea.
      def primary_key(name)
        column(name, native[:primary_key])
      end

      # Returns a ColumnDefinition for the column with name +name+.
      def [](name)
        @columns.find {|column| column.name.to_s == name.to_s}
      end

      # Instantiates a new column for the table.
      # The +type+ parameter must be one of the following values:
      # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
      # <tt>:integer</tt>, <tt>:float</tt>, <tt>:datetime</tt>,
      # <tt>:timestamp</tt>, <tt>:time</tt>, <tt>:date</tt>,
      # <tt>:binary</tt>, <tt>:boolean</tt>.
      #
      # Available options are (none of these exists by default):
      # * <tt>:limit</tt>:
      #   Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
      #   <tt>:binary</tt> or <tt>:integer</tt> columns only)
      # * <tt>:default</tt>:
      #   The column's default value.  You cannot explicitely set the default
      #   value to +NULL+.  Simply leave off this option if you want a +NULL+
      #   default value.
      # * <tt>:null</tt>:
      #   Allows or disallows +NULL+ values in the column.  This option could
      #   have been named <tt>:null_allowed</tt>.
      #
      # This method returns <tt>self</tt>.
      #
      # ===== Examples
      #  # Assuming def is an instance of TableDefinition
      #  def.column(:granted, :boolean)
      #    #=> granted BOOLEAN
      #
      #  def.column(:picture, :binary, :limit => 2.megabytes)
      #    #=> picture BLOB(2097152)
      #
      #  def.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
      #    #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
      def column(name, type, options = {})
        column = self[name] || ColumnDefinition.new(@base, name, type)
        column.limit = options[:limit] || native[type.to_sym][:limit] if options[:limit] or native[type.to_sym]
        column.default = options[:default]
        column.null = options[:null]
        @columns << column unless @columns.include? column
        self
      end

      # Returns a String whose contents are the column definitions
      # concatenated together.  This string can then be pre and appended to
      # to generate the final SQL to create the table.
      def to_sql
        @columns * ', '
      end

      private
        def native
          @base.native_database_types
        end
    end
  end
end
module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module SchemaStatements
      # Returns a Hash of mappings from the abstract data types to the native
      # database types.  See TableDefinition#column for details on the recognized
      # abstract data types.
      def native_database_types
        {}
      end

      # This is the maximum length a table alias can be
      def table_alias_length
        255
      end

      # Truncates a table alias according to the limits of the current adapter.  
      def table_alias_for(table_name)
        table_name[0..table_alias_length-1].gsub(/\./, '_')
      end

      # def tables(name = nil) end

      # Returns an array of indexes for the given table.
      # def indexes(table_name, name = nil) end

      # Returns an array of Column objects for the table specified by +table_name+.
      # See the concrete implementation for details on the expected parameter values.
      def columns(table_name, name = nil) end

      # Creates a new table
      # There are two ways to work with #create_table.  You can use the block
      # form or the regular form, like this:
      #
      # === Block form
      #  # create_table() yields a TableDefinition instance
      #  create_table(:suppliers) do |t|
      #    t.column :name, :string, :limit => 60
      #    # Other fields here
      #  end
      #
      # === Regular form
      #  create_table(:suppliers)
      #  add_column(:suppliers, :name, :string, {:limit => 60})
      #
      # The +options+ hash can include the following keys:
      # [<tt>:id</tt>]
      #   Set to true or false to add/not add a primary key column
      #   automatically.  Defaults to true.
      # [<tt>:primary_key</tt>]
      #   The name of the primary key, if one is to be added automatically.
      #   Defaults to +id+.
      # [<tt>:options</tt>]
      #   Any extra options you want appended to the table definition.
      # [<tt>:temporary</tt>]
      #   Make a temporary table.
      # [<tt>:force</tt>]
      #   Set to true or false to drop the table before creating it.
      #   Defaults to false.
      #
      # ===== Examples
      # ====== Add a backend specific option to the generated SQL (MySQL)
      #  create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
      # generates:
      #  CREATE TABLE suppliers (
      #    id int(11) DEFAULT NULL auto_increment PRIMARY KEY
      #  ) ENGINE=InnoDB DEFAULT CHARSET=utf8
      #
      # ====== Rename the primary key column
      #  create_table(:objects, :primary_key => 'guid') do |t|
      #    t.column :name, :string, :limit => 80
      #  end
      # generates:
      #  CREATE TABLE objects (
      #    guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
      #    name varchar(80)
      #  )
      #
      # ====== Do not add a primary key column
      #  create_table(:categories_suppliers, :id => false) do |t|
      #    t.column :category_id, :integer
      #    t.column :supplier_id, :integer
      #  end
      # generates:
      #  CREATE TABLE categories_suppliers_join (
      #    category_id int,
      #    supplier_id int
      #  )
      #
      # See also TableDefinition#column for details on how to create columns.
      def create_table(name, options = {})
        table_definition = TableDefinition.new(self)
        table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false

        yield table_definition

        if options[:force]
          drop_table(name) rescue nil
        end

        create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
        create_sql << "#{name} ("
        create_sql << table_definition.to_sql
        create_sql << ") #{options[:options]}"
        execute create_sql
      end
      
      # Renames a table.
      # ===== Example
      #  rename_table('octopuses', 'octopi')
      def rename_table(name, new_name)
        raise NotImplementedError, "rename_table is not implemented"
      end

      # Drops a table from the database.
      def drop_table(name)
        execute "DROP TABLE #{name}"
      end

      # Adds a new column to the named table.
      # See TableDefinition#column for details of the options you can use.
      def add_column(table_name, column_name, type, options = {})
        add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}"
        add_column_options!(add_column_sql, options)
        execute(add_column_sql)
      end

      # Removes the column from the table definition.
      # ===== Examples
      #  remove_column(:suppliers, :qualification)
      def remove_column(table_name, column_name)
        execute "ALTER TABLE #{table_name} DROP #{quote_column_name(column_name)}"
      end

      # Changes the column's definition according to the new options.
      # See TableDefinition#column for details of the options you can use.
      # ===== Examples
      #  change_column(:suppliers, :name, :string, :limit => 80)
      #  change_column(:accounts, :description, :text)
      def change_column(table_name, column_name, type, options = {})
        raise NotImplementedError, "change_column is not implemented"
      end

      # Sets a new default value for a column.  If you want to set the default
      # value to +NULL+, you are out of luck.  You need to
      # DatabaseStatements#execute the apppropriate SQL statement yourself.
      # ===== Examples
      #  change_column_default(:suppliers, :qualification, 'new')
      #  change_column_default(:accounts, :authorized, 1)
      def change_column_default(table_name, column_name, default)
        raise NotImplementedError, "change_column_default is not implemented"
      end

      # Renames a column.
      # ===== Example
      #  rename_column(:suppliers, :description, :name)
      def rename_column(table_name, column_name, new_column_name)
        raise NotImplementedError, "rename_column is not implemented"
      end

      # Adds a new index to the table.  +column_name+ can be a single Symbol, or
      # an Array of Symbols.
      #
      # The index will be named after the table and the first column names,
      # unless you pass +:name+ as an option.
      #
      # When creating an index on multiple columns, the first column is used as a name
      # for the index. For example, when you specify an index on two columns
      # [+:first+, +:last+], the DBMS creates an index for both columns as well as an
      # index for the first colum +:first+. Using just the first name for this index
      # makes sense, because you will never have to create a singular index with this
      # name.
      #
      # ===== Examples
      # ====== Creating a simple index
      #  add_index(:suppliers, :name)
      # generates
      #  CREATE INDEX suppliers_name_index ON suppliers(name)
      # ====== Creating a unique index
      #  add_index(:accounts, [:branch_id, :party_id], :unique => true)
      # generates
      #  CREATE UNIQUE INDEX accounts_branch_id_index ON accounts(branch_id, party_id)
      # ====== Creating a named index
      #  add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
      # generates
      #  CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
      def add_index(table_name, column_name, options = {})
        column_names = Array(column_name)
        index_name   = index_name(table_name, :column => column_names.first)

        if Hash === options # legacy support, since this param was a string
          index_type = options[:unique] ? "UNIQUE" : ""
          index_name = options[:name] || index_name
        else
          index_type = options
        end
        quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
        execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names})"
      end

      # Remove the given index from the table.
      #
      # Remove the suppliers_name_index in the suppliers table (legacy support, use the second or third forms).
      #   remove_index :suppliers, :name
      # Remove the index named accounts_branch_id in the accounts table.
      #   remove_index :accounts, :column => :branch_id
      # Remove the index named by_branch_party in the accounts table.
      #   remove_index :accounts, :name => :by_branch_party
      #
      # You can remove an index on multiple columns by specifying the first column.
      #   add_index :accounts, [:username, :password]
      #   remove_index :accounts, :username
      def remove_index(table_name, options = {})
        execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{table_name}"
      end

      def index_name(table_name, options) #:nodoc:
        if Hash === options # legacy support
          if options[:column]
            "#{table_name}_#{options[:column]}_index"
          elsif options[:name]
            options[:name]
          else
            raise ArgumentError, "You must specify the index name"
          end
        else
          "#{table_name}_#{options}_index"
        end
      end

      # Returns a string of <tt>CREATE TABLE</tt> SQL statement(s) for recreating the
      # entire structure of the database.
      def structure_dump
      end

      # Should not be called normally, but this operation is non-destructive.
      # The migrations module handles this automatically.
      def initialize_schema_information
        begin
          execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"
          execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)"
        rescue ActiveRecord::StatementInvalid
          # Schema has been intialized
        end
      end

      def dump_schema_information #:nodoc:
        begin
          if (current_schema = ActiveRecord::Migrator.current_version) > 0
            return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema})" 
          end
        rescue ActiveRecord::StatementInvalid 
          # No Schema Info
        end
      end


      def type_to_sql(type, limit = nil) #:nodoc:
        native = native_database_types[type]
        limit ||= native[:limit]
        column_type_sql = native[:name]
        column_type_sql << "(#{limit})" if limit
        column_type_sql
      end            
    
      def add_column_options!(sql, options) #:nodoc:
        sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
        sql << " NOT NULL" if options[:null] == false
      end
    end
  end
end
require 'benchmark'
require 'date'

require 'active_record/connection_adapters/abstract/schema_definitions'
require 'active_record/connection_adapters/abstract/schema_statements'
require 'active_record/connection_adapters/abstract/database_statements'
require 'active_record/connection_adapters/abstract/quoting'
require 'active_record/connection_adapters/abstract/connection_specification'

module ActiveRecord
  module ConnectionAdapters # :nodoc:
    # All the concrete database adapters follow the interface laid down in this class.
    # You can use this interface directly by borrowing the database connection from the Base with
    # Base.connection.
    #
    # Most of the methods in the adapter are useful during migrations.  Most
    # notably, SchemaStatements#create_table, SchemaStatements#drop_table,
    # SchemaStatements#add_index, SchemaStatements#remove_index,
    # SchemaStatements#add_column, SchemaStatements#change_column and
    # SchemaStatements#remove_column are very useful.
    class AbstractAdapter
      include Quoting, DatabaseStatements, SchemaStatements
      @@row_even = true
      
      def initialize(connection, logger = nil) #:nodoc:
        @connection, @logger = connection, logger
        @runtime = 0
        @last_verification = 0
      end

      # Returns the human-readable name of the adapter.  Use mixed case - one
      # can always use downcase if needed.
      def adapter_name
        'Abstract'
      end

      # Does this adapter support migrations?  Backend specific, as the
      # abstract adapter always returns +false+.
      def supports_migrations?
        false
      end
      
      # Does this adapter support using DISTINCT within COUNT?  This is +true+
      # for all adapters except sqlite.
      def supports_count_distinct?
        true
      end

      # Should primary key values be selected from their corresponding
      # sequence before the insert statement?  If true, next_sequence_value
      # is called before each insert to set the record's primary key.
      # This is false for all adapters but Firebird.
      def prefetch_primary_key?(table_name = nil)
        false
      end

      def reset_runtime #:nodoc:
        rt, @runtime = @runtime, 0
        rt
      end


      # CONNECTION MANAGEMENT ====================================

      # Is this connection active and ready to perform queries?
      def active?
        @active != false
      end

      # Close this connection and open a new one in its place.
      def reconnect!
        @active = true
      end

      # Close this connection
      def disconnect!
        @active = false
      end

      # Lazily verify this connection, calling +active?+ only if it hasn't
      # been called for +timeout+ seconds.       
      def verify!(timeout)
        now = Time.now.to_i
        if (now - @last_verification) > timeout
          reconnect! unless active?
          @last_verification = now
        end
      end
      
      # Provides access to the underlying database connection. Useful for
      # when you need to call a proprietary method such as postgresql's lo_*
      # methods
      def raw_connection
        @connection
      end

      protected
        def log(sql, name)
          if block_given?
            if @logger and @logger.level <= Logger::INFO
              result = nil
              seconds = Benchmark.realtime { result = yield }
              @runtime += seconds
              log_info(sql, name, seconds)
              result
            else
              yield
            end
          else
            log_info(sql, name, 0)
            nil
          end
        rescue Exception => e
          # Log message and raise exception.
          # Set last_verfication to 0, so that connection gets verified
          # upon reentering the request loop
          @last_verification = 0
          message = "#{e.class.name}: #{e.message}: #{sql}"
          log_info(message, name, 0)
          raise ActiveRecord::StatementInvalid, message
        end

        def log_info(sql, name, runtime)
          return unless @logger

          @logger.debug(
            format_log_entry(
              "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
              sql.gsub(/ +/, " ")
            )
          )
        end

        def format_log_entry(message, dump = nil)
          if ActiveRecord::Base.colorize_logging
            if @@row_even
              @@row_even = false
              message_color, dump_color = "4;36;1", "0;1"
            else
              @@row_even = true
              message_color, dump_color = "4;35;1", "0"
            end

            log_entry = "  \e[#{message_color}m#{message}\e[0m   "
            log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
            log_entry
          else
            "%s  %s" % [message, dump]
          end
        end
    end
  end
end
# Author/Maintainer: Maik Schmidt <contact@maik-schmidt.de>

require 'active_record/connection_adapters/abstract_adapter'

begin
  require 'db2/db2cli' unless self.class.const_defined?(:DB2CLI)
  require 'active_record/vendor/db2'

  module ActiveRecord
    class Base
      # Establishes a connection to the database that's used by
      # all Active Record objects
      def self.db2_connection(config) # :nodoc:
        config = config.symbolize_keys
        usr = config[:username]
        pwd = config[:password]
        schema = config[:schema]

        if config.has_key?(:database)
          database = config[:database]
        else
          raise ArgumentError, 'No database specified. Missing argument: database.'
        end

        connection = DB2::Connection.new(DB2::Environment.new)
        connection.connect(database, usr, pwd)
        ConnectionAdapters::DB2Adapter.new(connection, logger, :schema => schema)
      end
    end

    module ConnectionAdapters
      # The DB2 adapter works with the C-based CLI driver (http://rubyforge.org/projects/ruby-dbi/)
      #
      # Options:
      #
      # * <tt>:username</tt> -- Defaults to nothing
      # * <tt>:password</tt> -- Defaults to nothing
      # * <tt>:database</tt> -- The name of the database. No default, must be provided.
      # * <tt>:schema</tt> -- Database schema to be set initially.
      class DB2Adapter < AbstractAdapter
        def initialize(connection, logger, connection_options)
          super(connection, logger)
          @connection_options = connection_options
          if schema = @connection_options[:schema]
            with_statement do |stmt|
              stmt.exec_direct("SET SCHEMA=#{schema}")
            end
          end
        end
        
        def select_all(sql, name = nil)
          select(sql, name)
        end

        def select_one(sql, name = nil)
          select(sql, name).first
        end

        def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
          execute(sql, name = nil)
          id_value || last_insert_id
        end

        def execute(sql, name = nil)
          rows_affected = 0
          with_statement do |stmt|
            log(sql, name) do
              stmt.exec_direct(sql)
              rows_affected = stmt.row_count
            end
          end
          rows_affected
        end

        alias_method :update, :execute
        alias_method :delete, :execute

        def begin_db_transaction
          @connection.set_auto_commit_off
        end

        def commit_db_transaction
          @connection.commit
          @connection.set_auto_commit_on
        end
        
        def rollback_db_transaction
          @connection.rollback
          @connection.set_auto_commit_on
        end

        def quote_column_name(column_name)
          column_name
        end

        def adapter_name()
          'DB2'
        end

        def quote_string(string)
          string.gsub(/'/, "''") # ' (for ruby-mode)
        end

        def add_limit_offset!(sql, options)
          if limit = options[:limit]
            offset = options[:offset] || 0
            # The following trick was added by andrea+rails@webcom.it.
            sql.gsub!(/SELECT/i, 'SELECT B.* FROM (SELECT A.*, row_number() over () AS internal$rownum FROM (SELECT')
            sql << ") A ) B WHERE B.internal$rownum > #{offset} AND B.internal$rownum <= #{limit + offset}"
          end
        end

        def tables(name = nil)
          result = []
          schema = @connection_options[:schema] || '%'
          with_statement do |stmt|
            stmt.tables(schema).each { |t| result << t[2].downcase }
          end
          result
        end

        def indexes(table_name, name = nil)
          tmp = {}
          schema = @connection_options[:schema] || ''
          with_statement do |stmt|
            stmt.indexes(table_name, schema).each do |t|
              next unless t[5]
              next if t[4] == 'SYSIBM' # Skip system indexes.
              idx_name = t[5].downcase
              col_name = t[8].downcase
              if tmp.has_key?(idx_name)
                tmp[idx_name].columns << col_name
              else
                is_unique = t[3] == 0
                tmp[idx_name] = IndexDefinition.new(table_name, idx_name, is_unique, [col_name])
              end
            end
          end
          tmp.values
        end

        def columns(table_name, name = nil)
          result = []
          schema = @connection_options[:schema] || '%'
          with_statement do |stmt|
            stmt.columns(table_name, schema).each do |c| 
              c_name = c[3].downcase
              c_default = c[12] == 'NULL' ? nil : c[12]
              c_default.gsub!(/^'(.*)'$/, '\1') if !c_default.nil?
              c_type = c[5].downcase
              c_type += "(#{c[6]})" if !c[6].nil? && c[6] != ''
              result << Column.new(c_name, c_default, c_type, c[17] == 'YES')
            end 
          end
          result
        end

        def native_database_types
          {
            :primary_key => 'int generated by default as identity (start with 42) primary key',
            :string      => { :name => 'varchar', :limit => 255 },
            :text        => { :name => 'clob', :limit => 32768 },
            :integer     => { :name => 'int' },
            :float       => { :name => 'float' },
            :datetime    => { :name => 'timestamp' },
            :timestamp   => { :name => 'timestamp' },
            :time        => { :name => 'time' },
            :date        => { :name => 'date' },
            :binary      => { :name => 'blob', :limit => 32768 },
            :boolean     => { :name => 'decimal', :limit => 1 }
          }
        end

        def quoted_true
          '1'
        end

        def quoted_false
          '0'
        end

        def active?
          @connection.select_one 'select 1 from ibm.sysdummy1'
          true
        rescue Exception
          false
        end

        def reconnect!
        end

        def table_alias_length
          128
        end

        private

        def with_statement
          stmt = DB2::Statement.new(@connection)
          yield stmt
          stmt.free
        end

        def last_insert_id
          row = select_one(<<-GETID.strip)
          with temp(id) as (values (identity_val_local())) select * from temp
          GETID
          row['id'].to_i
        end

        def select(sql, name = nil)
          rows = []
          with_statement do |stmt|
            log(sql, name) do
              stmt.exec_direct("#{sql.gsub(/=\s*null/i, 'IS NULL')} with ur")
            end

            while row = stmt.fetch_as_hash
              row.delete('internal$rownum')
              rows << row
            end
          end
          rows
        end
      end
    end
  end
rescue LoadError
  # DB2 driver is unavailable.
  module ActiveRecord # :nodoc:
    class Base
      def self.db2_connection(config) # :nodoc:
        # Set up a reasonable error message
        raise LoadError, "DB2 Libraries could not be loaded."
      end
    end
  end
end
# Author: Ken Kunz <kennethkunz@gmail.com>

require 'active_record/connection_adapters/abstract_adapter'

module FireRuby # :nodoc: all
  class Database
    def self.new_from_params(database, host, port, service)
      db_string = ""
      if host
        db_string << host
        db_string << "/#{service || port}" if service || port
        db_string << ":"
      end
      db_string << database
      new(db_string)
    end
  end
end

module ActiveRecord
  class << Base
    def firebird_connection(config) # :nodoc:
      require_library_or_gem 'fireruby'
      unless defined? FireRuby::SQLType
        raise AdapterNotFound,
          'The Firebird adapter requires FireRuby version 0.4.0 or greater; you appear ' <<
          'to be running an older version -- please update FireRuby (gem install fireruby).'
      end
      config = config.symbolize_keys
      unless config.has_key?(:database)
        raise ArgumentError, "No database specified. Missing argument: database."
      end
      options = config[:charset] ? { CHARACTER_SET => config[:charset] } : {}
      connection_params = [config[:username], config[:password], options]
      db = FireRuby::Database.new_from_params(*config.values_at(:database, :host, :port, :service))
      connection = db.connect(*connection_params)
      ConnectionAdapters::FirebirdAdapter.new(connection, logger, connection_params)
    end
  end

  module ConnectionAdapters
    class FirebirdColumn < Column # :nodoc:
      VARCHAR_MAX_LENGTH = 32_765
      BLOB_MAX_LENGTH    = 32_767

      def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
        @firebird_type = FireRuby::SQLType.to_base_type(type, sub_type).to_s
        super(name.downcase, nil, @firebird_type, !null_flag)
        @default = parse_default(default_source) if default_source
        @limit = type == 'BLOB' ? BLOB_MAX_LENGTH : length
        @domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale
      end

      def type
        if @domain =~ /BOOLEAN/
          :boolean
        elsif @type == :binary and @sub_type == 1
          :text
        else
          @type
        end
      end

      # Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
      # This enables Firebird to provide an actual value when context variables are used as column
      # defaults (such as CURRENT_TIMESTAMP).
      def default
        if @default
          sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
          connection = ActiveRecord::Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' }
          if connection
            type_cast connection.execute(sql).to_a.first['CAST']
          else
            raise ConnectionNotEstablished, "No Firebird connections established."
          end
        end
      end

      def type_cast(value)
        if type == :boolean
          value == true or value == ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain[:true]
        else
          super
        end
      end

      private
        def parse_default(default_source)
          default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
          return $1 unless $1.upcase == "NULL"
        end

        def column_def
          case @firebird_type
            when 'BLOB'               then "VARCHAR(#{VARCHAR_MAX_LENGTH})"
            when 'CHAR', 'VARCHAR'    then "#{@firebird_type}(#{@limit})"
            when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
            when 'DOUBLE'             then "DOUBLE PRECISION"
            else @firebird_type
          end
        end

        def simplified_type(field_type)
          if field_type == 'TIMESTAMP'
            :datetime
          else
            super
          end
        end
    end

    # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/]
    # extension, version 0.4.0 or later (available as a gem or from
    # RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with
    # Firebird 1.5.x on Linux, OS X and Win32 platforms.
    #
    # == Usage Notes
    #
    # === Sequence (Generator) Names
    # The Firebird adapter supports the same approach adopted for the Oracle
    # adapter. See ActiveRecord::Base#set_sequence_name for more details.
    #
    # Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
    # trigger corresponding to a Firebird sequence generator when using
    # ActiveRecord. In other words, you don't have to try to make Firebird
    # simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
    # new record, ActiveRecord pre-fetches the next sequence value for the table
    # and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
    # next primary key value is the only reliable method for the Firebird
    # adapter to report back the +id+ after a successful insert.)
    #
    # === BOOLEAN Domain
    # Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
    # define a +BOOLEAN+ _domain_ for this purpose, e.g.:
    #
    #  CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1));
    #
    # When the Firebird adapter encounters a column that is based on a domain
    # that includes "BOOLEAN" in the domain name, it will attempt to treat
    # the column as a +BOOLEAN+.
    #
    # By default, the Firebird adapter will assume that the BOOLEAN domain is
    # defined as above.  This can be modified if needed.  For example, if you
    # have a legacy schema with the following +BOOLEAN+ domain defined:
    #
    #  CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
    #
    # ...you can add the following line to your <tt>environment.rb</tt> file:
    #
    #  ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain = { :true => 'T', :false => 'F' }
    #
    # === BLOB Elements
    # The Firebird adapter currently provides only limited support for +BLOB+
    # columns. You cannot currently retrieve or insert a +BLOB+ as an IO stream.
    # When selecting a +BLOB+, the entire element is converted into a String.
    # When inserting or updating a +BLOB+, the entire value is included in-line
    # in the SQL statement, limiting you to values <= 32KB in size.
    #
    # === Column Name Case Semantics
    # Firebird and ActiveRecord have somewhat conflicting case semantics for
    # column names.
    #
    # [*Firebird*]
    #   The standard practice is to use unquoted column names, which can be
    #   thought of as case-insensitive. (In fact, Firebird converts them to
    #   uppercase.) Quoted column names (not typically used) are case-sensitive.
    # [*ActiveRecord*]
    #   Attribute accessors corresponding to column names are case-sensitive.
    #   The defaults for primary key and inheritance columns are lowercase, and
    #   in general, people use lowercase attribute names.
    #
    # In order to map between the differing semantics in a way that conforms
    # to common usage for both Firebird and ActiveRecord, uppercase column names
    # in Firebird are converted to lowercase attribute names in ActiveRecord,
    # and vice-versa. Mixed-case column names retain their case in both
    # directions. Lowercase (quoted) Firebird column names are not supported.
    # This is similar to the solutions adopted by other adapters.
    #
    # In general, the best approach is to use unqouted (case-insensitive) column
    # names in your Firebird DDL (or if you must quote, use uppercase column
    # names). These will correspond to lowercase attributes in ActiveRecord.
    #
    # For example, a Firebird table based on the following DDL:
    #
    #  CREATE TABLE products (
    #    id BIGINT NOT NULL PRIMARY KEY,
    #    "TYPE" VARCHAR(50),
    #    name VARCHAR(255) );
    #
    # ...will correspond to an ActiveRecord model class called +Product+ with
    # the following attributes: +id+, +type+, +name+.
    #
    # ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words:
    # In ActiveRecord, the default inheritance column name is +type+. The word
    # _type_ is a Firebird reserved word, so it must be quoted in any Firebird
    # SQL statements. Because of the case mapping described above, you should
    # always reference this column using quoted-uppercase syntax
    # (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the
    # example above). This holds true for any other Firebird reserved words used
    # as column names as well.
    #
    # === Migrations
    # The Firebird adapter does not currently support Migrations.  I hope to
    # add this feature in the near future.
    #
    # == Connection Options
    # The following options are supported by the Firebird adapter. None of the
    # options have default values.
    #
    # <tt>:database</tt>::
    #   <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
    #   (ii) the full path of a database file; _or_ (iii) a full Firebird
    #   connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
    #   or <tt>:port</tt> as separate options when using a full connection
    #   string.</i>
    # <tt>:host</tt>::
    #   Set to <tt>"remote.host.name"</tt> for remote database connections.
    #   May be omitted for local connections if a full database path is
    #   specified for <tt>:database</tt>. Some platforms require a value of
    #   <tt>"localhost"</tt> for local connections when using a Firebird
    #   database _alias_.
    # <tt>:service</tt>::
    #   Specifies a service name for the connection. Only used if <tt>:host</tt>
    #   is provided. Required when connecting to a non-standard service.
    # <tt>:port</tt>::
    #   Specifies the connection port. Only used if <tt>:host</tt> is provided
    #   and <tt>:service</tt> is not. Required when connecting to a non-standard
    #   port and <tt>:service</tt> is not defined.
    # <tt>:username</tt>::
    #   Specifies the database user. May be omitted or set to +nil+ (together
    #   with <tt>:password</tt>) to use the underlying operating system user
    #   credentials on supported platforms.
    # <tt>:password</tt>::
    #   Specifies the database password. Must be provided if <tt>:username</tt>
    #   is explicitly specified; should be omitted if OS user credentials are
    #   are being used.
    # <tt>:charset</tt>::
    #   Specifies the character set to be used by the connection. Refer to
    #   Firebird documentation for valid options.
    class FirebirdAdapter < AbstractAdapter
      @@boolean_domain = { :true => 1, :false => 0 }
      cattr_accessor :boolean_domain

      def initialize(connection, logger, connection_params=nil)
        super(connection, logger)
        @connection_params = connection_params
      end

      def adapter_name # :nodoc:
        'Firebird'
      end

      # Returns true for Firebird adapter (since Firebird requires primary key
      # values to be pre-fetched before insert). See also #next_sequence_value.
      def prefetch_primary_key?(table_name = nil)
        true
      end

      def default_sequence_name(table_name, primary_key) # :nodoc:
        "#{table_name}_seq"
      end


      # QUOTING ==================================================

      def quote(value, column = nil) # :nodoc:
        if [Time, DateTime].include?(value.class)
          "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
        else
          super
        end
      end

      def quote_string(string) # :nodoc:
        string.gsub(/'/, "''")
      end

      def quote_column_name(column_name) # :nodoc:
        %Q("#{ar_to_fb_case(column_name)}")
      end

      def quoted_true # :nodoc:
        quote(boolean_domain[:true])
      end

      def quoted_false # :nodoc:
        quote(boolean_domain[:false])
      end


      # CONNECTION MANAGEMENT ====================================

      def active?
        not @connection.closed?
      end

      def reconnect!
        @connection.close
        @connection = @connection.database.connect(*@connection_params)
      end


      # DATABASE STATEMENTS ======================================

      def select_all(sql, name = nil) # :nodoc:
        select(sql, name)
      end

      def select_one(sql, name = nil) # :nodoc:
        result = select(sql, name)
        result.nil? ? nil : result.first
      end

      def execute(sql, name = nil, &block) # :nodoc:
        log(sql, name) do
          if @transaction
            @connection.execute(sql, @transaction, &block)
          else
            @connection.execute_immediate(sql, &block)
          end
        end
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
        execute(sql, name)
        id_value
      end

      alias_method :update, :execute
      alias_method :delete, :execute

      def begin_db_transaction() # :nodoc:
        @transaction = @connection.start_transaction
      end

      def commit_db_transaction() # :nodoc:
        @transaction.commit
      ensure
        @transaction = nil
      end

      def rollback_db_transaction() # :nodoc:
        @transaction.rollback
      ensure
        @transaction = nil
      end

      def add_limit_offset!(sql, options) # :nodoc:
        if options[:limit]
          limit_string = "FIRST #{options[:limit]}"
          limit_string << " SKIP #{options[:offset]}" if options[:offset]
          sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
        end
      end

      # Returns the next sequence value from a sequence generator. Not generally
      # called directly; used by ActiveRecord to get the next primary key value
      # when inserting a new database record (see #prefetch_primary_key?).
      def next_sequence_value(sequence_name)
        FireRuby::Generator.new(sequence_name, @connection).next(1)
      end


      # SCHEMA STATEMENTS ========================================

      def columns(table_name, name = nil) # :nodoc:
        sql = <<-END_SQL
          SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
                 f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
                 COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
                 COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
          FROM rdb$relation_fields r
          JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
          WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
          ORDER BY r.rdb$field_position
        END_SQL
        execute(sql, name).collect do |field|
          field_values = field.values.collect do |value|
            case value
              when String         then value.rstrip
              when FireRuby::Blob then value.to_s
              else value
            end
          end
          FirebirdColumn.new(*field_values)
        end
      end

      private
        def select(sql, name = nil)
          execute(sql, name).collect do |row|
            hashed_row = {}
            row.each do |column, value|
              value = value.to_s if FireRuby::Blob === value
              hashed_row[fb_to_ar_case(column)] = value
            end
            hashed_row
          end
        end

        # Maps uppercase Firebird column names to lowercase for ActiveRecord;
        # mixed-case columns retain their original case.
        def fb_to_ar_case(column_name)
          column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
        end

        # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
        # mixed-case columns retain their original case.
        def ar_to_fb_case(column_name)
          column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
        end
    end
  end
end
require 'active_record/connection_adapters/abstract_adapter'

module ActiveRecord
  class Base
    # Establishes a connection to the database that's used by all Active Record objects.
    def self.mysql_connection(config) # :nodoc:
      # Only include the MySQL driver if one hasn't already been loaded
      unless defined? Mysql
        begin
          require_library_or_gem 'mysql'
        rescue LoadError => cannot_require_mysql
          # Only use the supplied backup Ruby/MySQL driver if no driver is already in place
          begin
            require 'active_record/vendor/mysql'
          rescue LoadError
            raise cannot_require_mysql
          end
        end
      end

      config = config.symbolize_keys
      host     = config[:host]
      port     = config[:port]
      socket   = config[:socket]
      username = config[:username] ? config[:username].to_s : 'root'
      password = config[:password].to_s

      if config.has_key?(:database)
        database = config[:database]
      else
        raise ArgumentError, "No database specified. Missing argument: database."
      end

      mysql = Mysql.init
      mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
      ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
    end
  end

  module ConnectionAdapters
    class MysqlColumn < Column #:nodoc:
      private
        def simplified_type(field_type)
          return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
          return :string  if field_type =~ /enum/i
          super
        end
    end

    # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
    # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
    #
    # Options:
    #
    # * <tt>:host</tt> -- Defaults to localhost
    # * <tt>:port</tt> -- Defaults to 3306
    # * <tt>:socket</tt> -- Defaults to /tmp/mysql.sock
    # * <tt>:username</tt> -- Defaults to root
    # * <tt>:password</tt> -- Defaults to nothing
    # * <tt>:database</tt> -- The name of the database. No default, must be provided.
    # * <tt>:sslkey</tt> -- Necessary to use MySQL with an SSL connection
    # * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
    # * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
    # * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
    #
    # By default, the MysqlAdapter will consider all columns of type tinyint(1)
    # as boolean. If you wish to disable this emulation (which was the default
    # behavior in versions 0.13.1 and earlier) you can add the following line
    # to your environment.rb file:
    #
    #   ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
    class MysqlAdapter < AbstractAdapter
      @@emulate_booleans = true
      cattr_accessor :emulate_booleans

      LOST_CONNECTION_ERROR_MESSAGES = [
        "Server shutdown in progress",
        "Broken pipe",
        "Lost connection to MySQL server during query",
        "MySQL server has gone away"
      ]

      def initialize(connection, logger, connection_options, config)
        super(connection, logger)
        @connection_options, @config = connection_options, config
        @null_values_in_each_hash = Mysql.const_defined?(:VERSION)
        connect
      end

      def adapter_name #:nodoc:
        'MySQL'
      end

      def supports_migrations? #:nodoc:
        true
      end

      def native_database_types #:nodoc
        {
          :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
          :string      => { :name => "varchar", :limit => 255 },
          :text        => { :name => "text" },
          :integer     => { :name => "int", :limit => 11 },
          :float       => { :name => "float" },
          :datetime    => { :name => "datetime" },
          :timestamp   => { :name => "datetime" },
          :time        => { :name => "time" },
          :date        => { :name => "date" },
          :binary      => { :name => "blob" },
          :boolean     => { :name => "tinyint", :limit => 1 }
        }
      end


      # QUOTING ==================================================

      def quote(value, column = nil)
        if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
          s = column.class.string_to_binary(value).unpack("H*")[0]
          "x'#{s}'"
        else
          super
        end
      end

      def quote_column_name(name) #:nodoc:
        "`#{name}`"
      end

      def quote_string(string) #:nodoc:
        @connection.quote(string)
      end

      def quoted_true
        "1"
      end
      
      def quoted_false
        "0"
      end


      # CONNECTION MANAGEMENT ====================================

      def active?
        if @connection.respond_to?(:stat)
          @connection.stat
        else
          @connection.query 'select 1'
        end

        # mysql-ruby doesn't raise an exception when stat fails.
        if @connection.respond_to?(:errno)
          @connection.errno.zero?
        else
          true
        end
      rescue Mysql::Error
        false
      end

      def reconnect!
        disconnect!
        connect
      end
      
      def disconnect!
        @connection.close rescue nil
      end


      # DATABASE STATEMENTS ======================================

      def select_all(sql, name = nil) #:nodoc:
        select(sql, name)
      end

      def select_one(sql, name = nil) #:nodoc:
        result = select(sql, name)
        result.nil? ? nil : result.first
      end

      def execute(sql, name = nil, retries = 2) #:nodoc:
        log(sql, name) { @connection.query(sql) }
      rescue ActiveRecord::StatementInvalid => exception
        if exception.message.split(":").first =~ /Packets out of order/
          raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information.  If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
        else
          raise
        end
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
        execute(sql, name = nil)
        id_value || @connection.insert_id
      end

      def update(sql, name = nil) #:nodoc:
        execute(sql, name)
        @connection.affected_rows
      end

      alias_method :delete, :update #:nodoc:


      def begin_db_transaction #:nodoc:
        execute "BEGIN"
      rescue Exception
        # Transactions aren't supported
      end

      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      rescue Exception
        # Transactions aren't supported
      end

      def rollback_db_transaction #:nodoc:
        execute "ROLLBACK"
      rescue Exception
        # Transactions aren't supported
      end


      def add_limit_offset!(sql, options) #:nodoc
        if limit = options[:limit]
          unless offset = options[:offset]
            sql << " LIMIT #{limit}"
          else
            sql << " LIMIT #{offset}, #{limit}"
          end
        end
      end


      # SCHEMA STATEMENTS ========================================

      def structure_dump #:nodoc:
        if supports_views?
          sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
        else
          sql = "SHOW TABLES"
        end
        
        select_all(sql).inject("") do |structure, table|
          table.delete('Table_type')
          structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
        end
      end

      def recreate_database(name) #:nodoc:
        drop_database(name)
        create_database(name)
      end

      def create_database(name) #:nodoc:
        execute "CREATE DATABASE `#{name}`"
      end
      
      def drop_database(name) #:nodoc:
        execute "DROP DATABASE IF EXISTS `#{name}`"
      end

      def current_database
        select_one("SELECT DATABASE() as db")["db"]
      end

      def tables(name = nil) #:nodoc:
        tables = []
        execute("SHOW TABLES", name).each { |field| tables << field[0] }
        tables
      end

      def indexes(table_name, name = nil)#:nodoc:
        indexes = []
        current_index = nil
        execute("SHOW KEYS FROM #{table_name}", name).each do |row|
          if current_index != row[2]
            next if row[2] == "PRIMARY" # skip the primary key
            current_index = row[2]
            indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
          end

          indexes.last.columns << row[4]
        end
        indexes
      end

      def columns(table_name, name = nil)#:nodoc:
        sql = "SHOW FIELDS FROM #{table_name}"
        columns = []
        execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
        columns
      end

      def create_table(name, options = {}) #:nodoc:
        super(name, {:options => "ENGINE=InnoDB"}.merge(options))
      end
      
      def rename_table(name, new_name)
        execute "RENAME TABLE #{name} TO #{new_name}"
      end  

      def change_column_default(table_name, column_name, default) #:nodoc:
        current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]

        change_column(table_name, column_name, current_type, { :default => default })
      end

      def change_column(table_name, column_name, type, options = {}) #:nodoc:
        options[:default] ||= select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
        
        change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
        add_column_options!(change_column_sql, options)
        execute(change_column_sql)
      end

      def rename_column(table_name, column_name, new_column_name) #:nodoc:
        current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
        execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
      end


      private
        def connect
          encoding = @config[:encoding]
          if encoding
            @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
          end
          @connection.real_connect(*@connection_options)
          execute("SET NAMES '#{encoding}'") if encoding
        end

        def select(sql, name = nil)
          @connection.query_with_result = true
          result = execute(sql, name)
          rows = []
          if @null_values_in_each_hash
            result.each_hash { |row| rows << row }
          else
            all_fields = result.fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
            result.each_hash { |row| rows << all_fields.dup.update(row) }
          end
          result.free
          rows
        end
        
        def supports_views?
          version[0] >= 5
        end
        
        def version
          @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
        end
    end
  end
end
require 'active_record/connection_adapters/abstract_adapter'

module ActiveRecord
  class Base
    # Establishes a connection to the database that's used by all Active Record objects
    def self.openbase_connection(config) # :nodoc:
      require_library_or_gem 'openbase' unless self.class.const_defined?(:OpenBase)

      config = config.symbolize_keys
      host     = config[:host]
      username = config[:username].to_s
      password = config[:password].to_s
      

      if config.has_key?(:database)
        database = config[:database]
      else
        raise ArgumentError, "No database specified. Missing argument: database."
      end

      oba = ConnectionAdapters::OpenBaseAdapter.new(
        OpenBase.new(database, host, username, password), logger
      )
      
      oba
    end
    
  end

  module ConnectionAdapters
    class OpenBaseColumn < Column #:nodoc:
      private
        def simplified_type(field_type)
          return :integer if field_type.downcase =~ /long/
          return :float   if field_type.downcase == "money"
          return :binary  if field_type.downcase == "object"
          super
        end
    end
    # The OpenBase adapter works with the Ruby/Openbase driver by Tetsuya Suzuki.
    # http://www.spice-of-life.net/ruby-openbase/ (needs version 0.7.3+)
    #
    # Options:
    #
    # * <tt>:host</tt> -- Defaults to localhost
    # * <tt>:username</tt> -- Defaults to nothing
    # * <tt>:password</tt> -- Defaults to nothing
    # * <tt>:database</tt> -- The name of the database. No default, must be provided.
    #
    # The OpenBase adapter will make use of OpenBase's ability to generate unique ids
    # for any column with an unique index applied.  Thus, if the value of a primary
    # key is not specified at the time an INSERT is performed, the adapter will prefetch
    # a unique id for the primary key.  This prefetching is also necessary in order 
    # to return the id after an insert.
    #
    # Caveat: Operations involving LIMIT and OFFSET do not yet work!
    #
    # Maintainer: derrickspell@cdmplus.com
    class OpenBaseAdapter < AbstractAdapter
      def adapter_name
        'OpenBase'
      end
      
      def native_database_types
        {
          :primary_key => "integer UNIQUE INDEX DEFAULT _rowid",
          :string      => { :name => "char", :limit => 4096 },
          :text        => { :name => "text" },
          :integer     => { :name => "integer" },
          :float       => { :name => "float" },
          :datetime    => { :name => "datetime" },
          :timestamp   => { :name => "timestamp" },
          :time        => { :name => "time" },
          :date        => { :name => "date" },
          :binary      => { :name => "object" },
          :boolean     => { :name => "boolean" }
        }
      end
      
      def supports_migrations?
        false
      end      
      
      def prefetch_primary_key?(table_name = nil)
        true
      end
      
      def default_sequence_name(table_name, primary_key) # :nodoc:
        "#{table_name} #{primary_key}"
      end
      
      def next_sequence_value(sequence_name)
        ary = sequence_name.split(' ')
        if (!ary[1]) then
          ary[0] =~ /(\w+)_nonstd_seq/
          ary[0] = $1
        end
        @connection.unique_row_id(ary[0], ary[1])
      end

      
      # QUOTING ==================================================
      
      def quote(value, column = nil)
        if value.kind_of?(String) && column && column.type == :binary
          "'#{@connection.insert_binary(value)}'"
        else
          super
        end
      end
      
      def quoted_true
        "1"
      end
      
      def quoted_false
        "0"
      end
      


      # DATABASE STATEMENTS ======================================

      def add_limit_offset!(sql, options) #:nodoc
        if limit = options[:limit]
          unless offset = options[:offset]
            sql << " RETURN RESULTS #{limit}"
          else
            limit = limit + offset
            sql << " RETURN RESULTS #{offset} TO #{limit}"
          end
        end
      end
      
      def select_all(sql, name = nil) #:nodoc:
        select(sql, name)
      end

      def select_one(sql, name = nil) #:nodoc:
        add_limit_offset!(sql,{:limit => 1})
        results = select(sql, name)
        results.first if results
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
        execute(sql, name)
        update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
        id_value
      end
      
      def execute(sql, name = nil) #:nodoc:
        log(sql, name) { @connection.execute(sql) }
      end

      def update(sql, name = nil) #:nodoc:
        execute(sql, name).rows_affected
      end

      alias_method :delete, :update #:nodoc:
#=begin
      def begin_db_transaction #:nodoc:
        execute "START TRANSACTION"
      rescue Exception
        # Transactions aren't supported
      end

      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      rescue Exception
        # Transactions aren't supported
      end

      def rollback_db_transaction #:nodoc:
        execute "ROLLBACK"
      rescue Exception
        # Transactions aren't supported
      end
#=end      

      # SCHEMA STATEMENTS ========================================

      # Return the list of all tables in the schema search path.
      def tables(name = nil) #:nodoc:
        tables = @connection.tables
        tables.reject! { |t| /\A_SYS_/ === t }
        tables
      end

      def columns(table_name, name = nil) #:nodoc:
        sql = "SELECT * FROM _sys_tables "
        sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
        sql << "ORDER BY columnNumber"
        columns = []
        select_all(sql, name).each do |row|
          columns << OpenBaseColumn.new(row["fieldname"],
                                default_value(row["defaultvalue"]),
                                sql_type_name(row["typename"],row["length"]),
                                row["notnull"]
                                )
    #      breakpoint() if row["fieldname"] == "content"
        end
        columns
      end

      def indexes(table_name, name = nil)#:nodoc:
        sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
        sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
        sql << "AND primarykey=0 "
        sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
        sql << "ORDER BY columnNumber"
        indexes = []
        execute(sql, name).each do |row|
          indexes << IndexDefinition.new(table_name,index_name(row),row[3]==1,[row[0]])
        end
        indexes
      end


      private
        def select(sql, name = nil)
          sql = translate_sql(sql)
          results = execute(sql, name)

          date_cols = []
          col_names = []
          results.column_infos.each do |info|
            col_names << info.name
            date_cols << info.name if info.type == "date"
          end
          
          rows = []
          if ( results.rows_affected )
            results.each do |row|  # loop through result rows
              hashed_row = {}
              row.each_index do |index| 
                hashed_row["#{col_names[index]}"] = row[index] unless col_names[index] == "_rowid"
              end
              date_cols.each do |name|
                unless hashed_row["#{name}"].nil? or hashed_row["#{name}"].empty?
                  hashed_row["#{name}"] = Date.parse(hashed_row["#{name}"],false).to_s
                end
              end
              rows << hashed_row
            end
          end
          rows
        end
        
        def default_value(value)
          # Boolean type values
          return true if value =~ /true/
          return false if value =~ /false/
 
          # Date / Time magic values
          return Time.now.to_s if value =~ /^now\(\)/i
 
          # Empty strings should be set to null
          return nil if value.empty?
          
          # Otherwise return what we got from OpenBase
          # and hope for the best...
          return value
        end 
        
        def sql_type_name(type_name, length)
          return "#{type_name}(#{length})" if ( type_name =~ /char/ )
          type_name
        end
                
        def index_name(row = [])
          name = ""
          name << "UNIQUE " if row[3]
          name << "CLUSTERED " if row[4]
          name << "INDEX"
          name
        end
        
        def translate_sql(sql)
          
          # Change table.* to list of columns in table
          while (sql =~ /SELECT.*\s(\w+)\.\*/)
            table = $1
            cols = columns(table)
            if ( cols.size == 0 ) then
              # Maybe this is a table alias
              sql =~ /FROM(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/  
              $1 =~ /[\s|,](\w+)\s+#{table}[\s|,]/ # get the tablename for this alias
              cols = columns($1)
            end
            select_columns = []
            cols.each do |col|
              select_columns << table + '.' + col.name
            end
            sql.gsub!(table + '.*',select_columns.join(", ")) if select_columns
          end
   
          # Change JOIN clause to table list and WHERE condition
          while (sql =~ /JOIN/)
            sql =~ /((LEFT )?(OUTER )?JOIN (\w+) ON )(.+?)(?:LEFT|OUTER|JOIN|WHERE|GROUP|HAVING|ORDER|RETURN|$)/
            join_clause = $1 + $5
            is_outer_join = $3
            join_table = $4
            join_condition = $5
            join_condition.gsub!(/=/,"*") if is_outer_join
            if (sql =~ /WHERE/)
              sql.gsub!(/WHERE/,"WHERE (#{join_condition}) AND")
            else
              sql.gsub!(join_clause,"#{join_clause} WHERE #{join_condition}")
            end
            sql =~ /(FROM .+?)(?:LEFT|OUTER|JOIN|WHERE|$)/
            from_clause = $1
            sql.gsub!(from_clause,"#{from_clause}, #{join_table} ")
            sql.gsub!(join_clause,"")
          end
    
          # ORDER BY _rowid if no explicit ORDER BY
          # This will ensure that find(:first) returns the first inserted row
          if (sql !~ /(ORDER BY)|(GROUP BY)/)
            if (sql =~ /RETURN RESULTS/)
              sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
            else
              sql << " ORDER BY _rowid"
            end
          end
          
          sql
        end
        
        def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
          sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
          table = $1
          cols = $2
          values = $3
          cols = cols.split(',')
          values.gsub!(/'[^']*'/,"''")
          values.gsub!(/"[^"]*"/,"\"\"")
          values = values.split(',')
          update_cols = []
          values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
          update_sql = "UPDATE #{table} SET"
          update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
          update_sql.chop!()
          update_sql << " WHERE #{pk}=#{quote(id_value)}"
          execute(update_sql, name + " NULL Correction") if update_cols.size > 0
        end
        
      end
  end
end
# oracle_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g
#
# Original author: Graham Jenkins
#
# Current maintainer: Michael Schoen <schoenm@earthlink.net>
#
#########################################################################
#
# Implementation notes:
# 1. Redefines (safely) a method in ActiveRecord to make it possible to
#    implement an autonumbering solution for Oracle.
# 2. The OCI8 driver is patched to properly handle values for LONG and
#    TIMESTAMP columns. The driver-author has indicated that a future
#    release of the driver will obviate this patch.
# 3. LOB support is implemented through an after_save callback.
# 4. Oracle does not offer native LIMIT and OFFSET options; this
#    functionality is mimiced through the use of nested selects.
#    See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
#
# Do what you want with this code, at your own peril, but if any
# significant portion of my code remains then please acknowledge my
# contribution.
# portions Copyright 2005 Graham Jenkins

require 'active_record/connection_adapters/abstract_adapter'
require 'delegate'

begin
  require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8

  module ActiveRecord
    class Base
      def self.oracle_connection(config) #:nodoc:
        # Use OCI8AutoRecover instead of normal OCI8 driver.
        ConnectionAdapters::OracleAdapter.new OCI8AutoRecover.new(config), logger
      end

      # for backwards-compatibility
      def self.oci_connection(config) #:nodoc:
        config[:database] = config[:host]
        self.oracle_connection(config)
      end

      # Enable the id column to be bound into the sql later, by the adapter's insert method.
      # This is preferable to inserting the hard-coded value here, because the insert method
      # needs to know the id value explicitly.
      alias :attributes_with_quotes_pre_oracle :attributes_with_quotes
      def attributes_with_quotes(include_primary_key = true) #:nodoc:
        aq = attributes_with_quotes_pre_oracle(include_primary_key)
        if connection.class == ConnectionAdapters::OracleAdapter
          aq[self.class.primary_key] = ":id" if include_primary_key && aq[self.class.primary_key].nil?
        end
        aq
      end

      # After setting large objects to empty, select the OCI8::LOB
      # and write back the data.
      after_save :write_lobs 
      def write_lobs() #:nodoc:
        if connection.is_a?(ConnectionAdapters::OracleAdapter)
          self.class.columns.select { |c| c.type == :binary }.each { |c|
            value = self[c.name]
            next if value.nil?  || (value == '')
            lob = connection.select_one(
              "SELECT #{ c.name} FROM #{ self.class.table_name } WHERE #{ self.class.primary_key} = #{quote(id)}",
              'Writable Large Object')[c.name]
            lob.write value
          }
        end
      end

      private :write_lobs
    end


    module ConnectionAdapters #:nodoc:
      class OracleColumn < Column #:nodoc:
        attr_reader :sql_type

        # overridden to add the concept of scale, required to differentiate
        # between integer and float fields
        def initialize(name, default, sql_type, limit, scale, null)
          @name, @limit, @sql_type, @scale, @null = name, limit, sql_type, scale, null

          @type = simplified_type(sql_type)
          @default = type_cast(default)

          @primary = nil
          @text    = [:string, :text].include? @type
          @number  = [:float, :integer].include? @type
        end

        def type_cast(value)
          return nil if value.nil? || value =~ /^\s*null\s*$/i
          case type
          when :string   then value
          when :integer  then defined?(value.to_i) ? value.to_i : (value ? 1 : 0)
          when :float    then value.to_f
          when :datetime then cast_to_date_or_time(value)
          when :time     then cast_to_time(value)
          else value
          end
        end

        private
        def simplified_type(field_type)
          case field_type
          when /char/i                          : :string
          when /num|float|double|dec|real|int/i : @scale == 0 ? :integer : :float
          when /date|time/i                     : @name =~ /_at$/ ? :time : :datetime
          when /clob/i                           : :text
          when /blob/i                           : :binary
          end
        end

        def cast_to_date_or_time(value)
          return value if value.is_a? Date
          return nil if value.blank?
          guess_date_or_time (value.is_a? Time) ? value : cast_to_time(value)
        end

        def cast_to_time(value)
          return value if value.is_a? Time
          time_array = ParseDate.parsedate value
          time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
          Time.send(Base.default_timezone, *time_array) rescue nil
        end

        def guess_date_or_time(value)
          (value.hour == 0 and value.min == 0 and value.sec == 0) ?
            Date.new(value.year, value.month, value.day) : value
        end
      end


      # This is an Oracle/OCI adapter for the ActiveRecord persistence
      # framework. It relies upon the OCI8 driver, which works with Oracle 8i
      # and above. Most recent development has been on Debian Linux against
      # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
      # See: http://rubyforge.org/projects/ruby-oci8/
      #
      # Usage notes:
      # * Key generation assumes a "${table_name}_seq" sequence is available
      #   for all tables; the sequence name can be changed using
      #   ActiveRecord::Base.set_sequence_name. When using Migrations, these
      #   sequences are created automatically.
      # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
      #   Consequently some hacks are employed to map data back to Date or Time
      #   in Ruby. If the column_name ends in _time it's created as a Ruby Time.
      #   Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
      #   it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
      #   you'll probably not care very much. In 9i and up it's tempting to
      #   map DATE to Date and TIMESTAMP to Time, but too many databases use
      #   DATE for both. Timezones and sub-second precision on timestamps are
      #   not supported.
      # * Default values that are functions (such as "SYSDATE") are not
      #   supported. This is a restriction of the way ActiveRecord supports
      #   default values.
      # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
      #   is supported in Oracle9i and later. You will need to use #finder_sql for
      #   has_and_belongs_to_many associations to run against Oracle8.
      #
      # Required parameters:
      #
      # * <tt>:username</tt>
      # * <tt>:password</tt>
      # * <tt>:database</tt>
      class OracleAdapter < AbstractAdapter

        def adapter_name #:nodoc:
          'Oracle'
        end

        def supports_migrations? #:nodoc:
          true
        end
        
        def native_database_types #:nodoc
          {
            :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
            :string      => { :name => "VARCHAR2", :limit => 255 },
            :text        => { :name => "CLOB" },
            :integer     => { :name => "NUMBER", :limit => 38 },
            :float       => { :name => "NUMBER" },
            :datetime    => { :name => "DATE" },
            :timestamp   => { :name => "DATE" },
            :time        => { :name => "DATE" },
            :date        => { :name => "DATE" },
            :binary      => { :name => "BLOB" },
            :boolean     => { :name => "NUMBER", :limit => 1 }
          }
        end

        def table_alias_length
          30
        end

        # QUOTING ==================================================
        #
        # see: abstract/quoting.rb

        # camelCase column names need to be quoted; not that anyone using Oracle
        # would really do this, but handling this case means we pass the test...
        def quote_column_name(name) #:nodoc:
          name =~ /[A-Z]/ ? "\"#{name}\"" : name
        end

        def quote_string(string) #:nodoc:
          string.gsub(/'/, "''")
        end

        def quote(value, column = nil) #:nodoc:
          if column && column.type == :binary
            %Q{empty_#{ column.sql_type rescue 'blob' }()}
          else
            case value
            when String     : %Q{'#{quote_string(value)}'}
            when NilClass   : 'null'
            when TrueClass  : '1'
            when FalseClass : '0'
            when Numeric    : value.to_s
            when Date, Time : %Q{'#{value.strftime("%Y-%m-%d %H:%M:%S")}'}
            else              %Q{'#{quote_string(value.to_yaml)}'}
            end
          end
        end


        # CONNECTION MANAGEMENT ====================================
        #

        # Returns true if the connection is active.
        def active?
          # Pings the connection to check if it's still good. Note that an
          # #active? method is also available, but that simply returns the 
          # last known state, which isn't good enough if the connection has
          # gone stale since the last use.
          @connection.ping
        rescue OCIException
          false
        end

        # Reconnects to the database.
        def reconnect!
          @connection.reset!
        rescue OCIException => e
          @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
        end

        # Disconnects from the database.
        def disconnect!
          @connection.logoff rescue nil
          @connection.active = false
        end


        # DATABASE STATEMENTS ======================================
        #
        # see: abstract/database_statements.rb

        def select_all(sql, name = nil) #:nodoc:
          select(sql, name)
        end

        def select_one(sql, name = nil) #:nodoc:
          result = select_all(sql, name)
          result.size > 0 ? result.first : nil
        end

        def execute(sql, name = nil) #:nodoc:
          log(sql, name) { @connection.exec sql }
        end

        def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
          if pk.nil? # Who called us? What does the sql look like? No idea!
            execute sql, name
          elsif id_value # Pre-assigned id
            log(sql, name) { @connection.exec sql }
          else # Assume the sql contains a bind-variable for the id
            id_value = select_one("select #{sequence_name}.nextval id from dual")['id']
            log(sql, name) { @connection.exec sql, id_value }
          end

          id_value
        end

        alias :update :execute #:nodoc:
        alias :delete :execute #:nodoc:

        def begin_db_transaction #:nodoc:
          @connection.autocommit = false
        end

        def commit_db_transaction #:nodoc:
          @connection.commit
        ensure
          @connection.autocommit = true
        end

        def rollback_db_transaction #:nodoc:
          @connection.rollback
        ensure
          @connection.autocommit = true
        end

        def add_limit_offset!(sql, options) #:nodoc:
          offset = options[:offset] || 0

          if limit = options[:limit]
            sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
          elsif offset > 0
            sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
          end
        end

        def default_sequence_name(table, column) #:nodoc:
          "#{table}_seq"
        end


        # SCHEMA STATEMENTS ========================================
        #
        # see: abstract/schema_statements.rb

        def current_database #:nodoc:
          select_one("select sys_context('userenv','db_name') db from dual")["db"]
        end

        def tables(name = nil) #:nodoc:
          select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t |
            tabs << t.to_a.first.last
          end
        end

        def indexes(table_name, name = nil) #:nodoc:
          result = select_all(<<-SQL, name)
            SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
              FROM user_indexes i, user_ind_columns c
             WHERE i.table_name = '#{table_name.to_s.upcase}'
               AND c.index_name = i.index_name
               AND i.index_name NOT IN (SELECT index_name FROM user_constraints WHERE constraint_type = 'P')
              ORDER BY i.index_name, c.column_position
          SQL

          current_index = nil
          indexes = []

          result.each do |row|
            if current_index != row['index_name']
              indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", [])
              current_index = row['index_name']
            end

            indexes.last.columns << row['column_name']
          end

          indexes
        end

        def columns(table_name, name = nil) #:nodoc:
          (owner, table_name) = @connection.describe(table_name)

          table_cols = %Q{
            select column_name, data_type, data_default, nullable,
                   decode(data_type, 'NUMBER', data_precision,
                                     'VARCHAR2', data_length,
                                      null) as length,
                   decode(data_type, 'NUMBER', data_scale, null) as scale
              from all_tab_columns
             where owner      = '#{owner}'
               and table_name = '#{table_name}'
             order by column_id
          }

          select_all(table_cols, name).map do |row|
            if row['data_default']
              row['data_default'].sub!(/^(.*?)\s*$/, '\1')
              row['data_default'].sub!(/^'(.*)'$/, '\1')
            end
            OracleColumn.new(
              oracle_downcase(row['column_name']),
              row['data_default'],
              row['data_type'],
              (l = row['length']).nil? ? nil : l.to_i,
              (s = row['scale']).nil? ? nil : s.to_i,
              row['nullable'] == 'Y'
            )
          end
        end

        def create_table(name, options = {}) #:nodoc:
          super(name, options)
          execute "CREATE SEQUENCE #{name}_seq START WITH 10000" unless options[:id] == false
        end

        def rename_table(name, new_name) #:nodoc:
          execute "RENAME #{name} TO #{new_name}"
          execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
        end  

        def drop_table(name) #:nodoc:
          super(name)
          execute "DROP SEQUENCE #{name}_seq" rescue nil
        end

        def remove_index(table_name, options = {}) #:nodoc:
          execute "DROP INDEX #{index_name(table_name, options)}"
        end

        def change_column_default(table_name, column_name, default) #:nodoc:
          execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
        end

        def change_column(table_name, column_name, type, options = {}) #:nodoc:
          change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
          add_column_options!(change_column_sql, options)
          execute(change_column_sql)
        end

        def rename_column(table_name, column_name, new_column_name) #:nodoc:
          execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
        end

        def remove_column(table_name, column_name) #:nodoc:
          execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
        end

        def structure_dump #:nodoc:
          s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
            structure << "create sequence #{seq.to_a.first.last};\n\n"
          end

          select_all("select table_name from user_tables").inject(s) do |structure, table|
            ddl = "create table #{table.to_a.first.last} (\n "  
            cols = select_all(%Q{
              select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
              from user_tab_columns
              where table_name = '#{table.to_a.first.last}'
              order by column_id
            }).map do |row|              
              col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"      
              if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
                col << "(#{row['data_precision'].to_i}"
                col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
                col << ')'
              elsif row['data_type'].include?('CHAR')
                col << "(#{row['data_length'].to_i})"  
              end
              col << " default #{row['data_default']}" if !row['data_default'].nil?
              col << ' not null' if row['nullable'] == 'N'
              col
            end
            ddl << cols.join(",\n ")
            ddl << ");\n\n"
            structure << ddl
          end
        end

        def structure_drop #:nodoc:
          s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
            drop << "drop sequence #{seq.to_a.first.last};\n\n"
          end

          select_all("select table_name from user_tables").inject(s) do |drop, table|
            drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
          end
        end


        private

        def select(sql, name = nil)
          cursor = execute(sql, name)
          cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
          rows = []

          while row = cursor.fetch
            hash = Hash.new

            cols.each_with_index do |col, i|
              hash[col] =
                case row[i]
                when OCI8::LOB
                  name == 'Writable Large Object' ? row[i]: row[i].read
                when OraDate
                  (row[i].hour == 0 and row[i].minute == 0 and row[i].second == 0) ?
                  row[i].to_date : row[i].to_time
                else row[i]
                end unless col == 'raw_rnum_'
            end

            rows << hash
          end

          rows
        ensure
          cursor.close if cursor
        end

        # Oracle column names by default are case-insensitive, but treated as upcase;
        # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
        # their column names when creating Oracle tables, which makes then case-sensitive.
        # I don't know anybody who does this, but we'll handle the theoretical case of a
        # camelCase column name. I imagine other dbs handle this different, since there's a
        # unit test that's currently failing test_oci.
        def oracle_downcase(column_name)
          column_name =~ /[a-z]/ ? column_name : column_name.downcase
        end

      end
    end
  end


  class OCI8 #:nodoc:

    # This OCI8 patch may not longer be required with the upcoming
    # release of version 0.2.
    class Cursor #:nodoc:
      alias :define_a_column_pre_ar :define_a_column
      def define_a_column(i)
        case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
        when 8    : @stmt.defineByPos(i, String, 65535) # Read LONG values
        when 187  : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
        when 108
          if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
            @stmt.defineByPos(i, String, 65535)
          else
            raise 'unsupported datatype'
          end
        else define_a_column_pre_ar i
        end
      end
    end

    # missing constant from oci8 < 0.1.14
    OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)

    # Uses the describeAny OCI call to find the target owner and table_name
    # indicated by +name+, parsing through synonynms as necessary. Returns
    # an array of [owner, table_name].
    def describe(name)
      @desc ||= @@env.alloc(OCIDescribe)
      @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
      @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK)
      info = @desc.attrGet(OCI_ATTR_PARAM)

      case info.attrGet(OCI_ATTR_PTYPE)
      when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
        owner      = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
        table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
        [owner, table_name]
      when OCI_PTYPE_SYN
        schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
        name   = info.attrGet(OCI_ATTR_NAME)
        describe(schema + '.' + name)
      end
    end

  end


  # The OracleConnectionFactory factors out the code necessary to connect and
  # configure an Oracle/OCI connection.
  class OracleConnectionFactory #:nodoc:
    def new_connection(username, password, database)
      conn = OCI8.new username, password, database
      conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
      conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
      conn.autocommit = true
      conn
    end
  end


  # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
  # reset functionality. If a call to #exec fails, and autocommit is turned on
  # (ie., we're not in the middle of a longer transaction), it will 
  # automatically reconnect and try again. If autocommit is turned off,
  # this would be dangerous (as the earlier part of the implied transaction
  # may have failed silently if the connection died) -- so instead the 
  # connection is marked as dead, to be reconnected on it's next use.
  class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
    attr_accessor :active
    alias :active? :active

    cattr_accessor :auto_retry
    class << self
      alias :auto_retry? :auto_retry
    end
    @@auto_retry = false

    def initialize(config, factory = OracleConnectionFactory.new)
      @active = true
      @username, @password, @database = config[:username], config[:password], config[:database]
      @factory = factory
      @connection  = @factory.new_connection @username, @password, @database
      super @connection
    end

    # Checks connection, returns true if active. Note that ping actively
    # checks the connection, while #active? simply returns the last
    # known state.
    def ping
      @connection.exec("select 1 from dual") { |r| nil }
      @active = true
    rescue
      @active = false
      raise
    end

    # Resets connection, by logging off and creating a new connection.
    def reset!
      logoff rescue nil
      begin
        @connection = @factory.new_connection @username, @password, @database
        __setobj__ @connection
        @active = true
      rescue
        @active = false
        raise
      end
    end

    # ORA-00028: your session has been killed
    # ORA-01012: not logged on 
    # ORA-03113: end-of-file on communication channel
    # ORA-03114: not connected to ORACLE
    LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]

    # Adds auto-recovery functionality.
    #
    # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
    def exec(sql, *bindvars)
      should_retry = self.class.auto_retry? && autocommit?

      begin
        @connection.exec(sql, *bindvars)
      rescue OCIException => e
        raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
        @active = false
        raise unless should_retry
        should_retry = false
        reset! rescue nil
        retry
      end
    end

  end

rescue LoadError
  # OCI8 driver is unavailable.
  module ActiveRecord # :nodoc:
    class Base
      def self.oracle_connection(config) # :nodoc:
        # Set up a reasonable error message
        raise LoadError, "Oracle/OCI libraries could not be loaded."
      end
      def self.oci_connection(config) # :nodoc:
        # Set up a reasonable error message
        raise LoadError, "Oracle/OCI libraries could not be loaded."
      end
    end
  end
end
require 'active_record/connection_adapters/abstract_adapter'

module ActiveRecord
  class Base
    # Establishes a connection to the database that's used by all Active Record objects
    def self.postgresql_connection(config) # :nodoc:
      require_library_or_gem 'postgres' unless self.class.const_defined?(:PGconn)

      config = config.symbolize_keys
      host     = config[:host]
      port     = config[:port]     || 5432 unless host.nil?
      username = config[:username].to_s
      password = config[:password].to_s

      min_messages = config[:min_messages]

      if config.has_key?(:database)
        database = config[:database]
      else
        raise ArgumentError, "No database specified. Missing argument: database."
      end

      pga = ConnectionAdapters::PostgreSQLAdapter.new(
        PGconn.connect(host, port, "", "", database, username, password), logger, config
      )

      PGconn.translate_results = false if PGconn.respond_to? :translate_results=

      pga.schema_search_path = config[:schema_search_path] || config[:schema_order]

      pga
    end
  end

  module ConnectionAdapters
    # The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-base
    # (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1145) drivers.
    #
    # Options:
    #
    # * <tt>:host</tt> -- Defaults to localhost
    # * <tt>:port</tt> -- Defaults to 5432
    # * <tt>:username</tt> -- Defaults to nothing
    # * <tt>:password</tt> -- Defaults to nothing
    # * <tt>:database</tt> -- The name of the database. No default, must be provided.
    # * <tt>:schema_search_path</tt> -- An optional schema search path for the connection given as a string of comma-separated schema names.  This is backward-compatible with the :schema_order option.
    # * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
    # * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
    class PostgreSQLAdapter < AbstractAdapter
      def adapter_name
        'PostgreSQL'
      end

      def initialize(connection, logger, config = {})
        super(connection, logger)
        @config = config
        configure_connection
      end

      # Is this connection alive and ready for queries?
      def active?
        if @connection.respond_to?(:status)
          @connection.status == PGconn::CONNECTION_OK
        else
          @connection.query 'SELECT 1'
          true
        end
      # postgres-pr raises a NoMethodError when querying if no conn is available
      rescue PGError, NoMethodError
        false      
      end

      # Close then reopen the connection.
      def reconnect!
        # TODO: postgres-pr doesn't have PGconn#reset.
        if @connection.respond_to?(:reset)
          @connection.reset
          configure_connection
        end
      end
      
      def disconnect!
        # Both postgres and postgres-pr respond to :close
        @connection.close rescue nil
      end

      def native_database_types
        {
          :primary_key => "serial primary key",
          :string      => { :name => "character varying", :limit => 255 },
          :text        => { :name => "text" },
          :integer     => { :name => "integer" },
          :float       => { :name => "float" },
          :datetime    => { :name => "timestamp" },
          :timestamp   => { :name => "timestamp" },
          :time        => { :name => "time" },
          :date        => { :name => "date" },
          :binary      => { :name => "bytea" },
          :boolean     => { :name => "boolean" }
        }
      end
      
      def supports_migrations?
        true
      end      
      
      def table_alias_length
        63
      end

      # QUOTING ==================================================

      def quote(value, column = nil)
        if value.kind_of?(String) && column && column.type == :binary
          "'#{escape_bytea(value)}'"
        else
          super
        end
      end

      def quote_column_name(name)
        %("#{name}")
      end


      # DATABASE STATEMENTS ======================================

      def select_all(sql, name = nil) #:nodoc:
        select(sql, name)
      end

      def select_one(sql, name = nil) #:nodoc:
        result = select(sql, name)
        result.first if result
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
        execute(sql, name)
        table = sql.split(" ", 4)[2]
        id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk))
      end

      def query(sql, name = nil) #:nodoc:
        log(sql, name) { @connection.query(sql) }
      end

      def execute(sql, name = nil) #:nodoc:
        log(sql, name) { @connection.exec(sql) }
      end

      def update(sql, name = nil) #:nodoc:
        execute(sql, name).cmdtuples
      end

      alias_method :delete, :update #:nodoc:


      def begin_db_transaction #:nodoc:
        execute "BEGIN"
      end

      def commit_db_transaction #:nodoc:
        execute "COMMIT"
      end
      
      def rollback_db_transaction #:nodoc:
        execute "ROLLBACK"
      end


      # SCHEMA STATEMENTS ========================================

      # Return the list of all tables in the schema search path.
      def tables(name = nil) #:nodoc:
        schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
        query(<<-SQL, name).map { |row| row[0] }
          SELECT tablename
            FROM pg_tables
           WHERE schemaname IN (#{schemas})
        SQL
      end

      def indexes(table_name, name = nil) #:nodoc:
        result = query(<<-SQL, name)
          SELECT i.relname, d.indisunique, a.attname
            FROM pg_class t, pg_class i, pg_index d, pg_attribute a
           WHERE i.relkind = 'i'
             AND d.indexrelid = i.oid
             AND d.indisprimary = 'f'
             AND t.oid = d.indrelid
             AND t.relname = '#{table_name}'
             AND a.attrelid = t.oid
             AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
                OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
                OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
                OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
                OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
          ORDER BY i.relname
        SQL

        current_index = nil
        indexes = []

        result.each do |row|
          if current_index != row[0]
            indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
            current_index = row[0]
          end

          indexes.last.columns << row[2]
        end

        indexes
      end

      def columns(table_name, name = nil) #:nodoc:
        column_definitions(table_name).collect do |name, type, default, notnull|
          Column.new(name, default_value(default), translate_field_type(type),
            notnull == "f")
        end
      end

      # Set the schema search path to a string of comma-separated schema names.
      # Names beginning with $ are quoted (e.g. $user => '$user')
      # See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html
      def schema_search_path=(schema_csv) #:nodoc:
        if schema_csv
          execute "SET search_path TO #{schema_csv}"
          @schema_search_path = nil
        end
      end

      def schema_search_path #:nodoc:
        @schema_search_path ||= query('SHOW search_path')[0][0]
      end

      def default_sequence_name(table_name, pk = nil)
        default_pk, default_seq = pk_and_sequence_for(table_name)
        default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
      end

      # Resets sequence to the max value of the table's pk if present.
      def reset_pk_sequence!(table, pk = nil, sequence = nil)
        unless pk and sequence
          default_pk, default_sequence = pk_and_sequence_for(table)
          pk ||= default_pk
          sequence ||= default_sequence
        end
        if pk
          if sequence
            select_value <<-end_sql, 'Reset sequence'
              SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
            end_sql
          else
            @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
          end
        end
      end

      # Find a table's primary key and sequence.
      def pk_and_sequence_for(table)
        # First try looking for a sequence with a dependency on the
        # given table's primary key.
        result = execute(<<-end_sql, 'PK and serial sequence')[0]
          SELECT attr.attname, name.nspname, seq.relname
          FROM pg_class      seq,
               pg_attribute  attr,
               pg_depend     dep,
               pg_namespace  name,
               pg_constraint cons
          WHERE seq.oid           = dep.objid
            AND seq.relnamespace  = name.oid
            AND seq.relkind       = 'S'
            AND attr.attrelid     = dep.refobjid
            AND attr.attnum       = dep.refobjsubid
            AND attr.attrelid     = cons.conrelid
            AND attr.attnum       = cons.conkey[1]
            AND cons.contype      = 'p'
            AND dep.refobjid      = '#{table}'::regclass
        end_sql

        if result.nil? or result.empty?
          # If that fails, try parsing the primary key's default value.
          # Support the 7.x and 8.0 nextval('foo'::text) as well as
          # the 8.1+ nextval('foo'::regclass).
          # TODO: assumes sequence is in same schema as table.
          result = execute(<<-end_sql, 'PK and custom sequence')[0]
            SELECT attr.attname, name.nspname, split_part(def.adsrc, '\\\'', 2)
            FROM pg_class       t
            JOIN pg_namespace   name ON (t.relnamespace = name.oid)
            JOIN pg_attribute   attr ON (t.oid = attrelid)
            JOIN pg_attrdef     def  ON (adrelid = attrelid AND adnum = attnum)
            JOIN pg_constraint  cons ON (conrelid = adrelid AND adnum = conkey[1])
            WHERE t.oid = '#{table}'::regclass
              AND cons.contype = 'p'
              AND def.adsrc ~* 'nextval'
          end_sql
        end
        # check for existence of . in sequence name as in public.foo_sequence.  if it does not exist, join the current namespace
        result.last['.'] ? [result.first, result.last] : [result.first, "#{result[1]}.#{result[2]}"]
      rescue
        nil
      end

      def rename_table(name, new_name)
        execute "ALTER TABLE #{name} RENAME TO #{new_name}"
      end
            
      def add_column(table_name, column_name, type, options = {})
        execute("ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}")
        execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL") if options[:null] == false
        change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
      end

      def change_column(table_name, column_name, type, options = {}) #:nodoc:
        begin
          execute "ALTER TABLE #{table_name} ALTER  #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
        rescue ActiveRecord::StatementInvalid
          # This is PG7, so we use a more arcane way of doing it.
          begin_db_transaction
          add_column(table_name, "#{column_name}_ar_tmp", type, options)
          execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
          remove_column(table_name, column_name)
          rename_column(table_name, "#{column_name}_ar_tmp", column_name)
          commit_db_transaction
        end
        change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
      end      

      def change_column_default(table_name, column_name, default) #:nodoc:
        execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT '#{default}'"
      end
      
      def rename_column(table_name, column_name, new_column_name) #:nodoc:
        execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
      end

      def remove_index(table_name, options) #:nodoc:
        execute "DROP INDEX #{index_name(table_name, options)}"
      end

      private
        BYTEA_COLUMN_TYPE_OID = 17
        TIMESTAMPOID = 1114
        TIMESTAMPTZOID = 1184

        def configure_connection
          if @config[:encoding]
            execute("SET client_encoding TO '#{@config[:encoding]}'")
          end
          if @config[:min_messages]
            execute("SET client_min_messages TO '#{@config[:min_messages]}'")
          end
        end

        def last_insert_id(table, sequence_name)
          Integer(select_value("SELECT currval('#{sequence_name}')"))
        end

        def select(sql, name = nil)
          res = execute(sql, name)
          results = res.result
          rows = []
          if results.length > 0
            fields = res.fields
            results.each do |row|
              hashed_row = {}
              row.each_index do |cel_index|
                column = row[cel_index]
                
                case res.type(cel_index)
                  when BYTEA_COLUMN_TYPE_OID
                    column = unescape_bytea(column)
                  when TIMESTAMPTZOID, TIMESTAMPOID
                    column = cast_to_time(column)
                end

                hashed_row[fields[cel_index]] = column
              end
              rows << hashed_row
            end
          end
          return rows
        end

        def escape_bytea(s)
          if PGconn.respond_to? :escape_bytea
            self.class.send(:define_method, :escape_bytea) do |s|
              PGconn.escape_bytea(s) if s
            end
          else
            self.class.send(:define_method, :escape_bytea) do |s|
              if s
                result = ''
                s.each_byte { |c| result << sprintf('\\\\%03o', c) }
                result
              end
            end
          end
          escape_bytea(s)
        end

        def unescape_bytea(s)
          if PGconn.respond_to? :unescape_bytea
            self.class.send(:define_method, :unescape_bytea) do |s|
              PGconn.unescape_bytea(s) if s
            end
          else
            self.class.send(:define_method, :unescape_bytea) do |s|
              if s
                result = ''
                i, max = 0, s.size
                while i < max
                  char = s[i]
                  if char == ?\\
                    if s[i+1] == ?\\
                      char = ?\\
                      i += 1
                    else
                      char = s[i+1..i+3].oct
                      i += 3
                    end
                  end
                  result << char
                  i += 1
                end
                result
              end
            end
          end
          unescape_bytea(s)
        end
        
        # Query a table's column names, default values, and types.
        #
        # The underlying query is roughly:
        #  SELECT column.name, column.type, default.value
        #    FROM column LEFT JOIN default
        #      ON column.table_id = default.table_id
        #     AND column.num = default.column_num
        #   WHERE column.table_id = get_table_id('table_name')
        #     AND column.num > 0
        #     AND NOT column.is_dropped
        #   ORDER BY column.num
        #
        # If the table name is not prefixed with a schema, the database will
        # take the first match from the schema search path.
        #
        # Query implementation notes:
        #  - format_type includes the column size constraint, e.g. varchar(50)
        #  - ::regclass is a function that gives the id for a table name
        def column_definitions(table_name)
          query <<-end_sql
            SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
              FROM pg_attribute a LEFT JOIN pg_attrdef d
                ON a.attrelid = d.adrelid AND a.attnum = d.adnum
             WHERE a.attrelid = '#{table_name}'::regclass
               AND a.attnum > 0 AND NOT a.attisdropped
             ORDER BY a.attnum
          end_sql
        end

        # Translate PostgreSQL-specific types into simplified SQL types.
        # These are special cases; standard types are handled by
        # ConnectionAdapters::Column#simplified_type.
        def translate_field_type(field_type)
          # Match the beginning of field_type since it may have a size constraint on the end.
          case field_type
            when /^timestamp/i    then 'datetime'
            when /^real|^money/i  then 'float'
            when /^interval/i     then 'string'
            # geometric types (the line type is currently not implemented in postgresql)
            when /^(?:point|lseg|box|"?path"?|polygon|circle)/i  then 'string' 
            when /^bytea/i        then 'binary'
            else field_type       # Pass through standard types.
          end
        end

        def default_value(value)
          # Boolean types
          return "t" if value =~ /true/i
          return "f" if value =~ /false/i
          
          # Char/String/Bytea type values
          return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
          
          # Numeric values
          return value if value =~ /^-?[0-9]+(\.[0-9]*)?/

          # Fixed dates / times
          return $1 if value =~ /^'(.+)'::(date|timestamp)/
          
          # Anything else is blank, some user type, or some function
          # and we can't know the value of that, so return nil.
          return nil
        end

        # Only needed for DateTime instances
        def cast_to_time(value)
          return value unless value.class == DateTime
          v = value
          time_array = [v.year, v.month, v.day, v.hour, v.min, v.sec]
          Time.send(Base.default_timezone, *time_array) rescue nil
        end
    end
  end
end
# Author: Luke Holden <lholden@cablelan.net>
# Updated for SQLite3: Jamis Buck <jamis@37signals.com>

require 'active_record/connection_adapters/abstract_adapter'

module ActiveRecord
  class Base
    class << self
      # sqlite3 adapter reuses sqlite_connection.
      def sqlite3_connection(config) # :nodoc:
        parse_config!(config)

        unless self.class.const_defined?(:SQLite3)
          require_library_or_gem(config[:adapter])
        end

        db = SQLite3::Database.new(
          config[:database],
          :results_as_hash => true,
          :type_translation => false
        )
        ConnectionAdapters::SQLiteAdapter.new(db, logger)
      end

      # Establishes a connection to the database that's used by all Active Record objects
      def sqlite_connection(config) # :nodoc:
        parse_config!(config)

        unless self.class.const_defined?(:SQLite)
          require_library_or_gem(config[:adapter])

          db = SQLite::Database.new(config[:database], 0)
          db.show_datatypes   = "ON" if !defined? SQLite::Version
          db.results_as_hash  = true if defined? SQLite::Version
          db.type_translation = false

          # "Downgrade" deprecated sqlite API
          if SQLite.const_defined?(:Version)
            ConnectionAdapters::SQLite2Adapter.new(db, logger)
          else
            ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
          end
        end
      end

      private
        def parse_config!(config)
          config[:database] ||= config[:dbfile]
          # Require database.
          unless config[:database]
            raise ArgumentError, "No database file specified. Missing argument: database"
          end

          # Allow database path relative to RAILS_ROOT, but only if
          # the database path is not the special path that tells
          # Sqlite build a database only in memory.
          if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
            config[:database] = File.expand_path(config[:database], RAILS_ROOT)
          end
        end
    end
  end

  module ConnectionAdapters #:nodoc:
    class SQLiteColumn < Column #:nodoc:
      class <<  self
        def string_to_binary(value)
          value.gsub(/\0|\%/) do |b|
            case b
              when "\0" then "%00"
              when "%"  then "%25"
            end
          end                
        end
        
        def binary_to_string(value)
          value.gsub(/%00|%25/) do |b|
            case b
              when "%00" then "\0"
              when "%25" then "%"
            end
          end                
        end
      end
    end

    # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
    # from http://rubyforge.org/projects/sqlite-ruby/).
    #
    # Options:
    #
    # * <tt>:database</tt> -- Path to the database file.
    class SQLiteAdapter < AbstractAdapter
      def adapter_name #:nodoc:
        'SQLite'
      end

      def supports_migrations? #:nodoc:
        true
      end
      
      def supports_count_distinct? #:nodoc:
        false
      end

      def native_database_types #:nodoc:
        {
          :primary_key => "INTEGER PRIMARY KEY NOT NULL",
          :string      => { :name => "varchar", :limit => 255 },
          :text        => { :name => "text" },
          :integer     => { :name => "integer" },
          :float       => { :name => "float" },
          :datetime    => { :name => "datetime" },
          :timestamp   => { :name => "datetime" },
          :time        => { :name => "datetime" },
          :date        => { :name => "date" },
          :binary      => { :name => "blob" },
          :boolean     => { :name => "boolean" }
        }
      end


      # QUOTING ==================================================

      def quote_string(s) #:nodoc:
        @connection.class.quote(s)
      end

      def quote_column_name(name) #:nodoc:
        %Q("#{name}")
      end


      # DATABASE STATEMENTS ======================================

      def execute(sql, name = nil) #:nodoc:
        catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
      end

      def update(sql, name = nil) #:nodoc:
        execute(sql, name)
        @connection.changes
      end

      def delete(sql, name = nil) #:nodoc:
        sql += " WHERE 1=1" unless sql =~ /WHERE/i
        execute(sql, name)
        @connection.changes
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
        execute(sql, name = nil)
        id_value || @connection.last_insert_row_id
      end

      def select_all(sql, name = nil) #:nodoc:
        execute(sql, name).map do |row|
          record = {}
          row.each_key do |key|
            if key.is_a?(String)
              record[key.sub(/^\w+\./, '')] = row[key]
            end
          end
          record
        end
      end

      def select_one(sql, name = nil) #:nodoc:
        result = select_all(sql, name)
        result.nil? ? nil : result.first
      end


      def begin_db_transaction #:nodoc:
        catch_schema_changes { @connection.transaction }
      end
      
      def commit_db_transaction #:nodoc:
        catch_schema_changes { @connection.commit }
      end

      def rollback_db_transaction #:nodoc:
        catch_schema_changes { @connection.rollback }
      end


      # SCHEMA STATEMENTS ========================================

      def tables(name = nil) #:nodoc:
        execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
          row[0]
        end
      end

      def columns(table_name, name = nil) #:nodoc:
        table_structure(table_name).map do |field|
          SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
        end
      end

      def indexes(table_name, name = nil) #:nodoc:
        execute("PRAGMA index_list(#{table_name})", name).map do |row|
          index = IndexDefinition.new(table_name, row['name'])
          index.unique = row['unique'] != '0'
          index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
          index
        end
      end

      def primary_key(table_name) #:nodoc:
        column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
        column ? column['name'] : nil
      end

      def remove_index(table_name, options={}) #:nodoc:
        execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
      end
      
      def rename_table(name, new_name)
        move_table(name, new_name)
      end

      def add_column(table_name, column_name, type, options = {}) #:nodoc:
        alter_table(table_name) do |definition|
          definition.column(column_name, type, options)
        end
      end
      
      def remove_column(table_name, column_name) #:nodoc:
        alter_table(table_name) do |definition|
          definition.columns.delete(definition[column_name])
        end
      end
      
      def change_column_default(table_name, column_name, default) #:nodoc:
        alter_table(table_name) do |definition|
          definition[column_name].default = default
        end
      end

      def change_column(table_name, column_name, type, options = {}) #:nodoc:
        alter_table(table_name) do |definition|
          definition[column_name].instance_eval do
            self.type    = type
            self.limit   = options[:limit] if options[:limit]
            self.default = options[:default] if options[:default]
          end
        end
      end

      def rename_column(table_name, column_name, new_column_name) #:nodoc:
        alter_table(table_name, :rename => {column_name => new_column_name})
      end
          

      protected
        def table_structure(table_name)
          returning structure = execute("PRAGMA table_info(#{table_name})") do
            raise ActiveRecord::StatementInvalid if structure.empty?
          end
        end
        
        def alter_table(table_name, options = {}) #:nodoc:
          altered_table_name = "altered_#{table_name}"
          caller = lambda {|definition| yield definition if block_given?}

          transaction do
            move_table(table_name, altered_table_name, 
              options.merge(:temporary => true))
            move_table(altered_table_name, table_name, &caller)
          end
        end
        
        def move_table(from, to, options = {}, &block) #:nodoc:
          copy_table(from, to, options, &block)
          drop_table(from)
        end
        
        def copy_table(from, to, options = {}) #:nodoc:
          create_table(to, options) do |@definition|
            columns(from).each do |column|
              column_name = options[:rename] ?
                (options[:rename][column.name] ||
                 options[:rename][column.name.to_sym] ||
                 column.name) : column.name

              @definition.column(column_name, column.type, 
                :limit => column.limit, :default => column.default,
                :null => column.null)
            end
            @definition.primary_key(primary_key(from))
            yield @definition if block_given?
          end
          
          copy_table_indexes(from, to)
          copy_table_contents(from, to, 
            @definition.columns.map {|column| column.name}, 
            options[:rename] || {})
        end
        
        def copy_table_indexes(from, to) #:nodoc:
          indexes(from).each do |index|
            name = index.name
            if to == "altered_#{from}"
              name = "temp_#{name}"
            elsif from == "altered_#{to}"
              name = name[5..-1]
            end

            opts = { :name => name }
            opts[:unique] = true if index.unique
            add_index(to, index.columns, opts)
          end
        end
        
        def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
          column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
          rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
          
          @connection.execute "SELECT * FROM #{from}" do |row|
            sql = "INSERT INTO #{to} VALUES ("
            sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
            sql << ')'
            @connection.execute sql
          end
        end
        
        def catch_schema_changes
          return yield
        rescue ActiveRecord::StatementInvalid => exception
          if exception.message =~ /database schema has changed/
            reconnect!
            retry
          else
            raise
          end
        end
    end
    
    class SQLite2Adapter < SQLiteAdapter # :nodoc:
      # SQLite 2 does not support COUNT(DISTINCT) queries:
      #
      #   select COUNT(DISTINCT ArtistID) from CDs;    
      #
      # In order to get  the number of artists we execute the following statement
      # 
      #   SELECT COUNT(ArtistID) FROM (SELECT DISTINCT ArtistID FROM CDs);
      def execute(sql, name = nil) #:nodoc:
        super(rewrite_count_distinct_queries(sql), name)
      end
      
      def rewrite_count_distinct_queries(sql)
        if sql =~ /count\(distinct ([^\)]+)\)( AS \w+)? (.*)/i
          distinct_column = $1
          distinct_query  = $3
          column_name     = distinct_column.split('.').last
          "SELECT COUNT(#{column_name}) FROM (SELECT DISTINCT #{distinct_column} #{distinct_query})"
        else
          sql
        end
      end
    end

    class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
      def insert(sql, name = nil, pk = nil, id_value = nil)
        execute(sql, name = nil)
        id_value || @connection.last_insert_rowid
      end
    end
  end
end
require 'active_record/connection_adapters/abstract_adapter'

# sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
#
# Author: Joey Gibson <joey@joeygibson.com>
# Date:   10/14/2004
#
# Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
# Date: 3/22/2005
#
# Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
# Date: 6/26/2005
#
# Current maintainer: Ryan Tomayko <rtomayko@gmail.com>
#
# Modifications (Migrations): Tom Ward <tom@popdog.net>
# Date: 27/10/2005
#

module ActiveRecord
  class Base
    def self.sqlserver_connection(config) #:nodoc:
      require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
      
      config = config.symbolize_keys

      mode        = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
      username    = config[:username] ? config[:username].to_s : 'sa'
      password    = config[:password] ? config[:password].to_s : ''
      autocommit  = config.key?(:autocommit) ? config[:autocommit] : true
      if mode == "ODBC"
        raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
        dsn       = config[:dsn]
        driver_url = "DBI:ODBC:#{dsn}"
      else
        raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
        database  = config[:database]
        host      = config[:host] ? config[:host].to_s : 'localhost'
        driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
      end
      conn      = DBI.connect(driver_url, username, password)
      conn["AutoCommit"] = autocommit
      ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
    end
  end # class Base

  module ConnectionAdapters
    class ColumnWithIdentity < Column# :nodoc:
      attr_reader :identity, :is_special, :scale

      def initialize(name, default, sql_type = nil, is_identity = false, null = true, scale_value = 0)
        super(name, default, sql_type, null)
        @identity = is_identity
        @is_special = sql_type =~ /text|ntext|image/i ? true : false
        @scale = scale_value
        # SQL Server only supports limits on *char and float types
        @limit = nil unless @type == :float or @type == :string
      end

      def simplified_type(field_type)
        case field_type
          when /int|bigint|smallint|tinyint/i                        then :integer
          when /float|double|decimal|money|numeric|real|smallmoney/i then @scale == 0 ? :integer : :float
          when /datetime|smalldatetime/i                             then :datetime
          when /timestamp/i                                          then :timestamp
          when /time/i                                               then :time
          when /text|ntext/i                                         then :text
          when /binary|image|varbinary/i                             then :binary
          when /char|nchar|nvarchar|string|varchar/i                 then :string
          when /bit/i                                                then :boolean
          when /uniqueidentifier/i                                   then :string
        end
      end

      def type_cast(value)
        return nil if value.nil? || value =~ /^\s*null\s*$/i
        case type
        when :string    then value
        when :integer   then value == true || value == false ? value == true ? 1 : 0 : value.to_i
        when :float     then value.to_f
        when :datetime  then cast_to_datetime(value)
        when :timestamp then cast_to_time(value)
        when :time      then cast_to_time(value)
        when :date      then cast_to_datetime(value)
        when :boolean   then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
        else value
        end
      end

      def cast_to_time(value)
        return value if value.is_a?(Time)
        time_array = ParseDate.parsedate(value)
        time_array[0] ||= 2000
        time_array[1] ||= 1
        time_array[2] ||= 1
        Time.send(Base.default_timezone, *time_array) rescue nil
      end

      def cast_to_datetime(value)
        if value.is_a?(Time)
          if value.year != 0 and value.month != 0 and value.day != 0
            return value
          else
            return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
          end
        end
        return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
        value
      end

      # These methods will only allow the adapter to insert binary data with a length of 7K or less
      # because of a SQL Server statement length policy.
      def self.string_to_binary(value)
        value.gsub(/(\r|\n|\0|\x1a)/) do
          case $1
            when "\r"   then  "%00"
            when "\n"   then  "%01"
            when "\0"   then  "%02"
            when "\x1a" then  "%03"
          end
        end
      end

      def self.binary_to_string(value)
        value.gsub(/(%00|%01|%02|%03)/) do
          case $1
            when "%00"    then  "\r"
            when "%01"    then  "\n"
            when "%02\0"  then  "\0"
            when "%03"    then  "\x1a"
          end
        end
      end
    end

    # In ADO mode, this adapter will ONLY work on Windows systems, 
    # since it relies on Win32OLE, which, to my knowledge, is only 
    # available on Windows.
    #
    # This mode also relies on the ADO support in the DBI module. If you are using the
    # one-click installer of Ruby, then you already have DBI installed, but
    # the ADO module is *NOT* installed. You will need to get the latest
    # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
    # unzip it, and copy the file 
    # <tt>src/lib/dbd_ado/ADO.rb</tt> 
    # to
    # <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt> 
    # (you will more than likely need to create the ADO directory).
    # Once you've installed that file, you are ready to go.
    #
    # In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
    # the Ruby ODBC module.  Ruby ODBC 0.996 was used in development and testing,
    # and it is available at http://www.ch-werner.de/rubyodbc/
    #
    # Options:
    #
    # * <tt>:mode</tt>      -- ADO or ODBC. Defaults to ADO.
    # * <tt>:username</tt>  -- Defaults to sa.
    # * <tt>:password</tt>  -- Defaults to empty string.
    #
    # ADO specific options:
    #
    # * <tt>:host</tt>      -- Defaults to localhost.
    # * <tt>:database</tt>  -- The name of the database. No default, must be provided.
    #
    # ODBC specific options:                   
    #
    # * <tt>:dsn</tt>       -- Defaults to nothing.
    #
    # ADO code tested on Windows 2000 and higher systems,
    # running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
    #
    # ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63, 
    # unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
    # [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
    class SQLServerAdapter < AbstractAdapter
    
      def initialize(connection, logger, connection_options=nil)
        super(connection, logger)
        @connection_options = connection_options
      end

      def native_database_types
        {
          :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
          :string      => { :name => "varchar", :limit => 255  },
          :text        => { :name => "text" },
          :integer     => { :name => "int" },
          :float       => { :name => "float", :limit => 8 },
          :datetime    => { :name => "datetime" },
          :timestamp   => { :name => "datetime" },
          :time        => { :name => "datetime" },
          :date        => { :name => "datetime" },
          :binary      => { :name => "image"},
          :boolean     => { :name => "bit"}
        }
      end

      def adapter_name
        'SQLServer'
      end
      
      def supports_migrations? #:nodoc:
        true
      end

      # CONNECTION MANAGEMENT ====================================#

      # Returns true if the connection is active.
      def active?
        @connection.execute("SELECT 1") { }
        true
      rescue DBI::DatabaseError, DBI::InterfaceError
        false
      end

      # Reconnects to the database, returns false if no connection could be made.
      def reconnect!
        disconnect!
        @connection = DBI.connect(*@connection_options)
      rescue DBI::DatabaseError => e
        @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
        false
      end
      
      # Disconnects from the database
      
      def disconnect!
        @connection.disconnect rescue nil
      end

      def select_all(sql, name = nil)
        select(sql, name)
      end

      def select_one(sql, name = nil)
        add_limit!(sql, :limit => 1)
        result = select(sql, name)
        result.nil? ? nil : result.first
      end

      def columns(table_name, name = nil)
        return [] if table_name.blank?
        table_name = table_name.to_s if table_name.is_a?(Symbol)
        table_name = table_name.split('.')[-1] unless table_name.nil?
        sql = "SELECT COLUMN_NAME as ColName, COLUMN_DEFAULT as DefaultValue, DATA_TYPE as ColType, IS_NULLABLE As IsNullable, COL_LENGTH('#{table_name}', COLUMN_NAME) as Length, COLUMNPROPERTY(OBJECT_ID('#{table_name}'), COLUMN_NAME, 'IsIdentity') as IsIdentity, NUMERIC_SCALE as Scale FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '#{table_name}'"
        # Comment out if you want to have the Columns select statment logged.
        # Personally, I think it adds unnecessary bloat to the log. 
        # If you do comment it out, make sure to un-comment the "result" line that follows
        result = log(sql, name) { @connection.select_all(sql) }
        #result = @connection.select_all(sql)
        columns = []
        result.each do |field|
          default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
          type = "#{field[:ColType]}(#{field[:Length]})"
          is_identity = field[:IsIdentity] == 1
          is_nullable = field[:IsNullable] == 'YES'
          columns << ColumnWithIdentity.new(field[:ColName], default, type, is_identity, is_nullable, field[:Scale])
        end
        columns
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
        begin
          table_name = get_table_name(sql)
          col = get_identity_column(table_name)
          ii_enabled = false

          if col != nil
            if query_contains_identity_column(sql, col)
              begin
                execute enable_identity_insert(table_name, true)
                ii_enabled = true
              rescue Exception => e
                raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
              end
            end
          end
          log(sql, name) do
            @connection.execute(sql)
            id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
          end
        ensure
          if ii_enabled
            begin
              execute enable_identity_insert(table_name, false)
            rescue Exception => e
              raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
            end
          end
        end
      end

      def execute(sql, name = nil)
        if sql =~ /^\s*INSERT/i
          insert(sql, name)
        elsif sql =~ /^\s*UPDATE|^\s*DELETE/i
          log(sql, name) do
            @connection.execute(sql)
            retVal = select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
          end
        else
          log(sql, name) { @connection.execute(sql) }
        end
      end

      def update(sql, name = nil)
        execute(sql, name)
      end
      alias_method :delete, :update

      def begin_db_transaction
        @connection["AutoCommit"] = false
      rescue Exception => e
        @connection["AutoCommit"] = true
      end

      def commit_db_transaction
        @connection.commit
      ensure
        @connection["AutoCommit"] = true
      end

      def rollback_db_transaction
        @connection.rollback
      ensure
        @connection["AutoCommit"] = true
      end

      def quote(value, column = nil)
        case value
          when String                
            if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
              "'#{quote_string(column.class.string_to_binary(value))}'"
            else
              "'#{quote_string(value)}'"
            end
          when NilClass              then "NULL"
          when TrueClass             then '1'
          when FalseClass            then '0'
          when Float, Fixnum, Bignum then value.to_s
          when Date                  then "'#{value.to_s}'" 
          when Time, DateTime        then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
          else                            "'#{quote_string(value.to_yaml)}'"
        end
      end

      def quote_string(string)
        string.gsub(/\'/, "''")
      end

      def quoted_true
        "1"
      end
      
      def quoted_false
        "0"
      end

      def quote_column_name(name)
        "[#{name}]"
      end

      def add_limit_offset!(sql, options)
        if options[:limit] and options[:offset]
          total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT\b/i, "SELECT TOP 1000000000")}) tally")[0][:TotalRows].to_i
          if (options[:limit] + options[:offset]) >= total_rows
            options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
          end
          sql.sub!(/^\s*SELECT/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT TOP #{options[:limit] + options[:offset]} ")
          sql << ") AS tmp1"
          if options[:order]
            options[:order] = options[:order].split(',').map do |field|
              parts = field.split(" ")
              tc = parts[0]
              if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
                tc.gsub!(/\./, '\\.\\[')
                tc << '\\]'
              end
              if sql =~ /#{tc} AS (t\d_r\d\d?)/
                  parts[0] = $1
              end
              parts.join(' ')
            end.join(', ')
            sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
          else
            sql << " ) AS tmp2"
          end
        elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
          sql.sub!(/^\s*SELECT([\s]*distinct)?/i) do
            "SELECT#{$1} TOP #{options[:limit]}"
          end unless options[:limit].nil?
        end
      end

      def recreate_database(name)
        drop_database(name)
        create_database(name)
      end

      def drop_database(name)
        execute "DROP DATABASE #{name}"
      end

      def create_database(name)
        execute "CREATE DATABASE #{name}"
      end
   
      def current_database
        @connection.select_one("select DB_NAME()")[0]
      end

      def tables(name = nil)
        execute("SELECT table_name from information_schema.tables WHERE table_type = 'BASE TABLE'", name).inject([]) do |tables, field|
          table_name = field[0]
          tables << table_name unless table_name == 'dtproperties'
          tables
        end
      end

      def indexes(table_name, name = nil)
        indexes = []
        execute("EXEC sp_helpindex #{table_name}", name).each do |index| 
          unique = index[1] =~ /unique/
          primary = index[1] =~ /primary key/
          if !primary
            indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
          end
        end
        indexes
      end
            
      def rename_table(name, new_name)
        execute "EXEC sp_rename '#{name}', '#{new_name}'"
      end
      
      def remove_column(table_name, column_name)
        execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
      end 
      
      def rename_column(table, column, new_column_name)
        execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
      end
      
      def change_column(table_name, column_name, type, options = {}) #:nodoc:
        sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}"]
        if options[:default]
          remove_default_constraint(table_name, column_name)
          sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
        end
        sql_commands.each {|c|
          execute(c)
        }
      end
      
      def remove_column(table_name, column_name)
        remove_default_constraint(table_name, column_name)
        execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
      end
      
      def remove_default_constraint(table_name, column_name)
        defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
        defaults.each {|constraint|
          execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
        }
      end
      
      def remove_index(table_name, options = {})
        execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
      end

      def type_to_sql(type, limit = nil) #:nodoc:
        native = native_database_types[type]
        # if there's no :limit in the default type definition, assume that type doesn't support limits
        limit = limit || native[:limit]
        column_type_sql = native[:name]
        column_type_sql << "(#{limit})" if limit
        column_type_sql
      end

      private
        def select(sql, name = nil)
          rows = []
          repair_special_columns(sql)
          log(sql, name) do
            @connection.select_all(sql) do |row|
              record = {}
              row.column_names.each do |col|
                record[col] = row[col]
                record[col] = record[col].to_time if record[col].is_a? DBI::Timestamp
              end
              rows << record
            end
          end
          rows
        end

        def enable_identity_insert(table_name, enable = true)
          if has_identity_column(table_name)
            "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
          end
        end

        def get_table_name(sql)
          if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
            $1
          elsif sql =~ /from\s+([^\(\s]+)\s*/i
            $1
          else
            nil
          end
        end

        def has_identity_column(table_name)
          !get_identity_column(table_name).nil?
        end

        def get_identity_column(table_name)
          @table_columns = {} unless @table_columns
          @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
          @table_columns[table_name].each do |col|
            return col.name if col.identity
          end

          return nil
        end

        def query_contains_identity_column(sql, col)
          sql =~ /\[#{col}\]/
        end

        def change_order_direction(order)
          order.split(",").collect {|fragment|
            case fragment
              when  /\bDESC\b/i     then fragment.gsub(/\bDESC\b/i, "ASC")
              when  /\bASC\b/i      then fragment.gsub(/\bASC\b/i, "DESC")
              else                  String.new(fragment).split(',').join(' DESC,') + ' DESC'
            end
          }.join(",")
        end

        def get_special_columns(table_name)
          special = []
          @table_columns ||= {}
          @table_columns[table_name] ||= columns(table_name)
          @table_columns[table_name].each do |col|
            special << col.name if col.is_special
          end
          special
        end

        def repair_special_columns(sql)
          special_cols = get_special_columns(get_table_name(sql))
          for col in special_cols.to_a
            sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
            sql.gsub!(/ORDER BY #{col.to_s}/i, '')
          end
          sql
        end

    end #class SQLServerAdapter < AbstractAdapter
  end #module ConnectionAdapters
end #module ActiveRecord
# sybase_adaptor.rb
# Author: John Sheets <dev@metacasa.net>
# Date:   01 Mar 2006
#
# Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030)
#
# 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns.
#

require 'active_record/connection_adapters/abstract_adapter'

begin
require 'sybsql'

module ActiveRecord
  class Base
    # Establishes a connection to the database that's used by all Active Record objects
    def self.sybase_connection(config) # :nodoc:
      config = config.symbolize_keys

      username = config[:username] ? config[:username].to_s : 'sa'
      password = config[:password] ? config[:password].to_s : ''

      if config.has_key?(:host)
        host = config[:host]
      else
        raise ArgumentError, "No database server name specified. Missing argument: host."
      end

      if config.has_key?(:database)
        database = config[:database]
      else
        raise ArgumentError, "No database specified. Missing argument: database."
      end

      ConnectionAdapters::SybaseAdapter.new(
        SybSQL.new({'S' => host, 'U' => username, 'P' => password},
          ConnectionAdapters::SybaseAdapterContext), database, logger)
    end
  end # class Base

  module ConnectionAdapters

    # ActiveRecord connection adapter for Sybase Open Client bindings
    # (see http://raa.ruby-lang.org/project/sybase-ctlib).
    #
    # Options:
    #
    # * <tt>:host</tt> -- The name of the database server. No default, must be provided.
    # * <tt>:database</tt> -- The name of the database. No default, must be provided.
    # * <tt>:username</tt>  -- Defaults to sa.
    # * <tt>:password</tt>  -- Defaults to empty string.
    #
    # Usage Notes:
    #
    # * The sybase-ctlib bindings do not support the DATE SQL column type; use DATETIME instead.
    # * Table and column names are limited to 30 chars in Sybase 12.5
    # * :binary columns not yet supported
    # * :boolean columns use the BIT SQL type, which does not allow nulls or 
    #   indexes.  If a DEFAULT is not specified for ALTER TABLE commands, the
    #   column will be declared with DEFAULT 0 (false).
    #
    # Migrations:
    #
    # The Sybase adapter supports migrations, but for ALTER TABLE commands to
    # work, the database must have the database option 'select into' set to
    # 'true' with sp_dboption (see below).  The sp_helpdb command lists the current
    # options for all databases.
    #
    #   1> use mydb
    #   2> go
    #   1> master..sp_dboption mydb, "select into", true
    #   2> go
    #   1> checkpoint
    #   2> go
    class SybaseAdapter < AbstractAdapter # :nodoc:
      class ColumnWithIdentity < Column
        attr_reader :identity, :primary

        def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil)
          super(name, default, sql_type, nullable)
          @default, @identity, @primary = type_cast(default), identity, primary
        end

        def simplified_type(field_type)
          case field_type
            when /int|bigint|smallint|tinyint/i                        then :integer
            when /float|double|decimal|money|numeric|real|smallmoney/i then :float
            when /text|ntext/i                                         then :text
            when /binary|image|varbinary/i                             then :binary
            when /char|nchar|nvarchar|string|varchar/i                 then :string
            when /bit/i                                                then :boolean
            when /datetime|smalldatetime/i                             then :datetime
            else                                                       super
          end
        end

        def self.string_to_binary(value)
          "0x#{value.unpack("H*")[0]}"
        end

        def self.binary_to_string(value)
          # FIXME: sybase-ctlib uses separate sql method for binary columns.
          value
        end
      end # class ColumnWithIdentity

      # Sybase adapter
      def initialize(connection, database, logger = nil)
        super(connection, logger)
        context = connection.context
        context.init(logger)
        @limit = @offset = 0
        unless connection.sql_norow("USE #{database}")
          raise "Cannot USE #{database}"
        end
      end

      def native_database_types
        {
          :primary_key => "numeric(9,0) IDENTITY PRIMARY KEY",
          :string      => { :name => "varchar", :limit => 255 },
          :text        => { :name => "text" },
          :integer     => { :name => "int" },
          :float       => { :name => "float", :limit => 8 },
          :datetime    => { :name => "datetime" },
          :timestamp   => { :name => "timestamp" },
          :time        => { :name => "time" },
          :date        => { :name => "datetime" },
          :binary      => { :name => "image"},
          :boolean     => { :name => "bit" }
        }
      end

      def adapter_name
        'Sybase'
      end

      def active?
        !(@connection.connection.nil? || @connection.connection_dead?)
      end

      def disconnect!
        @connection.close rescue nil
      end

      def reconnect!
        raise "Sybase Connection Adapter does not yet support reconnect!"
        # disconnect!
        # connect! # Not yet implemented
      end

      def table_alias_length
        30
      end

      # Check for a limit statement and parse out the limit and
      # offset if specified. Remove the limit from the sql statement
      # and call select.
      def select_all(sql, name = nil)
        select(sql, name)
      end

      # Remove limit clause from statement. This will almost always
      # contain LIMIT 1 from the caller. set the rowcount to 1 before
      # calling select.
      def select_one(sql, name = nil)
        result = select(sql, name)
        result.nil? ? nil : result.first
      end

      def columns(table_name, name = nil)
        table_structure(table_name).inject([]) do |columns, column|
          name, default, type, nullable, identity, primary = column
          columns << ColumnWithIdentity.new(name, default, type, nullable, identity, primary)
          columns
        end
      end

      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
        begin
          table_name = get_table_name(sql)
          col = get_identity_column(table_name)
          ii_enabled = false

          if col != nil
            if query_contains_identity_column(sql, col)
              begin
                execute enable_identity_insert(table_name, true)
                ii_enabled = true
              rescue Exception => e
                raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
              end
            end
          end

          log(sql, name) do
            execute(sql, name)
            ident = select_one("SELECT @@IDENTITY AS last_id")["last_id"]
            id_value || ident
          end
        ensure
          if ii_enabled
            begin
              execute enable_identity_insert(table_name, false)
            rescue Exception => e
              raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
            end
          end
        end
      end

      def execute(sql, name = nil)
        log(sql, name) do
          @connection.context.reset
          @connection.set_rowcount(@limit || 0)
          @limit = @offset = nil
          @connection.sql_norow(sql)
          if @connection.cmd_fail? or @connection.context.failed?
            raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
          end
        end
        # Return rows affected
        @connection.results[0].row_count
      end

      alias_method :update, :execute
      alias_method :delete, :execute

      def begin_db_transaction()    execute "BEGIN TRAN" end
      def commit_db_transaction()   execute "COMMIT TRAN" end
      def rollback_db_transaction() execute "ROLLBACK TRAN" end

      def tables(name = nil)
        tables = []
        select("select name from sysobjects where type='U'", name).each do |row|
          tables << row['name']
        end
        tables
      end

      def indexes(table_name, name = nil)
        indexes = []
        select("exec sp_helpindex #{table_name}", name).each do |index|
          unique = index["index_description"] =~ /unique/
          primary = index["index_description"] =~ /^clustered/
          if !primary
            cols = index["index_keys"].split(", ").each { |col| col.strip! }
            indexes << IndexDefinition.new(table_name, index["index_name"], unique, cols)
          end
        end
        indexes
      end

      def quoted_true
        "1"
      end

      def quoted_false
        "0"
      end

      def quote(value, column = nil)
        case value
          when String                
            if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
              "#{quote_string(column.class.string_to_binary(value))}"
            elsif value =~ /^[+-]?[0-9]+$/o
              value
            else
              "'#{quote_string(value)}'"
            end
          when NilClass              then (column && column.type == :boolean) ? '0' : "NULL"
          when TrueClass             then '1'
          when FalseClass            then '0'
          when Float, Fixnum, Bignum then value.to_s
          when Date                  then "'#{value.to_s}'" 
          when Time, DateTime        then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
          else                            "'#{quote_string(value.to_yaml)}'"
        end
      end

      def quote_column(type, value)
        case type
          when :boolean
            case value
              when String then value =~ /^[ty]/o ? 1 : 0
              when true   then 1
              when false  then 0
              else             value.to_i
            end
          when :integer then value.to_i
          when :float   then value.to_f
          when :text, :string, :enum
            case value
              when String, Symbol, Fixnum, Float, Bignum, TrueClass, FalseClass 
                "'#{quote_string(value.to_s)}'"
              else
                "'#{quote_string(value.to_yaml)}'"
            end
          when :date, :datetime, :time
            case value
              when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
              when Date           then "'#{value.to_s}'"
              else                     "'#{quote_string(value)}'"
            end
          else "'#{quote_string(value.to_yaml)}'"
        end
      end

      def quote_string(s)
        s.gsub(/'/, "''") # ' (for ruby-mode)
      end

      def quote_column_name(name)
        "[#{name}]"
      end

      def add_limit_offset!(sql, options) # :nodoc:
        @limit = options[:limit]
        @offset = options[:offset]
        if !normal_select?
          # Use temp table to hack offset with Sybase
          sql.sub!(/ FROM /i, ' INTO #artemp FROM ')
        elsif zero_limit?
          # "SET ROWCOUNT 0" turns off limits, so we have
          # to use a cheap trick.
          if sql =~ /WHERE/i
            sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
          elsif sql =~ /ORDER\s+BY/i
            sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
          else
            sql << 'WHERE 1 = 2'
          end
        end
      end

      def supports_migrations? #:nodoc:
        true
      end

      def rename_table(name, new_name)
        execute "EXEC sp_rename '#{name}', '#{new_name}'"
      end

      def rename_column(table, column, new_column_name)
        execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
      end

      def change_column(table_name, column_name, type, options = {}) #:nodoc:
        sql_commands = ["ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"]
        if options[:default]
          remove_default_constraint(table_name, column_name)
          sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
        end
        sql_commands.each { |c| execute(c) }
      end

      def remove_column(table_name, column_name)
        remove_default_constraint(table_name, column_name)
        execute "ALTER TABLE #{table_name} DROP #{column_name}"
      end

      def remove_default_constraint(table_name, column_name)
        defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
        defaults.each {|constraint|
          execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
        }
      end

      def remove_index(table_name, options = {})
        execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
      end

      def add_column_options!(sql, options) #:nodoc:
        sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?

        if check_null_for_column?(options[:column], sql)
          sql << (options[:null] == false ? " NOT NULL" : " NULL")
        end
        sql
      end

    private
      def check_null_for_column?(col, sql)
        # Sybase columns are NOT NULL by default, so explicitly set NULL
        # if :null option is omitted.  Disallow NULLs for boolean.
        type = col.nil? ? "" : col[:type]

        # Ignore :null if a primary key
        return false if type =~ /PRIMARY KEY/i

        # Ignore :null if a :boolean or BIT column
        if (sql =~ /\s+bit(\s+DEFAULT)?/i) || type == :boolean
          # If no default clause found on a boolean column, add one.
          sql << " DEFAULT 0" if $1.nil?
          return false
        end
        true
      end

      # Return the last value of the identity global value.
      def last_insert_id
        @connection.sql("SELECT @@IDENTITY")
        unless @connection.cmd_fail?
          id = @connection.top_row_result.rows.first.first
          if id
            id = id.to_i
            id = nil if id == 0
          end
        else
          id = nil
        end
        id
      end

      def affected_rows(name = nil)
        @connection.sql("SELECT @@ROWCOUNT")
        unless @connection.cmd_fail?
          count = @connection.top_row_result.rows.first.first
          count = count.to_i if count
        else
          0
        end
      end

      def normal_select?
        # If limit is not set at all, we can ignore offset;
        # If limit *is* set but offset is zero, use normal select
        # with simple SET ROWCOUNT.  Thus, only use the temp table
        # if limit is set and offset > 0.
        has_limit = !@limit.nil?
        has_offset = !@offset.nil? && @offset > 0
        !has_limit || !has_offset
      end

      def zero_limit?
        !@limit.nil? && @limit == 0
      end

      # Select limit number of rows starting at optional offset.
      def select(sql, name = nil)
        @connection.context.reset
        log(sql, name) do
          if normal_select?
            # If limit is not explicitly set, return all results.
            @logger.debug "Setting row count to (#{@limit || 'off'})" if @logger

            # Run a normal select
            @connection.set_rowcount(@limit || 0)
            @connection.sql(sql)
          else
            # Select into a temp table and prune results
            @logger.debug "Selecting #{@limit + (@offset || 0)} or fewer rows into #artemp" if @logger
            @connection.set_rowcount(@limit + (@offset || 0))
            @connection.sql_norow(sql)  # Select into temp table
            @logger.debug "Deleting #{@offset || 0} or fewer rows from #artemp" if @logger
            @connection.set_rowcount(@offset || 0)
            @connection.sql_norow("delete from #artemp") # Delete leading rows
            @connection.set_rowcount(0)
            @connection.sql("select * from #artemp") # Return the rest
          end
        end

        rows = []
        if @connection.context.failed? or @connection.cmd_fail?
          raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
        else
          results = @connection.top_row_result
          if results && results.rows.length > 0
            fields = fixup_column_names(results.columns)
            results.rows.each do |row|
              hashed_row = {}
              row.zip(fields) { |cell, column| hashed_row[column] = cell }
              rows << hashed_row
            end
          end
        end
        @connection.sql_norow("drop table #artemp") if !normal_select?
        @limit = @offset = nil
        return rows
      end

      def enable_identity_insert(table_name, enable = true)
        if has_identity_column(table_name)
          "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
        end
      end

      def get_table_name(sql)
        if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
          $1
        elsif sql =~ /from\s+([^\(\s]+)\s*/i
          $1
        else
          nil
        end
      end

      def has_identity_column(table_name)
        !get_identity_column(table_name).nil?
      end

      def get_identity_column(table_name)
        @table_columns = {} unless @table_columns
        @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
        @table_columns[table_name].each do |col|
          return col.name if col.identity
        end

        return nil
      end

      def query_contains_identity_column(sql, col)
        sql =~ /\[#{col}\]/
      end

      # Remove trailing _ from names.
      def fixup_column_names(columns)
        columns.map { |column| column.sub(/_$/, '') }
      end

      def table_structure(table_name)
        sql = <<SQLTEXT
SELECT col.name AS name, type.name AS type, col.prec, col.scale, col.length,
  col.status, obj.sysstat2, def.text
 FROM sysobjects obj, syscolumns col, systypes type, syscomments def
 WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
  AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
SQLTEXT
        log(sql, "Get Column Info ") do
          @connection.set_rowcount(0)
          @connection.sql(sql)
        end
        if @connection.context.failed?
          raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}"
        elsif !@connection.cmd_fail?
          columns = []
          results = @connection.top_row_result
          results.rows.each do |row|
            name, type, prec, scale, length, status, sysstat2, default = row
            type = normalize_type(type, prec, scale, length)
            default_value = nil
            name.sub!(/_$/o, '')
            if default =~ /DEFAULT\s+(.+)/o
              default_value = $1.strip
              default_value = default_value[1...-1] if default_value =~ /^['"]/o
            end
            nullable = (status & 8) == 8
            identity = status >= 128
            primary = (sysstat2 & 8) == 8

            columns << [name, default_value, type, nullable, identity, primary]
          end
          columns
        else
          nil
        end
      end

      def normalize_type(field_type, prec, scale, length)
        if field_type =~ /numeric/i and (scale.nil? or scale == 0)
          type = 'int'
        elsif field_type =~ /money/i
          type = 'numeric'
        else
          type = field_type
        end
        size = ''
        if prec
          size = "(#{prec})"
        elsif length
          size = "(#{length})"
        end
        return type + size
      end

      def default_value(value)
      end
    end # class SybaseAdapter

    class SybaseAdapterContext < SybSQLContext
      DEADLOCK = 1205
      attr_reader :message

      def init(logger = nil)
        @deadlocked = false
        @failed = false
        @logger = logger
        @message = nil
      end

      def srvmsgCB(con, msg)
        # Do not log change of context messages.
        if msg['severity'] == 10 or msg['severity'] == 0
          return true
        end

        if msg['msgnumber'] == DEADLOCK
          @deadlocked = true
        else
          @logger.info "SQL Command failed!" if @logger
          @failed = true
        end

        if @logger
          @logger.error "** SybSQLContext Server Message: **"
          @logger.error "  Message number #{msg['msgnumber']} Severity #{msg['severity']} State #{msg['state']} Line #{msg['line']}"
          @logger.error "  Server #{msg['srvname']}"
          @logger.error "  Procedure #{msg['proc']}"
          @logger.error "  Message String:  #{msg['text']}"
        end

        @message = msg['text']

        true
      end

      def deadlocked?
        @deadlocked
      end

      def failed?
        @failed
      end

      def reset
        @deadlocked = false
        @failed = false
        @message = nil
      end

      def cltmsgCB(con, msg)
        return true unless ( msg.kind_of?(Hash) )
        unless ( msg[ "severity" ] ) then
          return true
        end

        if @logger
          @logger.error "** SybSQLContext Client-Message: **"
          @logger.error "  Message number: LAYER=#{msg[ 'layer' ]} ORIGIN=#{msg[ 'origin' ]} SEVERITY=#{msg[ 'severity' ]} NUMBER=#{msg[ 'number' ]}"
          @logger.error "  Message String: #{msg['msgstring']}"
          @logger.error "  OS Error: #{msg['osstring']}"

          @message = msg['msgstring']
        end

        @failed = true

        # Not retry , CS_CV_RETRY_FAIL( probability TimeOut ) 
        if( msg[ 'severity' ] == "RETRY_FAIL" ) then
          @timeout_p = true
          return false
        end

        return true
      end
    end # class SybaseAdapterContext

  end # module ConnectionAdapters
end # module ActiveRecord


# Allow identity inserts for fixtures.
require "active_record/fixtures"
class Fixtures
  alias :original_insert_fixtures :insert_fixtures

  def insert_fixtures
    values.each do |fixture|
      allow_identity_inserts table_name, true
      @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
      allow_identity_inserts table_name, false
    end
  end

  def allow_identity_inserts(table_name, enable)
      @connection.execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}" rescue nil
  end
end

rescue LoadError => cannot_require_sybase
  # Couldn't load sybase adapter
endmodule ActiveRecord
  module Associations # :nodoc:
    module ClassMethods
      def deprecated_collection_count_method(collection_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def #{collection_name}_count(force_reload = false)
            #{collection_name}.reload if force_reload
            #{collection_name}.size
          end
        end_eval
      end
      
      def deprecated_add_association_relation(association_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def add_#{association_name}(*items)
            #{association_name}.concat(items)
          end
        end_eval
      end
    
      def deprecated_remove_association_relation(association_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def remove_#{association_name}(*items)
            #{association_name}.delete(items)
          end
        end_eval
      end
    
      def deprecated_has_collection_method(collection_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def has_#{collection_name}?(force_reload = false)
            !#{collection_name}(force_reload).empty?
          end
        end_eval
      end
      
      def deprecated_find_in_collection_method(collection_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def find_in_#{collection_name}(association_id)
            #{collection_name}.find(association_id)
          end
        end_eval
      end
      
      def deprecated_find_all_in_collection_method(collection_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def find_all_in_#{collection_name}(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
            #{collection_name}.find_all(runtime_conditions, orderings, limit, joins)
          end
        end_eval
      end
    
      def deprecated_collection_create_method(collection_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def create_in_#{collection_name}(attributes = {})
            #{collection_name}.create(attributes)
          end
        end_eval
      end
    
      def deprecated_collection_build_method(collection_name)# :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def build_to_#{collection_name}(attributes = {})
            #{collection_name}.build(attributes)
          end
        end_eval
      end

      def deprecated_association_comparison_method(association_name, association_class_name) # :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def #{association_name}?(comparison_object, force_reload = false)
            if comparison_object.kind_of?(#{association_class_name})
              #{association_name}(force_reload) == comparison_object
            else
              raise "Comparison object is a #{association_class_name}, should have been \#{comparison_object.class.name}"
            end
          end
        end_eval
      end
        
      def deprecated_has_association_method(association_name) # :nodoc:
        module_eval <<-"end_eval", __FILE__, __LINE__
          def has_#{association_name}?(force_reload = false)
            !#{association_name}(force_reload).nil?
          end
        end_eval
      end      
    end
  end
end
module ActiveRecord
  class Base
    class << self
      # This method is deprecated in favor of find with the :conditions option.
      #
      # Works like find, but the record matching +id+ must also meet the +conditions+.
      # +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
      # Example:
      #   Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
      def find_on_conditions(ids, conditions) # :nodoc:
        find(ids, :conditions => conditions)
      end

      # This method is deprecated in favor of find(:first, options).
      #
      # Returns the object for the first record responding to the conditions in +conditions+, 
      # such as "group = 'master'". If more than one record is returned from the query, it's the first that'll
      # be used to create the object. In such cases, it might be beneficial to also specify 
      # +orderings+, like "income DESC, name", to control exactly which record is to be used. Example: 
      #   Employee.find_first "income > 50000", "income DESC, name"
      def find_first(conditions = nil, orderings = nil, joins = nil) # :nodoc:
        find(:first, :conditions => conditions, :order => orderings, :joins => joins)
      end

      # This method is deprecated in favor of find(:all, options).
      #
      # Returns an array of all the objects that could be instantiated from the associated
      # table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
      # such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
      # such as by "last_name, first_name DESC". A maximum of returned objects and their offset can be specified in 
      # +limit+ with either just a single integer as the limit or as an array with the first element as the limit, 
      # the second as the offset. Examples:
      #   Project.find_all "category = 'accounts'", "last_accessed DESC", 15
      #   Project.find_all ["category = ?", category_name], "created ASC", [15, 20]
      def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil) # :nodoc:
        limit, offset = limit.is_a?(Array) ? limit : [ limit, nil ]
        find(:all, :conditions => conditions, :order => orderings, :joins => joins, :limit => limit, :offset => offset)
      end
    end
  end
endrequire 'erb'
require 'yaml'
require 'csv'

module YAML #:nodoc:
  class Omap #:nodoc:
    def keys;   map { |k, v| k } end
    def values; map { |k, v| v } end
  end
end

class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
end

# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavours:
#
#   1.  YAML fixtures
#   2.  CSV fixtures
#   3.  Single-file fixtures
#
# = YAML fixtures
#
# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
# in a non-verbose, humanly-readable format. It ships with Ruby 1.8.1+.
#
# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
# by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
# put your files in <your-rails-app>/test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
# "<your-rails-app>/test/fixtures/web_sites.yml"). The format of a YAML fixture file looks like this:
#
#   rubyonrails:
#     id: 1
#     name: Ruby on Rails
#     url: http://www.rubyonrails.org
#
#   google:
#     id: 2
#     name: Google
#     url: http://www.google.com
#
# This YAML fixture file includes two fixtures.  Each YAML fixture (ie. record) is given a name and is followed by an
# indented list of key/value pairs in the "key: value" format.  Records are separated by a blank line for your viewing
# pleasure.
#
# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type.  See http://yaml.org/type/omap.html
# for the specification.  You will need ordered fixtures when you have foreign key constraints on keys in the same table.
# This is commonly needed for tree structures.  Example:
#
#    --- !omap
#    - parent:
#        id:         1
#        parent_id:  NULL
#        title:      Parent
#    - child:
#        id:         2
#        parent_id:  1
#        title:      Child
#
# = CSV fixtures
#
# Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
# in a single file, but instead end with the .csv file extension (Rails example: "<your-rails-app>/test/fixtures/web_sites.csv")
#
# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
# humans.  The first line of the CSV file is a comma-separated list of field names.  The rest of the file is then comprised
# of the actual data (1 per line).  Here's an example:
#
#   id, name, url
#   1, Ruby On Rails, http://www.rubyonrails.org
#   2, Google, http://www.google.com
#
# Should you have a piece of data with a comma character in it, you can place double quotes around that value.  If you
# need to use a double quote character, you must escape it with another double quote.
#
# Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats.  Instead, the
# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
# number to the end.  In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
# "web_site_2".
#
# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
# have existing data somewhere already.
#
# = Single-file fixtures
#
# This type of fixtures was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
# appointed by <tt>Test::Unit::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
# put your files in <your-rails-app>/test/fixtures/<your-model-name>/ -- like <your-rails-app>/test/fixtures/web_sites/ for the WebSite
# model).
#
# Each text file placed in this directory represents a "record".  Usually these types of fixtures are named without
# extensions, but if you are on a Windows machine, you might consider adding .txt as the extension.  Here's what the
# above example might look like:
#
#   web_sites/google
#   web_sites/yahoo.txt
#   web_sites/ruby-on-rails
#
# The file format of a standard fixture is simple.  Each line is a property (or column in db speak) and has the syntax
# of "name => value".  Here's an example of the ruby-on-rails fixture above:
#
#   id => 1
#   name => Ruby on Rails
#   url => http://www.rubyonrails.org
#
# = Using Fixtures
#
# Since fixtures are a testing construct, we use them in our unit and functional tests.  There are two ways to use the
# fixtures, but first let's take a look at a sample unit test found:
#
#   require 'web_site'
#
#   class WebSiteTest < Test::Unit::TestCase
#     def test_web_site_count
#       assert_equal 2, WebSite.count
#     end
#   end
#
# As it stands, unless we pre-load the web_site table in our database with two records, this test will fail.  Here's the
# easiest way to add fixtures to the database:
#
#   ...
#   class WebSiteTest < Test::Unit::TestCase
#     fixtures :web_sites # add more by separating the symbols with commas
#   ...
#
# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here tho), we trigger
# the testing environment to automatically load the appropriate fixtures into the database before each test.  
# To ensure consistent data, the environment deletes the fixtures before running the load.
#
# In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
# of the test case.  It is named after the symbol... so, in our example, there would be a hash available called
# @web_sites.  This is where the "fixture name" comes into play.
#
# On top of that, each record is automatically "found" (using Model.find(id)) and placed in the instance variable of its name.
# So for the YAML fixtures, we'd get @rubyonrails and @google, which could be interrogated using regular Active Record semantics:
#
#   # test if the object created from the fixture data has the same attributes as the data itself
#   def test_find
#     assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
#   end
#
# As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
# "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
# from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
# fixtures available as instance variables @web_site_1 and @web_site_2.
#
# If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
#
#   - to completely disable instantiated fixtures:
#       self.use_instantiated_fixtures = false
#
#   - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
#       self.use_instantiated_fixtures = :no_instances 
#
# Even if auto-instantiated fixtures are disabled, you can still access them
# by name via special dynamic methods. Each method has the same name as the
# model, and accepts the name of the fixture to instantiate:
#
#   fixtures :web_sites
#
#   def test_find
#     assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
#   end
#
# = Dynamic fixtures with ERb
#
# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
# mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
#
# <% for i in 1..1000 %>
# fix_<%= i %>:
#   id: <%= i %>
#   name: guy_<%= 1 %>
# <% end %>
#
# This will create 1000 very simple YAML fixtures.
#
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
#
# = Transactional fixtures
#
# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case. 
# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
#
#   class FooTest < Test::Unit::TestCase
#     self.use_transactional_fixtures = true
#     self.use_instantiated_fixtures = false
#   
#     fixtures :foos
#   
#     def test_godzilla
#       assert !Foo.find(:all).empty?
#       Foo.destroy_all
#       assert Foo.find(:all).empty?
#     end
#   
#     def test_godzilla_aftermath
#       assert !Foo.find(:all).empty?
#     end
#   end
#   
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, 
# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
#
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide 
# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
#
# When *not* to use transactional fixtures: 
#   1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit, 
#      particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify 
#      the results of your transaction until Active Record supports nested transactions or savepoints (in progress.) 
#   2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. 
#      Use InnoDB, MaxDB, or NDB instead.
class Fixtures < YAML::Omap
  DEFAULT_FILTER_RE = /\.ya?ml$/

  def self.instantiate_fixtures(object, table_name, fixtures, load_instances=true)
    object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
    if load_instances
      ActiveRecord::Base.silence do
        fixtures.each do |name, fixture|
          begin
            object.instance_variable_set "@#{name}", fixture.find
          rescue FixtureClassNotFound
            nil
          end
        end
      end
    end
  end

  def self.instantiate_all_loaded_fixtures(object, load_instances=true)
    all_loaded_fixtures.each do |table_name, fixtures|
      Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
    end
  end
  
  cattr_accessor :all_loaded_fixtures
  self.all_loaded_fixtures = {}

  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
    table_names = [table_names].flatten.map { |n| n.to_s }
    connection = block_given? ? yield : ActiveRecord::Base.connection
    ActiveRecord::Base.silence do
      fixtures_map = {}
      fixtures = table_names.map do |table_name|
        fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
      end               
      all_loaded_fixtures.merge! fixtures_map  

      connection.transaction do
        fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
        fixtures.each { |fixture| fixture.insert_fixtures }

        # Cap primary key sequences to max(pk).
        if connection.respond_to?(:reset_pk_sequence!)
          table_names.each do |table_name|
            connection.reset_pk_sequence!(table_name)
          end
        end
      end

      return fixtures.size > 1 ? fixtures : fixtures.first
    end
  end


  attr_reader :table_name

  def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
    @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
    @class_name = class_name || 
                  (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
    @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
    read_fixture_files
  end

  def delete_existing_fixtures
    @connection.delete "DELETE FROM #{@table_name}", 'Fixture Delete'
  end

  def insert_fixtures
    values.each do |fixture|
      @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
    end
  end

  private

    def read_fixture_files
      if File.file?(yaml_file_path)
        # YAML fixtures
        begin
          yaml_string = ""
          Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
            yaml_string << IO.read(subfixture_path)
          end
          yaml_string << IO.read(yaml_file_path)

          if yaml = YAML::load(erb_render(yaml_string))
            yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
            yaml.each do |name, data|
              self[name] = Fixture.new(data, @class_name)
            end
          end
        rescue Exception=>boom
          raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n  #{boom.class}: #{boom}"
        end
      elsif File.file?(csv_file_path)
        # CSV fixtures
        reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
        header = reader.shift
        i = 0
        reader.each do |row|
          data = {}
          row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
          self["#{Inflector::underscore(@class_name)}_#{i+=1}"]= Fixture.new(data, @class_name)
        end
      elsif File.file?(deprecated_yaml_file_path)
        raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}"
      else
        # Standard fixtures
        Dir.entries(@fixture_path).each do |file|
          path = File.join(@fixture_path, file)
          if File.file?(path) and file !~ @file_filter
            self[file] = Fixture.new(path, @class_name)
          end
        end
      end
    end

    def yaml_file_path
      "#{@fixture_path}.yml"
    end

    def deprecated_yaml_file_path
      "#{@fixture_path}.yaml"
    end

    def csv_file_path
      @fixture_path + ".csv"
    end

    def yaml_fixtures_key(path)
      File.basename(@fixture_path).split(".").first
    end

    def erb_render(fixture_content)
      ERB.new(fixture_content).result
    end
end

class Fixture #:nodoc:
  include Enumerable
  class FixtureError < StandardError#:nodoc:
  end
  class FormatError < FixtureError#:nodoc:
  end

  def initialize(fixture, class_name)
    case fixture
      when Hash, YAML::Omap
        @fixture = fixture
      when String
        @fixture = read_fixture_file(fixture)
      else
        raise ArgumentError, "Bad fixture argument #{fixture.inspect}"
    end

    @class_name = class_name
  end

  def each
    @fixture.each { |item| yield item }
  end

  def [](key)
    @fixture[key]
  end

  def to_hash
    @fixture
  end

  def key_list
    columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
    columns.join(", ")
  end

  def value_list
    @fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
  end

  def find
    klass = @class_name.is_a?(Class) ? @class_name : Object.const_get(@class_name) rescue nil
    if klass
      klass.find(self[klass.primary_key])
    else
      raise FixtureClassNotFound, "The class #{@class_name.inspect} was not found."
    end
  end

  private
    def read_fixture_file(fixture_file_path)
      IO.readlines(fixture_file_path).inject({}) do |fixture, line|
        # Mercifully skip empty lines.
        next if line =~ /^\s*$/

        # Use the same regular expression for attributes as Active Record.
        unless md = /^\s*([a-zA-Z][-_\w]*)\s*=>\s*(.+)\s*$/.match(line)
          raise FormatError, "#{fixture_file_path}: fixture format error at '#{line}'.  Expecting 'key => value'."
        end
        key, value = md.captures

        # Disallow duplicate keys to catch typos.
        raise FormatError, "#{fixture_file_path}: duplicate '#{key}' in fixture." if fixture[key]
        fixture[key] = value.strip
        fixture
      end
    end
end

module Test #:nodoc:
  module Unit #:nodoc:
    class TestCase #:nodoc:
      cattr_accessor :fixture_path
      class_inheritable_accessor :fixture_table_names
      class_inheritable_accessor :fixture_class_names
      class_inheritable_accessor :use_transactional_fixtures
      class_inheritable_accessor :use_instantiated_fixtures   # true, false, or :no_instances
      class_inheritable_accessor :pre_loaded_fixtures

      self.fixture_table_names = []
      self.use_transactional_fixtures = false
      self.use_instantiated_fixtures = true
      self.pre_loaded_fixtures = false
      
      self.fixture_class_names = {}
      
      @@already_loaded_fixtures = {}
      self.fixture_class_names = {}
      
      def self.set_fixture_class(class_names = {})
        self.fixture_class_names = self.fixture_class_names.merge(class_names)
      end
      
      def self.fixtures(*table_names)
        table_names = table_names.flatten.map { |n| n.to_s }
        self.fixture_table_names |= table_names
        require_fixture_classes(table_names)
        setup_fixture_accessors(table_names)
      end

      def self.require_fixture_classes(table_names=nil)
        (table_names || fixture_table_names).each do |table_name| 
          file_name = table_name.to_s
          file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
          begin
            require file_name
          rescue LoadError
            # Let's hope the developer has included it himself
          end
        end
      end

      def self.setup_fixture_accessors(table_names=nil)
        (table_names || fixture_table_names).each do |table_name|
          table_name = table_name.to_s.tr('.','_')
          define_method(table_name) do |fixture, *optionals|
            force_reload = optionals.shift
            @fixture_cache[table_name] ||= Hash.new
            @fixture_cache[table_name][fixture] = nil if force_reload
            if @loaded_fixtures[table_name][fixture.to_s]
              @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
            else
              raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
            end
          end
        end
      end

      def self.uses_transaction(*methods)
        @uses_transaction ||= []
        @uses_transaction.concat methods.map { |m| m.to_s }
      end

      def self.uses_transaction?(method)
        @uses_transaction && @uses_transaction.include?(method.to_s)
      end

      def use_transactional_fixtures?
        use_transactional_fixtures &&
          !self.class.uses_transaction?(method_name)
      end

      def setup_with_fixtures
        if pre_loaded_fixtures && !use_transactional_fixtures
          raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' 
        end

        @fixture_cache = Hash.new

        # Load fixtures once and begin transaction.
        if use_transactional_fixtures?
          if @@already_loaded_fixtures[self.class]
            @loaded_fixtures = @@already_loaded_fixtures[self.class]
          else
            load_fixtures
            @@already_loaded_fixtures[self.class] = @loaded_fixtures
          end
          ActiveRecord::Base.lock_mutex
          ActiveRecord::Base.connection.begin_db_transaction

        # Load fixtures for every test.
        else
          @@already_loaded_fixtures[self.class] = nil
          load_fixtures
        end

        # Instantiate fixtures for every test if requested.
        instantiate_fixtures if use_instantiated_fixtures
      end

      alias_method :setup, :setup_with_fixtures

      def teardown_with_fixtures
        # Rollback changes.
        if use_transactional_fixtures?
          ActiveRecord::Base.connection.rollback_db_transaction
          ActiveRecord::Base.unlock_mutex
        end
        ActiveRecord::Base.verify_active_connections!
      end

      alias_method :teardown, :teardown_with_fixtures

      def self.method_added(method)
        case method.to_s
        when 'setup'
          unless method_defined?(:setup_without_fixtures)
            alias_method :setup_without_fixtures, :setup
            define_method(:setup) do
              setup_with_fixtures
              setup_without_fixtures
            end
          end
        when 'teardown'
          unless method_defined?(:teardown_without_fixtures)
            alias_method :teardown_without_fixtures, :teardown
            define_method(:teardown) do
              teardown_without_fixtures
              teardown_with_fixtures
            end
          end
        end
      end

      private
        def load_fixtures
          @loaded_fixtures = {}
          fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
          unless fixtures.nil?
            if fixtures.instance_of?(Fixtures)
              @loaded_fixtures[fixtures.table_name] = fixtures
            else
              fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
            end
          end
        end

        # for pre_loaded_fixtures, only require the classes once. huge speed improvement
        @@required_fixture_classes = false

        def instantiate_fixtures
          if pre_loaded_fixtures
            raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
            unless @@required_fixture_classes
              self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
              @@required_fixture_classes = true
            end
            Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
          else
            raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
            @loaded_fixtures.each do |table_name, fixtures|
              Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
            end
          end
        end

        def load_instances?
          use_instantiated_fixtures != :no_instances
        end
    end

  end
end
module ActiveRecord
  # Active Records support optimistic locking if the field <tt>lock_version</tt> is present.  Each update to the
  # record increments the lock_version column and the locking facilities ensure that records instantiated twice
  # will let the last one saved raise a StaleObjectError if the first was also updated. Example:
  #
  #   p1 = Person.find(1)
  #   p2 = Person.find(1)
  #   
  #   p1.first_name = "Michael"
  #   p1.save
  #   
  #   p2.first_name = "should fail"
  #   p2.save # Raises a ActiveRecord::StaleObjectError
  #
  # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
  # or otherwise apply the business logic needed to resolve the conflict.
  #
  # You must ensure that your database schema defaults the lock_version column to 0.
  #
  # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
  # To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
  # This method uses the same syntax as <tt>set_table_name</tt>
  module Locking
    def self.append_features(base) #:nodoc:
      super
      base.class_eval do
        alias_method :update_without_lock, :update
        alias_method :update, :update_with_lock
      end
    end

    def update_with_lock #:nodoc:
      return update_without_lock unless locking_enabled?

      lock_col = self.class.locking_column
      previous_value = send(lock_col)
      send(lock_col + '=', previous_value + 1)

      affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
        UPDATE #{self.class.table_name}
        SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))}
        WHERE #{self.class.primary_key} = #{quote(id)} 
        AND #{lock_col} = #{quote(previous_value)}
      end_sql

      unless affected_rows == 1
        raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
      end

      return true
    end
  end

  class Base
    @@lock_optimistically = true
    cattr_accessor :lock_optimistically

    def locking_enabled? #:nodoc:
      lock_optimistically && respond_to?(self.class.locking_column)
    end
    
    class << self
      def set_locking_column(value = nil, &block)
        define_attr_method :locking_column, value, &block
      end
  
      def locking_column #:nodoc:
        reset_locking_column
      end
  
      def reset_locking_column #:nodoc:
        default = 'lock_version'
        set_locking_column(default)
        default
      end
    end
    
  end
end
module ActiveRecord
  class IrreversibleMigration < ActiveRecordError#:nodoc:
  end
  
  class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
    def initialize(version)
      super("Multiple migrations have the version number #{version}")
    end
  end
  
  # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
  # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
  # push that change to other developers and to the production server. With migrations, you can describe the transformations
  # in self-contained classes that can be checked into version control systems and executed against another database that
  # might be one, two, or five versions behind.
  #
  # Example of a simple migration:
  #
  #   class AddSsl < ActiveRecord::Migration
  #     def self.up
  #       add_column :accounts, :ssl_enabled, :boolean, :default => 1
  #     end
  #
  #     def self.down
  #       remove_column :accounts, :ssl_enabled
  #     end
  #   end
  #
  # This migration will add a boolean flag to the accounts table and remove it again, if you're backing out of the migration.
  # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
  # or remove the migration. These methods can consist of both the migration specific methods, like add_column and remove_column,
  # but may also contain regular Ruby code for generating data needed for the transformations.
  #
  # Example of a more complex migration that also needs to initialize data:
  #
  #   class AddSystemSettings < ActiveRecord::Migration
  #     def self.up
  #       create_table :system_settings do |t|
  #         t.column :name,     :string
  #         t.column :label,    :string
  #         t.column :value,    :text
  #         t.column :type,     :string
  #         t.column :position, :integer
  #       end
  #
  #       SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
  #     end
  #
  #     def self.down
  #       drop_table :system_settings
  #     end
  #   end
  #
  # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
  # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
  # in one block call.
  #
  # == Available transformations
  #
  # * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
  #   that can then add columns to it, following the same format as add_column. See example above. The options hash is for
  #   fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
  # * <tt>drop_table(name)</tt>: Drops the table called +name+.
  # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
  # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
  #   named +column_name+ specified to be one of the following types:
  #   :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean. A default value can be specified
  #   by passing an +options+ hash like { :default => 11 }.
  # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
  # * <tt>change_column(table_name, column_name, type, options)</tt>:  Changes the column to a different type using the same
  #   parameters as add_column.
  # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
  # * <tt>add_index(table_name, column_names, index_type, index_name)</tt>: Add a new index with the name of the column, or +index_name+ (if specified) on the column(s). Specify an optional +index_type+ (e.g. UNIQUE).
  # * <tt>remove_index(table_name, index_name)</tt>: Remove the index specified by +index_name+.
  #
  # == Irreversible transformations
  #
  # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
  # an <tt>IrreversibleMigration</tt> exception in their +down+ method.
  #
  # == Running migrations from within Rails
  #
  # The Rails package has several tools to help create and apply migrations.
  #
  # To generate a new migration, use <tt>script/generate migration MyNewMigration</tt>
  # where MyNewMigration is the name of your migration. The generator will
  # create a file <tt>nnn_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
  # directory, where <tt>nnn</tt> is the next largest migration number.
  # You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
  # n MyNewMigration.
  #
  # To run migrations against the currently configured database, use
  # <tt>rake migrate</tt>. This will update the database by running all of the
  # pending migrations, creating the <tt>schema_info</tt> table if missing.
  #
  # To roll the database back to a previous migration version, use
  # <tt>rake migrate VERSION=X</tt> where <tt>X</tt> is the version to which
  # you wish to downgrade. If any of the migrations throw an
  # <tt>IrreversibleMigration</tt> exception, that step will fail and you'll
  # have some manual work to do.
  #
  # == Database support
  #
  # Migrations are currently supported in MySQL, PostgreSQL, SQLite,
  # SQL Server, Sybase, and Oracle (all supported databases except DB2).
  #
  # == More examples
  #
  # Not all migrations change the schema. Some just fix the data:
  #
  #   class RemoveEmptyTags < ActiveRecord::Migration
  #     def self.up
  #       Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
  #     end
  #
  #     def self.down
  #       # not much we can do to restore deleted data
  #       raise IrreversibleMigration
  #     end
  #   end
  #
  # Others remove columns when they migrate up instead of down:
  #
  #   class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
  #     def self.up
  #       remove_column :items, :incomplete_items_count
  #       remove_column :items, :completed_items_count
  #     end
  #
  #     def self.down
  #       add_column :items, :incomplete_items_count
  #       add_column :items, :completed_items_count
  #     end
  #   end
  #
  # And sometimes you need to do something in SQL not abstracted directly by migrations:
  #
  #   class MakeJoinUnique < ActiveRecord::Migration
  #     def self.up
  #       execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
  #     end
  #
  #     def self.down
  #       execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
  #     end
  #   end
  #
  # == Using a model after changing its table
  #
  # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
  # to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
  # after the new column was added. Example:
  #
  #   class AddPeopleSalary < ActiveRecord::Migration
  #     def self.up
  #       add_column :people, :salary, :integer
  #       Person.reset_column_information
  #       Person.find(:all).each do |p|
  #         p.salary = SalaryCalculator.compute(p)
  #       end
  #     end
  #   end
  #
  # == Controlling verbosity
  #
  # By default, migrations will describe the actions they are taking, writing
  # them to the console as they happen, along with benchmarks describing how
  # long each step took.
  #
  # You can quiet them down by setting ActiveRecord::Migration.verbose = false.
  #
  # You can also insert your own messages and benchmarks by using the #say_with_time
  # method:
  #
  #   def self.up
  #     ...
  #     say_with_time "Updating salaries..." do
  #       Person.find(:all).each do |p|
  #         p.salary = SalaryCalculator.compute(p)
  #       end
  #     end
  #     ...
  #   end
  #
  # The phrase "Updating salaries..." would then be printed, along with the
  # benchmark for the block when the block completes.
  class Migration
    @@verbose = true
    cattr_accessor :verbose

    class << self
      def up_using_benchmarks #:nodoc:
        migrate(:up)
      end

      def down_using_benchmarks #:nodoc:
        migrate(:down)
      end

      # Execute this migration in the named direction
      def migrate(direction)
        return unless respond_to?(direction)

        case direction
          when :up   then announce "migrating"
          when :down then announce "reverting"
        end
        
        result = nil
        time = Benchmark.measure { result = send("real_#{direction}") }

        case direction
          when :up   then announce "migrated (%.4fs)" % time.real; write
          when :down then announce "reverted (%.4fs)" % time.real; write
        end
        
        result
      end

      # Because the method added may do an alias_method, it can be invoked
      # recursively. We use @ignore_new_methods as a guard to indicate whether
      # it is safe for the call to proceed.
      def singleton_method_added(sym) #:nodoc:
        return if @ignore_new_methods
        
        begin
          @ignore_new_methods = true

          case sym
            when :up, :down
              klass = (class << self; self; end)
              klass.send(:alias_method, "real_#{sym}", sym)
              klass.send(:alias_method, sym, "#{sym}_using_benchmarks")
          end
        ensure
          @ignore_new_methods = false
        end
      end

      def write(text="")
        puts(text) if verbose
      end

      def announce(message)
        text = "#{name}: #{message}"
        length = [0, 75 - text.length].max
        write "== %s %s" % [text, "=" * length]
      end

      def say(message, subitem=false)
        write "#{subitem ? "   ->" : "--"} #{message}"
      end

      def say_with_time(message)
        say(message)
        result = nil
        time = Benchmark.measure { result = yield }
        say "%.4fs" % time.real, :subitem
        result
      end

      def suppress_messages
        save = verbose
        self.verbose = false
        yield
      ensure
        self.verbose = save
      end

      def method_missing(method, *arguments, &block)
        say_with_time "#{method}(#{arguments.map { |a| a.inspect }.join(", ")})" do
          arguments[0] = Migrator.proper_table_name(arguments.first) unless arguments.empty? || method == :execute
          ActiveRecord::Base.connection.send(method, *arguments, &block)
        end
      end
    end
  end

  class Migrator#:nodoc:
    class << self
      def migrate(migrations_path, target_version = nil)
        Base.connection.initialize_schema_information

        case
          when target_version.nil?, current_version < target_version
            up(migrations_path, target_version)
          when current_version > target_version
            down(migrations_path, target_version)
          when current_version == target_version
            return # You're on the right version
        end
      end
      
      def up(migrations_path, target_version = nil)
        self.new(:up, migrations_path, target_version).migrate
      end
      
      def down(migrations_path, target_version = nil)
        self.new(:down, migrations_path, target_version).migrate
      end
      
      def schema_info_table_name
        Base.table_name_prefix + "schema_info" + Base.table_name_suffix
      end

      def current_version
        (Base.connection.select_one("SELECT version FROM #{schema_info_table_name}") || {"version" => 0})["version"].to_i
      end

      def proper_table_name(name)
        # Use the ActiveRecord objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
        name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
      end
        
    end
    
    def initialize(direction, migrations_path, target_version = nil)
      raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
      @direction, @migrations_path, @target_version = direction, migrations_path, target_version
      Base.connection.initialize_schema_information
    end

    def current_version
      self.class.current_version
    end

    def migrate
      migration_classes.each do |(version, migration_class)|
        Base.logger.info("Reached target version: #{@target_version}") and break if reached_target_version?(version)
        next if irrelevant_migration?(version)

        Base.logger.info "Migrating to #{migration_class} (#{version})"
        migration_class.migrate(@direction)
        set_schema_version(version)
      end
    end

    private
      def migration_classes
        migrations = migration_files.inject([]) do |migrations, migration_file|
          load(migration_file)
          version, name = migration_version_and_name(migration_file)
          assert_unique_migration_version(migrations, version.to_i)
          migrations << [ version.to_i, migration_class(name) ] 
        end

        down? ? migrations.sort.reverse : migrations.sort
      end
      
      def assert_unique_migration_version(migrations, version)
        if !migrations.empty? && migrations.transpose.first.include?(version)
          raise DuplicateMigrationVersionError.new(version)
        end
      end
      
      def migration_files
        files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f|
          migration_version_and_name(f).first.to_i
        end
        down? ? files.reverse : files
      end
                 
      def migration_class(migration_name)
        migration_name.camelize.constantize
      end
    
      def migration_version_and_name(migration_file)
        return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
      end
      
      def set_schema_version(version)
        Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
      end
      
      def up?
        @direction == :up
      end
      
      def down?
        @direction == :down
      end
      
      def reached_target_version?(version)
        (up? && version.to_i - 1 == @target_version) || (down? && version.to_i == @target_version)
      end
      
      def irrelevant_migration?(version)
        (up? && version.to_i <= current_version) || (down? && version.to_i > current_version)
      end
  end
end
require 'singleton'

module ActiveRecord
  module Observing # :nodoc:
    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    module ClassMethods
      # Activates the observers assigned. Examples:
      #
      #   # Calls PersonObserver.instance
      #   ActiveRecord::Base.observers = :person_observer
      #
      #   # Calls Cacher.instance and GarbageCollector.instance 
      #   ActiveRecord::Base.observers = :cacher, :garbage_collector
      #
      #   # Same as above, just using explicit class references
      #   ActiveRecord::Base.observers = Cacher, GarbageCollector
      def observers=(*observers)
        observers = [ observers ].flatten.each do |observer| 
          observer.is_a?(Symbol) ? 
            observer.to_s.camelize.constantize.instance :
            observer.instance
        end
      end
    end
  end

  # Observer classes respond to lifecycle callbacks to implement trigger-like
  # behavior outside the original class. This is a great way to reduce the
  # clutter that normally comes when the model class is burdened with
  # functionality that doesn't pertain to the core responsibility of the
  # class. Example:
  #
  #   class CommentObserver < ActiveRecord::Observer
  #     def after_save(comment)
  #       Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
  #     end
  #   end
  #
  # This Observer sends an email when a Comment#save is finished.
  #
  #   class ContactObserver < ActiveRecord::Observer
  #     def after_create(contact)
  #       contact.logger.info('New contact added!')
  #     end
  #
  #     def after_destroy(contact)
  #       contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
  #     end
  #   end
  #
  # This Observer uses logger to log when specific callbacks are triggered.
  #
  # == Observing a class that can't be inferred
  #
  # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
  # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
  # differently than the class you're interested in observing, you can use the Observer.observe class method:
  #
  #   class AuditObserver < ActiveRecord::Observer
  #     observe Account
  #
  #     def after_update(account)
  #       AuditTrail.new(account, "UPDATED")
  #     end
  #   end
  #
  # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
  #
  #   class AuditObserver < ActiveRecord::Observer
  #     observe Account, Balance
  #
  #     def after_update(record)
  #       AuditTrail.new(record, "UPDATED")
  #     end
  #   end
  #
  # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
  #
  # == Available callback methods
  #
  # The observer can implement callback methods for each of the methods described in the Callbacks module.
  #
  # == Storing Observers in Rails
  # 
  # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
  # naming convention of app/models/audit_observer.rb.
  #
  # == Configuration
  # 
  # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
  # <tt>config/environment.rb</tt> file.
  #
  #   config.active_record.observers = :comment_observer, :signup_observer
  #
  # Observers will not be invoked unless you define these in your application configuration.
  #
  class Observer
    include Singleton

    # Observer subclasses should be reloaded by the dispatcher in Rails
    # when Dependencies.mechanism = :load.
    include Reloadable::Subclasses
    
    # Attaches the observer to the supplied model classes.
    def self.observe(*models)
      define_method(:observed_class) { models }
    end

    def initialize
      observed_classes = [ observed_class ].flatten
      observed_subclasses_class = observed_classes.collect {|c| c.send(:subclasses) }.flatten!
      (observed_classes + observed_subclasses_class).each do |klass| 
        klass.add_observer(self)
        klass.send(:define_method, :after_find) unless klass.respond_to?(:after_find)
      end
    end
  
    def update(callback_method, object) #:nodoc:
      send(callback_method, object) if respond_to?(callback_method)
    end
    
    private
      def observed_class
        if self.class.respond_to? "observed_class"
          self.class.observed_class
        else
          Object.const_get(infer_observed_class_name)
        end
      end
      
      def infer_observed_class_name
        self.class.name.scan(/(.*)Observer/)[0][0]
      end
  end
end
module ActiveRecord
  class QueryCache #:nodoc:
    def initialize(connection)
      @connection = connection
      @query_cache = {}
    end

    def clear_query_cache
      @query_cache = {}
    end

    def select_all(sql, name = nil)
      (@query_cache[sql] ||= @connection.select_all(sql, name)).dup
    end

    def select_one(sql, name = nil)
      @query_cache[sql] ||= @connection.select_one(sql, name)
    end

    def columns(table_name, name = nil)
      @query_cache["SHOW FIELDS FROM #{table_name}"] ||= @connection.columns(table_name, name)
    end

    def insert(sql, name = nil, pk = nil, id_value = nil)
      clear_query_cache
      @connection.insert(sql, name, pk, id_value)
    end

    def update(sql, name = nil)
      clear_query_cache
      @connection.update(sql, name)
    end

    def delete(sql, name = nil)
      clear_query_cache
      @connection.delete(sql, name)
    end
    
    private
      def method_missing(method, *arguments, &proc)
        @connection.send(method, *arguments, &proc)
      end
  end
  
  class Base
    # Set the connection for the class with caching on
    class << self
      alias_method :connection_without_query_cache=, :connection=

      def connection=(spec)
        if spec.is_a?(ConnectionSpecification) and spec.config[:query_cache]
          spec = QueryCache.new(self.send(spec.adapter_method, spec.config))
        end
        self.connection_without_query_cache = spec
      end
    end
  end
  
  class AbstractAdapter #:nodoc:
    # Stub method to be able to treat the connection the same whether the query cache has been turned on or not
    def clear_query_cache
    end
  end
end
module ActiveRecord
  module Reflection # :nodoc:
    def self.included(base)
      base.extend(ClassMethods)
    end

    # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
    # This information can, for example, be used in a form builder that took an Active Record object and created input
    # fields for all of the attributes depending on their type and displayed the associations to other objects.
    #
    # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
    module ClassMethods
      def create_reflection(macro, name, options, active_record)
        case macro
          when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
            reflection = AssociationReflection.new(macro, name, options, active_record)
          when :composed_of
            reflection = AggregateReflection.new(macro, name, options, active_record)
        end
        write_inheritable_hash :reflections, name => reflection
        reflection
      end
      
      def reflections
        read_inheritable_attribute(:reflections) or write_inheritable_attribute(:reflections, {})
      end
      
      # Returns an array of AggregateReflection objects for all the aggregations in the class.
      def reflect_on_all_aggregations
        reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
      end

      # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
      #   Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
      def reflect_on_aggregation(aggregation)
        reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
      end

      # Returns an array of AssociationReflection objects for all the aggregations in the class. If you only want to reflect on a
      # certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter. Example:
      # Account.reflect_on_all_associations             # returns an array of all associations
      # Account.reflect_on_all_associations(:has_many)  # returns an array of all has_many associations
      def reflect_on_all_associations(macro = nil)
        association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
        macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
      end

      # Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
      #   Account.reflect_on_association(:owner) # returns the owner AssociationReflection
      #   Invoice.reflect_on_association(:line_items).macro  # returns :has_many
      def reflect_on_association(association)
        reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
      end
    end


    # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
    # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
    class MacroReflection
      attr_reader :active_record
      def initialize(macro, name, options, active_record)
        @macro, @name, @options, @active_record = macro, name, options, active_record
      end

      # Returns the name of the macro, so it would return :balance for "composed_of :balance, :class_name => 'Money'" or
      # :clients for "has_many :clients".
      def name
        @name
      end

      # Returns the name of the macro, so it would return :composed_of for
      # "composed_of :balance, :class_name => 'Money'" or :has_many for "has_many :clients".
      def macro
        @macro
      end

      # Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
      # "composed_of :balance, :class_name => 'Money'" or {} for "has_many :clients".
      def options
        @options
      end

      # Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" would return the Money class and
      # "has_many :clients" would return the Client class.
      def klass() end
        
      def class_name
        @class_name ||= name_to_class_name(name.id2name)
      end

      def ==(other_aggregation)
        name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
      end
    end


    # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
    class AggregateReflection < MacroReflection #:nodoc:
      def klass
        @klass ||= Object.const_get(options[:class_name] || class_name)
      end

      private
        def name_to_class_name(name)
          name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
        end
    end

    # Holds all the meta-data about an association as it was specified in the Active Record class.
    class AssociationReflection < MacroReflection #:nodoc:
      def klass
        @klass ||= active_record.send(:compute_type, class_name)
      end

      def table_name
        @table_name ||= klass.table_name
      end

      def primary_key_name
        return @primary_key_name if @primary_key_name
        case
          when macro == :belongs_to
            @primary_key_name = options[:foreign_key] || class_name.foreign_key
          when options[:as]
            @primary_key_name = options[:foreign_key] || "#{options[:as]}_id"
          else
            @primary_key_name = options[:foreign_key] || active_record.name.foreign_key
        end
      end
      
      def association_foreign_key
        @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
      end

      def counter_cache_column
        if options[:counter_cache] == true
          "#{active_record.name.underscore.pluralize}_count"
        elsif options[:counter_cache]
          options[:counter_cache]
        end
      end

      def through_reflection
        @through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
      end

      # Gets an array of possible :through source reflection names
      #
      #   [singularized, pluralized]
      def source_reflection_names
        @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
      end

      # Gets the source of the through reflection.  It checks both a singularized and pluralized form for :belongs_to or :has_many.
      # (The :tags association on Tagging below)
      # 
      #   class Post
      #     has_many :tags, :through => :taggings
      #   end
      #
      def source_reflection
        return nil unless through_reflection
        @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
      end

      def check_validity!
        if options[:through]
          if through_reflection.nil?
            raise HasManyThroughAssociationNotFoundError.new(self)
          end
          
          if source_reflection.nil?
            raise HasManyThroughSourceAssociationNotFoundError.new(self)
          end
          
          if source_reflection.options[:polymorphic]
            raise HasManyThroughAssociationPolymorphicError.new(class_name, self, source_reflection)
          end
          
          unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
            raise HasManyThroughSourceAssociationMacroError.new(self)
          end
        end
      end

      private
        def name_to_class_name(name)
          if name =~ /::/
            name
          else
            if options[:class_name]
              options[:class_name]
            elsif through_reflection # get the class_name of the belongs_to association of the through reflection
              source_reflection.class_name
            else
              class_name = name.to_s.camelize
              class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
              class_name
            end
          end
        end
    end
  end
end
module ActiveRecord
  # Allows programmers to programmatically define a schema in a portable
  # DSL. This means you can define tables, indexes, etc. without using SQL
  # directly, so your applications can more easily support multiple
  # databases.
  #
  # Usage:
  #
  #   ActiveRecord::Schema.define do
  #     create_table :authors do |t|
  #       t.column :name, :string, :null => false
  #     end
  #
  #     add_index :authors, :name, :unique
  #
  #     create_table :posts do |t|
  #       t.column :author_id, :integer, :null => false
  #       t.column :subject, :string
  #       t.column :body, :text
  #       t.column :private, :boolean, :default => false
  #     end
  #
  #     add_index :posts, :author_id
  #   end
  #
  # ActiveRecord::Schema is only supported by database adapters that also
  # support migrations, the two features being very similar.
  class Schema < Migration
    private_class_method :new

    # Eval the given block. All methods available to the current connection
    # adapter are available within the block, so you can easily use the
    # database definition DSL to build up your schema (#create_table,
    # #add_index, etc.).
    #
    # The +info+ hash is optional, and if given is used to define metadata
    # about the current schema (like the schema's version):
    #
    #   ActiveRecord::Schema.define(:version => 15) do
    #     ...
    #   end
    def self.define(info={}, &block)
      instance_eval(&block)

      unless info.empty?
        initialize_schema_information
        cols = columns('schema_info')

        info = info.map do |k,v|
          v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s })
          "#{k} = #{v}"
        end

        Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}"
      end
    end
  end
end
module ActiveRecord
  # This class is used to dump the database schema for some connection to some
  # output format (i.e., ActiveRecord::Schema).
  class SchemaDumper #:nodoc:
    private_class_method :new
    
    # A list of tables which should not be dumped to the schema. 
    # Acceptable values are strings as well as regexp.
    # This setting is only used if ActiveRecord::Base.schema_format == :ruby
    cattr_accessor :ignore_tables 
    @@ignore_tables = []

    def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
      new(connection).dump(stream)
      stream
    end

    def dump(stream)
      header(stream)
      tables(stream)
      trailer(stream)
      stream
    end

    private

      def initialize(connection)
        @connection = connection
        @types = @connection.native_database_types
        @info = @connection.select_one("SELECT * FROM schema_info") rescue nil
      end

      def header(stream)
        define_params = @info ? ":version => #{@info['version']}" : ""

        stream.puts <<HEADER
# This file is autogenerated. Instead of editing this file, please use the
# migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.

ActiveRecord::Schema.define(#{define_params}) do

HEADER
      end

      def trailer(stream)
        stream.puts "end"
      end

      def tables(stream)
        @connection.tables.sort.each do |tbl|
          next if ["schema_info", ignore_tables].flatten.any? do |ignored|
            case ignored
            when String: tbl == ignored
            when Regexp: tbl =~ ignored
            else
              raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
            end
          end 
          table(tbl, stream)
        end
      end

      def table(table, stream)
        columns = @connection.columns(table)
        begin
          tbl = StringIO.new

          if @connection.respond_to?(:pk_and_sequence_for)
            pk, pk_seq = @connection.pk_and_sequence_for(table)
          end
          pk ||= 'id'

          tbl.print "  create_table #{table.inspect}"
          if columns.detect { |c| c.name == pk }
            if pk != 'id'
              tbl.print %Q(, :primary_key => "#{pk}")
            end
          else
            tbl.print ", :id => false"
          end
          tbl.print ", :force => true"
          tbl.puts " do |t|"

          columns.each do |column|
            raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
            next if column.name == pk
            tbl.print "    t.column #{column.name.inspect}, #{column.type.inspect}"
            tbl.print ", :limit => #{column.limit.inspect}" if column.limit != @types[column.type][:limit] 
            tbl.print ", :default => #{column.default.inspect}" if !column.default.nil?
            tbl.print ", :null => false" if !column.null
            tbl.puts
          end

          tbl.puts "  end"
          tbl.puts
          
          indexes(table, tbl)

          tbl.rewind
          stream.print tbl.read
        rescue => e
          stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
          stream.puts "#   #{e.message}"
          stream.puts
        end
        
        stream
      end

      def indexes(table, stream)
        indexes = @connection.indexes(table)
        indexes.each do |index|
          stream.print "  add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
          stream.print ", :unique => true" if index.unique
          stream.puts
        end
        stream.puts unless indexes.empty?
      end
  end
end
module ActiveRecord
  # Active Records will automatically record creation and/or update timestamps of database objects
  # if fields of the names created_at/created_on or updated_at/updated_on are present. This module is
  # automatically included, so you don't need to do that manually.
  #
  # This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
  # This behavior by default uses local time, but can use UTC by setting <tt>ActiveRecord::Base.default_timezone = :utc</tt>
  module Timestamp
    def self.append_features(base) # :nodoc:
      super

      base.class_eval do
        alias_method :create_without_timestamps, :create
        alias_method :create, :create_with_timestamps

        alias_method :update_without_timestamps, :update
        alias_method :update, :update_with_timestamps
      end
    end

    def create_with_timestamps #:nodoc:
      if record_timestamps
      t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
        write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
        write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?

        write_attribute('updated_at', t) if respond_to?(:updated_at)
        write_attribute('updated_on', t) if respond_to?(:updated_on)
      end
      create_without_timestamps
    end

    def update_with_timestamps #:nodoc:
      if record_timestamps
      t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
        write_attribute('updated_at', t) if respond_to?(:updated_at)
        write_attribute('updated_on', t) if respond_to?(:updated_on)
      end
      update_without_timestamps
    end
  end

  class Base
    # Records the creation date and possibly time in created_on (date only) or created_at (date and time) and the update date and possibly
    # time in updated_on and updated_at. This only happens if the object responds to either of these messages, which they will do automatically
    # if the table has columns of either of these names. This feature is turned on by default.
    @@record_timestamps = true
    cattr_accessor :record_timestamps

    # deprecated: use ActiveRecord::Base.default_timezone instead.
    @@timestamps_gmt = false
    def self.timestamps_gmt=( gmt ) #:nodoc:
      warn "timestamps_gmt= is deprecated. use default_timezone= instead"
      self.default_timezone = ( gmt ? :utc : :local )
    end

    def self.timestamps_gmt #:nodoc:
      warn "timestamps_gmt is deprecated. use default_timezone instead"
      self.default_timezone == :utc
    end
  end
end
require 'active_record/vendor/simple.rb'
Transaction::Simple.send(:remove_method, :transaction)
require 'thread'

module ActiveRecord
  module Transactions # :nodoc:
    TRANSACTION_MUTEX = Mutex.new

    class TransactionError < ActiveRecordError # :nodoc:
    end

    def self.append_features(base)
      super
      base.extend(ClassMethods)

      base.class_eval do
        alias_method :destroy_without_transactions, :destroy
        alias_method :destroy, :destroy_with_transactions

        alias_method :save_without_transactions, :save
        alias_method :save, :save_with_transactions
      end
    end

    # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action. 
    # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
    # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
    # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
    # not at all. Example:
    #
    #   transaction do
    #     david.withdrawal(100)
    #     mary.deposit(100)
    #   end
    #
    # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
    # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
    # that the objects by default will _not_ have their instance data returned to their pre-transactional state.
    #
    # == Transactions are not distributed across database connections
    #
    # A transaction acts on a single database connection.  If you have
    # multiple class-specific databases, the transaction will not protect
    # interaction among them.  One workaround is to begin a transaction
    # on each class whose models you alter:
    #
    #   Student.transaction do
    #     Course.transaction do
    #       course.enroll(student)
    #       student.units += course.units
    #     end
    #   end
    #
    # This is a poor solution, but full distributed transactions are beyond
    # the scope of Active Record.
    #
    # == Save and destroy are automatically wrapped in a transaction
    #
    # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
    # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
    # depend on or you can raise exceptions in the callbacks to rollback.
    #
    # == Object-level transactions
    #
    # You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
    # that you want to enable object-level transactions for, like this:
    #
    #   Account.transaction(david, mary) do
    #     david.withdrawal(100)
    #     mary.deposit(100)
    #   end
    #
    # If the transaction fails, David and Mary will be returned to their pre-transactional state. No money will have changed hands in
    # neither object nor database.
    #
    # == Exception handling
    #
    # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
    # should be ready to catch those in your application code.
    #
    # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
    module ClassMethods
      def transaction(*objects, &block)
        previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
        lock_mutex
        
        begin
          objects.each { |o| o.extend(Transaction::Simple) }
          objects.each { |o| o.start_transaction }

          result = connection.transaction(Thread.current['start_db_transaction'], &block)

          objects.each { |o| o.commit_transaction }
          return result
        rescue Exception => object_transaction_rollback
          objects.each { |o| o.abort_transaction }
          raise
        ensure
          unlock_mutex
          trap('TERM', previous_handler)
        end
      end
      
      def lock_mutex#:nodoc:
        Thread.current['open_transactions'] ||= 0
        TRANSACTION_MUTEX.lock if Thread.current['open_transactions'] == 0
        Thread.current['start_db_transaction'] = (Thread.current['open_transactions'] == 0)
        Thread.current['open_transactions'] += 1
      end
      
      def unlock_mutex#:nodoc:
        Thread.current['open_transactions'] -= 1
        TRANSACTION_MUTEX.unlock if Thread.current['open_transactions'] == 0
      end
    end

    def transaction(*objects, &block)
      self.class.transaction(*objects, &block)
    end

    def destroy_with_transactions #:nodoc:
      transaction { destroy_without_transactions }
    end
    
    def save_with_transactions(perform_validation = true) #:nodoc:
      transaction { save_without_transactions(perform_validation) }
    end
  end
end
module ActiveRecord
  # Raised by save! and create! when the record is invalid.  Use the
  # record method to retrieve the record which did not validate.
  #   begin
  #     complex_operation_that_calls_save!_internally
  #   rescue ActiveRecord::RecordInvalid => invalid
  #     puts invalid.record.errors
  #   end
  class RecordInvalid < ActiveRecordError #:nodoc:
    attr_reader :record
    def initialize(record)
      @record = record
      super("Validation failed: #{@record.errors.full_messages.join(", ")}")
    end
  end

  # Active Record validation is reported to and from this object, which is used by Base#save to
  # determine whether the object in a valid state to be saved. See usage example in Validations.
  class Errors
    include Enumerable

    def initialize(base) # :nodoc:
      @base, @errors = base, {}
    end

    @@default_error_messages = {
      :inclusion => "is not included in the list",
      :exclusion => "is reserved",
      :invalid => "is invalid",
      :confirmation => "doesn't match confirmation",
      :accepted  => "must be accepted",
      :empty => "can't be empty",
      :blank => "can't be blank",
      :too_long => "is too long (maximum is %d characters)",
      :too_short => "is too short (minimum is %d characters)",
      :wrong_length => "is the wrong length (should be %d characters)",
      :taken => "has already been taken",
      :not_a_number => "is not a number"
    }

    # Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
    cattr_accessor :default_error_messages


    # Adds an error to the base object instead of any particular attribute. This is used
    # to report errors that don't tie to any specific attribute, but rather to the object
    # as a whole. These error messages don't get prepended with any field name when iterating
    # with each_full, so they should be complete sentences.
    def add_to_base(msg)
      add(:base, msg)
    end

    # Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
    # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
    # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
    # If no +msg+ is supplied, "invalid" is assumed.
    def add(attribute, msg = @@default_error_messages[:invalid])
      @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
      @errors[attribute.to_s] << msg
    end

    # Will add an error message to each of the attributes in +attributes+ that is empty.
    def add_on_empty(attributes, msg = @@default_error_messages[:empty])
      for attr in [attributes].flatten
        value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
        is_empty = value.respond_to?("empty?") ? value.empty? : false
        add(attr, msg) unless !value.nil? && !is_empty
      end
    end

    # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
    def add_on_blank(attributes, msg = @@default_error_messages[:blank])
      for attr in [attributes].flatten
        value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
        add(attr, msg) if value.blank?
      end
    end

    # Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
    # If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
    def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
      for attr in [attributes].flatten
        value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
        add(attr, too_short_msg % range.begin) if value && value.length < range.begin
        add(attr, too_long_msg % range.end) if value && value.length > range.end
      end
    end

    alias :add_on_boundry_breaking :add_on_boundary_breaking

    # Returns true if the specified +attribute+ has errors associated with it.
    def invalid?(attribute)
      !@errors[attribute.to_s].nil?
    end

    # * Returns nil, if no errors are associated with the specified +attribute+.
    # * Returns the error message, if one error is associated with the specified +attribute+.
    # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
    def on(attribute)
      if @errors[attribute.to_s].nil?
        nil
      elsif @errors[attribute.to_s].length == 1
        @errors[attribute.to_s].first
      else
        @errors[attribute.to_s]
      end
    end

    alias :[] :on

    # Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
    def on_base
      on(:base)
    end

    # Yields each attribute and associated message per error added.
    def each
      @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
    end

    # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
    # through iteration as "First name can't be empty".
    def each_full
      full_messages.each { |msg| yield msg }
    end

    # Returns all the full error messages in an array.
    def full_messages
      full_messages = []

      @errors.each_key do |attr|
        @errors[attr].each do |msg|
          next if msg.nil?

          if attr == "base"
            full_messages << msg
          else
            full_messages << @base.class.human_attribute_name(attr) + " " + msg
          end
        end
      end

      return full_messages
    end

    # Returns true if no errors have been added.
    def empty?
      return @errors.empty?
    end
    
    # Removes all the errors that have been added.
    def clear
      @errors = {}
    end

    # Returns the total number of errors added. Two errors added to the same attribute will be counted as such
    # with this as well.
    def size
      error_count = 0
      @errors.each_value { |attribute| error_count += attribute.length }
      error_count
    end
    
    alias_method :count, :size
    alias_method :length, :size
  end


  # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
  # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
  # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
  #
  # Example:
  #
  #   class Person < ActiveRecord::Base
  #     protected
  #       def validate
  #         errors.add_on_empty %w( first_name last_name )
  #         errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
  #       end
  #
  #       def validate_on_create # is only run the first time a new object is saved
  #         unless valid_discount?(membership_discount)
  #           errors.add("membership_discount", "has expired")
  #         end
  #       end
  #
  #       def validate_on_update
  #         errors.add_to_base("No changes have occurred") if unchanged_attributes?
  #       end
  #   end
  #
  #   person = Person.new("first_name" => "David", "phone_number" => "what?")
  #   person.save                         # => false (and doesn't do the save)
  #   person.errors.empty?                # => false
  #   person.errors.count                 # => 2
  #   person.errors.on "last_name"        # => "can't be empty"
  #   person.errors.on "phone_number"     # => "has invalid format"
  #   person.errors.each_full { |msg| puts msg }
  #                                       # => "Last name can't be empty\n" +
  #                                            "Phone number has invalid format"
  #
  #   person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
  #   person.save # => true (and person is now saved in the database)
  #
  # An +Errors+ object is automatically created for every Active Record.
  #
  # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
  module Validations
    VALIDATIONS = %w( validate validate_on_create validate_on_update )

    def self.append_features(base) # :nodoc:
      super
      base.extend ClassMethods
      base.class_eval do
        alias_method :save_without_validation, :save
        alias_method :save, :save_with_validation

        alias_method :save_without_validation!, :save!
        alias_method :save!, :save_with_validation!

        alias_method :update_attribute_without_validation_skipping, :update_attribute
        alias_method :update_attribute, :update_attribute_with_validation_skipping
      end
    end

    # All of the following validations are defined in the class scope of the model that you're interested in validating.
    # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
    # these over the low-level calls to validate and validate_on_create when possible.
    module ClassMethods
      DEFAULT_VALIDATION_OPTIONS = {
        :on => :save,
        :allow_nil => false,
        :message => nil
      }.freeze

      ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze

      def validate(*methods, &block)
        methods << block if block_given?
        write_inheritable_set(:validate, methods)
      end

      def validate_on_create(*methods, &block)
        methods << block if block_given?
        write_inheritable_set(:validate_on_create, methods)
      end

      def validate_on_update(*methods, &block)
        methods << block if block_given?
        write_inheritable_set(:validate_on_update, methods)
      end

      def condition_block?(condition)
        condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
      end

      # Determine from the given condition (whether a block, procedure, method or string)
      # whether or not to validate the record.  See #validates_each.
      def evaluate_condition(condition, record)
        case condition
          when Symbol: record.send(condition)
          when String: eval(condition, binding)
          else
            if condition_block?(condition)
              condition.call(record)
            else
              raise(
                ActiveRecordError,
                "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
                "class implementing a static validation method"
              )
            end
          end
      end

      # Validates each attribute against a block.
      #
      #   class Person < ActiveRecord::Base
      #     validates_each :first_name, :last_name do |record, attr, value|
      #       record.errors.add attr, 'starts with z.' if value[0] == ?z
      #     end
      #   end
      #
      # Options:
      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>allow_nil</tt> - Skip validation if attribute is nil.
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_each(*attrs)
        options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
        attrs = attrs.flatten

        # Declare the validation.
        send(validation_method(options[:on] || :save)) do |record|
          # Don't validate when there is an :if condition and that condition is false
          unless options[:if] && !evaluate_condition(options[:if], record)
            attrs.each do |attr|
              value = record.send(attr)
              next if value.nil? && options[:allow_nil]
              yield record, attr, value
            end
          end
        end
      end

      # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
      #
      #   Model:
      #     class Person < ActiveRecord::Base
      #       validates_confirmation_of :user_name, :password
      #       validates_confirmation_of :email_address, :message => "should match confirmation"
      #     end
      #
      #   View:
      #     <%= password_field "person", "password" %>
      #     <%= password_field "person", "password_confirmation" %>
      #
      # The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
      # It exists only as an in-memory variable for validating the password. This check is performed only if password_confirmation
      # is not nil and by default on save.
      #
      # Configuration options:
      # * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation")
      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_confirmation_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        attr_accessor *(attr_names.map { |n| "#{n}_confirmation" })

        validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
        end
      end

      # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
      #
      #   class Person < ActiveRecord::Base
      #     validates_acceptance_of :terms_of_service
      #     validates_acceptance_of :eula, :message => "must be abided"
      #   end
      #
      # The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed only if
      # terms_of_service is not nil and by default on save.
      #
      # Configuration options:
      # * <tt>message</tt> - A custom error message (default is: "must be accepted")
      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>accept</tt> - Specifies value that is considered accepted.  The default value is a string "1", which
      # makes it easy to relate to an HTML checkbox.
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_acceptance_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        attr_accessor *attr_names

        validates_each(attr_names,configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
        end
      end

      # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
      #
      #   class Person < ActiveRecord::Base
      #     validates_presence_of :first_name
      #   end
      #
      # The first_name attribute must be in the object and it cannot be blank.
      #      
      # Configuration options:
      # * <tt>message</tt> - A custom error message (default is: "can't be blank")
      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      #
      # === Warning
      # Validate the presence of the foreign key, not the instance variable itself.
      # Do this:
      #  validate_presence_of :invoice_id
      #
      # Not this:
      #  validate_presence_of :invoice
      #
      # If you validate the presence of the associated object, you will get
      # failures on saves when both the parent object and the child object are
      # new.
      def validates_presence_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        # can't use validates_each here, because it cannot cope with nonexistent attributes,
        # while errors.add_on_empty can
        attr_names.each do |attr_name|
          send(validation_method(configuration[:on])) do |record|
            unless configuration[:if] and not evaluate_condition(configuration[:if], record)
              record.errors.add_on_blank(attr_name,configuration[:message])
            end
          end
        end
      end

      # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
      #
      #   class Person < ActiveRecord::Base
      #     validates_length_of :first_name, :maximum=>30
      #     validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
      #     validates_length_of :fax, :in => 7..32, :allow_nil => true
      #     validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
      #     validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
      #     validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
      #   end
      #
      # Configuration options:
      # * <tt>minimum</tt> - The minimum size of the attribute
      # * <tt>maximum</tt> - The maximum size of the attribute
      # * <tt>is</tt> - The exact size of the attribute
      # * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
      # * <tt>in</tt> - A synonym(or alias) for :within
      # * <tt>allow_nil</tt> - Attribute may be nil; skip validation.
      #
      # * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
      # * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
      # * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
      # * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation.  An alias of the appropriate too_long/too_short/wrong_length message
      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_length_of(*attrs)
        # Merge given options with defaults.
        options = {
          :too_long     => ActiveRecord::Errors.default_error_messages[:too_long],
          :too_short    => ActiveRecord::Errors.default_error_messages[:too_short],
          :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
        }.merge(DEFAULT_VALIDATION_OPTIONS)
        options.update(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)

        # Ensure that one and only one range option is specified.
        range_options = ALL_RANGE_OPTIONS & options.keys
        case range_options.size
          when 0
            raise ArgumentError, 'Range unspecified.  Specify the :within, :maximum, :minimum, or :is option.'
          when 1
            # Valid number of options; do nothing.
          else
            raise ArgumentError, 'Too many range options specified.  Choose only one.'
        end

        # Get range option and value.
        option = range_options.first
        option_value = options[range_options.first]

        case option
          when :within, :in
            raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)

            too_short = options[:too_short] % option_value.begin
            too_long  = options[:too_long]  % option_value.end

            validates_each(attrs, options) do |record, attr, value|
              if value.nil? or value.split(//).size < option_value.begin
                record.errors.add(attr, too_short)
              elsif value.split(//).size > option_value.end
                record.errors.add(attr, too_long)
              end
            end
          when :is, :minimum, :maximum
            raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0

            # Declare different validations per option.
            validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
            message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }

            message = (options[:message] || options[message_options[option]]) % option_value

            validates_each(attrs, options) do |record, attr, value|
              if value.kind_of?(String)
                record.errors.add(attr, message) unless !value.nil? and value.split(//).size.method(validity_checks[option])[option_value]
              else
                record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
              end
            end
        end
      end

      alias_method :validates_size_of, :validates_length_of


      # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
      # can be named "davidhh".
      #
      #   class Person < ActiveRecord::Base
      #     validates_uniqueness_of :user_name, :scope => :account_id
      #   end
      #
      # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters.  For example,
      # making sure that a teacher can only be on the schedule once per semester for a particular class. 
      #
      #   class TeacherSchedule < ActiveRecord::Base
      #     validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id] 
      #   end
      #
      # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
      # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
      #
      # Configuration options:
      # * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
      # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniquness constraint.
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
       
      def validates_uniqueness_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        validates_each(attr_names,configuration) do |record, attr_name, value|
          condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}"
          condition_params = [value]
          if scope = configuration[:scope]
            Array(scope).map do |scope_item|
              scope_value = record.send(scope_item)
              condition_sql << " AND #{record.class.table_name}.#{scope_item} #{attribute_condition(scope_value)}"
              condition_params << scope_value
            end
          end
          unless record.new_record?
            condition_sql << " AND #{record.class.table_name}.#{record.class.primary_key} <> ?"
            condition_params << record.send(:id)
          end
          if record.class.find(:first, :conditions => [condition_sql, *condition_params])
            record.errors.add(attr_name, configuration[:message])
          end
        end
      end

      # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
      # provided.
      #
      #   class Person < ActiveRecord::Base
      #     validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :on => :create
      #   end
      #
      # A regular expression must be provided or else an exception will be raised.
      #
      # Configuration options:
      # * <tt>message</tt> - A custom error message (default is: "is invalid")
      # * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!)
      # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_format_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)

        validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
        end
      end

      # Validates whether the value of the specified attribute is available in a particular enumerable object.
      #
      #   class Person < ActiveRecord::Base
      #     validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
      #     validates_inclusion_of :age, :in=>0..99
      #   end
      #
      # Configuration options:
      # * <tt>in</tt> - An enumerable object of available items
      # * <tt>message</tt> - Specifies a customer error message (default is: "is not included in the list")
      # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_inclusion_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        enum = configuration[:in] || configuration[:within]

        raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")

        validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) unless enum.include?(value)
        end
      end

      # Validates that the value of the specified attribute is not in a particular enumerable object.
      #
      #   class Person < ActiveRecord::Base
      #     validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
      #     validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
      #   end
      #
      # Configuration options:
      # * <tt>in</tt> - An enumerable object of items that the value shouldn't be part of
      # * <tt>message</tt> - Specifies a customer error message (default is: "is reserved")
      # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_exclusion_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        enum = configuration[:in] || configuration[:within]

        raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")

        validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) if enum.include?(value)
        end
      end

      # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
      #
      #   class Book < ActiveRecord::Base
      #     has_many :pages
      #     belongs_to :library
      #
      #     validates_associated :pages, :library
      #   end
      #
      # Warning: If, after the above definition, you then wrote:
      #
      #   class Page < ActiveRecord::Base
      #     belongs_to :book
      #
      #     validates_associated :book
      #   end
      #
      # ...this would specify a circular dependency and cause infinite recursion.
      #
      # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
      # is both present and guaranteed to be valid, you also need to use validates_presence_of.
      #
      # Configuration options:
      # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_associated(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) unless
            (value.is_a?(Array) ? value : [value]).all? { |r| r.nil? or r.valid? }
        end
      end

      # Validates whether the value of the specified attribute is numeric by trying to convert it to
      # a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
      # <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
      #
      #   class Person < ActiveRecord::Base
      #     validates_numericality_of :value, :on => :create
      #   end
      #
      # Configuration options:
      # * <tt>message</tt> - A custom error message (default is: "is not a number")
      # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
      # * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
      # * <tt>allow_nil</tt> Skip validation if attribute is nil (default is false). Notice that for fixnum and float columns empty strings are converted to nil
      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
      # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The
      # method, proc or string should return or evaluate to a true or false value.
      def validates_numericality_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:not_a_number], :on => :save,
                           :only_integer => false, :allow_nil => false }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

        if configuration[:only_integer]
          validates_each(attr_names,configuration) do |record, attr_name,value|
            record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_before_type_cast").to_s =~ /^[+-]?\d+$/
          end
        else
          validates_each(attr_names,configuration) do |record, attr_name,value|
           next if configuration[:allow_nil] and record.send("#{attr_name}_before_type_cast").nil?
            begin
              Kernel.Float(record.send("#{attr_name}_before_type_cast").to_s)
            rescue ArgumentError, TypeError
              record.errors.add(attr_name, configuration[:message])
            end
          end
        end
      end


      # Creates an object just like Base.create but calls save! instead of save
      # so an exception is raised if the record is invalid.
      def create!(attributes = nil)
        if attributes.is_a?(Array)
          attributes.collect { |attr| create!(attr) }
        else
          attributes.reverse_merge!(scope(:create)) if scoped?(:create)

          object = new(attributes)
          object.save!
          object
        end
      end


      private
        def write_inheritable_set(key, methods)
          existing_methods = read_inheritable_attribute(key) || []
          write_inheritable_attribute(key, methods | existing_methods)
        end

        def validation_method(on)
          case on
            when :save   then :validate
            when :create then :validate_on_create
            when :update then :validate_on_update
          end
        end
    end

    # The validation process on save can be skipped by passing false. The regular Base#save method is
    # replaced with this when the validations module is mixed in, which it is by default.
    def save_with_validation(perform_validation = true)
      if perform_validation && valid? || !perform_validation
        save_without_validation
      else
        false
      end
    end

    # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
    # if the record is not valid.
    def save_with_validation!
      if valid?
        save_without_validation!
      else
        raise RecordInvalid.new(self)
      end
    end

    # Updates a single attribute and saves the record without going through the normal validation procedure.
    # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
    # in Base is replaced with this when the validations module is mixed in, which it is by default.
    def update_attribute_with_validation_skipping(name, value)
      send(name.to_s + '=', value)
      save(false)
    end

    # Runs validate and validate_on_create or validate_on_update and returns true if no errors were added otherwise false.
    def valid?
      errors.clear

      run_validations(:validate)
      validate

      if new_record?
        run_validations(:validate_on_create)
        validate_on_create
      else
        run_validations(:validate_on_update)
        validate_on_update
      end

      errors.empty?
    end

    # Returns the Errors object that holds all information about attribute error messages.
    def errors
      @errors ||= Errors.new(self)
    end

    protected
      # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
      def validate #:doc:
      end

      # Overwrite this method for validation checks used only on creation.
      def validate_on_create #:doc:
      end

      # Overwrite this method for validation checks used only on updates.
      def validate_on_update # :doc:
      end

    private
      def run_validations(validation_method)
        validations = self.class.read_inheritable_attribute(validation_method.to_sym)
        if validations.nil? then return end
        validations.each do |validation|
          if validation.is_a?(Symbol)
            self.send(validation)
          elsif validation.is_a?(String)
            eval(validation, binding)
          elsif validation_block?(validation)
            validation.call(self)
          elsif validation_class?(validation, validation_method)
            validation.send(validation_method, self)
          else
            raise(
              ActiveRecordError,
              "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
              "class implementing a static validation method"
            )
          end
        end
      end

      def validation_block?(validation)
        validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1)
      end

      def validation_class?(validation, validation_method)
        validation.respond_to?(validation_method)
      end
  end
end
require 'db2/db2cli.rb'

module DB2
  module DB2Util
    include DB2CLI

    def free() SQLFreeHandle(@handle_type, @handle); end
    def handle() @handle; end

    def check_rc(rc)
      if ![SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA_FOUND].include?(rc)
        rec = 1
        msg = ''
        loop do
          a = SQLGetDiagRec(@handle_type, @handle, rec, 500)
          break if a[0] != SQL_SUCCESS
          msg << a[3] if !a[3].nil? and a[3] != '' # Create message.
          rec += 1
        end
        raise "DB2 error: #{msg}"
      end
    end
  end

  class Environment
    include DB2Util

    def initialize
      @handle_type = SQL_HANDLE_ENV
      rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE)
      check_rc(rc)
    end

    def data_sources(buffer_length = 1024)
      retval = []
      max_buffer_length = buffer_length

      a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length)
      retval << [a[1], a[3]]
      max_buffer_length = [max_buffer_length, a[4]].max

      loop do
        a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length)
        break if a[0] == SQL_NO_DATA_FOUND

        retval << [a[1], a[3]]
        max_buffer_length = [max_buffer_length, a[4]].max
      end

      if max_buffer_length > buffer_length
        get_data_sources(max_buffer_length)
      else
        retval
      end
    end
  end

  class Connection
    include DB2Util

    def initialize(environment)
      @env = environment
      @handle_type = SQL_HANDLE_DBC
      rc, @handle = SQLAllocHandle(@handle_type, @env.handle)
      check_rc(rc)
    end

    def connect(server_name, user_name = '', auth = '')
      check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s))
    end

    def set_connect_attr(attr, value)
      value += "\0" if value.class == String
      check_rc(SQLSetConnectAttr(@handle, attr, value))
    end

    def set_auto_commit_on
      set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON)
    end

    def set_auto_commit_off
      set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
    end

    def disconnect
      check_rc(SQLDisconnect(@handle))
    end

    def rollback
      check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK))
    end

    def commit
      check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT))
    end
  end

  class Statement
    include DB2Util

    def initialize(connection)
      @conn = connection
      @handle_type = SQL_HANDLE_STMT
      @parms = []                           #yun
      @sql = ''                             #yun
      @numParms = 0                         #yun
      @prepared = false                     #yun
      @parmArray = []                       #yun. attributes of the parameter markers
      rc, @handle = SQLAllocHandle(@handle_type, @conn.handle)
      check_rc(rc)
    end

    def columns(table_name, schema_name = '%')
      check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%'))
      fetch_all
    end

    def tables(schema_name = '%')
      check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE'))
      fetch_all
    end

    def indexes(table_name, schema_name = '')
      check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE))
      fetch_all
    end

    def prepare(sql)
      @sql = sql
      check_rc(SQLPrepare(@handle, sql))
      rc, @numParms = SQLNumParams(@handle) #number of question marks
      check_rc(rc)
      #--------------------------------------------------------------------------
      # parameter attributes are stored in instance variable @parmArray so that
      # they are available when execute method is called.
      #--------------------------------------------------------------------------
      if @numParms > 0           # get parameter marker attributes
        1.upto(@numParms) do |i| # parameter number starts from 1
          rc, type, size, decimalDigits = SQLDescribeParam(@handle, i)
          check_rc(rc)
          @parmArray << Parameter.new(type, size, decimalDigits)
        end
      end
      @prepared = true
      self
    end

    def execute(*parms)
      raise "The statement was not prepared" if @prepared == false

      if parms.size == 1 and parms[0].class == Array
        parms = parms[0]
      end

      if @numParms != parms.size
        raise "Number of parameters supplied does not match with the SQL statement"
      end

      if @numParms > 0            #need to bind parameters
        #--------------------------------------------------------------------
        #calling bindParms may not be safe. Look comment below.
        #--------------------------------------------------------------------
        #bindParms(parms)

        valueArray = []
        1.upto(@numParms) do |i|  # parameter number starts from 1
          type = @parmArray[i - 1].class
          size = @parmArray[i - 1].size
          decimalDigits = @parmArray[i - 1].decimalDigits

          if parms[i - 1].class == String
            valueArray << parms[i - 1]
          else
            valueArray << parms[i - 1].to_s
          end

          rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1])
          check_rc(rc)
        end
      end

      check_rc(SQLExecute(@handle))

      if @numParms != 0
        check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters
      end

      self
    end

    #-------------------------------------------------------------------------------
    # The last argument(value) to SQLBindParameter is a deferred argument, that is,
    # it should be available when SQLExecute is called. Even though "value" is
    # local to bindParms method, it seems that it is available when SQLExecute
    # is called. I am not sure whether it would still work if garbage collection
    # is done between bindParms call and SQLExecute call inside the execute method
    # above.
    #-------------------------------------------------------------------------------
    def bindParms(parms)        # This is the real thing. It uses SQLBindParms
      1.upto(@numParms) do |i|  # parameter number starts from 1
        rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i)
        check_rc(rc)
        if parms[i - 1].class == String
          value = parms[i - 1]
        else
          value = parms[i - 1].to_s
        end
        rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value)
        check_rc(rc)
      end
    end
 
    #------------------------------------------------------------------------------
    # bind method does not use DB2's SQLBindParams, but replaces "?" in the
    # SQL statement with the value before passing the SQL statement to DB2.
    # It is not efficient and can handle only strings since it puts everything in
    # quotes.
    #------------------------------------------------------------------------------
    def bind(sql, args)                #does not use SQLBindParams
      arg_index = 0
      result = ""
      tokens(sql).each do |part|
        case part
        when '?'
          result << "'" + (args[arg_index]) + "'"  #put it into quotes
          arg_index += 1
        when '??'
          result << "?"
        else
          result << part
        end
      end
      if arg_index < args.size
        raise "Too many SQL parameters"
      elsif arg_index > args.size
        raise "Not enough SQL parameters"
      end
      result
    end

    ## Break the sql string into parts.
    #
    # This is NOT a full lexer for SQL.  It just breaks up the SQL
    # string enough so that question marks, double question marks and
    # quoted strings are separated.  This is used when binding
    # arguments to "?" in the SQL string.  Note: comments are not
    # handled.
    #
    def tokens(sql)
      toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/)
      toks.collect { |t| t[0] }
    end

    def exec_direct(sql)
      check_rc(SQLExecDirect(@handle, sql))
      self
    end

    def set_cursor_name(name)
      check_rc(SQLSetCursorName(@handle, name))
      self
    end

    def get_cursor_name
      rc, name = SQLGetCursorName(@handle)
      check_rc(rc)
      name
    end

    def row_count
      rc, rowcount = SQLRowCount(@handle)
      check_rc(rc)
      rowcount
    end

    def num_result_cols
      rc, cols = SQLNumResultCols(@handle)
      check_rc(rc)
      cols
    end

    def fetch_all
      if block_given?
        while row = fetch do
          yield row
        end
      else
        res = []
        while row = fetch do
          res << row
        end
        res
      end
    end

    def fetch
      cols = get_col_desc
      rc = SQLFetch(@handle)
      if rc == SQL_NO_DATA_FOUND
        SQLFreeStmt(@handle, SQL_CLOSE)        # Close cursor
        SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
        return nil
      end
      raise "ERROR" unless rc == SQL_SUCCESS

      retval = []
      cols.each_with_index do |c, i|
        rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
        retval << adjust_content(content)
      end
      retval
    end

    def fetch_as_hash
      cols = get_col_desc
      rc = SQLFetch(@handle)
      if rc == SQL_NO_DATA_FOUND
        SQLFreeStmt(@handle, SQL_CLOSE)        # Close cursor
        SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
        return nil
      end
      raise "ERROR" unless rc == SQL_SUCCESS

      retval = {}
      cols.each_with_index do |c, i|
        rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1)   #yun added 1 to c[2]
        retval[c[0]] = adjust_content(content)
      end
      retval
    end

    def get_col_desc
      rc, nr_cols = SQLNumResultCols(@handle)
      cols = (1..nr_cols).collect do |c|
        rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
        [name.downcase, type, col_sz]
      end
    end

    def adjust_content(c)
      case c.class.to_s
      when 'DB2CLI::NullClass'
        return nil
      when 'DB2CLI::Time'
        "%02d:%02d:%02d" % [c.hour, c.minute, c.second]
      when 'DB2CLI::Date'
        "%04d-%02d-%02d" % [c.year, c.month, c.day]
      when 'DB2CLI::Timestamp'
        "%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second]
      else
        return c
      end
    end
  end

  class Parameter
    attr_reader :type, :size, :decimalDigits
    def initialize(type, size, decimalDigits)
      @type, @size, @decimalDigits = type, size, decimalDigits
    end
  end
end
# $Id: mysql.rb,v 1.24 2005/02/12 11:37:15 tommy Exp $
#
# Copyright (C) 2003-2005 TOMITA Masahiro
# tommy@tmtm.org
#

class Mysql

  VERSION = "4.0-ruby-0.2.5"

  require "socket"
  require "digest/sha1"

  MAX_PACKET_LENGTH = 256*256*256-1
  MAX_ALLOWED_PACKET = 1024*1024*1024

  MYSQL_UNIX_ADDR = "/tmp/mysql.sock"
  MYSQL_PORT = 3306
  PROTOCOL_VERSION = 10

  # Command
  COM_SLEEP		= 0
  COM_QUIT		= 1
  COM_INIT_DB		= 2
  COM_QUERY		= 3
  COM_FIELD_LIST	= 4
  COM_CREATE_DB		= 5
  COM_DROP_DB		= 6
  COM_REFRESH		= 7
  COM_SHUTDOWN		= 8
  COM_STATISTICS	= 9
  COM_PROCESS_INFO	= 10
  COM_CONNECT		= 11
  COM_PROCESS_KILL	= 12
  COM_DEBUG		= 13
  COM_PING		= 14
  COM_TIME		= 15
  COM_DELAYED_INSERT	= 16
  COM_CHANGE_USER	= 17
  COM_BINLOG_DUMP	= 18
  COM_TABLE_DUMP	= 19
  COM_CONNECT_OUT	= 20
  COM_REGISTER_SLAVE	= 21

  # Client flag
  CLIENT_LONG_PASSWORD	= 1
  CLIENT_FOUND_ROWS	= 1 << 1
  CLIENT_LONG_FLAG	= 1 << 2
  CLIENT_CONNECT_WITH_DB= 1 << 3
  CLIENT_NO_SCHEMA	= 1 << 4
  CLIENT_COMPRESS	= 1 << 5
  CLIENT_ODBC		= 1 << 6
  CLIENT_LOCAL_FILES	= 1 << 7
  CLIENT_IGNORE_SPACE	= 1 << 8
  CLIENT_PROTOCOL_41	= 1 << 9
  CLIENT_INTERACTIVE	= 1 << 10
  CLIENT_SSL		= 1 << 11
  CLIENT_IGNORE_SIGPIPE	= 1 << 12
  CLIENT_TRANSACTIONS	= 1 << 13
  CLIENT_RESERVED	= 1 << 14
  CLIENT_SECURE_CONNECTION	= 1 << 15
  CLIENT_CAPABILITIES = CLIENT_LONG_PASSWORD|CLIENT_LONG_FLAG|CLIENT_TRANSACTIONS
  PROTO_AUTH41 = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION

  # Connection Option
  OPT_CONNECT_TIMEOUT	= 0
  OPT_COMPRESS		= 1
  OPT_NAMED_PIPE	= 2
  INIT_COMMAND		= 3
  READ_DEFAULT_FILE	= 4
  READ_DEFAULT_GROUP	= 5
  SET_CHARSET_DIR	= 6
  SET_CHARSET_NAME	= 7
  OPT_LOCAL_INFILE	= 8

  # Server Status
  SERVER_STATUS_IN_TRANS	= 1
  SERVER_STATUS_AUTOCOMMIT	= 2

  # Refresh parameter
  REFRESH_GRANT		= 1
  REFRESH_LOG		= 2
  REFRESH_TABLES	= 4
  REFRESH_HOSTS		= 8
  REFRESH_STATUS	= 16
  REFRESH_THREADS	= 32
  REFRESH_SLAVE		= 64
  REFRESH_MASTER	= 128

  def initialize(*args)
    @client_flag = 0
    @max_allowed_packet = MAX_ALLOWED_PACKET
    @query_with_result = true
    @status = :STATUS_READY
    if args[0] != :INIT then
      real_connect(*args)
    end
  end

  def real_connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
    @server_status = SERVER_STATUS_AUTOCOMMIT
    if (host == nil or host == "localhost") and defined? UNIXSocket then
      unix_socket = socket || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_ADDR
      sock = UNIXSocket::new(unix_socket)
      @host_info = Error::err(Error::CR_LOCALHOST_CONNECTION)
      @unix_socket = unix_socket
    else      
      sock = TCPSocket::new(host, port||ENV["MYSQL_TCP_PORT"]||(Socket::getservbyname("mysql","tcp") rescue MYSQL_PORT))
      @host_info = sprintf Error::err(Error::CR_TCP_CONNECTION), host
    end
    @host = host ? host.dup : nil
    sock.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true
    @net = Net::new sock

    a = read
    @protocol_version = a.slice!(0)
    @server_version, a = a.split(/\0/,2)
    @thread_id, @scramble_buff = a.slice!(0,13).unpack("La8")
    if a.size >= 2 then
      @server_capabilities, = a.slice!(0,2).unpack("v")
    end
    if a.size >= 16 then
      @server_language, @server_status = a.slice!(0,3).unpack("cv")
    end

    flag = 0 if flag == nil
    flag |= @client_flag | CLIENT_CAPABILITIES
    flag |= CLIENT_CONNECT_WITH_DB if db

    @pre_411 = (0 == @server_capabilities & PROTO_AUTH41)
    if @pre_411
      data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+
             (user||"")+"\0"+
                   scramble(passwd, @scramble_buff, @protocol_version==9)
    else
      dummy, @salt2 = a.unpack("a13a12")
      @scramble_buff += @salt2
      flag |= PROTO_AUTH41
      data = Net::int4str(flag) + Net::int4str(@max_allowed_packet) +
             ([8] + Array.new(23, 0)).pack("c24") + (user||"")+"\0"+
             scramble41(passwd, @scramble_buff)
    end

    if db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0
      data << "\0" if @pre_411
      data << db
      @db = db.dup
    end
    write data
    read
    ObjectSpace.define_finalizer(self, Mysql.finalizer(@net))
    self
  end
  alias :connect :real_connect

  def escape_string(str)
    Mysql::escape_string str
  end
  alias :quote :escape_string

  def get_client_info()
    VERSION
  end
  alias :client_info :get_client_info

  def options(option, arg=nil)
    if option == OPT_LOCAL_INFILE then
      if arg == false or arg == 0 then
	@client_flag &= ~CLIENT_LOCAL_FILES
      else
	@client_flag |= CLIENT_LOCAL_FILES
      end
    else
      raise "not implemented"
    end
  end

  def real_query(query)
    command COM_QUERY, query, true
    read_query_result
    self
  end

  def use_result()
    if @status != :STATUS_GET_RESULT then
      error Error::CR_COMMANDS_OUT_OF_SYNC
    end
    res = Result::new self, @fields, @field_count
    @status = :STATUS_USE_RESULT
    res
  end

  def store_result()
    if @status != :STATUS_GET_RESULT then
      error Error::CR_COMMANDS_OUT_OF_SYNC
    end
    @status = :STATUS_READY
    data = read_rows @field_count
    res = Result::new self, @fields, @field_count, data
    @fields = nil
    @affected_rows = data.length
    res
  end

  def change_user(user="", passwd="", db="")
    if @pre_411
      data = user+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)+"\0"+db
    else
      data = user+"\0"+scramble41(passwd, @scramble_buff)+db
    end
    command COM_CHANGE_USER, data
    @user = user
    @passwd = passwd
    @db = db
  end

  def character_set_name()
    raise "not implemented"
  end

  def close()
    @status = :STATUS_READY
    command COM_QUIT, nil, true
    @net.close
    self
  end

  def create_db(db)
    command COM_CREATE_DB, db
    self
  end

  def drop_db(db)
    command COM_DROP_DB, db
    self
  end

  def dump_debug_info()
    command COM_DEBUG
    self
  end

  def get_host_info()
    @host_info
  end
  alias :host_info :get_host_info

  def get_proto_info()
    @protocol_version
  end
  alias :proto_info :get_proto_info

  def get_server_info()
    @server_version
  end
  alias :server_info :get_server_info

  def kill(id)
    command COM_PROCESS_KILL, Net::int4str(id)
    self
  end

  def list_dbs(db=nil)
    real_query "show databases #{db}"
    @status = :STATUS_READY
    read_rows(1).flatten
  end

  def list_fields(table, field=nil)
    command COM_FIELD_LIST, "#{table}\0#{field}", true
    if @pre_411
      f = read_rows 6
    else
      f = read_rows 7
    end
    fields = unpack_fields(f, @server_capabilities & CLIENT_LONG_FLAG != 0)
    res = Result::new self, fields, f.length
    res.eof = true
    res
  end

  def list_processes()
    data = command COM_PROCESS_INFO
    @field_count = get_length data
    if @pre_411
      fields = read_rows 5
    else
      fields = read_rows 7
    end
    @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
    @status = :STATUS_GET_RESULT
    store_result
  end

  def list_tables(table=nil)
    real_query "show tables #{table}"
    @status = :STATUS_READY
    read_rows(1).flatten
  end

  def ping()
    command COM_PING
    self
  end

  def query(query)
    real_query query
    if not @query_with_result then
      return self
    end
    if @field_count == 0 then
      return nil
    end
    store_result
  end

  def refresh(r)
    command COM_REFRESH, r.chr
    self
  end

  def reload()
    refresh REFRESH_GRANT
    self
  end

  def select_db(db)
    command COM_INIT_DB, db
    @db = db
    self
  end

  def shutdown()
    command COM_SHUTDOWN
    self
  end

  def stat()
    command COM_STATISTICS
  end

  attr_reader :info, :insert_id, :affected_rows, :field_count, :thread_id
  attr_accessor :query_with_result, :status

  def read_one_row(field_count)
    data = read
    if data[0] == 254 and data.length == 1 ## EOF
      return
    elsif data[0] == 254 and data.length == 5
      return
    end
    rec = []
    field_count.times do
      len = get_length data
      if len == nil then
	rec << len
      else
	rec << data.slice!(0,len)
      end
    end
    rec
  end

  def skip_result()
    if @status == :STATUS_USE_RESULT then
      loop do
	data = read
	break if data[0] == 254 and data.length == 1
      end
      @status = :STATUS_READY
    end
  end

  def inspect()
    "#<#{self.class}>"
  end

  private

  def read_query_result()
    data = read
    @field_count = get_length(data)
    if @field_count == nil then		# LOAD DATA LOCAL INFILE
      File::open(data) do |f|
	write f.read
      end
      write ""		# mark EOF
      data = read
      @field_count = get_length(data)
    end
    if @field_count == 0 then
      @affected_rows = get_length(data, true)
      @insert_id = get_length(data, true)
      if @server_capabilities & CLIENT_TRANSACTIONS != 0 then
	a = data.slice!(0,2)
	@server_status = a[0]+a[1]*256
      end
      if data.size > 0 and get_length(data) then
	@info = data
      end
    else
      @extra_info = get_length(data, true)
      if @pre_411
        fields = read_rows(5)
      else
        fields = read_rows(7)
      end
      @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
      @status = :STATUS_GET_RESULT
    end
    self
  end

  def unpack_fields(data, long_flag_protocol)
    ret = []
    data.each do |f|
      if @pre_411
        table = org_table = f[0]
        name = f[1]
        length = f[2][0]+f[2][1]*256+f[2][2]*256*256
        type = f[3][0]
        if long_flag_protocol then
          flags = f[4][0]+f[4][1]*256
          decimals = f[4][2]
        else
          flags = f[4][0]
          decimals = f[4][1]
        end
        def_value = f[5]
        max_length = 0
      else
        catalog = f[0]
        db = f[1]
        table = f[2]
        org_table = f[3]
        name = f[4]
        org_name = f[5]
        length = f[6][2]+f[6][3]*256+f[6][4]*256*256
        type = f[6][6]
        flags = f[6][7]+f[6][8]*256
        decimals = f[6][9]
        def_value = ""
        max_length = 0
      end
      ret << Field::new(table, org_table, name, length, type, flags, decimals, def_value, max_length)
    end
    ret
  end

  def read_rows(field_count)
    ret = []
    while rec = read_one_row(field_count) do
      ret << rec
    end
    ret
  end

  def get_length(data, longlong=nil)
    return if data.length == 0
    c = data.slice!(0)
    case c
    when 251
      return nil
    when 252
      a = data.slice!(0,2)
      return a[0]+a[1]*256
    when 253
      a = data.slice!(0,3)
      return a[0]+a[1]*256+a[2]*256**2
    when 254
      a = data.slice!(0,8)
      if longlong then
	return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3+
	  a[4]*256**4+a[5]*256**5+a[6]*256**6+a[7]*256**7
      else
	return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3
      end
    else
      c
    end
  end

  def command(cmd, arg=nil, skip_check=nil)
    unless @net then
      error Error::CR_SERVER_GONE_ERROR
    end
    if @status != :STATUS_READY then
      error Error::CR_COMMANDS_OUT_OF_SYNC
    end
    @net.clear
    write cmd.chr+(arg||"")
    read unless skip_check
  end

  def read()
    unless @net then
      error Error::CR_SERVER_GONE_ERROR
    end
    a = @net.read
    if a[0] == 255 then
      if a.length > 3 then
	@errno = a[1]+a[2]*256
	@error = a[3 .. -1]
      else
	@errno = Error::CR_UNKNOWN_ERROR
	@error = Error::err @errno
      end
      raise Error::new(@errno, @error)
    end
    a
  end

  def write(arg)
    unless @net then
      error Error::CR_SERVER_GONE_ERROR
    end
    @net.write arg
  end

  def hash_password(password)
    nr = 1345345333
    add = 7
    nr2 = 0x12345671
    password.each_byte do |i|
      next if i == 0x20 or i == 9
      nr ^= (((nr & 63) + add) * i) + (nr << 8)
      nr2 += (nr2 << 8) ^ nr
      add += i
    end
    [nr & ((1 << 31) - 1), nr2 & ((1 << 31) - 1)]
  end

  def scramble(password, message, old_ver)
    return "" if password == nil or password == ""
    raise "old version password is not implemented" if old_ver
    hash_pass = hash_password password
    hash_message = hash_password message
    rnd = Random::new hash_pass[0] ^ hash_message[0], hash_pass[1] ^ hash_message[1]
    to = []
    1.upto(message.length) do
      to << ((rnd.rnd*31)+64).floor
    end
    extra = (rnd.rnd*31).floor
    to.map! do |t| (t ^ extra).chr end
    to.join
  end

  def scramble41(password, message)
    return 0x00.chr if password.nil? or password.empty?
    buf = [0x14]
    s1 = Digest::SHA1.new(password).digest
    s2 = Digest::SHA1.new(s1).digest
    x = Digest::SHA1.new(message + s2).digest
    (0..s1.length - 1).each {|i| buf.push(s1[i] ^ x[i])}
    buf.pack("C*")
  end

  def error(errno)
    @errno = errno
    @error = Error::err errno
    raise Error::new(@errno, @error)
  end

  class Result
    def initialize(mysql, fields, field_count, data=nil)
      @handle = mysql
      @fields = fields
      @field_count = field_count
      @data = data
      @current_field = 0
      @current_row = 0
      @eof = false
      @row_count = 0
    end
    attr_accessor :eof

    def data_seek(n)
      @current_row = n
    end

    def fetch_field()
      return if @current_field >= @field_count
      f = @fields[@current_field]
      @current_field += 1
      f
    end

    def fetch_fields()
      @fields
    end

    def fetch_field_direct(n)
      @fields[n]
    end

    def fetch_lengths()
      @data ? @data[@current_row].map{|i| i ? i.length : 0} : @lengths
    end

    def fetch_row()
      if @data then
	if @current_row >= @data.length then
	  @handle.status = :STATUS_READY
	  return
	end
	ret = @data[@current_row]
	@current_row += 1
      else
	return if @eof
	ret = @handle.read_one_row @field_count
	if ret == nil then
	  @eof = true
	  return
	end
	@lengths = ret.map{|i| i ? i.length : 0}
	@row_count += 1
      end
      ret
    end

    def fetch_hash(with_table=nil)
      row = fetch_row
      return if row == nil
      hash = {}
      @fields.each_index do |i|
	f = with_table ? @fields[i].table+"."+@fields[i].name : @fields[i].name
	hash[f] = row[i]
      end
      hash
    end

    def field_seek(n)
      @current_field = n
    end

    def field_tell()
      @current_field
    end

    def free()
      @handle.skip_result
      @handle = @fields = @data = nil
    end

    def num_fields()
      @field_count
    end

    def num_rows()
      @data ? @data.length : @row_count
    end

    def row_seek(n)
      @current_row = n
    end

    def row_tell()
      @current_row
    end

    def each()
      while row = fetch_row do
	yield row
      end
    end

    def each_hash(with_table=nil)
      while hash = fetch_hash(with_table) do
	yield hash
      end
    end

    def inspect()
      "#<#{self.class}>"
    end

  end

  class Field
    # Field type
    TYPE_DECIMAL = 0
    TYPE_TINY = 1
    TYPE_SHORT = 2
    TYPE_LONG = 3
    TYPE_FLOAT = 4
    TYPE_DOUBLE = 5
    TYPE_NULL = 6
    TYPE_TIMESTAMP = 7
    TYPE_LONGLONG = 8
    TYPE_INT24 = 9
    TYPE_DATE = 10
    TYPE_TIME = 11
    TYPE_DATETIME = 12
    TYPE_YEAR = 13
    TYPE_NEWDATE = 14
    TYPE_ENUM = 247
    TYPE_SET = 248
    TYPE_TINY_BLOB = 249
    TYPE_MEDIUM_BLOB = 250
    TYPE_LONG_BLOB = 251
    TYPE_BLOB = 252
    TYPE_VAR_STRING = 253
    TYPE_STRING = 254
    TYPE_GEOMETRY = 255
    TYPE_CHAR = TYPE_TINY
    TYPE_INTERVAL = TYPE_ENUM

    # Flag
    NOT_NULL_FLAG = 1
    PRI_KEY_FLAG = 2
    UNIQUE_KEY_FLAG  = 4
    MULTIPLE_KEY_FLAG  = 8
    BLOB_FLAG = 16
    UNSIGNED_FLAG = 32
    ZEROFILL_FLAG = 64
    BINARY_FLAG = 128
    ENUM_FLAG = 256
    AUTO_INCREMENT_FLAG = 512
    TIMESTAMP_FLAG  = 1024
    SET_FLAG = 2048
    NUM_FLAG = 32768
    PART_KEY_FLAG = 16384
    GROUP_FLAG = 32768
    UNIQUE_FLAG = 65536

    def initialize(table, org_table, name, length, type, flags, decimals, def_value, max_length)
      @table = table
      @org_table = org_table
      @name = name
      @length = length
      @type = type
      @flags = flags
      @decimals = decimals
      @def = def_value
      @max_length = max_length
      if (type <= TYPE_INT24 and (type != TYPE_TIMESTAMP or length == 14 or length == 8)) or type == TYPE_YEAR then
	@flags |= NUM_FLAG
      end
    end
    attr_reader :table, :org_table, :name, :length, :type, :flags, :decimals, :def, :max_length

    def inspect()
      "#<#{self.class}:#{@name}>"
    end
  end

  class Error < StandardError
    # Server Error
    ER_HASHCHK			= 1000
    ER_NISAMCHK			= 1001
    ER_NO			= 1002
    ER_YES			= 1003
    ER_CANT_CREATE_FILE		= 1004
    ER_CANT_CREATE_TABLE	= 1005
    ER_CANT_CREATE_DB		= 1006
    ER_DB_CREATE_EXISTS		= 1007
    ER_DB_DROP_EXISTS		= 1008
    ER_DB_DROP_DELETE		= 1009
    ER_DB_DROP_RMDIR		= 1010
    ER_CANT_DELETE_FILE		= 1011
    ER_CANT_FIND_SYSTEM_REC	= 1012
    ER_CANT_GET_STAT		= 1013
    ER_CANT_GET_WD		= 1014
    ER_CANT_LOCK		= 1015
    ER_CANT_OPEN_FILE		= 1016
    ER_FILE_NOT_FOUND		= 1017
    ER_CANT_READ_DIR		= 1018
    ER_CANT_SET_WD		= 1019
    ER_CHECKREAD		= 1020
    ER_DISK_FULL		= 1021
    ER_DUP_KEY			= 1022
    ER_ERROR_ON_CLOSE		= 1023
    ER_ERROR_ON_READ		= 1024
    ER_ERROR_ON_RENAME		= 1025
    ER_ERROR_ON_WRITE		= 1026
    ER_FILE_USED		= 1027
    ER_FILSORT_ABORT		= 1028
    ER_FORM_NOT_FOUND		= 1029
    ER_GET_ERRNO		= 1030
    ER_ILLEGAL_HA		= 1031
    ER_KEY_NOT_FOUND		= 1032
    ER_NOT_FORM_FILE		= 1033
    ER_NOT_KEYFILE		= 1034
    ER_OLD_KEYFILE		= 1035
    ER_OPEN_AS_READONLY		= 1036
    ER_OUTOFMEMORY		= 1037
    ER_OUT_OF_SORTMEMORY	= 1038
    ER_UNEXPECTED_EOF		= 1039
    ER_CON_COUNT_ERROR		= 1040
    ER_OUT_OF_RESOURCES		= 1041
    ER_BAD_HOST_ERROR		= 1042
    ER_HANDSHAKE_ERROR		= 1043
    ER_DBACCESS_DENIED_ERROR	= 1044
    ER_ACCESS_DENIED_ERROR	= 1045
    ER_NO_DB_ERROR		= 1046
    ER_UNKNOWN_COM_ERROR	= 1047
    ER_BAD_NULL_ERROR		= 1048
    ER_BAD_DB_ERROR		= 1049
    ER_TABLE_EXISTS_ERROR	= 1050
    ER_BAD_TABLE_ERROR		= 1051
    ER_NON_UNIQ_ERROR		= 1052
    ER_SERVER_SHUTDOWN		= 1053
    ER_BAD_FIELD_ERROR		= 1054
    ER_WRONG_FIELD_WITH_GROUP	= 1055
    ER_WRONG_GROUP_FIELD	= 1056
    ER_WRONG_SUM_SELECT		= 1057
    ER_WRONG_VALUE_COUNT	= 1058
    ER_TOO_LONG_IDENT		= 1059
    ER_DUP_FIELDNAME		= 1060
    ER_DUP_KEYNAME		= 1061
    ER_DUP_ENTRY		= 1062
    ER_WRONG_FIELD_SPEC		= 1063
    ER_PARSE_ERROR		= 1064
    ER_EMPTY_QUERY		= 1065
    ER_NONUNIQ_TABLE		= 1066
    ER_INVALID_DEFAULT		= 1067
    ER_MULTIPLE_PRI_KEY		= 1068
    ER_TOO_MANY_KEYS		= 1069
    ER_TOO_MANY_KEY_PARTS	= 1070
    ER_TOO_LONG_KEY		= 1071
    ER_KEY_COLUMN_DOES_NOT_EXITS	= 1072
    ER_BLOB_USED_AS_KEY		= 1073
    ER_TOO_BIG_FIELDLENGTH	= 1074
    ER_WRONG_AUTO_KEY		= 1075
    ER_READY			= 1076
    ER_NORMAL_SHUTDOWN		= 1077
    ER_GOT_SIGNAL		= 1078
    ER_SHUTDOWN_COMPLETE	= 1079
    ER_FORCING_CLOSE		= 1080
    ER_IPSOCK_ERROR		= 1081
    ER_NO_SUCH_INDEX		= 1082
    ER_WRONG_FIELD_TERMINATORS	= 1083
    ER_BLOBS_AND_NO_TERMINATED	= 1084
    ER_TEXTFILE_NOT_READABLE	= 1085
    ER_FILE_EXISTS_ERROR	= 1086
    ER_LOAD_INFO		= 1087
    ER_ALTER_INFO		= 1088
    ER_WRONG_SUB_KEY		= 1089
    ER_CANT_REMOVE_ALL_FIELDS	= 1090
    ER_CANT_DROP_FIELD_OR_KEY	= 1091
    ER_INSERT_INFO		= 1092
    ER_INSERT_TABLE_USED	= 1093
    ER_NO_SUCH_THREAD		= 1094
    ER_KILL_DENIED_ERROR	= 1095
    ER_NO_TABLES_USED		= 1096
    ER_TOO_BIG_SET		= 1097
    ER_NO_UNIQUE_LOGFILE	= 1098
    ER_TABLE_NOT_LOCKED_FOR_WRITE	= 1099
    ER_TABLE_NOT_LOCKED		= 1100
    ER_BLOB_CANT_HAVE_DEFAULT	= 1101
    ER_WRONG_DB_NAME		= 1102
    ER_WRONG_TABLE_NAME		= 1103
    ER_TOO_BIG_SELECT		= 1104
    ER_UNKNOWN_ERROR		= 1105
    ER_UNKNOWN_PROCEDURE	= 1106
    ER_WRONG_PARAMCOUNT_TO_PROCEDURE	= 1107
    ER_WRONG_PARAMETERS_TO_PROCEDURE	= 1108
    ER_UNKNOWN_TABLE		= 1109
    ER_FIELD_SPECIFIED_TWICE	= 1110
    ER_INVALID_GROUP_FUNC_USE	= 1111
    ER_UNSUPPORTED_EXTENSION	= 1112
    ER_TABLE_MUST_HAVE_COLUMNS	= 1113
    ER_RECORD_FILE_FULL		= 1114
    ER_UNKNOWN_CHARACTER_SET	= 1115
    ER_TOO_MANY_TABLES		= 1116
    ER_TOO_MANY_FIELDS		= 1117
    ER_TOO_BIG_ROWSIZE		= 1118
    ER_STACK_OVERRUN		= 1119
    ER_WRONG_OUTER_JOIN		= 1120
    ER_NULL_COLUMN_IN_INDEX	= 1121
    ER_CANT_FIND_UDF		= 1122
    ER_CANT_INITIALIZE_UDF	= 1123
    ER_UDF_NO_PATHS		= 1124
    ER_UDF_EXISTS		= 1125
    ER_CANT_OPEN_LIBRARY	= 1126
    ER_CANT_FIND_DL_ENTRY	= 1127
    ER_FUNCTION_NOT_DEFINED	= 1128
    ER_HOST_IS_BLOCKED		= 1129
    ER_HOST_NOT_PRIVILEGED	= 1130
    ER_PASSWORD_ANONYMOUS_USER	= 1131
    ER_PASSWORD_NOT_ALLOWED	= 1132
    ER_PASSWORD_NO_MATCH	= 1133
    ER_UPDATE_INFO		= 1134
    ER_CANT_CREATE_THREAD	= 1135
    ER_WRONG_VALUE_COUNT_ON_ROW	= 1136
    ER_CANT_REOPEN_TABLE	= 1137
    ER_INVALID_USE_OF_NULL	= 1138
    ER_REGEXP_ERROR		= 1139
    ER_MIX_OF_GROUP_FUNC_AND_FIELDS	= 1140
    ER_NONEXISTING_GRANT	= 1141
    ER_TABLEACCESS_DENIED_ERROR	= 1142
    ER_COLUMNACCESS_DENIED_ERROR	= 1143
    ER_ILLEGAL_GRANT_FOR_TABLE	= 1144
    ER_GRANT_WRONG_HOST_OR_USER	= 1145
    ER_NO_SUCH_TABLE		= 1146
    ER_NONEXISTING_TABLE_GRANT	= 1147
    ER_NOT_ALLOWED_COMMAND	= 1148
    ER_SYNTAX_ERROR		= 1149
    ER_DELAYED_CANT_CHANGE_LOCK	= 1150
    ER_TOO_MANY_DELAYED_THREADS	= 1151
    ER_ABORTING_CONNECTION	= 1152
    ER_NET_PACKET_TOO_LARGE	= 1153
    ER_NET_READ_ERROR_FROM_PIPE	= 1154
    ER_NET_FCNTL_ERROR		= 1155
    ER_NET_PACKETS_OUT_OF_ORDER	= 1156
    ER_NET_UNCOMPRESS_ERROR	= 1157
    ER_NET_READ_ERROR		= 1158
    ER_NET_READ_INTERRUPTED	= 1159
    ER_NET_ERROR_ON_WRITE	= 1160
    ER_NET_WRITE_INTERRUPTED	= 1161
    ER_TOO_LONG_STRING		= 1162
    ER_TABLE_CANT_HANDLE_BLOB	= 1163
    ER_TABLE_CANT_HANDLE_AUTO_INCREMENT	= 1164
    ER_DELAYED_INSERT_TABLE_LOCKED	= 1165
    ER_WRONG_COLUMN_NAME	= 1166
    ER_WRONG_KEY_COLUMN		= 1167
    ER_WRONG_MRG_TABLE		= 1168
    ER_DUP_UNIQUE		= 1169
    ER_BLOB_KEY_WITHOUT_LENGTH	= 1170
    ER_PRIMARY_CANT_HAVE_NULL	= 1171
    ER_TOO_MANY_ROWS		= 1172
    ER_REQUIRES_PRIMARY_KEY	= 1173
    ER_NO_RAID_COMPILED		= 1174
    ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE	= 1175
    ER_KEY_DOES_NOT_EXITS	= 1176
    ER_CHECK_NO_SUCH_TABLE	= 1177
    ER_CHECK_NOT_IMPLEMENTED	= 1178
    ER_CANT_DO_THIS_DURING_AN_TRANSACTION	= 1179
    ER_ERROR_DURING_COMMIT	= 1180
    ER_ERROR_DURING_ROLLBACK	= 1181
    ER_ERROR_DURING_FLUSH_LOGS	= 1182
    ER_ERROR_DURING_CHECKPOINT	= 1183
    ER_NEW_ABORTING_CONNECTION	= 1184
    ER_DUMP_NOT_IMPLEMENTED   	= 1185
    ER_FLUSH_MASTER_BINLOG_CLOSED	= 1186
    ER_INDEX_REBUILD 		= 1187
    ER_MASTER			= 1188
    ER_MASTER_NET_READ		= 1189
    ER_MASTER_NET_WRITE		= 1190
    ER_FT_MATCHING_KEY_NOT_FOUND	= 1191
    ER_LOCK_OR_ACTIVE_TRANSACTION	= 1192
    ER_UNKNOWN_SYSTEM_VARIABLE	= 1193
    ER_CRASHED_ON_USAGE		= 1194
    ER_CRASHED_ON_REPAIR	= 1195
    ER_WARNING_NOT_COMPLETE_ROLLBACK	= 1196
    ER_TRANS_CACHE_FULL		= 1197
    ER_SLAVE_MUST_STOP		= 1198
    ER_SLAVE_NOT_RUNNING	= 1199
    ER_BAD_SLAVE		= 1200
    ER_MASTER_INFO		= 1201
    ER_SLAVE_THREAD		= 1202
    ER_TOO_MANY_USER_CONNECTIONS	= 1203
    ER_SET_CONSTANTS_ONLY	= 1204
    ER_LOCK_WAIT_TIMEOUT	= 1205
    ER_LOCK_TABLE_FULL		= 1206
    ER_READ_ONLY_TRANSACTION	= 1207
    ER_DROP_DB_WITH_READ_LOCK	= 1208
    ER_CREATE_DB_WITH_READ_LOCK	= 1209
    ER_WRONG_ARGUMENTS		= 1210
    ER_NO_PERMISSION_TO_CREATE_USER	= 1211
    ER_UNION_TABLES_IN_DIFFERENT_DIR	= 1212
    ER_LOCK_DEADLOCK		= 1213
    ER_TABLE_CANT_HANDLE_FULLTEXT	= 1214
    ER_CANNOT_ADD_FOREIGN	= 1215
    ER_NO_REFERENCED_ROW	= 1216
    ER_ROW_IS_REFERENCED	= 1217
    ER_CONNECT_TO_MASTER	= 1218
    ER_QUERY_ON_MASTER		= 1219
    ER_ERROR_WHEN_EXECUTING_COMMAND	= 1220
    ER_WRONG_USAGE		= 1221
    ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT	= 1222
    ER_CANT_UPDATE_WITH_READLOCK	= 1223
    ER_MIXING_NOT_ALLOWED	= 1224
    ER_DUP_ARGUMENT		= 1225
    ER_USER_LIMIT_REACHED	= 1226
    ER_SPECIFIC_ACCESS_DENIED_ERROR	= 1227
    ER_LOCAL_VARIABLE		= 1228
    ER_GLOBAL_VARIABLE		= 1229
    ER_NO_DEFAULT		= 1230
    ER_WRONG_VALUE_FOR_VAR	= 1231
    ER_WRONG_TYPE_FOR_VAR	= 1232
    ER_VAR_CANT_BE_READ		= 1233
    ER_CANT_USE_OPTION_HERE	= 1234
    ER_NOT_SUPPORTED_YET   	= 1235
    ER_MASTER_FATAL_ERROR_READING_BINLOG	= 1236
    ER_SLAVE_IGNORED_TABLE	= 1237
    ER_ERROR_MESSAGES 		= 238

    # Client Error
    CR_MIN_ERROR		= 2000
    CR_MAX_ERROR		= 2999
    CR_UNKNOWN_ERROR		= 2000
    CR_SOCKET_CREATE_ERROR	= 2001
    CR_CONNECTION_ERROR		= 2002
    CR_CONN_HOST_ERROR		= 2003
    CR_IPSOCK_ERROR		= 2004
    CR_UNKNOWN_HOST		= 2005
    CR_SERVER_GONE_ERROR	= 2006
    CR_VERSION_ERROR		= 2007
    CR_OUT_OF_MEMORY		= 2008
    CR_WRONG_HOST_INFO		= 2009
    CR_LOCALHOST_CONNECTION	= 2010
    CR_TCP_CONNECTION		= 2011
    CR_SERVER_HANDSHAKE_ERR	= 2012
    CR_SERVER_LOST		= 2013
    CR_COMMANDS_OUT_OF_SYNC	= 2014
    CR_NAMEDPIPE_CONNECTION	= 2015
    CR_NAMEDPIPEWAIT_ERROR	= 2016
    CR_NAMEDPIPEOPEN_ERROR	= 2017
    CR_NAMEDPIPESETSTATE_ERROR	= 2018
    CR_CANT_READ_CHARSET	= 2019
    CR_NET_PACKET_TOO_LARGE	= 2020
    CR_EMBEDDED_CONNECTION	= 2021
    CR_PROBE_SLAVE_STATUS	= 2022
    CR_PROBE_SLAVE_HOSTS	= 2023
    CR_PROBE_SLAVE_CONNECT	= 2024
    CR_PROBE_MASTER_CONNECT	= 2025
    CR_SSL_CONNECTION_ERROR	= 2026
    CR_MALFORMED_PACKET		= 2027

    CLIENT_ERRORS = [
      "Unknown MySQL error",
      "Can't create UNIX socket (%d)",
      "Can't connect to local MySQL server through socket '%-.64s' (%d)",
      "Can't connect to MySQL server on '%-.64s' (%d)",
      "Can't create TCP/IP socket (%d)",
      "Unknown MySQL Server Host '%-.64s' (%d)",
      "MySQL server has gone away",
      "Protocol mismatch. Server Version = %d Client Version = %d",
      "MySQL client run out of memory",
      "Wrong host info",
      "Localhost via UNIX socket",
      "%-.64s via TCP/IP",
      "Error in server handshake",
      "Lost connection to MySQL server during query",
      "Commands out of sync;  You can't run this command now",
      "%-.64s via named pipe",
      "Can't wait for named pipe to host: %-.64s  pipe: %-.32s (%lu)",
      "Can't open named pipe to host: %-.64s  pipe: %-.32s (%lu)",
      "Can't set state of named pipe to host: %-.64s  pipe: %-.32s (%lu)",
      "Can't initialize character set %-.64s (path: %-.64s)",
      "Got packet bigger than 'max_allowed_packet'",
      "Embedded server",
      "Error on SHOW SLAVE STATUS:",
      "Error on SHOW SLAVE HOSTS:",
      "Error connecting to slave:",
      "Error connecting to master:",
      "SSL connection error",
      "Malformed packet"
    ]

    def initialize(errno, error)
      @errno = errno
      @error = error
      super error
    end
    attr_reader :errno, :error

    def Error::err(errno)
      CLIENT_ERRORS[errno - Error::CR_MIN_ERROR]
    end
  end

  class Net
    def initialize(sock)
      @sock = sock
      @pkt_nr = 0
    end

    def clear()
      @pkt_nr = 0
    end

    def read()
      buf = []
      len = nil
      @sock.sync = false
      while len == nil or len == MAX_PACKET_LENGTH do
	a = @sock.read(4)
	len = a[0]+a[1]*256+a[2]*256*256
	pkt_nr = a[3]
	if @pkt_nr != pkt_nr then
	  raise "Packets out of order: #{@pkt_nr}<>#{pkt_nr}"
	end
	@pkt_nr = @pkt_nr + 1 & 0xff
	buf << @sock.read(len)
      end
      @sock.sync = true
      buf.join
    rescue
      errno = Error::CR_SERVER_LOST 
      raise Error::new(errno, Error::err(errno)) 
    end
    
    def write(data)
      if data.is_a? Array then
	data = data.join
      end
      @sock.sync = false
      ptr = 0
      while data.length >= MAX_PACKET_LENGTH do
	@sock.write Net::int3str(MAX_PACKET_LENGTH)+@pkt_nr.chr+data[ptr, MAX_PACKET_LENGTH]
	@pkt_nr = @pkt_nr + 1 & 0xff
	ptr += MAX_PACKET_LENGTH
      end
      @sock.write Net::int3str(data.length-ptr)+@pkt_nr.chr+data[ptr .. -1]
      @pkt_nr = @pkt_nr + 1 & 0xff
      @sock.sync = true
      @sock.flush
    rescue
      errno = Error::CR_SERVER_LOST 
      raise Error::new(errno, Error::err(errno)) 
    end

    def close()
      @sock.close
    end

    def Net::int2str(n)
      [n].pack("v")
    end

    def Net::int3str(n)
      [n%256, n>>8].pack("cv")
    end

    def Net::int4str(n)
      [n].pack("V")
    end

  end

  class Random
    def initialize(seed1, seed2)
      @max_value = 0x3FFFFFFF
      @seed1 = seed1 % @max_value
      @seed2 = seed2 % @max_value
    end

    def rnd()
      @seed1 = (@seed1*3+@seed2) % @max_value
      @seed2 = (@seed1+@seed2+33) % @max_value
      @seed1.to_f / @max_value
    end
  end

end

class << Mysql
  def init()
    Mysql::new :INIT
  end

  def real_connect(*args)
    Mysql::new(*args)
  end
  alias :connect :real_connect

  def finalizer(net)
    proc {
      net.clear
      net.write Mysql::COM_QUIT.chr
    }
  end

  def escape_string(str)
    str.gsub(/([\0\n\r\032\'\"\\])/) do
      case $1
      when "\0" then "\\0"
      when "\n" then "\\n"
      when "\r" then "\\r"
      when "\032" then "\\Z"
      else "\\"+$1
      end
    end
  end
  alias :quote :escape_string

  def get_client_info()
    Mysql::VERSION
  end
  alias :client_info :get_client_info

  def debug(str)
    raise "not implemented"
  end
end

#
# for compatibility
#

MysqlRes = Mysql::Result
MysqlField = Mysql::Field
MysqlError = Mysql::Error
# :title: Transaction::Simple -- Active Object Transaction Support for Ruby
# :main: Transaction::Simple
#
# == Licence
#
# 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.
#--
# Transaction::Simple
#   Simple object transaction support for Ruby
#   Version 1.3.0
#
# Copyright (c) 2003 - 2005 Austin Ziegler
#
# $Id: simple.rb,v 1.5 2005/05/05 16:16:49 austin Exp $
#++
  # The "Transaction" namespace can be used for additional transaction
  # support objects and modules.
module Transaction
    # A standard exception for transaction errors.
  class TransactionError < StandardError; end
    # The TransactionAborted exception is used to indicate when a
    # transaction has been aborted in the block form.
  class TransactionAborted < Exception; end
    # The TransactionCommitted exception is used to indicate when a
    # transaction has been committed in the block form.
  class TransactionCommitted < Exception; end

  te = "Transaction Error: %s"

  Messages = {
    :bad_debug_object =>
      te % "the transaction debug object must respond to #<<.",
    :unique_names =>
      te % "named transactions must be unique.",
    :no_transaction_open =>
      te % "no transaction open.",
    :cannot_rewind_no_transaction =>
      te % "cannot rewind; there is no current transaction.",
    :cannot_rewind_named_transaction =>
      te % "cannot rewind to transaction %s because it does not exist.",
    :cannot_rewind_transaction_before_block =>
      te % "cannot rewind a transaction started before the execution block.",
    :cannot_abort_no_transaction =>
      te % "cannot abort; there is no current transaction.",
    :cannot_abort_transaction_before_block =>
      te % "cannot abort a transaction started before the execution block.",
    :cannot_abort_named_transaction =>
      te % "cannot abort nonexistant transaction %s.",
    :cannot_commit_no_transaction =>
      te % "cannot commit; there is no current transaction.",
    :cannot_commit_transaction_before_block =>
      te % "cannot commit a transaction started before the execution block.",
    :cannot_commit_named_transaction =>
      te % "cannot commit nonexistant transaction %s.",
    :cannot_start_empty_block_transaction =>
      te % "cannot start a block transaction with no objects.",
    :cannot_obtain_transaction_lock =>
      te % "cannot obtain transaction lock for #%s.",
  }

    # = Transaction::Simple for Ruby
    # Simple object transaction support for Ruby
    #
    # == Introduction
    # Transaction::Simple provides a generic way to add active transaction
    # support to objects. The transaction methods added by this module will
    # work with most objects, excluding those that cannot be
    # <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
    # singleton objects).
    #
    # The transactions supported by Transaction::Simple are not backed
    # transactions; they are not associated with any sort of data store.
    # They are "live" transactions occurring in memory and in the object
    # itself. This is to allow "test" changes to be made to an object
    # before making the changes permanent.
    #
    # Transaction::Simple can handle an "infinite" number of transaction
    # levels (limited only by memory). If I open two transactions, commit
    # the second, but abort the first, the object will revert to the
    # original version.
    # 
    # Transaction::Simple supports "named" transactions, so that multiple
    # levels of transactions can be committed, aborted, or rewound by
    # referring to the appropriate name of the transaction. Names may be any
    # object *except* +nil+. As with Hash keys, String names will be
    # duplicated and frozen before using.
    #
    # Copyright::   Copyright © 2003 - 2005 by Austin Ziegler
    # Version::     1.3.0
    # Licence::     MIT-Style
    #
    # Thanks to David Black for help with the initial concept that led to
    # this library.
    #
    # == Usage
    #   include 'transaction/simple'
    #
    #   v = "Hello, you."               # -> "Hello, you."
    #   v.extend(Transaction::Simple)   # -> "Hello, you."
    #
    #   v.start_transaction             # -> ... (a Marshal string)
    #   v.transaction_open?             # -> true
    #   v.gsub!(/you/, "world")         # -> "Hello, world."
    #
    #   v.rewind_transaction            # -> "Hello, you."
    #   v.transaction_open?             # -> true
    #
    #   v.gsub!(/you/, "HAL")           # -> "Hello, HAL."
    #   v.abort_transaction             # -> "Hello, you."
    #   v.transaction_open?             # -> false
    #
    #   v.start_transaction             # -> ... (a Marshal string)
    #   v.start_transaction             # -> ... (a Marshal string)
    #
    #   v.transaction_open?             # -> true
    #   v.gsub!(/you/, "HAL")           # -> "Hello, HAL."
    #
    #   v.commit_transaction            # -> "Hello, HAL."
    #   v.transaction_open?             # -> true
    #   v.abort_transaction             # -> "Hello, you."
    #   v.transaction_open?             # -> false
    #
    # == Named Transaction Usage
    #   v = "Hello, you."               # -> "Hello, you."
    #   v.extend(Transaction::Simple)   # -> "Hello, you."
    #   
    #   v.start_transaction(:first)     # -> ... (a Marshal string)
    #   v.transaction_open?             # -> true
    #   v.transaction_open?(:first)     # -> true
    #   v.transaction_open?(:second)    # -> false
    #   v.gsub!(/you/, "world")         # -> "Hello, world."
    #   
    #   v.start_transaction(:second)    # -> ... (a Marshal string)
    #   v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
    #   v.rewind_transaction(:first)    # -> "Hello, you."
    #   v.transaction_open?             # -> true
    #   v.transaction_open?(:first)     # -> true
    #   v.transaction_open?(:second)    # -> false
    #   
    #   v.gsub!(/you/, "world")         # -> "Hello, world."
    #   v.start_transaction(:second)    # -> ... (a Marshal string)
    #   v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
    #   v.transaction_name              # -> :second
    #   v.abort_transaction(:first)     # -> "Hello, you."
    #   v.transaction_open?             # -> false
    #   
    #   v.start_transaction(:first)     # -> ... (a Marshal string)
    #   v.gsub!(/you/, "world")         # -> "Hello, world."
    #   v.start_transaction(:second)    # -> ... (a Marshal string)
    #   v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
    #   
    #   v.commit_transaction(:first)    # -> "Hello, HAL."
    #   v.transaction_open?             # -> false
    #
    # == Block Usage
    #   v = "Hello, you."               # -> "Hello, you."
    #   Transaction::Simple.start(v) do |tv|
    #       # v has been extended with Transaction::Simple and an unnamed
    #       # transaction has been started.
    #     tv.transaction_open?          # -> true
    #     tv.gsub!(/you/, "world")      # -> "Hello, world."
    #
    #     tv.rewind_transaction         # -> "Hello, you."
    #     tv.transaction_open?          # -> true
    #
    #     tv.gsub!(/you/, "HAL")        # -> "Hello, HAL."
    #       # The following breaks out of the transaction block after
    #       # aborting the transaction.
    #     tv.abort_transaction          # -> "Hello, you."
    #   end
    #     # v still has Transaction::Simple applied from here on out.
    #   v.transaction_open?             # -> false
    #
    #   Transaction::Simple.start(v) do |tv|
    #     tv.start_transaction          # -> ... (a Marshal string)
    #
    #     tv.transaction_open?          # -> true
    #     tv.gsub!(/you/, "HAL")        # -> "Hello, HAL."
    #
    #       # If #commit_transaction were called without having started a
    #       # second transaction, then it would break out of the transaction
    #       # block after committing the transaction.
    #     tv.commit_transaction         # -> "Hello, HAL."
    #     tv.transaction_open?          # -> true
    #     tv.abort_transaction          # -> "Hello, you."
    #   end
    #   v.transaction_open?             # -> false
    #
    # == Named Transaction Usage
    #   v = "Hello, you."               # -> "Hello, you."
    #   v.extend(Transaction::Simple)   # -> "Hello, you."
    #   
    #   v.start_transaction(:first)     # -> ... (a Marshal string)
    #   v.transaction_open?             # -> true
    #   v.transaction_open?(:first)     # -> true
    #   v.transaction_open?(:second)    # -> false
    #   v.gsub!(/you/, "world")         # -> "Hello, world."
    #   
    #   v.start_transaction(:second)    # -> ... (a Marshal string)
    #   v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
    #   v.rewind_transaction(:first)    # -> "Hello, you."
    #   v.transaction_open?             # -> true
    #   v.transaction_open?(:first)     # -> true
    #   v.transaction_open?(:second)    # -> false
    #   
    #   v.gsub!(/you/, "world")         # -> "Hello, world."
    #   v.start_transaction(:second)    # -> ... (a Marshal string)
    #   v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
    #   v.transaction_name              # -> :second
    #   v.abort_transaction(:first)     # -> "Hello, you."
    #   v.transaction_open?             # -> false
    #   
    #   v.start_transaction(:first)     # -> ... (a Marshal string)
    #   v.gsub!(/you/, "world")         # -> "Hello, world."
    #   v.start_transaction(:second)    # -> ... (a Marshal string)
    #   v.gsub!(/world/, "HAL")         # -> "Hello, HAL."
    #   
    #   v.commit_transaction(:first)    # -> "Hello, HAL."
    #   v.transaction_open?             # -> false
    #
    # == Thread Safety
    # Threadsafe version of Transaction::Simple and
    # Transaction::Simple::Group exist; these are loaded from
    # 'transaction/simple/threadsafe' and
    # 'transaction/simple/threadsafe/group', respectively, and are
    # represented in Ruby code as Transaction::Simple::ThreadSafe and
    # Transaction::Simple::ThreadSafe::Group, respectively.
    #
    # == Contraindications
    # While Transaction::Simple is very useful, it has some severe
    # limitations that must be understood. Transaction::Simple:
    #
    # * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
    #   cannot use Transaction::Simple. In my experience, this affects
    #   singleton objects more often than any other object. It may be that
    #   Ruby 2.0 will solve this problem.
    # * does not manage resources. Resources external to the object and its
    #   instance variables are not managed at all. However, all instance
    #   variables and objects "belonging" to those instance variables are
    #   managed. If there are object reference counts to be handled,
    #   Transaction::Simple will probably cause problems.
    # * is not inherently thread-safe. In the ACID ("atomic, consistent,
    #   isolated, durable") test, Transaction::Simple provides CD, but it is
    #   up to the user of Transaction::Simple to provide isolation and
    #   atomicity. Transactions should be considered "critical sections" in
    #   multi-threaded applications. If thread safety and atomicity is
    #   absolutely required, use Transaction::Simple::ThreadSafe, which uses
    #   a Mutex object to synchronize the accesses on the object during the
    #   transaction operations.
    # * does not necessarily maintain Object#__id__ values on rewind or
    #   abort. This may change for future versions that will be Ruby 1.8 or
    #   better *only*. Certain objects that support #replace will maintain
    #   Object#__id__.
    # * Can be a memory hog if you use many levels of transactions on many
    #   objects.
    #
  module Simple
    TRANSACTION_SIMPLE_VERSION = '1.3.0'

      # Sets the Transaction::Simple debug object. It must respond to #<<.
      # Sets the transaction debug object. Debugging will be performed
      # automatically if there's a debug object. The generic transaction
      # error class.
    def self.debug_io=(io)
      if io.nil?
        @tdi        = nil
        @debugging  = false
      else
        unless io.respond_to?(:<<)
          raise TransactionError, Messages[:bad_debug_object]
        end
        @tdi = io
        @debugging = true
      end
    end

      # Returns +true+ if we are debugging.
    def self.debugging?
      @debugging
    end

      # Returns the Transaction::Simple debug object. It must respond to
      # #<<.
    def self.debug_io
      @tdi ||= ""
      @tdi
    end

      # If +name+ is +nil+ (default), then returns +true+ if there is
      # currently a transaction open.
      #
      # If +name+ is specified, then returns +true+ if there is currently a
      # transaction that responds to +name+ open.
    def transaction_open?(name = nil)
      if name.nil?
        if Transaction::Simple.debugging?
          Transaction::Simple.debug_io << "Transaction " <<
            "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
        end
        return (not @__transaction_checkpoint__.nil?)
      else
        if Transaction::Simple.debugging?
          Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " <<
            "[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
        end
        return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
      end
    end

      # Returns the current name of the transaction. Transactions not
      # explicitly named are named +nil+.
    def transaction_name
      if @__transaction_checkpoint__.nil?
        raise TransactionError, Messages[:no_transaction_open]
      end
      if Transaction::Simple.debugging?
        Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
          "Transaction Name: #{@__transaction_names__[-1].inspect}\n"
      end
      if @__transaction_names__[-1].kind_of?(String)
        @__transaction_names__[-1].dup
      else
        @__transaction_names__[-1]
      end
    end

      # Starts a transaction. Stores the current object state. If a
      # transaction name is specified, the transaction will be named.
      # Transaction names must be unique. Transaction names of +nil+ will be
      # treated as unnamed transactions.
    def start_transaction(name = nil)
      @__transaction_level__ ||= 0
      @__transaction_names__ ||= []

      if name.nil?
        @__transaction_names__ << nil
        ss = "" if Transaction::Simple.debugging?
      else
        if @__transaction_names__.include?(name)
          raise TransactionError, Messages[:unique_names]
        end
        name = name.dup.freeze if name.kind_of?(String)
        @__transaction_names__ << name
        ss = "(#{name.inspect})" if Transaction::Simple.debugging?
      end

      @__transaction_level__ += 1

      if Transaction::Simple.debugging?
        Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " <<
          "Start Transaction#{ss}\n"
      end

      @__transaction_checkpoint__ = Marshal.dump(self)
    end

      # Rewinds the transaction. If +name+ is specified, then the
      # intervening transactions will be aborted and the named transaction
      # will be rewound. Otherwise, only the current transaction is rewound.
    def rewind_transaction(name = nil)
      if @__transaction_checkpoint__.nil?
        raise TransactionError, Messages[:cannot_rewind_no_transaction]
      end

        # Check to see if we are trying to rewind a transaction that is
        # outside of the current transaction block.
      if @__transaction_block__ and name
        nix = @__transaction_names__.index(name) + 1
        if nix < @__transaction_block__
          raise TransactionError, Messages[:cannot_rewind_transaction_before_block]
        end
      end

      if name.nil?
        __rewind_this_transaction
        ss = "" if Transaction::Simple.debugging?
      else
        unless @__transaction_names__.include?(name)
          raise TransactionError, Messages[:cannot_rewind_named_transaction] % name.inspect
        end
        ss = "(#{name})" if Transaction::Simple.debugging?

        while @__transaction_names__[-1] != name
          @__transaction_checkpoint__ = __rewind_this_transaction
          if Transaction::Simple.debugging?
            Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
              "Rewind Transaction#{ss}\n"
          end
          @__transaction_level__ -= 1
          @__transaction_names__.pop
        end
        __rewind_this_transaction
      end
      if Transaction::Simple.debugging?
        Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
          "Rewind Transaction#{ss}\n"
      end
      self
    end

      # Aborts the transaction. Resets the object state to what it was
      # before the transaction was started and closes the transaction. If
      # +name+ is specified, then the intervening transactions and the named
      # transaction will be aborted. Otherwise, only the current transaction
      # is aborted.
      #
      # If the current or named transaction has been started by a block
      # (Transaction::Simple.start), then the execution of the block will be
      # halted with +break+ +self+.
    def abort_transaction(name = nil)
      if @__transaction_checkpoint__.nil?
        raise TransactionError, Messages[:cannot_abort_no_transaction]
      end

        # Check to see if we are trying to abort a transaction that is
        # outside of the current transaction block. Otherwise, raise
        # TransactionAborted if they are the same.
      if @__transaction_block__ and name
        nix = @__transaction_names__.index(name) + 1
        if nix < @__transaction_block__
          raise TransactionError, Messages[:cannot_abort_transaction_before_block]
        end

        raise TransactionAborted if @__transaction_block__ == nix
      end

      raise TransactionAborted if @__transaction_block__ == @__transaction_level__

      if name.nil?
        __abort_transaction(name)
      else
        unless @__transaction_names__.include?(name)
          raise TransactionError, Messages[:cannot_abort_named_transaction] % name.inspect
        end
        __abort_transaction(name) while @__transaction_names__.include?(name)
      end
      self
    end

      # If +name+ is +nil+ (default), the current transaction level is
      # closed out and the changes are committed.
      #
      # If +name+ is specified and +name+ is in the list of named
      # transactions, then all transactions are closed and committed until
      # the named transaction is reached.
    def commit_transaction(name = nil)
      if @__transaction_checkpoint__.nil?
        raise TransactionError, Messages[:cannot_commit_no_transaction]
      end
      @__transaction_block__ ||= nil

        # Check to see if we are trying to commit a transaction that is
        # outside of the current transaction block. Otherwise, raise
        # TransactionCommitted if they are the same.
      if @__transaction_block__ and name
        nix = @__transaction_names__.index(name) + 1
        if nix < @__transaction_block__
          raise TransactionError, Messages[:cannot_commit_transaction_before_block]
        end

        raise TransactionCommitted if @__transaction_block__ == nix
      end

      raise TransactionCommitted if @__transaction_block__ == @__transaction_level__

      if name.nil?
        ss = "" if Transaction::Simple.debugging?
        __commit_transaction
        if Transaction::Simple.debugging?
          Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
            "Commit Transaction#{ss}\n"
        end
      else
        unless @__transaction_names__.include?(name)
          raise TransactionError, Messages[:cannot_commit_named_transaction] % name.inspect
        end
        ss = "(#{name})" if Transaction::Simple.debugging?

        while @__transaction_names__[-1] != name
          if Transaction::Simple.debugging?
            Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
              "Commit Transaction#{ss}\n"
          end
          __commit_transaction
        end
        if Transaction::Simple.debugging?
          Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
            "Commit Transaction#{ss}\n"
        end
        __commit_transaction
      end

      self
    end

      # Alternative method for calling the transaction methods. An optional
      # name can be specified for named transaction support.
      #
      # #transaction(:start)::  #start_transaction
      # #transaction(:rewind):: #rewind_transaction
      # #transaction(:abort)::  #abort_transaction
      # #transaction(:commit):: #commit_transaction
      # #transaction(:name)::   #transaction_name
      # #transaction::          #transaction_open?
    def transaction(action = nil, name = nil)
      case action
      when :start
        start_transaction(name)
      when :rewind
        rewind_transaction(name)
      when :abort
        abort_transaction(name)
      when :commit
        commit_transaction(name)
      when :name
        transaction_name
      when nil
        transaction_open?(name)
      end
    end

      # Allows specific variables to be excluded from transaction support.
      # Must be done after extending the object but before starting the
      # first transaction on the object.
      #
      #   vv.transaction_exclusions << "@io"
    def transaction_exclusions
      @transaction_exclusions ||= []
    end

    class << self
      def __common_start(name, vars, &block)
        if vars.empty?
          raise TransactionError, Messages[:cannot_start_empty_block_transaction]
        end

        if block
          begin
            vlevel = {}

            vars.each do |vv|
              vv.extend(Transaction::Simple)
              vv.start_transaction(name)
              vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
              vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
            end

            yield(*vars)
          rescue TransactionAborted
            vars.each do |vv|
              if name.nil? and vv.transaction_open?
                loop do
                  tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
                  vv.instance_variable_set(:@__transaction_block__, -1)
                  break if tlevel < vlevel[vv.__id__]
                  vv.abort_transaction if vv.transaction_open?
                end
              elsif vv.transaction_open?(name)
                vv.instance_variable_set(:@__transaction_block__, -1)
                vv.abort_transaction(name)
              end
            end
          rescue TransactionCommitted
            nil
          ensure
            vars.each do |vv|
              if name.nil? and vv.transaction_open?
                loop do
                  tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
                  break if tlevel < vlevel[vv.__id__]
                  vv.instance_variable_set(:@__transaction_block__, -1)
                  vv.commit_transaction if vv.transaction_open?
                end
              elsif vv.transaction_open?(name)
                vv.instance_variable_set(:@__transaction_block__, -1)
                vv.commit_transaction(name)
              end
            end
          end
        else
          vars.each do |vv|
            vv.extend(Transaction::Simple)
            vv.start_transaction(name)
          end
        end
      end
      private :__common_start

      def start_named(name, *vars, &block)
        __common_start(name, vars, &block)
      end

      def start(*vars, &block)
        __common_start(nil, vars, &block)
      end
    end

    def __abort_transaction(name = nil) #:nodoc:
      @__transaction_checkpoint__ = __rewind_this_transaction

      if name.nil?
        ss = "" if Transaction::Simple.debugging?
      else
        ss = "(#{name.inspect})" if Transaction::Simple.debugging?
      end

      if Transaction::Simple.debugging?
        Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
          "Abort Transaction#{ss}\n"
      end
      @__transaction_level__ -= 1
      @__transaction_names__.pop
      if @__transaction_level__ < 1
        @__transaction_level__ = 0
        @__transaction_names__ = []
      end
    end

    TRANSACTION_CHECKPOINT  = "@__transaction_checkpoint__" #:nodoc:
    SKIP_TRANSACTION_VARS   = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:

    def __rewind_this_transaction #:nodoc:
      rr = Marshal.restore(@__transaction_checkpoint__)

      begin
        self.replace(rr) if respond_to?(:replace)
      rescue
        nil
      end

      rr.instance_variables.each do |vv|
        next if SKIP_TRANSACTION_VARS.include?(vv)
        next if self.transaction_exclusions.include?(vv)
        if respond_to?(:instance_variable_get)
          instance_variable_set(vv, rr.instance_variable_get(vv))
        else
          instance_eval(%q|#{vv} = rr.instance_eval("#{vv}")|)
        end
      end

      new_ivar = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS
      new_ivar.each do |vv|
        if respond_to?(:instance_variable_set)
          instance_variable_set(vv, nil)
        else
          instance_eval(%q|#{vv} = nil|)
        end
      end

      if respond_to?(:instance_variable_get)
        rr.instance_variable_get(TRANSACTION_CHECKPOINT)
      else
        rr.instance_eval(TRANSACTION_CHECKPOINT)
      end
    end

    def __commit_transaction #:nodoc:
      if respond_to?(:instance_variable_get)
        @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
      else
        @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
      end

      @__transaction_level__ -= 1
      @__transaction_names__.pop

      if @__transaction_level__ < 1
        @__transaction_level__ = 0
        @__transaction_names__ = []
      end
    end

    private :__abort_transaction
    private :__rewind_this_transaction
    private :__commit_transaction
  end
end
module ActiveRecord
  module VERSION #:nodoc:
    MAJOR = 1
    MINOR = 14
    TINY  = 4

    STRING = [MAJOR, MINOR, TINY].join('.')
  end
end
require 'yaml'

module ActiveRecord
  module Wrappings #:nodoc:
    class YamlWrapper < AbstractWrapper #:nodoc:
      def wrap(attribute)   attribute.to_yaml end
      def unwrap(attribute) YAML::load(attribute) end
    end

    module ClassMethods #:nodoc:
      # Wraps the attribute in Yaml encoding
      def wrap_in_yaml(*attributes) wrap_with(YamlWrapper, attributes) end
    end
  end
endmodule ActiveRecord
  # A plugin framework for wrapping attribute values before they go in and unwrapping them after they go out of the database.
  # This was intended primarily for YAML wrapping of arrays and hashes, but this behavior is now native in the Base class.
  # So for now this framework is laying dormant until a need pops up.
  module Wrappings #:nodoc:
    module ClassMethods #:nodoc:
      def wrap_with(wrapper, *attributes)
        [ attributes ].flat.each { |attribute| wrapper.wrap(attribute) }
      end
    end

    def self.append_features(base)
      super
      base.extend(ClassMethods)
    end

    class AbstractWrapper #:nodoc:
      def self.wrap(attribute, record_binding) #:nodoc:
        %w( before_save after_save after_initialize ).each do |callback|
          eval "#{callback} #{name}.new('#{attribute}')", record_binding
        end
      end

      def initialize(attribute) #:nodoc:
        @attribute = attribute
      end

      def save_wrapped_attribute(record) #:nodoc:
        if record.attribute_present?(@attribute)
          record.send(
            "write_attribute", 
            @attribute, 
            wrap(record.send("read_attribute", @attribute))
          )
        end
      end

      def load_wrapped_attribute(record) #:nodoc:
        if record.attribute_present?(@attribute)
          record.send(
            "write_attribute", 
            @attribute, 
            unwrap(record.send("read_attribute", @attribute))
          )
        end
      end
  
      alias_method :before_save, :save_wrapped_attribute #:nodoc:
      alias_method :after_save, :load_wrapped_attribute #:nodoc:
      alias_method :after_initialize, :after_save #:nodoc:

      # Overwrite to implement the logic that'll take the regular attribute and wrap it.
      def wrap(attribute) end
  
      # Overwrite to implement the logic that'll take the wrapped attribute and unwrap it.
      def unwrap(attribute) end
    end
  end
end
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# 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.
#++

$:.unshift(File.dirname(__FILE__)) unless
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

unless defined?(ActiveSupport)
  begin
    $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")  
    require 'active_support'  
  rescue LoadError
    require 'rubygems'
    require_gem 'activesupport'
  end
end

require 'active_record/base'
require 'active_record/observer'
require 'active_record/validations'
require 'active_record/callbacks'
require 'active_record/reflection'
require 'active_record/associations'
require 'active_record/aggregations'
require 'active_record/transactions'
require 'active_record/timestamp'
require 'active_record/acts/list'
require 'active_record/acts/tree'
require 'active_record/acts/nested_set'
require 'active_record/locking'
require 'active_record/migration'
require 'active_record/schema'
require 'active_record/calculations'

ActiveRecord::Base.class_eval do
  include ActiveRecord::Validations
  include ActiveRecord::Locking
  include ActiveRecord::Callbacks
  include ActiveRecord::Observing
  include ActiveRecord::Timestamp
  include ActiveRecord::Associations
  include ActiveRecord::Aggregations
  include ActiveRecord::Transactions
  include ActiveRecord::Reflection
  include ActiveRecord::Acts::Tree
  include ActiveRecord::Acts::List
  include ActiveRecord::Acts::NestedSet
  include ActiveRecord::Calculations
end

unless defined?(RAILS_CONNECTION_ADAPTERS)
  RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase )
end

RAILS_CONNECTION_ADAPTERS.each do |adapter|
  require "active_record/connection_adapters/" + adapter + "_adapter"
end

require 'active_record/query_cache'
require 'active_record/schema_dumper'
# The filename begins with "aaa" to ensure this is the first test.
require 'abstract_unit'

class AAACreateTablesTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false

  def setup
    @base_path = "#{File.dirname(__FILE__)}/fixtures/db_definitions"
  end

  def test_drop_and_create_main_tables
    recreate ActiveRecord::Base
    assert true
  end

  def test_load_schema
    eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb"))
    assert true
  end
  
  def test_drop_and_create_courses_table
    recreate Course, '2'
    assert true
  end

  private
    def recreate(base, suffix = nil)
      connection = base.connection
      adapter_name = connection.adapter_name.downcase + suffix.to_s
      execute_sql_file "#{@base_path}/#{adapter_name}.drop.sql", connection
      execute_sql_file "#{@base_path}/#{adapter_name}.sql", connection
    end

    def execute_sql_file(path, connection)
      # OpenBase has a different format for sql files
      if current_adapter?(:OpenBaseAdapter) then
          File.read(path).split("go").each_with_index do |sql, i|
            begin
              # OpenBase does not support comments embedded in sql
              connection.execute(sql,"SQL statement ##{i}") unless sql.blank?
            rescue ActiveRecord::StatementInvalid
              #$stderr.puts "warning: #{$!}"
            end
          end
      else
        File.read(path).split(';').each_with_index do |sql, i|
          begin
            connection.execute("\n\n-- statement ##{i}\n#{sql}\n") unless sql.blank?
          rescue ActiveRecord::StatementInvalid
            #$stderr.puts "warning: #{$!}"
          end
        end
      end
    end
end
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')

require 'test/unit'
require 'active_record'
require 'active_record/fixtures'
require 'active_support/binding_of_caller'
require 'active_support/breakpoint'
require 'connection'

QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type') unless Object.const_defined?(:QUOTED_TYPE)

class Test::Unit::TestCase #:nodoc:
  self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
  self.use_instantiated_fixtures = false
  self.use_transactional_fixtures = (ENV['AR_NO_TX_FIXTURES'] != "yes")

  def create_fixtures(*table_names, &block)
    Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block)
  end
  
  def assert_date_from_db(expected, actual, message = nil)
    # SQL Server doesn't have a separate column type just for dates, 
    # so the time is in the string and incorrectly formatted
    if current_adapter?(:SQLServerAdapter)
      assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
    elsif current_adapter?(:SybaseAdapter)
      assert_equal expected.to_s, actual.to_date.to_s, message
    else
      assert_equal expected.to_s, actual.to_s, message
    end
  end

  def assert_queries(num = 1)
    ActiveRecord::Base.connection.class.class_eval do
      self.query_count = 0
      alias_method :execute, :execute_with_query_counting
    end
    yield
  ensure
    ActiveRecord::Base.connection.class.class_eval do
      alias_method :execute, :execute_without_query_counting
    end
    assert_equal num, ActiveRecord::Base.connection.query_count, "#{ActiveRecord::Base.connection.query_count} instead of #{num} queries were executed."
  end

  def assert_no_queries(&block)
    assert_queries(0, &block)
  end
end

def current_adapter?(type)
  ActiveRecord::ConnectionAdapters.const_defined?(type) &&
    ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type))
end

ActiveRecord::Base.connection.class.class_eval do
  cattr_accessor :query_count
  alias_method :execute_without_query_counting, :execute
  def execute_with_query_counting(sql, name = nil)
    self.query_count += 1
    execute_without_query_counting(sql, name)
  end
end

#ActiveRecord::Base.logger = Logger.new(STDOUT)
#ActiveRecord::Base.colorize_logging = false
require 'abstract_unit'

class ActiveSchemaTest < Test::Unit::TestCase
  def setup
    ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
      alias_method :real_execute, :execute
      def execute(sql, name = nil) return sql end
    end    
  end
  
  def teardown
    ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:alias_method, :execute, :real_execute)
  end

  def test_drop_table
    assert_equal "DROP TABLE people", drop_table(:people)
  end
  
  def test_add_column
    assert_equal "ALTER TABLE people ADD last_name varchar(255)", add_column(:people, :last_name, :string)
  end
  
  def test_add_column_with_limit
    assert_equal "ALTER TABLE people ADD key varchar(32)", add_column(:people, :key, :string, :limit => 32)
  end
  
  private
    def method_missing(method_symbol, *arguments)
      ActiveRecord::Base.connection.send(method_symbol, *arguments)
    end
endrequire 'abstract_unit'

class AdapterTest < Test::Unit::TestCase
  def setup
    @connection = ActiveRecord::Base.connection
  end

  def test_tables
    if @connection.respond_to?(:tables)
      tables = @connection.tables
      assert tables.include?("accounts")
      assert tables.include?("authors")
      assert tables.include?("tasks")
      assert tables.include?("topics")
    else
      warn "#{@connection.class} does not respond to #tables"
    end
  end

  def test_indexes
    idx_name = "accounts_idx"
      
    if @connection.respond_to?(:indexes)
      indexes = @connection.indexes("accounts")
      assert indexes.empty?

      @connection.add_index :accounts, :firm_id, :name => idx_name
      indexes = @connection.indexes("accounts")
      assert_equal "accounts", indexes.first.table
      # OpenBase does not have the concept of a named index
      # Indexes are merely properties of columns.
      assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter)
      assert !indexes.first.unique
      assert_equal ["firm_id"], indexes.first.columns
    else
      warn "#{@connection.class} does not respond to #indexes"
    end

  ensure
    @connection.remove_index(:accounts, :name => idx_name) rescue nil
  end
  
  def test_current_database
    if @connection.respond_to?(:current_database)
      assert_equal ENV['ARUNIT_DB_NAME'] || "activerecord_unittest", @connection.current_database
    end
  end

  def test_table_alias
    def @connection.test_table_alias_length() 10; end
    class << @connection
      alias_method :old_table_alias_length, :table_alias_length
      alias_method :table_alias_length,     :test_table_alias_length
    end
      
    assert_equal 'posts',      @connection.table_alias_for('posts')
    assert_equal 'posts_comm', @connection.table_alias_for('posts_comments')
    assert_equal 'dbo_posts',  @connection.table_alias_for('dbo.posts')
    
    class << @connection
      alias_method :table_alias_length, :old_table_alias_length
    end
  end

  # test resetting sequences in odd tables in postgreSQL
  if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
    require 'fixtures/movie'
    require 'fixtures/subscriber'
    def test_reset_empty_table_with_custom_pk
      Movie.delete_all
      Movie.connection.reset_pk_sequence! 'movies'
      assert_equal 1, Movie.create(:name => 'fight club').id
    end

    def test_reset_table_with_non_integer_pk
      Subscriber.delete_all
      Subscriber.connection.reset_pk_sequence! 'subscribers'

      sub = Subscriber.new(:name => 'robert drake')
      sub.id = 'bob drake'
      assert_nothing_raised { sub.save! }
    end
  end

end
require 'abstract_unit'
require 'fixtures/customer'

class AggregationsTest < Test::Unit::TestCase
  fixtures :customers

  def test_find_single_value_object
    assert_equal 50, customers(:david).balance.amount
    assert_kind_of Money, customers(:david).balance
    assert_equal 300, customers(:david).balance.exchange_to("DKK").amount
  end
  
  def test_find_multiple_value_object
    assert_equal customers(:david).address_street, customers(:david).address.street
    assert(
      customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country))
    )
  end
  
  def test_change_single_value_object
    customers(:david).balance = Money.new(100)
    customers(:david).save
    assert_equal 100, Customer.find(1).balance.amount
  end
  
  def test_immutable_value_objects
    customers(:david).balance = Money.new(100)
    assert_raises(TypeError) {  customers(:david).balance.instance_eval { @amount = 20 } }
  end  
  
  def test_inferred_mapping
    assert_equal "35.544623640962634", customers(:david).gps_location.latitude
    assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
    
    customers(:david).gps_location = GpsLocation.new("39x-110")

    assert_equal "39", customers(:david).gps_location.latitude
    assert_equal "-110", customers(:david).gps_location.longitude
    
    customers(:david).save
    
    customers(:david).reload

    assert_equal "39", customers(:david).gps_location.latitude
    assert_equal "-110", customers(:david).gps_location.longitude
  end

  def test_reloaded_instance_refreshes_aggregations
    assert_equal "35.544623640962634", customers(:david).gps_location.latitude
    assert_equal "-105.9309951055148", customers(:david).gps_location.longitude

    Customer.update_all("gps_location = '24x113'")
    customers(:david).reload
    assert_equal '24x113', customers(:david)['gps_location']

    assert_equal GpsLocation.new('24x113'), customers(:david).gps_location 
  end

  def test_gps_equality
    assert GpsLocation.new('39x110') == GpsLocation.new('39x110')
  end

  def test_gps_inequality
    assert GpsLocation.new('39x110') != GpsLocation.new('39x111')
  end
end
require 'abstract_unit'
require "#{File.dirname(__FILE__)}/../lib/active_record/schema"

if ActiveRecord::Base.connection.supports_migrations? 

  class ActiveRecordSchemaTest < Test::Unit::TestCase
    self.use_transactional_fixtures = false

    def setup
      @connection = ActiveRecord::Base.connection
    end

    def teardown
      @connection.drop_table :fruits rescue nil
    end

    def test_schema_define
      ActiveRecord::Schema.define(:version => 7) do
        create_table :fruits do |t|
          t.column :color, :string
          t.column :fruit_size, :string  # NOTE: "size" is reserved in Oracle
          t.column :texture, :string
          t.column :flavor, :string
        end
      end

      assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
      assert_nothing_raised { @connection.select_all "SELECT * FROM schema_info" }
      assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i
    end
  end

end
require 'abstract_unit'
require 'fixtures/post'
require 'fixtures/comment'
require 'fixtures/author'
require 'fixtures/category'
require 'fixtures/project'
require 'fixtures/developer'

class AssociationCallbacksTest < Test::Unit::TestCase
  fixtures :posts, :authors, :projects, :developers

  def setup
    @david = authors(:david)
    @thinking = posts(:thinking)
    @authorless = posts(:authorless)
    assert @david.post_log.empty?
  end
  
  def test_adding_macro_callbacks
    @david.posts_with_callbacks << @thinking
    assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
    @david.posts_with_callbacks << @thinking
    assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", 
                  "after_adding#{@thinking.id}"], @david.post_log
  end
  
  def test_adding_with_proc_callbacks
    @david.posts_with_proc_callbacks << @thinking
    assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
    @david.posts_with_proc_callbacks << @thinking
    assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}", 
                  "after_adding#{@thinking.id}"], @david.post_log
  end
  
  def test_removing_with_macro_callbacks
    first_post, second_post = @david.posts_with_callbacks[0, 2]
    @david.posts_with_callbacks.delete(first_post)
    assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
    @david.posts_with_callbacks.delete(second_post)
    assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", 
                  "after_removing#{second_post.id}"], @david.post_log
  end

  def test_removing_with_proc_callbacks
    first_post, second_post = @david.posts_with_callbacks[0, 2]
    @david.posts_with_proc_callbacks.delete(first_post)
    assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
    @david.posts_with_proc_callbacks.delete(second_post)
    assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}", 
                  "after_removing#{second_post.id}"], @david.post_log
  end
  
  def test_multiple_callbacks
    @david.posts_with_multiple_callbacks << @thinking
    assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", 
                  "after_adding_proc#{@thinking.id}"], @david.post_log
    @david.posts_with_multiple_callbacks << @thinking
    assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}", 
                  "after_adding_proc#{@thinking.id}", "before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", 
                  "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log
  end
  
  def test_has_and_belongs_to_many_add_callback
    david = developers(:david)
    ar = projects(:active_record)
    assert ar.developers_log.empty?
    ar.developers_with_callbacks << david
    assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log
    ar.developers_with_callbacks << david
    assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}", 
                  "after_adding#{david.id}"], ar.developers_log
  end
  
  def test_has_and_belongs_to_many_remove_callback
    david = developers(:david)
    jamis = developers(:jamis)
    activerecord = projects(:active_record)
    assert activerecord.developers_log.empty?
    activerecord.developers_with_callbacks.delete(david)
    assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log
    
    activerecord.developers_with_callbacks.delete(jamis)
    assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}", 
                  "after_removing#{jamis.id}"], activerecord.developers_log
  end

  def test_has_and_belongs_to_many_remove_callback_on_clear
    activerecord = projects(:active_record)
    assert activerecord.developers_log.empty?
    if activerecord.developers_with_callbacks.size == 0
      activerecord.developers << developers(:david)
      activerecord.developers << developers(:jamis)
      activerecord.reload
      assert activerecord.developers_with_callbacks.size == 2
    end
    log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort
    assert activerecord.developers_with_callbacks.clear
    assert_equal log_array, activerecord.developers_log.sort
  end
  
  def test_dont_add_if_before_callback_raises_exception
    assert !@david.unchangable_posts.include?(@authorless)
    begin
      @david.unchangable_posts << @authorless
    rescue Exception => e
    end
    assert @david.post_log.empty?
    assert !@david.unchangable_posts.include?(@authorless)
    @david.reload
    assert !@david.unchangable_posts.include?(@authorless)
  end
  
  def test_push_with_attributes
    david = developers(:david)
    activerecord = projects(:active_record)
    assert activerecord.developers_log.empty?
    activerecord.developers_with_callbacks.push_with_attributes(david, {})
    assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], activerecord.developers_log
    activerecord.developers_with_callbacks.push_with_attributes(david, {})
    assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}", 
                  "after_adding#{david.id}"], activerecord.developers_log
  end
end

require 'abstract_unit'
require 'fixtures/company'

class AssociationInheritanceReloadTest < Test::Unit::TestCase
  fixtures :companies

  def test_set_attributes
    assert_equal ["errors.add_on_empty('name', \"can't be empty\")"], Firm.read_inheritable_attribute("validate"), "Second run"
    # ActiveRecord::Base.reset_column_information_and_inheritable_attributes_for_all_subclasses
    remove_subclass_of(ActiveRecord::Base)
    load 'fixtures/company.rb'
    assert_equal ["errors.add_on_empty('name', \"can't be empty\")"], Firm.read_inheritable_attribute("validate"), "Second run"
  end
endrequire 'abstract_unit'
require 'active_record/acts/list'
require 'fixtures/post'
require 'fixtures/comment'
require 'fixtures/author'
require 'fixtures/category'
require 'fixtures/categorization'
require 'fixtures/mixin'
require 'fixtures/company'
require 'fixtures/topic'
require 'fixtures/reply'

class CascadedEagerLoadingTest < Test::Unit::TestCase
  fixtures :authors, :mixins, :companies, :posts, :categorizations, :topics

  def test_eager_association_loading_with_cascaded_two_levels
    authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
    assert_equal 2, authors.size
    assert_equal 5, authors[0].posts.size
    assert_equal 1, authors[1].posts.size
    assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  end

  def test_eager_association_loading_with_cascaded_two_levels_and_one_level
    authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
    assert_equal 2, authors.size
    assert_equal 5, authors[0].posts.size
    assert_equal 1, authors[1].posts.size
    assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
    assert_equal 1, authors[0].categorizations.size
    assert_equal 1, authors[1].categorizations.size
  end

  def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
    authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
    assert_equal 2, authors.size
    assert_equal 5, authors[0].posts.size
    assert_equal 1, authors[1].posts.size
    assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  end

  def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
    authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
    assert_equal 2, authors.size
    assert_equal 5, authors[0].posts.size
    assert_equal authors(:david).name, authors[0].name
    assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
  end

  def test_eager_association_loading_with_cascaded_two_levels_with_condition
    authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id")
    assert_equal 1, authors.size
    assert_equal 5, authors[0].posts.size
  end

  def test_eager_association_loading_with_acts_as_tree
    roots = TreeMixin.find(:all, :include=>"children", :conditions=>"mixins.parent_id IS NULL", :order=>"mixins.id")
    assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], roots
    assert_no_queries do
      assert_equal 2, roots[0].children.size
      assert_equal 0, roots[1].children.size
      assert_equal 0, roots[2].children.size
    end
  end

  def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
    firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id")
    assert_equal 2, firms.size
    assert_equal firms.first.account, firms.first.account.firm.account
    assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
    assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account }
  end

  def test_eager_association_loading_with_has_many_sti
    topics = Topic.find(:all, :include => :replies, :order => 'topics.id')
    assert_equal [topics(:first), topics(:second)], topics
    assert_no_queries do
      assert_equal 1, topics[0].replies.size
      assert_equal 0, topics[1].replies.size
    end
  end

  def test_eager_association_loading_with_belongs_to_sti
    replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
    assert_equal [topics(:second)], replies
    assert_equal topics(:first), assert_no_queries { replies.first.topic }
  end

  def test_eager_association_loading_with_multiple_stis_and_order
    author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
    assert_equal authors(:david), author
    assert_no_queries do
      author.posts.first.special_comments
      author.posts.first.very_special_comment
    end
  end
  
  def test_eager_association_loading_of_stis_with_multiple_references
    authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
    assert_equal [authors(:david)], authors
    assert_no_queries do
      authors.first.posts.first.special_comments.first.post.special_comments
      authors.first.posts.first.special_comments.first.post.very_special_comment
    end
  end
end
require 'abstract_unit'
require 'fixtures/post'
require 'fixtures/comment'
require 'fixtures/project'
require 'fixtures/developer'

class AssociationsExtensionsTest < Test::Unit::TestCase
  fixtures :projects, :developers, :developers_projects, :comments, :posts

  def test_extension_on_has_many
    assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent
  end
  
  def test_extension_on_habtm
    assert_equal projects(:action_controller), developers(:david).projects.find_most_recent
  end

  def test_named_extension_on_habtm
    assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent
  end
  
  def test_marshalling_extensions
    david = developers(:david)
    assert_equal projects(:action_controller), david.projects.find_most_recent

    david = Marshal.load(Marshal.dump(david))
    assert_equal projects(:action_controller), david.projects.find_most_recent
  end

  def test_marshalling_named_extensions
    david = developers(:david)
    assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent

    david = Marshal.load(Marshal.dump(david))
    assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
  end
endrequire 'abstract_unit'
require 'fixtures/post'
require 'fixtures/comment'
require 'fixtures/author'
require 'fixtures/category'
require 'fixtures/company'
require 'fixtures/person'
require 'fixtures/reader'

class EagerAssociationTest < Test::Unit::TestCase
  fixtures :posts, :comments, :authors, :categories, :categories_posts,
            :companies, :accounts, :tags, :people, :readers

  def test_loading_with_one_association
    posts = Post.find(:all, :include => :comments)
    post = posts.find { |p| p.id == 1 }
    assert_equal 2, post.comments.size
    assert post.comments.include?(comments(:greetings))

    post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'")
    assert_equal 2, post.comments.size
    assert post.comments.include?(comments(:greetings))
  end

  def test_loading_conditions_with_or
    posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
    assert_nil posts.detect { |p| p.author_id != authors(:david).id },
      "expected to find only david's posts"
  end

  def test_with_ordering
    list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
    [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments,
     :authorless, :thinking, :welcome
    ].each_with_index do |post, index|
      assert_equal posts(post), list[index]
    end
  end

  def test_loading_with_multiple_associations
    posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id")
    assert_equal 2, posts.first.comments.size
    assert_equal 2, posts.first.categories.size
    assert posts.first.comments.include?(comments(:greetings))
  end

  def test_loading_from_an_association
    posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id")
    assert_equal 2, posts.first.comments.size
  end

  def test_loading_with_no_associations
    assert_nil Post.find(posts(:authorless).id, :include => :author).author
  end

  def test_eager_association_loading_with_belongs_to
    comments = Comment.find(:all, :include => :post)
    assert_equal 10, comments.length
    titles = comments.map { |c| c.post.title }
    assert titles.include?(posts(:welcome).title)
    assert titles.include?(posts(:sti_post_and_comments).title)
  end
  
  def test_eager_association_loading_with_belongs_to_and_limit
    comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id')
    assert_equal 5, comments.length
    assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
  end

  def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
    comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id')
    assert_equal 3, comments.length
    assert_equal [5,6,7], comments.collect { |c| c.id }
  end

  def test_eager_association_loading_with_belongs_to_and_limit_and_offset
    comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id')
    assert_equal 3, comments.length
    assert_equal [3,5,6], comments.collect { |c| c.id }
  end

  def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
    comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id')
    assert_equal 3, comments.length
    assert_equal [6,7,8], comments.collect { |c| c.id }
  end
  
  def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
    comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id')
    assert_equal 3, comments.length
    assert_equal [6,7,8], comments.collect { |c| c.id }
  end

  def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
    posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id')
    assert_equal 1, posts.length
    assert_equal [1], posts.collect { |p| p.id }
  end
  
  def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
    posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id')
    assert_equal 1, posts.length
    assert_equal [2], posts.collect { |p| p.id }
  end
  
  def test_eager_with_has_many_through
    posts_with_comments = people(:michael).posts.find(:all, :include => :comments )
    posts_with_author = people(:michael).posts.find(:all, :include => :author )
    posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ])
    assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
    assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
    assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
  end

  def test_eager_with_has_many_and_limit
    posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
    assert_equal 2, posts.size
    assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
  end

  def test_eager_with_has_many_and_limit_and_conditions
    posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id")
    assert_equal 2, posts.size
    assert_equal [4,5], posts.collect { |p| p.id }
  end

  def test_eager_with_has_many_and_limit_and_conditions_array
    posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id")
    assert_equal 2, posts.size
    assert_equal [4,5], posts.collect { |p| p.id }    
  end

  def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
    posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
    assert_equal 2, posts.size
    
    count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
    assert_equal count, posts.size
  end

  def test_eager_with_has_many_and_limit_ond_high_offset
    posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
    assert_equal 0, posts.size
  end

  def test_count_eager_with_has_many_and_limit_ond_high_offset
    posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
    assert_equal 0, posts
  end

  def test_eager_with_has_many_and_limit_with_no_results
    posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'")
    assert_equal 0, posts.size
  end

  def test_eager_with_has_and_belongs_to_many_and_limit
    posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
    assert_equal 3, posts.size
    assert_equal 2, posts[0].categories.size
    assert_equal 1, posts[1].categories.size
    assert_equal 0, posts[2].categories.size
    assert posts[0].categories.include?(categories(:technology))
    assert posts[1].categories.include?(categories(:general))
  end

  def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
    posts = authors(:david).posts.find(:all, 
      :include    => :comments, 
      :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
      :limit      => 2
    )
    assert_equal 2, posts.size
    
    count = Post.count(
      :include    => [ :comments, :author ], 
      :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
      :limit      => 2
    )
    assert_equal count, posts.size
  end

  def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
    posts = nil
    Post.with_scope(:find => {
      :include    => :comments, 
      :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'"
    }) do
      posts = authors(:david).posts.find(:all, :limit => 2)
      assert_equal 2, posts.size
    end
    
    Post.with_scope(:find => {
      :include    => [ :comments, :author ], 
      :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')"
    }) do
      count = Post.count(:limit => 2)
      assert_equal count, posts.size
    end
  end

  def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
    Post.with_scope(:find => { :conditions => "1=1" }) do
      posts = authors(:david).posts.find(:all, 
        :include    => :comments, 
        :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
        :limit      => 2
      )
      assert_equal 2, posts.size
      
      count = Post.count(
        :include    => [ :comments, :author ], 
        :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
        :limit      => 2
      )
      assert_equal count, posts.size
    end
  end
  def test_eager_association_loading_with_habtm
    posts = Post.find(:all, :include => :categories, :order => "posts.id")
    assert_equal 2, posts[0].categories.size
    assert_equal 1, posts[1].categories.size
    assert_equal 0, posts[2].categories.size
    assert posts[0].categories.include?(categories(:technology))
    assert posts[1].categories.include?(categories(:general))
  end

  def test_eager_with_inheritance
    posts = SpecialPost.find(:all, :include => [ :comments ])
  end

  def test_eager_has_one_with_association_inheritance
    post = Post.find(4, :include => [ :very_special_comment ])
    assert_equal "VerySpecialComment", post.very_special_comment.class.to_s
  end

  def test_eager_has_many_with_association_inheritance
    post = Post.find(4, :include => [ :special_comments ])
    post.special_comments.each do |special_comment|
      assert_equal "SpecialComment", special_comment.class.to_s
    end
  end

  def test_eager_habtm_with_association_inheritance
    post = Post.find(6, :include => [ :special_categories ])
    assert_equal 1, post.special_categories.size
    post.special_categories.each do |special_category|
      assert_equal "SpecialCategory", special_category.class.to_s
    end
  end

  def test_eager_with_has_one_dependent_does_not_destroy_dependent
    assert_not_nil companies(:first_firm).account
    f = Firm.find(:first, :include => :account,
            :conditions => ["companies.name = ?", "37signals"])
    assert_not_nil f.account
    assert_equal companies(:first_firm, :reload).account, f.account
  end

  def test_eager_with_invalid_association_reference
    assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys") {
      post = Post.find(6, :include=> :monkeys )
    }
    assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys") {
      post = Post.find(6, :include=>[ :monkeys ])
    }
    assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys") {
      post = Post.find(6, :include=>[ 'monkeys' ])
    }
    assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys, :elephants") {
      post = Post.find(6, :include=>[ :monkeys, :elephants ])
    }
  end
  
  def find_all_ordered(className, include=nil)
    className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include)
  end

  def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm
    # Eager includes of has many and habtm associations aren't necessarily sorted in the same way
    def assert_equal_after_sort(item1, item2, item3 = nil)
      assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id})
      assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3
    end
    # Test regular association, association with conditions, association with
    # STI, and association with conditions assured not to be true
    post_types = [:posts, :hello_posts, :special_posts, :nonexistent_posts]
    # test both has_many and has_and_belongs_to_many
    [Author, Category].each do |className|
      d1 = find_all_ordered(className)
      # test including all post types at once
      d2 = find_all_ordered(className, post_types) 
      d1.each_index do |i| 
        assert_equal(d1[i], d2[i])
        assert_equal_after_sort(d1[i].posts, d2[i].posts)
        post_types[1..-1].each do |post_type|
          # test including post_types together
          d3 = find_all_ordered(className, [:posts, post_type])
          assert_equal(d1[i], d3[i])
          assert_equal_after_sort(d1[i].posts, d3[i].posts)
          assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type))
        end
      end
    end
  end
  
  def test_eager_with_multiple_associations_with_same_table_has_one
    d1 = find_all_ordered(Firm)
    d2 = find_all_ordered(Firm, :account)
    d1.each_index do |i| 
      assert_equal(d1[i], d2[i])
      assert_equal(d1[i].account, d2[i].account)
    end
  end
  
  def test_eager_with_multiple_associations_with_same_table_belongs_to
    firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition]
    d1 = find_all_ordered(Client)
    d2 = find_all_ordered(Client, firm_types)
    d1.each_index do |i| 
      assert_equal(d1[i], d2[i])
      firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) }
    end
  end
  def test_eager_with_valid_association_as_string_not_symbol
    assert_nothing_raised { Post.find(:all, :include => 'comments') }
  end

  def test_preconfigured_includes_with_belongs_to
    author = posts(:welcome).author_with_posts
    assert_equal 5, author.posts.size
  end

  def test_preconfigured_includes_with_has_one
    comment = posts(:sti_comments).very_special_comment_with_post
    assert_equal posts(:sti_comments), comment.post
  end

  def test_preconfigured_includes_with_has_many
    posts = authors(:david).posts_with_comments
    one = posts.detect { |p| p.id == 1 }
    assert_equal 5, posts.size
    assert_equal 2, one.comments.size
  end

  def test_preconfigured_includes_with_habtm
    posts = authors(:david).posts_with_categories
    one = posts.detect { |p| p.id == 1 }
    assert_equal 5, posts.size
    assert_equal 2, one.categories.size
  end

  def test_preconfigured_includes_with_has_many_and_habtm
    posts = authors(:david).posts_with_comments_and_categories
    one = posts.detect { |p| p.id == 1 }
    assert_equal 5, posts.size
    assert_equal 2, one.comments.size
    assert_equal 2, one.categories.size
  end
end
require 'abstract_unit'
require 'fixtures/tag'
require 'fixtures/tagging'
require 'fixtures/post'
require 'fixtures/comment'
require 'fixtures/author'
require 'fixtures/category'
require 'fixtures/categorization'

class AssociationsJoinModelTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false
  fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites

  def test_has_many
    assert_equal categories(:general), authors(:david).categories.first
  end
  
  def test_has_many_inherited
    assert_equal categories(:sti_test), authors(:mary).categories.first
  end

  def test_inherited_has_many
    assert_equal authors(:mary), categories(:sti_test).authors.first
  end
  
  def test_polymorphic_has_many
    assert_equal taggings(:welcome_general), posts(:welcome).taggings.first
  end
  
  def test_polymorphic_has_one
    assert_equal taggings(:welcome_general), posts(:welcome).tagging
  end

  def test_polymorphic_belongs_to
    assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable
  end

  def test_polymorphic_has_many_going_through_join_model
    assert_equal tags(:general), tag = posts(:welcome).tags.first
    assert_no_queries do
      tag.tagging
    end
  end

  def test_count_polymorphic_has_many
    assert_equal 1, posts(:welcome).taggings.count
    assert_equal 1, posts(:welcome).tags.count
  end

  def test_polymorphic_has_many_going_through_join_model_with_find
    assert_equal tags(:general), tag = posts(:welcome).tags.find(:first)
    assert_no_queries do
      tag.tagging
    end
  end

  def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection
    assert_equal tags(:general), tag = posts(:welcome).funky_tags.first
    assert_no_queries do
      tag.tagging
    end
  end

  def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find
    assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first)
    assert_no_queries do
      tag.tagging
    end
  end

  def test_polymorphic_has_many_going_through_join_model_with_disabled_include
    assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
    assert_queries 1 do
      tag.tagging
    end
  end

  def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
    assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
    tag.author_id
  end

  def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key
    assert_equal tags(:misc), taggings(:welcome_general).super_tag
    assert_equal tags(:misc), posts(:welcome).super_tags.first
  end

  def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class
    post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body'
    assert_instance_of SubStiPost, post
    
    tagging = tags(:misc).taggings.create(:taggable => post)
    assert_equal "SubStiPost", tagging.taggable_type
  end

  def test_polymorphic_has_many_going_through_join_model_with_inheritance
    assert_equal tags(:general), posts(:thinking).tags.first
  end

  def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name
    assert_equal tags(:general), posts(:thinking).funky_tags.first
  end

  def test_polymorphic_has_many_create_model_with_inheritance
    post = posts(:thinking)
    assert_instance_of SpecialPost, post
    
    tagging = tags(:misc).taggings.create(:taggable => post)
    assert_equal "Post", tagging.taggable_type
  end

  def test_polymorphic_has_one_create_model_with_inheritance
    tagging = tags(:misc).create_tagging(:taggable => posts(:thinking))
    assert_equal "Post", tagging.taggable_type
  end

  def test_set_polymorphic_has_many
    tagging = tags(:misc).taggings.create
    posts(:thinking).taggings << tagging
    assert_equal "Post", tagging.taggable_type
  end

  def test_set_polymorphic_has_one
    tagging = tags(:misc).taggings.create
    posts(:thinking).tagging = tagging
    assert_equal "Post", tagging.taggable_type
  end

  def test_create_polymorphic_has_many_with_scope
    old_count = posts(:welcome).taggings.count
    tagging = posts(:welcome).taggings.create(:tag => tags(:misc))
    assert_equal "Post", tagging.taggable_type
    assert_equal old_count+1, posts(:welcome).taggings.count
  end

  def test_create_polymorphic_has_one_with_scope
    old_count = Tagging.count
    tagging = posts(:welcome).tagging.create(:tag => tags(:misc))
    assert_equal "Post", tagging.taggable_type
    assert_equal old_count+1, Tagging.count
  end

  def test_delete_polymorphic_has_many_with_delete_all
    assert_equal 1, posts(:welcome).taggings.count
    posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll'
    post = find_post_with_dependency(1, :has_many, :taggings, :delete_all)

    old_count = Tagging.count
    post.destroy
    assert_equal old_count-1, Tagging.count
    assert_equal 0, posts(:welcome).taggings.count
  end

  def test_delete_polymorphic_has_many_with_destroy
    assert_equal 1, posts(:welcome).taggings.count
    posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy'
    post = find_post_with_dependency(1, :has_many, :taggings, :destroy)

    old_count = Tagging.count
    post.destroy
    assert_equal old_count-1, Tagging.count
    assert_equal 0, posts(:welcome).taggings.count
  end

  def test_delete_polymorphic_has_many_with_nullify
    assert_equal 1, posts(:welcome).taggings.count
    posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify'
    post = find_post_with_dependency(1, :has_many, :taggings, :nullify)

    old_count = Tagging.count
    post.destroy
    assert_equal old_count, Tagging.count
    assert_equal 0, posts(:welcome).taggings.count
  end

  def test_delete_polymorphic_has_one_with_destroy
    assert posts(:welcome).tagging
    posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy'
    post = find_post_with_dependency(1, :has_one, :tagging, :destroy)

    old_count = Tagging.count
    post.destroy
    assert_equal old_count-1, Tagging.count
    assert_nil posts(:welcome).tagging(true)
  end

  def test_delete_polymorphic_has_one_with_nullify
    assert posts(:welcome).tagging
    posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify'
    post = find_post_with_dependency(1, :has_one, :tagging, :nullify)

    old_count = Tagging.count
    post.destroy
    assert_equal old_count, Tagging.count
    assert_nil posts(:welcome).tagging(true)
  end

  def test_has_many_with_piggyback
    assert_equal "2", categories(:sti_test).authors.first.post_id.to_s
  end

  def test_include_has_many_through
    posts              = Post.find(:all, :order => 'posts.id')
    posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id')
    assert_equal posts.length, posts_with_authors.length
    posts.length.times do |i|
      assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length }
    end
  end

  def test_include_polymorphic_has_one
    post    = Post.find_by_id(posts(:welcome).id, :include => :tagging)
    tagging = taggings(:welcome_general)
    assert_no_queries do
      assert_equal tagging, post.tagging
    end
  end

  def test_include_polymorphic_has_many_through
    posts           = Post.find(:all, :order => 'posts.id')
    posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
    assert_equal posts.length, posts_with_tags.length
    posts.length.times do |i|
      assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
    end
  end

  def test_include_polymorphic_has_many
    posts               = Post.find(:all, :order => 'posts.id')
    posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
    assert_equal posts.length, posts_with_taggings.length
    posts.length.times do |i|
      assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
    end
  end

  def test_has_many_find_all
    assert_equal [categories(:general)], authors(:david).categories.find(:all)
  end
  
  def test_has_many_find_first
    assert_equal categories(:general), authors(:david).categories.find(:first)
  end
  
  def test_has_many_find_conditions
    assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'")
    assert_equal nil, authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'")
  end
  
  def test_has_many_class_methods_called_by_method_missing
    assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first
#    assert_equal nil, authors(:david).categories.find_by_name('Technology')
  end

  def test_has_many_going_through_join_model_with_custom_foreign_key
    assert_equal [], posts(:thinking).authors
    assert_equal [authors(:mary)], posts(:authorless).authors
  end

  def test_belongs_to_polymorphic_with_counter_cache
    assert_equal 0, posts(:welcome)[:taggings_count]
    tagging = posts(:welcome).taggings.create(:tag => tags(:general))
    assert_equal 1, posts(:welcome, :reload)[:taggings_count]
    tagging.destroy
    assert posts(:welcome, :reload)[:taggings_count].zero?
  end

  def test_unavailable_through_reflection
    assert_raises (ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings }
  end

  def test_has_many_through_join_model_with_conditions
    assert_equal [], posts(:welcome).invalid_taggings
    assert_equal [], posts(:welcome).invalid_tags
  end

  def test_has_many_polymorphic
    assert_raises ActiveRecord::HasManyThroughAssociationPolymorphicError do
      assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggables
    end
    assert_raises ActiveRecord::EagerLoadPolymorphicError do
      assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggings.find(:all, :include => :taggable)
    end
  end

  def test_has_many_through_has_many_find_all
    assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first
  end

  def test_has_many_through_has_many_find_all_with_custom_class
    assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first
  end

  def test_has_many_through_has_many_find_first
    assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id')
  end

  def test_has_many_through_has_many_find_conditions
    assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, :conditions => "comments.type='SpecialComment'", :order => 'comments.id')
  end

  def test_has_many_through_has_many_find_by_id
    assert_equal comments(:more_greetings), authors(:david).comments.find(2)
  end

  def test_has_many_through_polymorphic_has_one
    assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging }
  end

  def test_has_many_through_polymorphic_has_many
    assert_equal [taggings(:welcome_general), taggings(:thinking_general)], authors(:david).taggings.uniq.sort_by { |t| t.id }
  end

  def test_include_has_many_through_polymorphic_has_many
    author            = Author.find_by_id(authors(:david).id, :include => :taggings)
    expected_taggings = [taggings(:welcome_general), taggings(:thinking_general)]
    assert_no_queries do
      assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
    end
  end

  def test_has_many_through_has_many_through
    assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags }
  end

  def test_has_many_through_habtm
    assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories }
  end

  def test_eager_load_has_many_through_has_many
    author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
    SpecialComment.new; VerySpecialComment.new
    assert_no_queries do
      assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id)
    end
  end

  def test_eager_belongs_to_and_has_one_not_singularized
    assert_nothing_raised do
      Author.find(:first, :include => :author_address)
      AuthorAddress.find(:first, :include => :author)
    end
  end

  def test_self_referential_has_many_through
    assert_equal [authors(:mary)], authors(:david).favorite_authors
    assert_equal [],               authors(:mary).favorite_authors
  end

  def test_add_to_self_referential_has_many_through
    new_author = Author.create(:name => "Bob")
    authors(:david).author_favorites.create :favorite_author => new_author
    assert_equal new_author, authors(:david).reload.favorite_authors.first
  end

  def test_has_many_through_uses_correct_attributes
    assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"]
  end

  private
    # create dynamic Post models to allow different dependency options
    def find_post_with_dependency(post_id, association, association_name, dependency)
      class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
      Post.find(post_id).update_attribute :type, class_name
      klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
      klass.set_table_name 'posts'
      klass.send(association, association_name, :as => :taggable, :dependent => dependency)
      klass.find(post_id)
    end
end
require 'abstract_unit'
require 'fixtures/developer'
require 'fixtures/project'
require 'fixtures/company'
require 'fixtures/topic'
require 'fixtures/reply'
require 'fixtures/computer'
require 'fixtures/customer'
require 'fixtures/order'
require 'fixtures/category'
require 'fixtures/post'
require 'fixtures/author'

# Can't declare new classes in test case methods, so tests before that
bad_collection_keys = false
begin
  class Car < ActiveRecord::Base; has_many :wheels, :name => "wheels"; end
rescue ArgumentError
  bad_collection_keys = true
end
raise "ActiveRecord should have barked on bad collection keys" unless bad_collection_keys


class AssociationsTest < Test::Unit::TestCase
  fixtures :accounts, :companies, :developers, :projects, :developers_projects,
           :computers

  def test_force_reload
    firm = Firm.new("name" => "A New Firm, Inc")
    firm.save
    firm.clients.each {|c|} # forcing to load all clients
    assert firm.clients.empty?, "New firm shouldn't have client objects"
    assert !firm.has_clients?, "New firm shouldn't have clients"
    assert_equal 0, firm.clients.size, "New firm should have 0 clients"

    client = Client.new("name" => "TheClient.com", "firm_id" => firm.id)
    client.save

    assert firm.clients.empty?, "New firm should have cached no client objects"
    assert !firm.has_clients?, "New firm should have cached a no-clients response"
    assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"

    assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
    assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
  end

  def test_storing_in_pstore
    require "tmpdir"
    store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test")
    File.delete(store_filename) if File.exists?(store_filename)
    require "pstore"
    apple = Firm.create("name" => "Apple")
    natural = Client.new("name" => "Natural Company")
    apple.clients << natural

    db = PStore.new(store_filename)
    db.transaction do
      db["apple"] = apple
    end

    db = PStore.new(store_filename)
    db.transaction do
      assert_equal "Natural Company", db["apple"].clients.first.name
    end
  end
end

class HasOneAssociationsTest < Test::Unit::TestCase
  fixtures :accounts, :companies, :developers, :projects, :developers_projects
  
  def test_has_one
    assert_equal companies(:first_firm).account, Account.find(1)
    assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
  end

  def test_proxy_assignment
    company = companies(:first_firm)
    assert_nothing_raised { company.account = company.account }
  end

  def test_triple_equality
    assert Account === companies(:first_firm).account
    assert companies(:first_firm).account === Account
  end

  def test_type_mismatch
    assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 }
    assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) }
  end

  def test_natural_assignment
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    apple.account = citibank
    assert_equal apple.id, citibank.firm_id
  end
  
  def test_natural_assignment_to_nil
    old_account_id = companies(:first_firm).account.id
    companies(:first_firm).account = nil
    companies(:first_firm).save
    assert_nil companies(:first_firm).account
    # account is dependent, therefore is destroyed when reference to owner is lost
    assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) } 
  end
  
  def test_assignment_without_replacement
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    apple.account = citibank
    assert_equal apple.id, citibank.firm_id
    
    hsbc = apple.build_account({ :credit_limit => 20}, false)
    assert_equal apple.id, hsbc.firm_id
    hsbc.save
    assert_equal apple.id, citibank.firm_id

    nykredit = apple.create_account({ :credit_limit => 30}, false)
    assert_equal apple.id, nykredit.firm_id
    assert_equal apple.id, citibank.firm_id
    assert_equal apple.id, hsbc.firm_id
  end

  def test_assignment_without_replacement_on_create
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    apple.account = citibank
    assert_equal apple.id, citibank.firm_id
    
    hsbc = apple.create_account({:credit_limit => 10}, false)
    assert_equal apple.id, hsbc.firm_id
    hsbc.save
    assert_equal apple.id, citibank.firm_id
  end

  def test_dependence
    num_accounts = Account.count
    firm = Firm.find(1)
    assert !firm.account.nil?
    firm.destroy                
    assert_equal num_accounts - 1, Account.count
  end

  def test_succesful_build_association
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save

    account = firm.build_account("credit_limit" => 1000)
    assert account.save
    assert_equal account, firm.account
  end

  def test_failing_build_association
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save

    account = firm.build_account
    assert !account.save
    assert_equal "can't be empty", account.errors.on("credit_limit")
  end

  def test_build_association_twice_without_saving_affects_nothing
    count_of_account = Account.count
    firm = Firm.find(:first)
    account1 = firm.build_account("credit_limit" => 1000)
    account2 = firm.build_account("credit_limit" => 2000)

    assert_equal count_of_account, Account.count
  end

  def test_create_association
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save
    assert_equal firm.create_account("credit_limit" => 1000), firm.account
  end

  def test_build
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save

    firm.account = account = Account.new("credit_limit" => 1000)
    assert_equal account, firm.account
    assert account.save
    assert_equal account, firm.account
  end

  def test_build_before_child_saved
    firm = Firm.find(1)

    account = firm.account.build("credit_limit" => 1000)
    assert_equal account, firm.account
    assert account.new_record?
    assert firm.save
    assert_equal account, firm.account
    assert !account.new_record?
  end

  def test_build_before_either_saved
    firm = Firm.new("name" => "GlobalMegaCorp")

    firm.account = account = Account.new("credit_limit" => 1000)
    assert_equal account, firm.account
    assert account.new_record?
    assert firm.save
    assert_equal account, firm.account
    assert !account.new_record?
  end

  def test_failing_build_association
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save
    
    firm.account = account = Account.new
    assert_equal account, firm.account
    assert !account.save
    assert_equal account, firm.account
    assert_equal "can't be empty", account.errors.on("credit_limit")
  end

  def test_create
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save
    firm.account = account = Account.create("credit_limit" => 1000)
    assert_equal account, firm.account
  end

  def test_create_before_save
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.account = account = Account.create("credit_limit" => 1000)
    assert_equal account, firm.account
  end

  def test_dependence_with_missing_association
    Account.destroy_all
    firm = Firm.find(1)
    assert firm.account.nil?
    firm.destroy
  end

  def test_assignment_before_parent_saved
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.account = a = Account.find(1)
    assert firm.new_record?
    assert_equal a, firm.account
    assert firm.save
    assert_equal a, firm.account
    assert_equal a, firm.account(true)
  end

  def test_assignment_before_child_saved
    firm = Firm.find(1)
    firm.account = a = Account.new("credit_limit" => 1000)
    assert !a.new_record?
    assert_equal a, firm.account
    assert_equal a, firm.account
    assert_equal a, firm.account(true)
  end

  def test_assignment_before_either_saved
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.account = a = Account.new("credit_limit" => 1000)
    assert firm.new_record?
    assert a.new_record?
    assert_equal a, firm.account
    assert firm.save
    assert !firm.new_record?
    assert !a.new_record?
    assert_equal a, firm.account
    assert_equal a, firm.account(true)
  end
end


class HasManyAssociationsTest < Test::Unit::TestCase
  fixtures :accounts, :companies, :developers, :projects,
           :developers_projects, :topics

  def setup
    Client.destroyed_client_ids.clear
  end

  def force_signal37_to_load_all_clients_of_firm
    companies(:first_firm).clients_of_firm.each {|f| }
  end

  def test_counting
    assert_equal 2, Firm.find(:first).clients.count
  end
  
  def test_finding
    assert_equal 2, Firm.find(:first).clients.length
  end

  def test_find_many_with_merged_options
    assert_equal 1, companies(:first_firm).limited_clients.size
    assert_equal 1, companies(:first_firm).limited_clients.find(:all).size
    assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size
  end

  def test_triple_equality
    assert !(Array === Firm.find(:first).clients)
    assert Firm.find(:first).clients === Array
  end

  def test_finding_default_orders
    assert_equal "Summit", Firm.find(:first).clients.first.name
  end

  def test_finding_with_different_class_name_and_order
    assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name
  end

  def test_finding_with_foreign_key
    assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name
  end

  def test_finding_with_condition
    assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name
  end

  def test_finding_using_sql
    firm = Firm.find(:first)
    first_client = firm.clients_using_sql.first
    assert_not_nil first_client
    assert_equal "Microsoft", first_client.name
    assert_equal 1, firm.clients_using_sql.size
    assert_equal 1, Firm.find(:first).clients_using_sql.size
  end

  def test_counting_using_sql
    assert_equal 1, Firm.find(:first).clients_using_counter_sql.size
    assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size
  end

  def test_counting_non_existant_items_using_sql
    assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size
  end
  
  def test_belongs_to_sanity
    c = Client.new
    assert_nil c.firm

    if c.firm
      assert false, "belongs_to failed if check"
    end

    unless c.firm
    else
      assert false,  "belongs_to failed unless check"
    end
  end

  def test_find_ids
    firm = Firm.find(:first)

    assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find }

    client = firm.clients.find(2)
    assert_kind_of Client, client

    client_ary = firm.clients.find([2])
    assert_kind_of Array, client_ary
    assert_equal client, client_ary.first

    client_ary = firm.clients.find(2, 3)
    assert_kind_of Array, client_ary
    assert_equal 2, client_ary.size
    assert_equal client, client_ary.first

    assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) }
  end

  def test_find_all
    firm = Firm.find_first
    assert_equal firm.clients, firm.clients.find_all
    assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length
    assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length
  end

  def test_find_all_sanitized
    firm = Firm.find_first
    assert_equal firm.clients.find_all("name = 'Summit'"), firm.clients.find_all(["name = '%s'", "Summit"])
    summit = firm.clients.find(:all, :conditions => "name = 'Summit'")
    assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"])
    assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }])
  end

  def test_find_first
    firm = Firm.find_first
    client2 = Client.find(2)
    assert_equal firm.clients.first, firm.clients.find_first
    assert_equal client2, firm.clients.find_first("#{QUOTED_TYPE} = 'Client'")
    assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'")
  end

  def test_find_first_sanitized
    firm = Firm.find_first
    client2 = Client.find(2)
    assert_equal client2, firm.clients.find_first(["#{QUOTED_TYPE} = ?", "Client"])
    assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'])
    assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }])
  end

  def test_find_in_collection
    assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
    assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
  end

  def test_find_grouped
    all_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1")
    grouped_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count')
    assert_equal 2, all_clients_of_firm1.size
    assert_equal 1, grouped_clients_of_firm1.size
  end

  def test_adding
    force_signal37_to_load_all_clients_of_firm
    natural = Client.new("name" => "Natural Company")
    companies(:first_firm).clients_of_firm << natural
    assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection
    assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db
    assert_equal natural, companies(:first_firm).clients_of_firm.last
  end
  
  def test_adding_a_mismatch_class
    assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil }
    assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 }
    assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) }
  end
  
  def test_adding_a_collection
    force_signal37_to_load_all_clients_of_firm
    companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
    assert_equal 3, companies(:first_firm).clients_of_firm.size
    assert_equal 3, companies(:first_firm).clients_of_firm(true).size
  end

  def test_adding_before_save
    no_of_firms = Firm.count
    no_of_clients = Client.count
    new_firm = Firm.new("name" => "A New Firm, Inc")
    new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
    new_firm.clients_of_firm << (c = Client.new("name" => "Apple"))
    assert new_firm.new_record?
    assert c.new_record?
    assert_equal 2, new_firm.clients_of_firm.size
    assert_equal no_of_firms, Firm.count      # Firm was not saved to database.
    assert_equal no_of_clients, Client.count  # Clients were not saved to database.
    assert new_firm.save
    assert !new_firm.new_record?
    assert !c.new_record?
    assert_equal new_firm, c.firm
    assert_equal no_of_firms+1, Firm.count      # Firm was saved to database.
    assert_equal no_of_clients+2, Client.count  # Clients were saved to database.
    assert_equal 2, new_firm.clients_of_firm.size
    assert_equal 2, new_firm.clients_of_firm(true).size
  end

  def test_invalid_adding
    firm = Firm.find(1)
    assert !(firm.clients_of_firm << c = Client.new)
    assert c.new_record?
    assert !firm.valid?
    assert !firm.save
    assert c.new_record?
  end

  def test_invalid_adding_before_save
    no_of_firms = Firm.count
    no_of_clients = Client.count
    new_firm = Firm.new("name" => "A New Firm, Inc")
    new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
    assert c.new_record?
    assert !c.valid?
    assert !new_firm.valid?
    assert !new_firm.save
    assert c.new_record?
    assert new_firm.new_record?
  end

  def test_build
    new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client")
    assert_equal "Another Client", new_client.name
    assert new_client.new_record?
    assert_equal new_client, companies(:first_firm).clients_of_firm.last
    assert companies(:first_firm).save
    assert !new_client.new_record?
    assert_equal 2, companies(:first_firm).clients_of_firm(true).size
  end

  def test_build_many
    new_clients = companies(:first_firm).clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}])
    assert_equal 2, new_clients.size

    assert companies(:first_firm).save
    assert_equal 3, companies(:first_firm).clients_of_firm(true).size
  end

  def test_invalid_build
    new_client = companies(:first_firm).clients_of_firm.build
    assert new_client.new_record?
    assert !new_client.valid?
    assert_equal new_client, companies(:first_firm).clients_of_firm.last
    assert !companies(:first_firm).save
    assert new_client.new_record?
    assert_equal 1, companies(:first_firm).clients_of_firm(true).size
  end
  
  def test_create
    force_signal37_to_load_all_clients_of_firm
    new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
    assert !new_client.new_record?
    assert_equal new_client, companies(:first_firm).clients_of_firm.last
    assert_equal new_client, companies(:first_firm).clients_of_firm(true).last
  end
  
  def test_create_many
    companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
    assert_equal 3, companies(:first_firm).clients_of_firm(true).size
  end

  def test_find_or_create
    number_of_clients = companies(:first_firm).clients.size
    the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
    assert_equal number_of_clients + 1, companies(:first_firm, :refresh).clients.size
    assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
    assert_equal number_of_clients + 1, companies(:first_firm, :refresh).clients.size
  end

  def test_deleting
    force_signal37_to_load_all_clients_of_firm
    companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
    assert_equal 0, companies(:first_firm).clients_of_firm.size
    assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  end

  def test_deleting_before_save
    new_firm = Firm.new("name" => "A New Firm, Inc.")
    new_client = new_firm.clients_of_firm.build("name" => "Another Client")
    assert_equal 1, new_firm.clients_of_firm.size
    new_firm.clients_of_firm.delete(new_client)
    assert_equal 0, new_firm.clients_of_firm.size
  end

  def test_deleting_a_collection
    force_signal37_to_load_all_clients_of_firm
    companies(:first_firm).clients_of_firm.create("name" => "Another Client")
    assert_equal 2, companies(:first_firm).clients_of_firm.size
    companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]])
    assert_equal 0, companies(:first_firm).clients_of_firm.size
    assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  end
  
  def test_delete_all
    force_signal37_to_load_all_clients_of_firm
    companies(:first_firm).clients_of_firm.create("name" => "Another Client")
    assert_equal 2, companies(:first_firm).clients_of_firm.size
    companies(:first_firm).clients_of_firm.delete_all
    assert_equal 0, companies(:first_firm).clients_of_firm.size
    assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  end

  def test_delete_all_with_not_yet_loaded_association_collection
    force_signal37_to_load_all_clients_of_firm
    companies(:first_firm).clients_of_firm.create("name" => "Another Client")
    assert_equal 2, companies(:first_firm).clients_of_firm.size
    companies(:first_firm).clients_of_firm.reset
    companies(:first_firm).clients_of_firm.delete_all
    assert_equal 0, companies(:first_firm).clients_of_firm.size
    assert_equal 0, companies(:first_firm).clients_of_firm(true).size
  end

  def test_clearing_an_association_collection
    firm = companies(:first_firm)
    client_id = firm.clients_of_firm.first.id
    assert_equal 1, firm.clients_of_firm.size

    firm.clients_of_firm.clear

    assert_equal 0, firm.clients_of_firm.size
    assert_equal 0, firm.clients_of_firm(true).size
    assert_equal [], Client.destroyed_client_ids[firm.id]

    # Should not be destroyed since the association is not dependent.
    assert_nothing_raised do
      assert Client.find(client_id).firm.nil?
    end
  end

  def test_clearing_a_dependent_association_collection
    firm = companies(:first_firm)
    client_id = firm.dependent_clients_of_firm.first.id
    assert_equal 1, firm.dependent_clients_of_firm.size

    # :dependent means destroy is called on each client
    firm.dependent_clients_of_firm.clear

    assert_equal 0, firm.dependent_clients_of_firm.size
    assert_equal 0, firm.dependent_clients_of_firm(true).size
    assert_equal [client_id], Client.destroyed_client_ids[firm.id]

    # Should be destroyed since the association is dependent.
    assert Client.find_by_id(client_id).nil?
  end

  def test_clearing_an_exclusively_dependent_association_collection
    firm = companies(:first_firm)
    client_id = firm.exclusively_dependent_clients_of_firm.first.id
    assert_equal 1, firm.exclusively_dependent_clients_of_firm.size

    assert_equal [], Client.destroyed_client_ids[firm.id]

    # :exclusively_dependent means each client is deleted directly from
    # the database without looping through them calling destroy.
    firm.exclusively_dependent_clients_of_firm.clear

    assert_equal 0, firm.exclusively_dependent_clients_of_firm.size
    assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size
    assert_equal [3], Client.destroyed_client_ids[firm.id]

    # Should be destroyed since the association is exclusively dependent.
    assert Client.find_by_id(client_id).nil?
  end                                                    

  def test_clearing_without_initial_access
    firm = companies(:first_firm)

    firm.clients_of_firm.clear

    assert_equal 0, firm.clients_of_firm.size
    assert_equal 0, firm.clients_of_firm(true).size
  end

  def test_deleting_a_item_which_is_not_in_the_collection
    force_signal37_to_load_all_clients_of_firm
    summit = Client.find_first("name = 'Summit'")
    companies(:first_firm).clients_of_firm.delete(summit)
    assert_equal 1, companies(:first_firm).clients_of_firm.size
    assert_equal 1, companies(:first_firm).clients_of_firm(true).size
    assert_equal 2, summit.client_of
  end

  def test_deleting_type_mismatch
    david = Developer.find(1)
    david.projects.reload
    assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) }
  end

  def test_deleting_self_type_mismatch
    david = Developer.find(1)
    david.projects.reload
    assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) }
  end

  def test_destroy_all
    force_signal37_to_load_all_clients_of_firm
    assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load"
    companies(:first_firm).clients_of_firm.destroy_all
    assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
    assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
  end

  def test_dependence
    firm = companies(:first_firm)
    assert_equal 2, firm.clients.size
    firm.destroy
    assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty?
  end

  def test_destroy_dependent_when_deleted_from_association
    firm = Firm.find(:first)
    assert_equal 2, firm.clients.size

    client = firm.clients.first
    firm.clients.delete(client)

    assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) }
    assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) }
    assert_equal 1, firm.clients.size
  end

  def test_three_levels_of_dependence
    topic = Topic.create "title" => "neat and simple"
    reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it"
    silly_reply = reply.replies.create "title" => "neat and simple", "content" => "ain't complaining"
    
    assert_nothing_raised { topic.destroy }
  end

  uses_transaction :test_dependence_with_transaction_support_on_failure
  def test_dependence_with_transaction_support_on_failure
    firm = companies(:first_firm)
    clients = firm.clients
    assert_equal 2, clients.length
    clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end }

    firm.destroy rescue "do nothing"

    assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size
  end

  def test_dependence_on_account
    num_accounts = Account.count
    companies(:first_firm).destroy
    assert_equal num_accounts - 1, Account.count
  end

  def test_depends_and_nullify
    num_accounts = Account.count
    num_companies = Company.count
    
    core = companies(:rails_core)
    assert_equal accounts(:rails_core_account), core.account
    assert_equal [companies(:leetsoft), companies(:jadedpixel)], core.companies
    core.destroy                                         
    assert_nil accounts(:rails_core_account).reload.firm_id
    assert_nil companies(:leetsoft).reload.client_of
    assert_nil companies(:jadedpixel).reload.client_of
    
    
    assert_equal num_accounts, Account.count
  end

  def test_included_in_collection
    assert companies(:first_firm).clients.include?(Client.find(2))
  end

  def test_adding_array_and_collection
    assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients }
  end

  def test_find_all_without_conditions
    firm = companies(:first_firm)
    assert_equal 2, firm.clients.find(:all).length
  end

  def test_replace_with_less
    firm = Firm.find(:first)
    firm.clients = [companies(:first_client)]
    assert firm.save, "Could not save firm"
    firm.reload
    assert_equal 1, firm.clients.length
  end 
  
  
  def test_replace_with_new
    firm = Firm.find(:first)
    new_client = Client.new("name" => "New Client")
    firm.clients = [companies(:second_client),new_client]
    firm.save
    firm.reload
    assert_equal 2, firm.clients.length
    assert !firm.clients.include?(:first_client)
  end
  
  def test_replace_on_new_object
    firm = Firm.new("name" => "New Firm")
    firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
    assert firm.save
    firm.reload
    assert_equal 2, firm.clients.length
    assert firm.clients.include?(Client.find_by_name("New Client"))
  end
  
  def test_assign_ids
    firm = Firm.new("name" => "Apple")
    firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
    firm.save
    firm.reload
    assert_equal 2, firm.clients.length
    assert firm.clients.include?(companies(:second_client))
  end
end

class BelongsToAssociationsTest < Test::Unit::TestCase
  fixtures :accounts, :companies, :developers, :projects, :topics,
           :developers_projects, :computers, :authors, :posts
  
  def test_belongs_to
    Client.find(3).firm.name
    assert_equal companies(:first_firm).name, Client.find(3).firm.name
    assert !Client.find(3).firm.nil?, "Microsoft should have a firm"   
  end

  def test_proxy_assignment
    account = Account.find(1)
    assert_nothing_raised { account.firm = account.firm }
  end

  def test_triple_equality
    assert Client.find(3).firm === Firm
    assert Firm === Client.find(3).firm
  end

  def test_type_mismatch
    assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 }
    assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
  end

  def test_natural_assignment
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    citibank.firm = apple
    assert_equal apple.id, citibank.firm_id
  end
  
  def test_creating_the_belonging_object
    citibank = Account.create("credit_limit" => 10)
    apple    = citibank.create_firm("name" => "Apple")
    assert_equal apple, citibank.firm
    citibank.save
    citibank.reload
    assert_equal apple, citibank.firm
  end

  def test_building_the_belonging_object
    citibank = Account.create("credit_limit" => 10)
    apple    = citibank.build_firm("name" => "Apple")
    citibank.save
    assert_equal apple.id, citibank.firm_id
  end
  
  def test_natural_assignment_to_nil
    client = Client.find(3)
    client.firm = nil
    client.save
    assert_nil client.firm(true)
    assert_nil client.client_of
  end
  
  def test_with_different_class_name
    assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
    assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm"
  end

  def test_with_condition
    assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name
    assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm"
  end
  
  def test_belongs_to_counter
    debate = Topic.create("title" => "debate")
    assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"

    trash = debate.replies.create("title" => "blah!", "content" => "world around!")
    assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"

    trash.destroy
    assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
  end

  def test_belongs_to_counter_with_reassigning
    t1 = Topic.create("title" => "t1")
    t2 = Topic.create("title" => "t2")
    r1 = Reply.new("title" => "r1", "content" => "r1")
    r1.topic = t1

    assert r1.save
    assert_equal 1, Topic.find(t1.id).replies.size
    assert_equal 0, Topic.find(t2.id).replies.size

    r1.topic = Topic.find(t2.id)

    assert r1.save
    assert_equal 0, Topic.find(t1.id).replies.size
    assert_equal 1, Topic.find(t2.id).replies.size

    r1.topic = nil

    assert_equal 0, Topic.find(t1.id).replies.size
    assert_equal 0, Topic.find(t2.id).replies.size

    r1.topic = t1

    assert_equal 1, Topic.find(t1.id).replies.size
    assert_equal 0, Topic.find(t2.id).replies.size

    r1.destroy

    assert_equal 0, Topic.find(t1.id).replies.size
    assert_equal 0, Topic.find(t2.id).replies.size
  end

  def test_assignment_before_parent_saved
    client = Client.find(:first)
    apple = Firm.new("name" => "Apple")
    client.firm = apple
    assert_equal apple, client.firm
    assert apple.new_record?
    assert client.save
    assert apple.save
    assert !apple.new_record?
    assert_equal apple, client.firm
    assert_equal apple, client.firm(true)
  end

  def test_assignment_before_child_saved
    final_cut = Client.new("name" => "Final Cut")
    firm = Firm.find(1)
    final_cut.firm = firm
    assert final_cut.new_record?
    assert final_cut.save
    assert !final_cut.new_record?
    assert !firm.new_record?
    assert_equal firm, final_cut.firm
    assert_equal firm, final_cut.firm(true)
  end

  def test_assignment_before_either_saved
    final_cut = Client.new("name" => "Final Cut")
    apple = Firm.new("name" => "Apple")
    final_cut.firm = apple
    assert final_cut.new_record?
    assert apple.new_record?
    assert final_cut.save
    assert !final_cut.new_record?
    assert !apple.new_record?
    assert_equal apple, final_cut.firm
    assert_equal apple, final_cut.firm(true)
  end

  def test_new_record_with_foreign_key_but_no_object
    c = Client.new("firm_id" => 1)
    assert_equal Firm.find(:first), c.firm_with_basic_id
  end

  def test_forgetting_the_load_when_foreign_key_enters_late
    c = Client.new
    assert_nil c.firm_with_basic_id

    c.firm_id = 1
    assert_equal Firm.find(:first), c.firm_with_basic_id
  end

  def test_field_name_same_as_foreign_key
    computer = Computer.find(1)
    assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # '
  end

  def test_counter_cache
    topic = Topic.create :title => "Zoom-zoom-zoom"
    assert_equal 0, topic[:replies_count]
    
    reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
    reply.topic = topic

    assert_equal 1, topic.reload[:replies_count]
    assert_equal 1, topic.replies.size

    topic[:replies_count] = 15
    assert_equal 15, topic.replies.size
  end

  def test_custom_counter_cache
    reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
    assert_equal 0, reply[:replies_count]

    silly = SillyReply.create(:title => "gaga", :content => "boo-boo")
    silly.reply = reply

    assert_equal 1, reply.reload[:replies_count]
    assert_equal 1, reply.replies.size

    reply[:replies_count] = 17
    assert_equal 17, reply.replies.size
  end

  def test_store_two_association_with_one_save
    num_orders = Order.count
    num_customers = Customer.count
    order = Order.new 

    customer1 = order.billing = Customer.new
    customer2 = order.shipping = Customer.new 
    assert order.save
    assert_equal customer1, order.billing
    assert_equal customer2, order.shipping

    order.reload

    assert_equal customer1, order.billing
    assert_equal customer2, order.shipping        

    assert_equal num_orders +1, Order.count
    assert_equal num_customers +2, Customer.count
  end

  
  def test_store_association_in_two_relations_with_one_save
    num_orders = Order.count
    num_customers = Customer.count
    order = Order.new 
    
    customer = order.billing = order.shipping = Customer.new 
    assert order.save
    assert_equal customer, order.billing
    assert_equal customer, order.shipping
    
    order.reload
    
    assert_equal customer, order.billing
    assert_equal customer, order.shipping        
    
    assert_equal num_orders +1, Order.count
    assert_equal num_customers +1, Customer.count
  end

  def test_store_association_in_two_relations_with_one_save_in_existing_object
    num_orders = Order.count
    num_customers = Customer.count
    order = Order.create
    
    customer = order.billing = order.shipping = Customer.new 
    assert order.save
    assert_equal customer, order.billing
    assert_equal customer, order.shipping
    
    order.reload
    
    assert_equal customer, order.billing
    assert_equal customer, order.shipping        
    
    assert_equal num_orders +1, Order.count
    assert_equal num_customers +1, Customer.count
  end
  
  def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
    num_orders = Order.count
    num_customers = Customer.count
    order = Order.create
    
    customer = order.billing = order.shipping = Customer.new 
    assert order.save
    assert_equal customer, order.billing
    assert_equal customer, order.shipping
    
    order.reload
    
    customer = order.billing = order.shipping = Customer.new 
    
    assert order.save
    order.reload    
    
    assert_equal customer, order.billing
    assert_equal customer, order.shipping        
    
    assert_equal num_orders +1, Order.count
    assert_equal num_customers +2, Customer.count
  end
  
  
  def test_association_assignment_sticks
    post = Post.find(:first)

    author1, author2 = Author.find(:all, :limit => 2)
    assert_not_nil author1
    assert_not_nil author2

    # make sure the association is loaded
    post.author

    # set the association by id, directly
    post.author_id = author2.id

    # save and reload
    post.save!
    post.reload

    # the author id of the post should be the id we set
    assert_equal post.author_id, author2.id
  end
  
end


class ProjectWithAfterCreateHook < ActiveRecord::Base
  set_table_name 'projects'
  has_and_belongs_to_many :developers,
    :class_name => "DeveloperForProjectWithAfterCreateHook",
    :join_table => "developers_projects",
    :foreign_key => "project_id",
    :association_foreign_key => "developer_id"

  after_create :add_david

  def add_david
    david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
    david.projects << self
  end
end

class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base
  set_table_name 'developers'
  has_and_belongs_to_many :projects,
    :class_name => "ProjectWithAfterCreateHook",
    :join_table => "developers_projects",
    :association_foreign_key => "project_id",
    :foreign_key => "developer_id"
end


class HasAndBelongsToManyAssociationsTest < Test::Unit::TestCase
  fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects
  
  def test_has_and_belongs_to_many
    david = Developer.find(1)

    assert !david.projects.empty?
    assert_equal 2, david.projects.size

    active_record = Project.find(1)
    assert !active_record.developers.empty?
    assert_equal 3, active_record.developers.size
    assert active_record.developers.include?(david)
  end

  def test_triple_equality
    assert !(Array === Developer.find(1).projects)
    assert Developer.find(1).projects === Array
  end

  def test_adding_single
    jamis = Developer.find(2)
    jamis.projects.reload # causing the collection to load 
    action_controller = Project.find(2)
    assert_equal 1, jamis.projects.size
    assert_equal 1, action_controller.developers.size    
    
    jamis.projects << action_controller

    assert_equal 2, jamis.projects.size
    assert_equal 2, jamis.projects(true).size
    assert_equal 2, action_controller.developers(true).size
  end

  def test_adding_type_mismatch
    jamis = Developer.find(2)
    assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << nil }
    assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << 1 }
  end

  def test_adding_from_the_project
    jamis = Developer.find(2)
    action_controller = Project.find(2)
    action_controller.developers.reload
    assert_equal 1, jamis.projects.size
    assert_equal 1, action_controller.developers.size

    action_controller.developers << jamis 
    
    assert_equal 2, jamis.projects(true).size
    assert_equal 2, action_controller.developers.size
    assert_equal 2, action_controller.developers(true).size
  end

  def test_adding_from_the_project_fixed_timestamp
    jamis = Developer.find(2)
    action_controller = Project.find(2)
    action_controller.developers.reload
    assert_equal 1, jamis.projects.size
    assert_equal 1, action_controller.developers.size
    updated_at = jamis.updated_at

    action_controller.developers << jamis

    assert_equal updated_at, jamis.updated_at
    assert_equal 2, jamis.projects(true).size
    assert_equal 2, action_controller.developers.size
    assert_equal 2, action_controller.developers(true).size
  end

  def test_adding_multiple
    aredridel = Developer.new("name" => "Aredridel")
    aredridel.save
    aredridel.projects.reload
    aredridel.projects.push(Project.find(1), Project.find(2))
    assert_equal 2, aredridel.projects.size
    assert_equal 2, aredridel.projects(true).size
  end

  def test_adding_a_collection
    aredridel = Developer.new("name" => "Aredridel")
    aredridel.save
    aredridel.projects.reload
    aredridel.projects.concat([Project.find(1), Project.find(2)])
    assert_equal 2, aredridel.projects.size
    assert_equal 2, aredridel.projects(true).size
  end

  def test_adding_uses_default_values_on_join_table
    ac = projects(:action_controller)
    assert !developers(:jamis).projects.include?(ac)
    developers(:jamis).projects << ac

    assert developers(:jamis, :reload).projects.include?(ac)
    project = developers(:jamis).projects.detect { |p| p == ac }
    assert_equal 1, project.access_level.to_i
  end

  def test_adding_uses_explicit_values_on_join_table
    ac = projects(:action_controller)
    assert !developers(:jamis).projects.include?(ac)
    developers(:jamis).projects.push_with_attributes(ac, :access_level => 3)

    assert developers(:jamis, :reload).projects.include?(ac)
    project = developers(:jamis).projects.detect { |p| p == ac }
    assert_equal 3, project.access_level.to_i
  end

  def test_hatbm_attribute_access_and_respond_to
    project = developers(:jamis).projects[0]
    assert project.has_attribute?("name")
    assert project.has_attribute?("joined_on")
    assert project.has_attribute?("access_level")
    assert project.respond_to?("name")
    assert project.respond_to?("name=")
    assert project.respond_to?("name?")
    assert project.respond_to?("joined_on")
    assert project.respond_to?("joined_on=")
    assert project.respond_to?("joined_on?")
    assert project.respond_to?("access_level")
    assert project.respond_to?("access_level=")
    assert project.respond_to?("access_level?")
  end

  def test_habtm_adding_before_save
    no_of_devels = Developer.count
    no_of_projects = Project.count
    aredridel = Developer.new("name" => "Aredridel")
    aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")])
    assert aredridel.new_record?
    assert p.new_record?
    assert aredridel.save
    assert !aredridel.new_record?
    assert_equal no_of_devels+1, Developer.count
    assert_equal no_of_projects+1, Project.count
    assert_equal 2, aredridel.projects.size
    assert_equal 2, aredridel.projects(true).size
  end

  def test_habtm_adding_before_save_with_join_attributes
    no_of_devels = Developer.count
    no_of_projects = Project.count
    now = Date.today
    ken = Developer.new("name" => "Ken")
    ken.projects.push_with_attributes( Project.find(1), :joined_on => now )
    p = Project.new("name" => "Foomatic")
    ken.projects.push_with_attributes( p, :joined_on => now )
    assert ken.new_record?
    assert p.new_record?
    assert ken.save
    assert !ken.new_record?
    assert_equal no_of_devels+1, Developer.count
    assert_equal no_of_projects+1, Project.count
    assert_equal 2, ken.projects.size
    assert_equal 2, ken.projects(true).size

    kenReloaded = Developer.find_by_name 'Ken'
    kenReloaded.projects.each {|prj| assert_date_from_db(now, prj.joined_on)}
  end

  def test_habtm_saving_multiple_relationships
    new_project = Project.new("name" => "Grimetime")
    amount_of_developers = 4
    developers = (0..amount_of_developers).collect {|i| Developer.create(:name => "JME #{i}") }
  
    new_project.developer_ids = [developers[0].id, developers[1].id]
    new_project.developers_with_callback_ids = [developers[2].id, developers[3].id]
    assert new_project.save
    
    new_project.reload
    assert_equal amount_of_developers, new_project.developers.size
    amount_of_developers.times do |i|
      assert_equal developers[i].name, new_project.developers[i].name
    end
  end

  def test_build
    devel = Developer.find(1)
    proj = devel.projects.build("name" => "Projekt")
    assert_equal devel.projects.last, proj
    assert proj.new_record?
    devel.save
    assert !proj.new_record?
    assert_equal devel.projects.last, proj
  end
  
  def test_create
    devel = Developer.find(1)
    proj = devel.projects.create("name" => "Projekt")
    assert_equal devel.projects.last, proj
    assert !proj.new_record?
  end
  
  def test_uniq_after_the_fact
    developers(:jamis).projects << projects(:active_record)
    developers(:jamis).projects << projects(:active_record)
    assert_equal 3, developers(:jamis).projects.size
    assert_equal 1, developers(:jamis).projects.uniq.size
  end

  def test_uniq_before_the_fact
    projects(:active_record).developers << developers(:jamis)
    projects(:active_record).developers << developers(:david)
    assert_equal 3, projects(:active_record, :reload).developers.size
  end
  
  def test_deleting
    david = Developer.find(1)
    active_record = Project.find(1)
    david.projects.reload
    assert_equal 2, david.projects.size
    assert_equal 3, active_record.developers.size

    david.projects.delete(active_record)
    
    assert_equal 1, david.projects.size
    assert_equal 1, david.projects(true).size
    assert_equal 2, active_record.developers(true).size
  end

  def test_deleting_array
    david = Developer.find(1)
    david.projects.reload
    david.projects.delete(Project.find(:all))
    assert_equal 0, david.projects.size
    assert_equal 0, david.projects(true).size
  end

  def test_deleting_with_sql
    david = Developer.find(1)
    active_record = Project.find(1)
    active_record.developers.reload
    assert_equal 3, active_record.developers_by_sql.size
    
    active_record.developers_by_sql.delete(david)
    assert_equal 2, active_record.developers_by_sql(true).size
  end

  def test_deleting_array_with_sql
    active_record = Project.find(1)
    active_record.developers.reload
    assert_equal 3, active_record.developers_by_sql.size
    
    active_record.developers_by_sql.delete(Developer.find(:all))
    assert_equal 0, active_record.developers_by_sql(true).size
  end

  def test_deleting_all
    david = Developer.find(1)
    david.projects.reload
    david.projects.clear
    assert_equal 0, david.projects.size
    assert_equal 0, david.projects(true).size
  end

  def test_removing_associations_on_destroy
    david = DeveloperWithBeforeDestroyRaise.find(1)
    assert !david.projects.empty?
    assert_nothing_raised { david.destroy }
    assert david.projects.empty?
    assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty?
  end

  def test_additional_columns_from_join_table
    assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on
  end
  
  def test_destroy_all
    david = Developer.find(1)
    david.projects.reload
    assert !david.projects.empty?
    david.projects.destroy_all
    assert david.projects.empty?
    assert david.projects(true).empty?
  end

  def test_rich_association
    jamis = developers(:jamis)
    jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today)
    
    assert_date_from_db Date.today, jamis.projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on
    assert_date_from_db Date.today, developers(:jamis).projects.select { |p| p.name == projects(:action_controller).name }.first.joined_on
  end

  def test_associations_with_conditions
    assert_equal 3, projects(:active_record).developers.size
    assert_equal 1, projects(:active_record).developers_named_david.size

    assert_equal developers(:david), projects(:active_record).developers_named_david.find(developers(:david).id)
    assert_equal developers(:david), projects(:active_record).salaried_developers.find(developers(:david).id)

    projects(:active_record).developers_named_david.clear
    assert_equal 2, projects(:active_record, :reload).developers.size
  end
  
  def test_find_in_association
    # Using sql
    assert_equal developers(:david), projects(:active_record).developers.find(developers(:david).id), "SQL find"
    
    # Using ruby
    active_record = projects(:active_record)
    active_record.developers.reload
    assert_equal developers(:david), active_record.developers.find(developers(:david).id), "Ruby find"
  end

  def test_find_in_association_with_custom_finder_sql
    assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find"
   
    active_record = projects(:active_record)
    active_record.developers_with_finder_sql.reload
    assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find"
  end

  def test_find_in_association_with_custom_finder_sql_and_string_id
    assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find"
  end

  def test_find_with_merged_options
    assert_equal 1, projects(:active_record).limited_developers.size
    assert_equal 1, projects(:active_record).limited_developers.find(:all).size
    assert_equal 3, projects(:active_record).limited_developers.find(:all, :limit => nil).size
  end

  def test_new_with_values_in_collection
    jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis')
    david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
    project = ProjectWithAfterCreateHook.new(:name => "Cooking with Bertie")
    project.developers << jamis
    project.save!
    project.reload

    assert project.developers.include?(jamis)
    assert project.developers.include?(david)
  end

  def test_find_in_association_with_options
    developers = projects(:active_record).developers.find(:all)
    assert_equal 3, developers.size
    
    assert_equal developers(:poor_jamis), projects(:active_record).developers.find(:first, :conditions => "salary < 10000")
    assert_equal developers(:jamis),      projects(:active_record).developers.find(:first, :order => "salary DESC")
  end
  
  def test_replace_with_less
    david = developers(:david)
    david.projects = [projects(:action_controller)]
    assert david.save
    assert_equal 1, david.projects.length
  end

  def test_replace_with_new
    david = developers(:david)
    david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")]
    david.save
    assert_equal 2, david.projects.length
    assert !david.projects.include?(projects(:active_record))
  end
  
  def test_replace_on_new_object
    new_developer = Developer.new("name" => "Matz")
    new_developer.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")]
    new_developer.save
    assert_equal 2, new_developer.projects.length
  end

  def test_consider_type
    developer = Developer.find(:first)
    special_project = SpecialProject.create("name" => "Special Project")
  
    other_project = developer.projects.first
    developer.special_projects << special_project
    developer.reload
  
    assert developer.projects.include?(special_project)
    assert developer.special_projects.include?(special_project)
    assert !developer.special_projects.include?(other_project)
  end
  
  def test_update_attributes_after_push_without_duplicate_join_table_rows
    developer = Developer.new("name" => "Kano")
    project = SpecialProject.create("name" => "Special Project")
    assert developer.save
    developer.projects << project
    developer.update_attribute("name", "Bruza")
    assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i
      SELECT count(*) FROM developers_projects
      WHERE project_id = #{project.id}
      AND developer_id = #{developer.id}
    end_sql
  end
  
  def test_updating_attributes_on_non_rich_associations
    welcome = categories(:technology).posts.first
    welcome.title = "Something else"
    assert welcome.save!
  end
  
  def test_updating_attributes_on_rich_associations
    david = projects(:action_controller).developers.first
    david.name = "DHH"
    assert_raises(ActiveRecord::ReadOnlyRecord) { david.save! }
  end

  
  def test_updating_attributes_on_rich_associations_with_limited_find
    david = projects(:action_controller).developers.find(:all, :select => "developers.*").first
    david.name = "DHH"
    assert david.save!
  end

  def test_join_table_alias
    assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size
  end
end
require 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/reply'
require 'fixtures/company'
require 'fixtures/customer'
require 'fixtures/developer'
require 'fixtures/project'
require 'fixtures/default'
require 'fixtures/auto_id'
require 'fixtures/column_name'
require 'fixtures/subscriber'
require 'fixtures/keyboard'

class Category < ActiveRecord::Base; end
class Smarts < ActiveRecord::Base; end
class CreditCard < ActiveRecord::Base; end
class MasterCreditCard < ActiveRecord::Base; end
class Post < ActiveRecord::Base; end
class Computer < ActiveRecord::Base; end
class NonExistentTable < ActiveRecord::Base; end
class TestOracleDefault < ActiveRecord::Base; end

class LoosePerson < ActiveRecord::Base
  attr_protected :credit_rating, :administrator
  self.abstract_class = true
end

class LooseDescendant < LoosePerson
  attr_protected :phone_number
end

class TightPerson < ActiveRecord::Base
  attr_accessible :name, :address
end

class TightDescendant < TightPerson
  attr_accessible :phone_number
end

class Booleantest < ActiveRecord::Base; end

class Task < ActiveRecord::Base
  attr_protected :starting
end

class BasicsTest < Test::Unit::TestCase
  fixtures :topics, :companies, :developers, :projects, :computers

  def test_table_exists
    assert !NonExistentTable.table_exists?
    assert Topic.table_exists?
  end
  
  def test_set_attributes
    topic = Topic.find(1)
    topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
    topic.save
    assert_equal("Budget", topic.title)
    assert_equal("Jason", topic.author_name)
    assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
  end
  
  def test_integers_as_nil
    test = AutoId.create('value' => '')
    assert_nil AutoId.find(test.id).value
  end
  
  def test_set_attributes_with_block
    topic = Topic.new do |t|
      t.title       = "Budget"
      t.author_name = "Jason"
    end

    assert_equal("Budget", topic.title)
    assert_equal("Jason", topic.author_name)
  end
  
  def test_respond_to?
    topic = Topic.find(1)
    assert topic.respond_to?("title")
    assert topic.respond_to?("title?")
    assert topic.respond_to?("title=")
    assert topic.respond_to?(:title)
    assert topic.respond_to?(:title?)
    assert topic.respond_to?(:title=)
    assert topic.respond_to?("author_name")
    assert topic.respond_to?("attribute_names")
    assert !topic.respond_to?("nothingness")
    assert !topic.respond_to?(:nothingness)
  end
  
  def test_array_content
    topic = Topic.new
    topic.content = %w( one two three )
    topic.save

    assert_equal(%w( one two three ), Topic.find(topic.id).content)
  end

  def test_hash_content
    topic = Topic.new
    topic.content = { "one" => 1, "two" => 2 }
    topic.save

    assert_equal 2, Topic.find(topic.id).content["two"]
    
    topic.content["three"] = 3
    topic.save

    assert_equal 3, Topic.find(topic.id).content["three"]
  end
  
  def test_update_array_content
    topic = Topic.new
    topic.content = %w( one two three )

    topic.content.push "four"
    assert_equal(%w( one two three four ), topic.content)

    topic.save
    
    topic = Topic.find(topic.id)
    topic.content << "five"
    assert_equal(%w( one two three four five ), topic.content)
  end
 
  def test_case_sensitive_attributes_hash
    # DB2 is not case-sensitive
    return true if current_adapter?(:DB2Adapter)

    assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
  end

  def test_create
    topic = Topic.new
    topic.title = "New Topic"
    topic.save
    topic_reloaded = Topic.find(topic.id)
    assert_equal("New Topic", topic_reloaded.title)
  end
  
  def test_save!
    topic = Topic.new(:title => "New Topic")
    assert topic.save!
  end
    
  def test_hashes_not_mangled
    new_topic = { :title => "New Topic" }
    new_topic_values = { :title => "AnotherTopic" }

    topic = Topic.new(new_topic)
    assert_equal new_topic[:title], topic.title

    topic.attributes= new_topic_values
    assert_equal new_topic_values[:title], topic.title
  end
  
  def test_create_many
    topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
    assert_equal 2, topics.size
    assert_equal "first", topics.first.title
  end

  def test_create_columns_not_equal_attributes
    topic = Topic.new
    topic.title = 'Another New Topic'
    topic.send :write_attribute, 'does_not_exist', 'test'
    assert_nothing_raised { topic.save }
  end

  def test_create_through_factory
    topic = Topic.create("title" => "New Topic")
    topicReloaded = Topic.find(topic.id)
    assert_equal(topic, topicReloaded)
  end

  def test_update
    topic = Topic.new
    topic.title = "Another New Topic"
    topic.written_on = "2003-12-12 23:23:00"
    topic.save
    topicReloaded = Topic.find(topic.id)
    assert_equal("Another New Topic", topicReloaded.title)

    topicReloaded.title = "Updated topic"
    topicReloaded.save
    
    topicReloadedAgain = Topic.find(topic.id)
    
    assert_equal("Updated topic", topicReloadedAgain.title)
  end

  def test_update_columns_not_equal_attributes
    topic = Topic.new
    topic.title = "Still another topic"
    topic.save
    
    topicReloaded = Topic.find(topic.id)
    topicReloaded.title = "A New Topic"
    topicReloaded.send :write_attribute, 'does_not_exist', 'test'
    assert_nothing_raised { topicReloaded.save }
  end
  
  def test_write_attribute
    topic = Topic.new
    topic.send(:write_attribute, :title, "Still another topic")
    assert_equal "Still another topic", topic.title

    topic.send(:write_attribute, "title", "Still another topic: part 2")
    assert_equal "Still another topic: part 2", topic.title
  end

  def test_read_attribute
    topic = Topic.new
    topic.title = "Don't change the topic"
    assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
    assert_equal "Don't change the topic", topic["title"]

    assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
    assert_equal "Don't change the topic", topic[:title]
  end

  def test_read_attribute_when_false
    topic = topics(:first)
    topic.approved = false
    assert !topic.approved?, "approved should be false"
    topic.approved = "false"
    assert !topic.approved?, "approved should be false"
  end

  def test_read_attribute_when_true
    topic = topics(:first)
    topic.approved = true
    assert topic.approved?, "approved should be true"
    topic.approved = "true"
    assert topic.approved?, "approved should be true"
  end

  def test_read_write_boolean_attribute
    topic = Topic.new
    # puts ""
    # puts "New Topic"
    # puts topic.inspect
    topic.approved = "false"
    # puts "Expecting false"
    # puts topic.inspect
    assert !topic.approved?, "approved should be false"
    topic.approved = "false"
    # puts "Expecting false"
    # puts topic.inspect
    assert !topic.approved?, "approved should be false"
    topic.approved = "true"
    # puts "Expecting true"
    # puts topic.inspect
    assert topic.approved?, "approved should be true"
    topic.approved = "true"
    # puts "Expecting true"
    # puts topic.inspect
    assert topic.approved?, "approved should be true"
    # puts ""
  end

  def test_reader_generation
    Topic.find(:first).title
    Firm.find(:first).name
    Client.find(:first).name
    if ActiveRecord::Base.generate_read_methods
      assert_readers(Topic,  %w(type replies_count))
      assert_readers(Firm,   %w(type))
      assert_readers(Client, %w(type ruby_type rating?))
    else
      [Topic, Firm, Client].each {|klass| assert_equal klass.read_methods, {}}
    end
  end

  def test_reader_for_invalid_column_names
    # column names which aren't legal ruby ids
    topic = Topic.find(:first)
    topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
    assert !Topic.read_methods.include?("mumub-jumbo")
  end

  def test_non_attribute_access_and_assignment
    topic = Topic.new
    assert !topic.respond_to?("mumbo")
    assert_raises(NoMethodError) { topic.mumbo }
    assert_raises(NoMethodError) { topic.mumbo = 5 }
  end

  def test_preserving_date_objects
    # SQL Server doesn't have a separate column type just for dates, so all are returned as time
    return true if current_adapter?(:SQLServerAdapter)

    if current_adapter?(:SybaseAdapter)
      # Sybase ctlib does not (yet?) support the date type; use datetime instead.
      assert_kind_of(
        Time, Topic.find(1).last_read, 
        "The last_read attribute should be of the Time class"
      )
    else
      assert_kind_of(
        Date, Topic.find(1).last_read, 
        "The last_read attribute should be of the Date class"
      )
    end
  end

  def test_preserving_time_objects
    assert_kind_of(
      Time, Topic.find(1).bonus_time,
      "The bonus_time attribute should be of the Time class"
    )

    assert_kind_of(
      Time, Topic.find(1).written_on,
      "The written_on attribute should be of the Time class"
    )
  end
  
  def test_destroy
    topic = Topic.new
    topic.title = "Yet Another New Topic"
    topic.written_on = "2003-12-12 23:23:00"
    topic.save
    topic.destroy
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
  end
  
  def test_destroy_returns_self
    topic = Topic.new("title" => "Yet Another Title")
    assert topic.save
    assert_equal topic, topic.destroy, "destroy did not return destroyed object"
  end
  
  def test_record_not_found_exception
    assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
  end
  
  def test_initialize_with_attributes
    topic = Topic.new({ 
      "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
    })
    
    assert_equal("initialized from attributes", topic.title)
  end
  
  def test_initialize_with_invalid_attribute
    begin
      topic = Topic.new({ "title" => "test", 
        "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
    rescue ActiveRecord::MultiparameterAssignmentErrors => ex
      assert_equal(1, ex.errors.size)
      assert_equal("last_read", ex.errors[0].attribute)
    end
  end
  
  def test_load
    topics = Topic.find(:all, :order => 'id')    
    assert_equal(2, topics.size)
    assert_equal(topics(:first).title, topics.first.title)
  end
  
  def test_load_with_condition
    topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
    
    assert_equal(1, topics.size)
    assert_equal(topics(:second).title, topics.first.title)
  end

  def test_table_name_guesses
    assert_equal "topics", Topic.table_name
    
    assert_equal "categories", Category.table_name
    assert_equal "smarts", Smarts.table_name
    assert_equal "credit_cards", CreditCard.table_name
    assert_equal "master_credit_cards", MasterCreditCard.table_name

    ActiveRecord::Base.pluralize_table_names = false
    [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
    assert_equal "category", Category.table_name
    assert_equal "smarts", Smarts.table_name
    assert_equal "credit_card", CreditCard.table_name
    assert_equal "master_credit_card", MasterCreditCard.table_name
    ActiveRecord::Base.pluralize_table_names = true
    [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}

    ActiveRecord::Base.table_name_prefix = "test_"
    Category.reset_table_name
    assert_equal "test_categories", Category.table_name
    ActiveRecord::Base.table_name_suffix = "_test"
    Category.reset_table_name
    assert_equal "test_categories_test", Category.table_name
    ActiveRecord::Base.table_name_prefix = ""
    Category.reset_table_name
    assert_equal "categories_test", Category.table_name
    ActiveRecord::Base.table_name_suffix = ""
    Category.reset_table_name
    assert_equal "categories", Category.table_name

    ActiveRecord::Base.pluralize_table_names = false
    ActiveRecord::Base.table_name_prefix = "test_"
    Category.reset_table_name
    assert_equal "test_category", Category.table_name
    ActiveRecord::Base.table_name_suffix = "_test"
    Category.reset_table_name
    assert_equal "test_category_test", Category.table_name
    ActiveRecord::Base.table_name_prefix = ""
    Category.reset_table_name
    assert_equal "category_test", Category.table_name
    ActiveRecord::Base.table_name_suffix = ""
    Category.reset_table_name
    assert_equal "category", Category.table_name
    ActiveRecord::Base.pluralize_table_names = true
    [Category, Smarts, CreditCard, MasterCreditCard].each{|c| c.reset_table_name}
  end
  
  def test_destroy_all
    assert_equal 2, Topic.count

    Topic.destroy_all "author_name = 'Mary'"
    assert_equal 1, Topic.count
  end

  def test_destroy_many
    assert_equal 3, Client.count
    Client.destroy([2, 3])
    assert_equal 1, Client.count
  end

  def test_delete_many
    Topic.delete([1, 2])
    assert_equal 0, Topic.count
  end

  def test_boolean_attributes
    assert ! Topic.find(1).approved?
    assert Topic.find(2).approved?
  end
  
  def test_increment_counter
    Topic.increment_counter("replies_count", 1)
    assert_equal 1, Topic.find(1).replies_count

    Topic.increment_counter("replies_count", 1)
    assert_equal 2, Topic.find(1).replies_count
  end
  
  def test_decrement_counter
    Topic.decrement_counter("replies_count", 2)
    assert_equal 1, Topic.find(2).replies_count

    Topic.decrement_counter("replies_count", 2)
    assert_equal 0, Topic.find(1).replies_count
  end
  
  def test_update_all
    # The ADO library doesn't support the number of affected rows
    return true if current_adapter?(:SQLServerAdapter)

    assert_equal 2, Topic.update_all("content = 'bulk updated!'")
    assert_equal "bulk updated!", Topic.find(1).content
    assert_equal "bulk updated!", Topic.find(2).content
    assert_equal 2, Topic.update_all(['content = ?', 'bulk updated again!'])
    assert_equal "bulk updated again!", Topic.find(1).content
    assert_equal "bulk updated again!", Topic.find(2).content
  end

  def test_update_many
    topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
    updated = Topic.update(topic_data.keys, topic_data.values)

    assert_equal 2, updated.size
    assert_equal "1 updated", Topic.find(1).content
    assert_equal "2 updated", Topic.find(2).content
  end

  def test_delete_all
    # The ADO library doesn't support the number of affected rows
    return true if current_adapter?(:SQLServerAdapter)

    assert_equal 2, Topic.delete_all
  end

  def test_update_by_condition
    Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
    assert_equal "Have a nice day", Topic.find(1).content
    assert_equal "bulk updated!", Topic.find(2).content
  end
    
  def test_attribute_present
    t = Topic.new
    t.title = "hello there!"
    t.written_on = Time.now
    assert t.attribute_present?("title")
    assert t.attribute_present?("written_on")
    assert !t.attribute_present?("content")
  end
  
  def test_attribute_keys_on_new_instance
    t = Topic.new
    assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
    assert_raise(NoMethodError) { t.title2 }
  end
  
  def test_class_name
    assert_equal "Firm", ActiveRecord::Base.class_name("firms")
    assert_equal "Category", ActiveRecord::Base.class_name("categories")
    assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")

    ActiveRecord::Base.pluralize_table_names = false
    assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
    ActiveRecord::Base.pluralize_table_names = true

    ActiveRecord::Base.table_name_prefix = "test_"
    assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
    ActiveRecord::Base.table_name_suffix = "_tests"
    assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
    ActiveRecord::Base.table_name_prefix = ""
    assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
    ActiveRecord::Base.table_name_suffix = ""
    assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
  end
  
  def test_null_fields
    assert_nil Topic.find(1).parent_id
    assert_nil Topic.create("title" => "Hey you").parent_id
  end
  
  def test_default_values
    topic = Topic.new
    assert topic.approved?
    assert_nil topic.written_on
    assert_nil topic.bonus_time
    assert_nil topic.last_read
    
    topic.save

    topic = Topic.find(topic.id)
    assert topic.approved?
    assert_nil topic.last_read

    # Oracle has some funky default handling, so it requires a bit of 
    # extra testing. See ticket #2788.
    if current_adapter?(:OracleAdapter)
      test = TestOracleDefault.new
      assert_equal "X", test.test_char
      assert_equal "hello", test.test_string
      assert_equal 3, test.test_int
    end
  end

  def test_utc_as_time_zone
    # Oracle and SQLServer do not have a TIME datatype.
    return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)

    Topic.default_timezone = :utc
    attributes = { "bonus_time" => "5:42:00AM" }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
    Topic.default_timezone = :local
  end

  def test_default_values_on_empty_strings
    topic = Topic.new
    topic.approved  = nil
    topic.last_read = nil

    topic.save

    topic = Topic.find(topic.id)
    assert_nil topic.last_read

    # Sybase adapter does not allow nulls in boolean columns
    if current_adapter?(:SybaseAdapter)
      assert topic.approved == false
    else
      assert_nil topic.approved
    end
  end

  def test_equality
    assert_equal Topic.find(1), Topic.find(2).topic
  end
  
  def test_equality_of_new_records
    assert_not_equal Topic.new, Topic.new
  end
  
  def test_hashing
    assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
  end
  
  def test_destroy_new_record
    client = Client.new
    client.destroy
    assert client.frozen?
  end
  
  def test_destroy_record_with_associations
    client = Client.find(3)
    client.destroy
    assert client.frozen?
    assert_kind_of Firm, client.firm
    assert_raises(TypeError) { client.name = "something else" }
  end
  
  def test_update_attribute
    assert !Topic.find(1).approved?
    Topic.find(1).update_attribute("approved", true)
    assert Topic.find(1).approved?

    Topic.find(1).update_attribute(:approved, false)
    assert !Topic.find(1).approved?
  end
  
  def test_mass_assignment_protection
    firm = Firm.new
    firm.attributes = { "name" => "Next Angle", "rating" => 5 }
    assert_equal 1, firm.rating
  end

  def test_customized_primary_key_remains_protected
    subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
    assert_nil subscriber.id

    keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
    assert_nil keyboard.id
  end

  def test_customized_primary_key_remains_protected_when_refered_to_as_id
    subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
    assert_nil subscriber.id

    keyboard = Keyboard.new(:id => 9, :name => 'nice try')
    assert_nil keyboard.id
  end
  
  def test_mass_assignment_protection_on_defaults
    firm = Firm.new
    firm.attributes = { "id" => 5, "type" => "Client" }
    assert_nil firm.id
    assert_equal "Firm", firm[:type]
  end
  
  def test_mass_assignment_accessible
    reply = Reply.new("title" => "hello", "content" => "world", "approved" => true)
    reply.save

    assert reply.approved?
    
    reply.approved = false
    reply.save

    assert !reply.approved?
  end
  
  def test_mass_assignment_protection_inheritance
    assert_nil LoosePerson.accessible_attributes
    assert_equal [ :credit_rating, :administrator ], LoosePerson.protected_attributes

    assert_nil LooseDescendant.accessible_attributes
    assert_equal [ :credit_rating, :administrator, :phone_number  ], LooseDescendant.protected_attributes

    assert_nil TightPerson.protected_attributes
    assert_equal [ :name, :address ], TightPerson.accessible_attributes

    assert_nil TightDescendant.protected_attributes
    assert_equal [ :name, :address, :phone_number  ], TightDescendant.accessible_attributes
  end

  def test_multiparameter_attributes_on_date
    attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
    topic = Topic.find(1)
    topic.attributes = attributes
    # note that extra #to_date call allows test to pass for Oracle, which 
    # treats dates/times the same
    assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
  end

  def test_multiparameter_attributes_on_date_with_empty_date
    attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
    topic = Topic.find(1)
    topic.attributes = attributes
    # note that extra #to_date call allows test to pass for Oracle, which 
    # treats dates/times the same
    assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
  end

  def test_multiparameter_attributes_on_date_with_all_empty
    attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_nil topic.last_read
  end

  def test_multiparameter_attributes_on_time
    attributes = { 
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", 
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
  end

  def test_multiparameter_attributes_on_time_with_empty_seconds
    attributes = { 
      "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", 
      "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
  end

  def test_multiparameter_mass_assignment_protector
    task = Task.new
    time = Time.mktime(2000, 1, 1, 1)
    task.starting = time 
    attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
    task.attributes = attributes
    assert_equal time, task.starting
  end
  
  def test_multiparameter_assignment_of_aggregation
    customer = Customer.new
    address = Address.new("The Street", "The City", "The Country")
    attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
    customer.attributes = attributes
    assert_equal address, customer.address
  end

  def test_attributes_on_dummy_time
    # Oracle and SQL Server do not have a TIME datatype.
    return true if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter)

    attributes = {
      "bonus_time" => "5:42:00AM"
    }
    topic = Topic.find(1)
    topic.attributes = attributes
    assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
  end

  def test_boolean
    b_false = Booleantest.create({ "value" => false })
    false_id = b_false.id
    b_true = Booleantest.create({ "value" => true })
    true_id = b_true.id

    b_false = Booleantest.find(false_id)
    assert !b_false.value?
    b_true = Booleantest.find(true_id)
    assert b_true.value?
  end

  def test_boolean_cast_from_string
    b_false = Booleantest.create({ "value" => "0" })
    false_id = b_false.id
    b_true = Booleantest.create({ "value" => "1" })
    true_id = b_true.id

    b_false = Booleantest.find(false_id)
    assert !b_false.value?
    b_true = Booleantest.find(true_id)
    assert b_true.value?    
  end
  
  def test_clone
    topic = Topic.find(1)
    cloned_topic = nil
    assert_nothing_raised { cloned_topic = topic.clone }
    assert_equal topic.title, cloned_topic.title
    assert cloned_topic.new_record?

    # test if the attributes have been cloned
    topic.title = "a" 
    cloned_topic.title = "b" 
    assert_equal "a", topic.title
    assert_equal "b", cloned_topic.title

    # test if the attribute values have been cloned
    topic.title = {"a" => "b"}
    cloned_topic = topic.clone
    cloned_topic.title["a"] = "c" 
    assert_equal "b", topic.title["a"]

    cloned_topic.save
    assert !cloned_topic.new_record?
    assert cloned_topic.id != topic.id
  end

  def test_clone_with_aggregate_of_same_name_as_attribute
    dev = DeveloperWithAggregate.find(1)
    assert_kind_of DeveloperSalary, dev.salary

    clone = nil
    assert_nothing_raised { clone = dev.clone }
    assert_kind_of DeveloperSalary, clone.salary
    assert_equal dev.salary.amount, clone.salary.amount
    assert clone.new_record?

    # test if the attributes have been cloned
    original_amount = clone.salary.amount
    dev.salary.amount = 1
    assert_equal original_amount, clone.salary.amount

    assert clone.save
    assert !clone.new_record?
    assert clone.id != dev.id
  end

  def test_clone_preserves_subtype
    clone = nil
    assert_nothing_raised { clone = Company.find(3).clone }
    assert_kind_of Client, clone
  end

  def test_bignum
    company = Company.find(1)
    company.rating = 2147483647
    company.save
    company = Company.find(1)
    assert_equal 2147483647, company.rating
  end

  # TODO: extend defaults tests to other databases!
  if current_adapter?(:PostgreSQLAdapter)
    def test_default
      default = Default.new
  
      # fixed dates / times
      assert_equal Date.new(2004, 1, 1), default.fixed_date
      assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
  
      # char types
      assert_equal 'Y', default.char1
      assert_equal 'a varchar field', default.char2
      assert_equal 'a text field', default.char3
    end

    class Geometric < ActiveRecord::Base; end
    def test_geometric_content

      # accepted format notes:
      # ()'s aren't required
      # values can be a mix of float or integer

      g = Geometric.new(
        :a_point        => '(5.0, 6.1)',
        #:a_line         => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
        :a_line_segment => '(2.0, 3), (5.5, 7.0)',
        :a_box          => '2.0, 3, 5.5, 7.0',
        :a_path         => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]',  # [ ] is an open path
        :a_polygon      => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
        :a_circle       => '<(5.3, 10.4), 2>'
      )
        
      assert g.save

      # Reload and check that we have all the geometric attributes.
      h = Geometric.find(g.id)

      assert_equal '(5,6.1)', h.a_point
      assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
      assert_equal '(5.5,7),(2,3)', h.a_box   # reordered to store upper right corner then bottom left corner
      assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
      assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
      assert_equal '<(5.3,10.4),2>', h.a_circle

      # use a geometric function to test for an open path
      objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
      assert_equal objs[0].isopen, 't'

      # test alternate formats when defining the geometric types
      
      g = Geometric.new(
        :a_point        => '5.0, 6.1',
        #:a_line         => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
        :a_line_segment => '((2.0, 3), (5.5, 7.0))',
        :a_box          => '(2.0, 3), (5.5, 7.0)',
        :a_path         => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',  # ( ) is a closed path
        :a_polygon      => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
        :a_circle       => '((5.3, 10.4), 2)'
      )

      assert g.save

      # Reload and check that we have all the geometric attributes.
      h = Geometric.find(g.id)
      
      assert_equal '(5,6.1)', h.a_point
      assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
      assert_equal '(5.5,7),(2,3)', h.a_box   # reordered to store upper right corner then bottom left corner
      assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
      assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
      assert_equal '<(5.3,10.4),2>', h.a_circle

      # use a geometric function to test for an closed path
      objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
      assert_equal objs[0].isclosed, 't'
    end
  end

  def test_auto_id
    auto = AutoId.new
    auto.save
    assert (auto.id > 0)
  end
  
  def quote_column_name(name)
    "<#{name}>"
  end

  def test_quote_keys
    ar = AutoId.new
    source = {"foo" => "bar", "baz" => "quux"}
    actual = ar.send(:quote_columns, self, source)
    inverted = actual.invert
    assert_equal("<foo>", inverted["bar"])
    assert_equal("<baz>", inverted["quux"])
  end

  def test_sql_injection_via_find
    assert_raises(ActiveRecord::RecordNotFound) do
      Topic.find("123456 OR id > 0")
    end

    assert_raises(ActiveRecord::RecordNotFound) do
      Topic.find(";;; this should raise an RecordNotFound error")
    end
  end

  def test_column_name_properly_quoted
    col_record = ColumnName.new
    col_record.references = 40
    assert col_record.save
    col_record.references = 41
    assert col_record.save
    assert_not_nil c2 = ColumnName.find(col_record.id)
    assert_equal(41, c2.references)
  end

  MyObject = Struct.new :attribute1, :attribute2
  
  def test_serialized_attribute
    myobj = MyObject.new('value1', 'value2')
    topic = Topic.create("content" => myobj)  
    Topic.serialize("content", MyObject)
    assert_equal(myobj, topic.content)
  end

  def test_serialized_attribute_with_class_constraint
    myobj = MyObject.new('value1', 'value2')
    topic = Topic.create("content" => myobj)
    Topic.serialize(:content, Hash)

    assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }

    settings = { "color" => "blue" }
    Topic.find(topic.id).update_attribute("content", settings)
    assert_equal(settings, Topic.find(topic.id).content)
    Topic.serialize(:content)
  end

  def test_quote
    author_name = "\\ \001 ' \n \\n \""
    topic = Topic.create('author_name' => author_name)
    assert_equal author_name, Topic.find(topic.id).author_name
  end
  
  def test_class_level_destroy
    should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
    Topic.find(1).replies << should_be_destroyed_reply

    Topic.destroy(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
    assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
  end

  def test_class_level_delete
    should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
    Topic.find(1).replies << should_be_destroyed_reply

    Topic.delete(1)
    assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
    assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
  end

  def test_increment_attribute
    assert_equal 0, topics(:first).replies_count
    topics(:first).increment! :replies_count
    assert_equal 1, topics(:first, :reload).replies_count
    
    topics(:first).increment(:replies_count).increment!(:replies_count)
    assert_equal 3, topics(:first, :reload).replies_count
  end
  
  def test_increment_nil_attribute
    assert_nil topics(:first).parent_id
    topics(:first).increment! :parent_id
    assert_equal 1, topics(:first).parent_id
  end
  
  def test_decrement_attribute
    topics(:first).increment(:replies_count).increment!(:replies_count)
    assert_equal 2, topics(:first).replies_count
    
    topics(:first).decrement!(:replies_count)
    assert_equal 1, topics(:first, :reload).replies_count

    topics(:first).decrement(:replies_count).decrement!(:replies_count)
    assert_equal -1, topics(:first, :reload).replies_count
  end
  
  def test_toggle_attribute
    assert !topics(:first).approved?
    topics(:first).toggle!(:approved)
    assert topics(:first).approved?
    topic = topics(:first)
    topic.toggle(:approved)
    assert !topic.approved?
    topic.reload
    assert topic.approved?
  end

  def test_reload
    t1 = Topic.find(1)
    t2 = Topic.find(1)
    t1.title = "something else"
    t1.save
    t2.reload
    assert_equal t1.title, t2.title
  end

  def test_define_attr_method_with_value
    k = Class.new( ActiveRecord::Base )
    k.send(:define_attr_method, :table_name, "foo")
    assert_equal "foo", k.table_name
  end

  def test_define_attr_method_with_block
    k = Class.new( ActiveRecord::Base )
    k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key }
    assert_equal "sys_id", k.primary_key
  end

  def test_set_table_name_with_value
    k = Class.new( ActiveRecord::Base )
    k.table_name = "foo"
    assert_equal "foo", k.table_name
    k.set_table_name "bar"
    assert_equal "bar", k.table_name
  end

  def test_set_table_name_with_block
    k = Class.new( ActiveRecord::Base )
    k.set_table_name { "ks" }
    assert_equal "ks", k.table_name
  end

  def test_set_primary_key_with_value
    k = Class.new( ActiveRecord::Base )
    k.primary_key = "foo"
    assert_equal "foo", k.primary_key
    k.set_primary_key "bar"
    assert_equal "bar", k.primary_key
  end

  def test_set_primary_key_with_block
    k = Class.new( ActiveRecord::Base )
    k.set_primary_key { "sys_" + original_primary_key }
    assert_equal "sys_id", k.primary_key
  end

  def test_set_inheritance_column_with_value
    k = Class.new( ActiveRecord::Base )
    k.inheritance_column = "foo"
    assert_equal "foo", k.inheritance_column
    k.set_inheritance_column "bar"
    assert_equal "bar", k.inheritance_column
  end

  def test_set_inheritance_column_with_block
    k = Class.new( ActiveRecord::Base )
    k.set_inheritance_column { original_inheritance_column + "_id" }
    assert_equal "type_id", k.inheritance_column
  end

  def test_count_with_join
    res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
    res2 = nil
    assert_nothing_raised do
      res2 = Post.count("posts.#{QUOTED_TYPE} = 'Post'",
                        "LEFT JOIN comments ON posts.id=comments.post_id")
    end
    assert_equal res, res2
    
    res3 = nil
    assert_nothing_raised do
      res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
                        :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
    end
    assert_equal res, res3
    
    res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
    res5 = nil
    assert_nothing_raised do
      res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
                        :joins => "p, comments c",
                        :select => "p.id")
    end

    assert_equal res4, res5 

    res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
    res7 = nil
    assert_nothing_raised do
      res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
                        :joins => "p, comments c",
                        :select => "p.id",
                        :distinct => true)
    end
    assert_equal res6, res7
  end
  
  def test_clear_association_cache_stored     
    firm = Firm.find(1)
    assert_kind_of Firm, firm

    firm.clear_association_cache
    assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
  end
  
  def test_clear_association_cache_new_record
     firm            = Firm.new
     client_stored   = Client.find(3)
     client_new      = Client.new
     client_new.name = "The Joneses"
     clients         = [ client_stored, client_new ]
     
     firm.clients    << clients

     firm.clear_association_cache

     assert_equal    firm.clients.collect{ |x| x.name }.sort, clients.collect{ |x| x.name }.sort
  end

  def test_interpolate_sql
    assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
    assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
    assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') }
  end

  def test_scoped_find_conditions
    scoped_developers = Developer.with_scope(:find => { :conditions => 'salary > 90000' }) do
      Developer.find(:all, :conditions => 'id < 5')
    end
    assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
    assert_equal 3, scoped_developers.size
  end
  
  def test_scoped_find_limit_offset
    scoped_developers = Developer.with_scope(:find => { :limit => 3, :offset => 2 }) do
      Developer.find(:all, :order => 'id')
    end    
    assert !scoped_developers.include?(developers(:david))
    assert !scoped_developers.include?(developers(:jamis))
    assert_equal 3, scoped_developers.size
    
    # Test without scoped find conditions to ensure we get the whole thing
    developers = Developer.find(:all, :order => 'id')
    assert_equal Developer.count, developers.size
  end

  def test_base_class
    assert LoosePerson.abstract_class?
    assert !LooseDescendant.abstract_class?
    assert_equal LoosePerson,     LoosePerson.base_class
    assert_equal LooseDescendant, LooseDescendant.base_class
    assert_equal TightPerson,     TightPerson.base_class
    assert_equal TightPerson,     TightDescendant.base_class
  end

  def test_assert_queries
    query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
    assert_queries(2) { 2.times { query.call } }
    assert_queries 1, &query
    assert_no_queries { assert true }
  end

  def test_to_xml
    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true)
    bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
    written_on_in_current_timezone = topics(:first).written_on.xmlschema
    last_read_in_current_timezone = topics(:first).last_read.xmlschema
    assert_equal "<topic>", xml.first(7)
    assert xml.include?(%(<title>The First Topic</title>))
    assert xml.include?(%(<author-name>David</author-name>))
    assert xml.include?(%(<id type="integer">1</id>))
    assert xml.include?(%(<replies-count type="integer">0</replies-count>))
    assert xml.include?(%(<written-on type="datetime">#{written_on_in_current_timezone}</written-on>))
    assert xml.include?(%(<content>Have a nice day</content>))
    assert xml.include?(%(<author-email-address>david@loudthinking.com</author-email-address>))
    assert xml.include?(%(<parent-id></parent-id>))
    if current_adapter?(:SybaseAdapter) or current_adapter?(:SQLServerAdapter)
      assert xml.include?(%(<last-read type="datetime">#{last_read_in_current_timezone}</last-read>))
    else
      assert xml.include?(%(<last-read type="date">2004-04-15</last-read>))
    end
    # Oracle and DB2 don't have true boolean or time-only fields
    unless current_adapter?(:OracleAdapter) || current_adapter?(:DB2Adapter)
      assert xml.include?(%(<approved type="boolean">false</approved>)), "Approved should be a boolean"
      assert xml.include?(%(<bonus-time type="datetime">#{bonus_time_in_current_timezone}</bonus-time>))
    end
  end
  
  def test_to_xml_skipping_attributes
    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => :title)
    assert_equal "<topic>", xml.first(7)
    assert !xml.include?(%(<title>The First Topic</title>))
    assert xml.include?(%(<author-name>David</author-name>))    

    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [ :title, :author_name ])
    assert !xml.include?(%(<title>The First Topic</title>))
    assert !xml.include?(%(<author-name>David</author-name>))    
  end
  
  def test_to_xml_including_has_many_association
    xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
    assert_equal "<topic>", xml.first(7)
    assert xml.include?(%(<replies><reply>))
    assert xml.include?(%(<title>The Second Topic's of the day</title>))
  end

  def test_to_xml_including_belongs_to_association
    xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
    assert !xml.include?("<firm>")

    xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
    assert xml.include?("<firm>")
  end
  
  def test_to_xml_including_multiple_associations
    xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
    assert_equal "<firm>", xml.first(6)
    assert xml.include?(%(<account>))
    assert xml.include?(%(<clients><client>))
  end

  def test_to_xml_including_multiple_associations_with_options
    xml = companies(:first_firm).to_xml(
      :indent  => 0, :skip_instruct => true, 
      :include => { :clients => { :only => :name } }
    )
    
    assert_equal "<firm>", xml.first(6)
    assert xml.include?(%(<client><name>Summit</name></client>))
    assert xml.include?(%(<clients><client>))
  end
  
  def test_except_attributes
    assert_equal(
      %w( author_name type id approved replies_count bonus_time written_on content author_email_address parent_id last_read), 
      topics(:first).attributes(:except => :title).keys
    )

    assert_equal(
      %w( replies_count bonus_time written_on content author_email_address parent_id last_read), 
      topics(:first).attributes(:except => [ :title, :id, :type, :approved, :author_name ]).keys
    )
  end
  
  def test_include_attributes
    assert_equal(%w( title ), topics(:first).attributes(:only => :title).keys)
    assert_equal(%w( title author_name type id approved ), topics(:first).attributes(:only => [ :title, :id, :type, :approved, :author_name ]).keys)
  end
  
  def test_type_name_with_module_should_handle_beginning
    assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
    assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
  end

  # FIXME: this test ought to run, but it needs to run sandboxed so that it
  # doesn't b0rk the current test environment by undefing everything.
  #
  #def test_dev_mode_memory_leak
  #  counts = []
  #  2.times do
  #    require_dependency 'fixtures/company'
  #    Firm.find(:first)
  #    Dependencies.clear
  #    ActiveRecord::Base.reset_subclasses
  #    Dependencies.remove_subclasses_for(ActiveRecord::Base)
  #
  #    GC.start
  #    
  #    count = 0
  #    ObjectSpace.each_object(Proc) { count += 1 }
  #    counts << count
  #  end
  #  assert counts.last <= counts.first,
  #    "expected last count (#{counts.last}) to be <= first count (#{counts.first})"
  #end
  
  private
    def assert_readers(model, exceptions)
      expected_readers = Set.new(model.column_names - (model.serialized_attributes.keys + ['id']))
      expected_readers += expected_readers.map { |col| "#{col}?" }
      expected_readers -= exceptions
      assert_equal expected_readers, model.read_methods
    end
end
require 'abstract_unit'
require 'fixtures/binary'

class BinaryTest < Test::Unit::TestCase
  BINARY_FIXTURE_PATH = File.dirname(__FILE__) + '/fixtures/flowers.jpg'

  def setup
    Binary.connection.execute 'DELETE FROM binaries'
    @data = File.read(BINARY_FIXTURE_PATH).freeze
  end
  
  def test_truth
    assert true
  end

  # Without using prepared statements, it makes no sense to test
  # BLOB data with SQL Server, because the length of a statement is
  # limited to 8KB.
  #
  # Without using prepared statements, it makes no sense to test
  # BLOB data with DB2 or Firebird, because the length of a statement
  # is limited to 32KB.
  unless %w(SQLServer Sybase DB2 Oracle Firebird).include? ActiveRecord::Base.connection.adapter_name
    def test_load_save
      bin = Binary.new
      bin.data = @data

      assert @data == bin.data, 'Newly assigned data differs from original'
          
      bin.save
      assert @data == bin.data, 'Data differs from original after save'

      db_bin = Binary.find(bin.id)
      assert @data == db_bin.data, 'Reloaded data differs from original'
    end
  end
end
require 'abstract_unit'
require 'fixtures/company'
require 'fixtures/topic'

Company.has_many :accounts

class CalculationsTest < Test::Unit::TestCase
  fixtures :companies, :accounts, :topics

  def test_should_sum_field
    assert_equal 265, Account.sum(:credit_limit)
  end

  def test_should_average_field
    value = Account.average(:credit_limit)
    assert_equal 53, value
    assert_kind_of Float, value
  end

  def test_should_get_maximum_of_field
    assert_equal 60, Account.maximum(:credit_limit)
  end

  def test_should_get_minimum_of_field
    assert_equal 50, Account.minimum(:credit_limit)
  end

  def test_should_group_by_field
    c = Account.sum(:credit_limit, :group => :firm_id)
    [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
  end

  def test_should_group_by_summed_field
    c = Account.sum(:credit_limit, :group => :firm_id)
    assert_equal 50,   c[1]
    assert_equal 105,  c[6]
    assert_equal 60,   c[2]
  end

  def test_should_order_by_grouped_field
    c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
    assert_equal [1, 2, 6], c.keys.compact
  end

  def test_should_order_by_calculation
    c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
    assert_equal [105, 60, 50, 50], c.keys.collect { |k| c[k] }
    assert_equal [6, 2, 1], c.keys.compact
  end

  def test_should_limit_calculation
    c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
                    :group => :firm_id, :order => "firm_id", :limit => 2)
    assert_equal [1, 2], c.keys.compact
  end

  def test_should_limit_calculation_with_offset
    c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
                    :group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1)
    assert_equal [2, 6], c.keys.compact
  end

  def test_should_group_by_summed_field_having_condition
    c = Account.sum(:credit_limit, :group => :firm_id, 
                                   :having => 'sum(credit_limit) > 50')
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
  end

  def test_should_group_by_summed_association
    c = Account.sum(:credit_limit, :group => :firm)
    assert_equal 50,   c[companies(:first_firm)]
    assert_equal 105,  c[companies(:rails_core)]
    assert_equal 60,   c[companies(:first_client)]
  end
  
  def test_should_sum_field_with_conditions
    assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6')
  end

  def test_should_group_by_summed_field_with_conditions
    c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', 
                                   :group => :firm_id)
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
  end
  
  def test_should_group_by_summed_field_with_conditions_and_having
    c = Account.sum(:credit_limit, :conditions => 'firm_id > 1', 
                                   :group => :firm_id, 
                                   :having => 'sum(credit_limit) > 60')
    assert_nil        c[1]
    assert_equal 105, c[6]
    assert_nil        c[2]
  end

  def test_should_group_by_fields_with_table_alias
    c = Account.sum(:credit_limit, :group => 'accounts.firm_id')
    assert_equal 50,  c[1]
    assert_equal 105, c[6]
    assert_equal 60,  c[2]
  end
  
  def test_should_calculate_with_invalid_field
    assert_equal 5, Account.calculate(:count, '*')
    assert_equal 5, Account.calculate(:count, :all)
  end
  
  def test_should_calculate_grouped_with_invalid_field
    c = Account.count(:all, :group => 'accounts.firm_id')
    assert_equal 1, c[1]
    assert_equal 2, c[6]
    assert_equal 1, c[2]
  end
  
  def test_should_calculate_grouped_association_with_invalid_field
    c = Account.count(:all, :group => :firm)
    assert_equal 1, c[companies(:first_firm)]
    assert_equal 2, c[companies(:rails_core)]
    assert_equal 1, c[companies(:first_client)]
  end

  def test_should_calculate_grouped_by_function
    c = Company.count(:all, :group => 'UPPER(type)')
    assert_equal 2, c[nil]
    assert_equal 1, c['DEPENDENTFIRM']
    assert_equal 3, c['CLIENT']
    assert_equal 2, c['FIRM']
  end
  
  def test_should_calculate_grouped_by_function_with_table_alias
    c = Company.count(:all, :group => 'UPPER(companies.type)')
    assert_equal 2, c[nil]
    assert_equal 1, c['DEPENDENTFIRM']
    assert_equal 3, c['CLIENT']
    assert_equal 2, c['FIRM']
  end

  def test_should_sum_scoped_field
    assert_equal 15, companies(:rails_core).companies.sum(:id)
  end

  def test_should_sum_scoped_field_with_conditions
    assert_equal 8,  companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
  end

  def test_should_group_by_scoped_field
    c = companies(:rails_core).companies.sum(:id, :group => :name)
    assert_equal 7, c['Leetsoft']
    assert_equal 8, c['Jadedpixel']
  end

  def test_should_group_by_summed_field_with_conditions_and_having
    c = companies(:rails_core).companies.sum(:id, :group => :name,
                                                  :having => 'sum(id) > 7')
    assert_nil      c['Leetsoft']
    assert_equal 8, c['Jadedpixel']
  end

  def test_should_reject_invalid_options
    assert_nothing_raised do
      [:count, :sum].each do |func|
        # empty options are valid
        Company.send(:validate_calculation_options, func)
        # these options are valid for all calculations
        [:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt| 
          Company.send(:validate_calculation_options, func, opt => true)
        end
      end
      
      # :include is only valid on :count
      Company.send(:validate_calculation_options, :count, :include => true)
    end
    
    assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum,   :include => :posts) }
    assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum,   :foo => :bar) }
    assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) }
  end
end
require 'abstract_unit'

class CallbackDeveloper < ActiveRecord::Base
  set_table_name 'developers'

  class << self
    def callback_string(callback_method)
      "history << [#{callback_method.to_sym.inspect}, :string]"
    end

    def callback_proc(callback_method)
      Proc.new { |model| model.history << [callback_method, :proc] }
    end

    def define_callback_method(callback_method)
      define_method("#{callback_method}_method") do |model|
        model.history << [callback_method, :method]
      end
    end

    def callback_object(callback_method)
      klass = Class.new
      klass.send(:define_method, callback_method) do |model|
        model.history << [callback_method, :object]
      end
      klass.new
    end
  end

  ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
    callback_method_sym = callback_method.to_sym
    define_callback_method(callback_method_sym)
    send(callback_method, callback_method_sym)
    send(callback_method, callback_string(callback_method_sym))
    send(callback_method, callback_proc(callback_method_sym))
    send(callback_method, callback_object(callback_method_sym))
    send(callback_method) { |model| model.history << [callback_method_sym, :block] }
  end

  def history
    @history ||= []
  end

  # after_initialize and after_find are invoked only if instance methods have been defined.
  def after_initialize
  end

  def after_find
  end
end

class RecursiveCallbackDeveloper < ActiveRecord::Base
  set_table_name 'developers'

  before_save :on_before_save
  after_save :on_after_save

  attr_reader :on_before_save_called, :on_after_save_called

  def on_before_save
    @on_before_save_called ||= 0
    @on_before_save_called += 1
    save unless @on_before_save_called > 1
  end

  def on_after_save
    @on_after_save_called ||= 0
    @on_after_save_called += 1
    save unless @on_after_save_called > 1
  end
end

class ImmutableDeveloper < ActiveRecord::Base
  set_table_name 'developers'

  validates_inclusion_of :salary, :in => 50000..200000

  before_save :cancel
  before_destroy :cancel

  def cancelled?
    @cancelled == true
  end

  private
    def cancel
      @cancelled = true
      false
    end
end

class ImmutableMethodDeveloper < ActiveRecord::Base
  set_table_name 'developers'

  validates_inclusion_of :salary, :in => 50000..200000

  def cancelled?
    @cancelled == true
  end

  def before_save
    @cancelled = true
    false
  end

  def before_destroy
    @cancelled = true
    false
  end
end

class CallbacksTest < Test::Unit::TestCase
  fixtures :developers

  def test_initialize
    david = CallbackDeveloper.new
    assert_equal [
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
    ], david.history
  end

  def test_find
    david = CallbackDeveloper.find(1)
    assert_equal [
      [ :after_find,            :string ],
      [ :after_find,            :proc   ],
      [ :after_find,            :object ],
      [ :after_find,            :block  ],
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
    ], david.history
  end

  def test_new_valid?
    david = CallbackDeveloper.new
    david.valid?
    assert_equal [
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
      [ :before_validation,           :string ],
      [ :before_validation,           :proc   ],
      [ :before_validation,           :object ],
      [ :before_validation,           :block  ],
      [ :before_validation_on_create, :string ],
      [ :before_validation_on_create, :proc   ],
      [ :before_validation_on_create, :object ],
      [ :before_validation_on_create, :block  ],
      [ :after_validation,            :string ],
      [ :after_validation,            :proc   ],
      [ :after_validation,            :object ],
      [ :after_validation,            :block  ],
      [ :after_validation_on_create,  :string ],
      [ :after_validation_on_create,  :proc   ],
      [ :after_validation_on_create,  :object ],
      [ :after_validation_on_create,  :block  ]
    ], david.history
  end

  def test_existing_valid?
    david = CallbackDeveloper.find(1)
    david.valid?
    assert_equal [
      [ :after_find,            :string ],
      [ :after_find,            :proc   ],
      [ :after_find,            :object ],
      [ :after_find,            :block  ],
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
      [ :before_validation,           :string ],
      [ :before_validation,           :proc   ],
      [ :before_validation,           :object ],
      [ :before_validation,           :block  ],
      [ :before_validation_on_update, :string ],
      [ :before_validation_on_update, :proc   ],
      [ :before_validation_on_update, :object ],
      [ :before_validation_on_update, :block  ],
      [ :after_validation,            :string ],
      [ :after_validation,            :proc   ],
      [ :after_validation,            :object ],
      [ :after_validation,            :block  ],
      [ :after_validation_on_update,  :string ],
      [ :after_validation_on_update,  :proc   ],
      [ :after_validation_on_update,  :object ],
      [ :after_validation_on_update,  :block  ]
    ], david.history
  end

  def test_create
    david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000)
    assert_equal [
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
      [ :before_validation,           :string ],
      [ :before_validation,           :proc   ],
      [ :before_validation,           :object ],
      [ :before_validation,           :block  ],
      [ :before_validation_on_create, :string ],
      [ :before_validation_on_create, :proc   ],
      [ :before_validation_on_create, :object ],
      [ :before_validation_on_create, :block  ],
      [ :after_validation,            :string ],
      [ :after_validation,            :proc   ],
      [ :after_validation,            :object ],
      [ :after_validation,            :block  ],
      [ :after_validation_on_create,  :string ],
      [ :after_validation_on_create,  :proc   ],
      [ :after_validation_on_create,  :object ],
      [ :after_validation_on_create,  :block  ],
      [ :before_save,                 :string ],
      [ :before_save,                 :proc   ],
      [ :before_save,                 :object ],
      [ :before_save,                 :block  ],
      [ :before_create,               :string ],
      [ :before_create,               :proc   ],
      [ :before_create,               :object ],
      [ :before_create,               :block  ],
      [ :after_create,                :string ],
      [ :after_create,                :proc   ],
      [ :after_create,                :object ],
      [ :after_create,                :block  ],
      [ :after_save,                  :string ],
      [ :after_save,                  :proc   ],
      [ :after_save,                  :object ],
      [ :after_save,                  :block  ]
    ], david.history
  end

  def test_save
    david = CallbackDeveloper.find(1)
    david.save
    assert_equal [
      [ :after_find,            :string ],
      [ :after_find,            :proc   ],
      [ :after_find,            :object ],
      [ :after_find,            :block  ],
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
      [ :before_validation,           :string ],
      [ :before_validation,           :proc   ],
      [ :before_validation,           :object ],
      [ :before_validation,           :block  ],
      [ :before_validation_on_update, :string ],
      [ :before_validation_on_update, :proc   ],
      [ :before_validation_on_update, :object ],
      [ :before_validation_on_update, :block  ],
      [ :after_validation,            :string ],
      [ :after_validation,            :proc   ],
      [ :after_validation,            :object ],
      [ :after_validation,            :block  ],
      [ :after_validation_on_update,  :string ],
      [ :after_validation_on_update,  :proc   ],
      [ :after_validation_on_update,  :object ],
      [ :after_validation_on_update,  :block  ],
      [ :before_save,                 :string ],
      [ :before_save,                 :proc   ],
      [ :before_save,                 :object ],
      [ :before_save,                 :block  ],
      [ :before_update,               :string ],
      [ :before_update,               :proc   ],
      [ :before_update,               :object ],
      [ :before_update,               :block  ],
      [ :after_update,                :string ],
      [ :after_update,                :proc   ],
      [ :after_update,                :object ],
      [ :after_update,                :block  ],
      [ :after_save,                  :string ],
      [ :after_save,                  :proc   ],
      [ :after_save,                  :object ],
      [ :after_save,                  :block  ]
    ], david.history
  end

  def test_destroy
    david = CallbackDeveloper.find(1)
    david.destroy
    assert_equal [
      [ :after_find,            :string ],
      [ :after_find,            :proc   ],
      [ :after_find,            :object ],
      [ :after_find,            :block  ],
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
      [ :before_destroy,              :string ],
      [ :before_destroy,              :proc   ],
      [ :before_destroy,              :object ],
      [ :before_destroy,              :block  ],
      [ :after_destroy,               :string ],
      [ :after_destroy,               :proc   ],
      [ :after_destroy,               :object ],
      [ :after_destroy,               :block  ]
    ], david.history
  end

  def test_delete
    david = CallbackDeveloper.find(1)
    CallbackDeveloper.delete(david.id)
    assert_equal [
      [ :after_find,            :string ],
      [ :after_find,            :proc   ],
      [ :after_find,            :object ],
      [ :after_find,            :block  ],
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
    ], david.history
  end

  def test_before_save_returning_false
    david = ImmutableDeveloper.find(1)
    assert david.valid?
    assert !david.save
    assert_raises(ActiveRecord::RecordNotSaved) { david.save! }

    david = ImmutableDeveloper.find(1)
    david.salary = 10_000_000
    assert !david.valid?
    assert !david.save
    assert_raises(ActiveRecord::RecordInvalid) { david.save! }
  end

  def test_before_destroy_returning_false
    david = ImmutableDeveloper.find(1)
    assert !david.destroy
    assert_not_nil ImmutableDeveloper.find_by_id(1)
  end  

  def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper
    david = CallbackDeveloper.find(1)
    CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false }
    CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
    david.save
    assert_equal [
      [ :after_find,            :string ],
      [ :after_find,            :proc   ],
      [ :after_find,            :object ],
      [ :after_find,            :block  ],
      [ :after_initialize,            :string ],
      [ :after_initialize,            :proc   ],
      [ :after_initialize,            :object ],
      [ :after_initialize,            :block  ],
      [ :before_validation,           :string ],
      [ :before_validation,           :proc   ],
      [ :before_validation,           :object ],
      [ :before_validation,           :block  ],
      [ :before_validation, :returning_false  ]
    ], david.history
  end
end
require 'test/unit'
require 'abstract_unit'
require 'active_support/core_ext/class/inheritable_attributes'

class A
  include ClassInheritableAttributes
end

class B < A
  write_inheritable_array "first", [ :one, :two ]
end

class C < A
  write_inheritable_array "first", [ :three ]
end

class D < B
  write_inheritable_array "first", [ :four ]
end


class ClassInheritableAttributesTest < Test::Unit::TestCase
  def test_first_level
    assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
    assert_equal [ :three ], C.read_inheritable_attribute("first")
  end
  
  def test_second_level
    assert_equal [ :one, :two, :four ], D.read_inheritable_attribute("first")
    assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
  end
end
require 'abstract_unit'
require 'fixtures/topic'

class TestColumnAlias < Test::Unit::TestCase
  fixtures :topics

  QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name
            'SELECT id AS pk FROM topics WHERE ROWNUM < 2'
          else
            'SELECT id AS pk FROM topics'
          end

  def test_column_alias
    records = Topic.connection.select_all(QUERY)
    assert_equal 'pk', records[0].keys[0]
  end
end
print "Using native DB2\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

db1 = 'arunit'
db2 = 'arunit2'

ActiveRecord::Base.establish_connection(
  :adapter  => "db2",
  :host     => "localhost",
  :username => "arunit",
  :password => "arunit",
  :database => db1
)

Course.establish_connection(
  :adapter  => "db2",
  :host     => "localhost",
  :username => "arunit2",
  :password => "arunit2",
  :database => db2
)
print "Using native Firebird\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

db1 = 'activerecord_unittest'
db2 = 'activerecord_unittest2'

ActiveRecord::Base.establish_connection(
  :adapter  => "firebird",
  :host     => "localhost",
  :username => "rails",
  :password => "rails",
  :database => db1
)

Course.establish_connection(
  :adapter  => "firebird",
  :host     => "localhost",
  :username => "rails",
  :password => "rails",
  :database => db2
)
print "Using native MySQL\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

db1 = 'activerecord_unittest'
db2 = 'activerecord_unittest2'

ActiveRecord::Base.establish_connection(
  :adapter  => "mysql",
  :username => "rails",
  :encoding => "utf8",
  :database => db1
)

Course.establish_connection(
  :adapter  => "mysql",
  :username => "rails",
  :database => db2
)
print "Using native OpenBase\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

db1 = 'activerecord_unittest'
db2 = 'activerecord_unittest2'

ActiveRecord::Base.establish_connection(
  :adapter  => "openbase",
  :username => "admin",
  :password => "", 
  :database => db1
)

Course.establish_connection(
  :adapter  => "openbase",
  :username => "admin",
  :password => "", 
  :database => db2
)
print "Using Oracle\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new STDOUT
ActiveRecord::Base.logger.level = Logger::WARN

# Set these to your database connection strings
db = ENV['ARUNIT_DB'] || 'activerecord_unittest'

ActiveRecord::Base.establish_connection(
  :adapter  => 'oracle',
  :username => 'arunit',
  :password => 'arunit',
  :database => db
)

Course.establish_connection(
  :adapter  => 'oracle',
  :username => 'arunit2',
  :password => 'arunit2',
  :database => db
)
print "Using native PostgreSQL\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

db1 = 'activerecord_unittest'
db2 = 'activerecord_unittest2'

ActiveRecord::Base.establish_connection(
  :adapter  => "postgresql",
  :username => "postgres",
  :password => "postgres", 
  :database => db1,
  :min_messages => "warning"
)

Course.establish_connection(
  :adapter  => "postgresql",
  :username => "postgres",
  :password => "postgres", 
  :database => db2,
  :min_messages => "warning"
)
print "Using native SQlite\n"
require_dependency 'fixtures/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")

class SqliteError < StandardError
end

BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../fixtures')
sqlite_test_db  = "#{BASE_DIR}/fixture_database.sqlite"
sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite"

def make_connection(clazz, db_file, db_definitions_file)
  unless File.exist?(db_file)
    puts "SQLite database not found at #{db_file}. Rebuilding it."
    sqlite_command = %Q{sqlite #{db_file} "create table a (a integer); drop table a;"}
    puts "Executing '#{sqlite_command}'"
    raise SqliteError.new("Seems that there is no sqlite executable available") unless system(sqlite_command)
    clazz.establish_connection(
        :adapter => "sqlite",
        :database  => db_file)
    script = File.read("#{BASE_DIR}/db_definitions/#{db_definitions_file}")
    # SQLite-Ruby has problems with semi-colon separated commands, so split and execute one at a time
    script.split(';').each do
      |command|
      clazz.connection.execute(command) unless command.strip.empty?
    end
  else
    clazz.establish_connection(
        :adapter => "sqlite",
        :database  => db_file)
  end
end

make_connection(ActiveRecord::Base, sqlite_test_db, 'sqlite.sql')
make_connection(Course, sqlite_test_db2, 'sqlite2.sql')
load(File.join(BASE_DIR, 'db_definitions', 'schema.rb'))
print "Using native SQLite3\n"
require_dependency 'fixtures/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")

class SqliteError < StandardError
end

BASE_DIR = File.expand_path(File.dirname(__FILE__) + '/../../fixtures')
sqlite_test_db  = "#{BASE_DIR}/fixture_database.sqlite3"
sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3"

def make_connection(clazz, db_file, db_definitions_file)
  unless File.exist?(db_file)
    puts "SQLite3 database not found at #{db_file}. Rebuilding it."
    sqlite_command = %Q{sqlite3 #{db_file} "create table a (a integer); drop table a;"}
    puts "Executing '#{sqlite_command}'"
    raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command)
    clazz.establish_connection(
        :adapter => "sqlite3",
        :database  => db_file)
    script = File.read("#{BASE_DIR}/db_definitions/#{db_definitions_file}")
    # SQLite-Ruby has problems with semi-colon separated commands, so split and execute one at a time
    script.split(';').each do
      |command|
      clazz.connection.execute(command) unless command.strip.empty?
    end
  else
    clazz.establish_connection(
        :adapter => "sqlite3",
        :database  => db_file)
  end
end

make_connection(ActiveRecord::Base, sqlite_test_db, 'sqlite.sql')
make_connection(Course, sqlite_test_db2, 'sqlite2.sql')
load(File.join(BASE_DIR, 'db_definitions', 'schema.rb'))
print "Using native SQLite3\n"
require_dependency 'fixtures/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")

class SqliteError < StandardError
end

def make_connection(clazz, db_definitions_file)
  clazz.establish_connection(:adapter => 'sqlite3', :database  => ':memory:')
  File.read("#{File.dirname(__FILE__)}/../../fixtures/db_definitions/#{db_definitions_file}").split(';').each do |command|
    clazz.connection.execute(command) unless command.strip.empty?
  end
end

make_connection(ActiveRecord::Base, 'sqlite.sql')
make_connection(Course, 'sqlite2.sql')
  load("#{File.dirname(__FILE__)}/../../fixtures/db_definitions/schema.rb"))
print "Using native SQLServer\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

db1 = 'activerecord_unittest'
db2 = 'activerecord_unittest2'

ActiveRecord::Base.establish_connection(
  :adapter  => "sqlserver",
  :host     => "localhost",
  :username => "sa",
  :password => "",
  :database => db1
)

Course.establish_connection(
  :adapter  => "sqlserver",
  :host     => "localhost",
  :username => "sa",
  :password => "",
  :database => db2
)
print "Using native SQLServer via ODBC\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

dsn1 = 'activerecord_unittest'
dsn2 = 'activerecord_unittest2'

ActiveRecord::Base.establish_connection(
  :adapter  => "sqlserver",
  :mode     => "ODBC",
  :host     => "localhost",
  :username => "sa",
  :password => "",
  :dsn => dsn1
)

Course.establish_connection(
  :adapter  => "sqlserver",
  :mode     => "ODBC",
  :host     => "localhost",
  :username => "sa",
  :password => "",
  :dsn => dsn2
)
print "Using native Sybase Open Client\n"
require_dependency 'fixtures/course'
require 'logger'

ActiveRecord::Base.logger = Logger.new("debug.log")

db1 = 'activerecord_unittest'
db2 = 'activerecord_unittest2'

ActiveRecord::Base.establish_connection(
  :adapter  => "sybase",
  :host     => "database_ASE",
  :username => "sa",
  :password => "",
  :database => db1
)

Course.establish_connection(
  :adapter  => "sybase",
  :host     => "database_ASE",
  :username => "sa",
  :password => "",
  :database => db2
)
require 'abstract_unit'

class CopyTableTest < Test::Unit::TestCase
  fixtures :companies, :comments
  
  def setup
    @connection = ActiveRecord::Base.connection
    class << @connection
      public :copy_table, :table_structure, :indexes
    end
  end
  
  def test_copy_table(from = 'companies', to = 'companies2', options = {})
    assert_nothing_raised {copy_table(from, to, options)}
    assert_equal row_count(from), row_count(to)
      
    if block_given?
      yield from, to, options
    else
      assert_equal column_names(from), column_names(to)
    end
    
    @connection.drop_table(to) rescue nil
  end
  
  def test_copy_table_renaming_column
    test_copy_table('companies', 'companies2', 
        :rename => {'client_of' => 'fan_of'}) do |from, to, options|
      assert_equal column_values(from, 'client_of').compact.sort, 
                   column_values(to, 'fan_of').compact.sort
    end
  end
  
  def test_copy_table_with_index
    test_copy_table('comments', 'comments_with_index') do
      @connection.add_index('comments_with_index', ['post_id', 'type'])
      test_copy_table('comments_with_index', 'comments_with_index2') do
        assert_equal table_indexes_without_name('comments_with_index'),
                     table_indexes_without_name('comments_with_index2')
      end
    end
  end
  
protected
  def copy_table(from, to, options = {})
    @connection.copy_table(from, to, {:temporary => true}.merge(options))
  end

  def column_names(table)
    @connection.table_structure(table).map {|column| column['name']}
  end
  
  def column_values(table, column)
    @connection.select_all("SELECT #{column} FROM #{table}").map {|row| row[column]}
  end

  def table_indexes_without_name(table)
    @connection.indexes('comments_with_index').delete(:name)
  end
  
  def row_count(table)
    @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count']
  end
end
require 'abstract_unit'
require 'fixtures/default'

class DefaultTest < Test::Unit::TestCase
  def test_default_timestamp
    default = Default.new
    assert_instance_of(Time, default.default_timestamp)
    assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type)

    # Variance should be small; increase if required -- e.g., if test db is on
    # remote host and clocks aren't synchronized.
    t1 = Time.new
    accepted_variance = 1.0
    assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance)
  end
end
require 'abstract_unit'
require 'fixtures/default'

class DefaultsTest < Test::Unit::TestCase
  if %w(PostgreSQL).include? ActiveRecord::Base.connection.adapter_name
    def test_default_integers
      default = Default.new
      assert_instance_of(Fixnum, default.positive_integer)
      assert_equal(default.positive_integer, 1)
      assert_instance_of(Fixnum, default.negative_integer)
      assert_equal(default.negative_integer, -1)
    end
  else
    def test_dummy
      assert true
    end
  end
end
require 'abstract_unit'
require 'fixtures/developer'
require 'fixtures/project'
require 'fixtures/company'
require 'fixtures/topic'
require 'fixtures/reply'

# Can't declare new classes in test case methods, so tests before that
bad_collection_keys = false
begin
  class Car < ActiveRecord::Base; has_many :wheels, :name => "wheels"; end
rescue ArgumentError
  bad_collection_keys = true
end
raise "ActiveRecord should have barked on bad collection keys" unless bad_collection_keys


class DeprecatedAssociationsTest < Test::Unit::TestCase
  fixtures :accounts, :companies, :developers, :projects, :topics,
           :developers_projects

  def test_has_many_find
    assert_equal 2, Firm.find_first.clients.length
  end

  def test_has_many_orders
    assert_equal "Summit", Firm.find_first.clients.first.name
  end

  def test_has_many_class_name
    assert_equal "Microsoft", Firm.find_first.clients_sorted_desc.first.name
  end

  def test_has_many_foreign_key
    assert_equal "Microsoft", Firm.find_first.clients_of_firm.first.name
  end

  def test_has_many_conditions
    assert_equal "Microsoft", Firm.find_first.clients_like_ms.first.name
  end

  def test_has_many_sql
    firm = Firm.find_first
    assert_equal "Microsoft", firm.clients_using_sql.first.name
    assert_equal 1, firm.clients_using_sql_count
    assert_equal 1, Firm.find_first.clients_using_sql_count
  end

  def test_has_many_counter_sql
    assert_equal 1, Firm.find_first.clients_using_counter_sql_count
  end

  def test_has_many_queries
    assert Firm.find_first.has_clients?
    firm = Firm.find_first
    assert_equal 2, firm.clients_count # tests using class count
    firm.clients
    assert firm.has_clients?
    assert_equal 2, firm.clients_count # tests using collection length
  end

  def test_has_many_dependence
    assert_equal 3, Client.find_all.length
    Firm.find_first.destroy
    assert_equal 1, Client.find_all.length
  end

  uses_transaction :test_has_many_dependence_with_transaction_support_on_failure
  def test_has_many_dependence_with_transaction_support_on_failure
    assert_equal 3, Client.find_all.length

    firm = Firm.find_first
    clients = firm.clients
    clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end }

    firm.destroy rescue "do nothing"

    assert_equal 3, Client.find_all.length
  end

  def test_has_one_dependence
    num_accounts = Account.count
    firm = Firm.find(1)
    assert firm.has_account?
    firm.destroy            
    assert_equal num_accounts - 1, Account.count
  end

  def test_has_one_dependence_with_missing_association
    Account.destroy_all
    firm = Firm.find(1)
    assert !firm.has_account?
    firm.destroy
  end

  def test_belongs_to
    assert_equal companies(:first_firm).name, Client.find(3).firm.name
    assert Client.find(3).has_firm?, "Microsoft should have a firm"
    # assert !Company.find(1).has_firm?, "37signals shouldn't have a firm"
  end

  def test_belongs_to_with_different_class_name
    assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
    assert Company.find(3).has_firm_with_other_name?, "Microsoft should have a firm"
  end

  def test_belongs_to_with_condition
    assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name
    assert Company.find(3).has_firm_with_condition?, "Microsoft should have a firm"
  end

  def test_belongs_to_equality
    assert Company.find(3).firm?(Company.find(1)), "Microsoft should have 37signals as firm"
    assert_raises(RuntimeError) { !Company.find(3).firm?(Company.find(3)) } # "Summit shouldn't have itself as firm"
  end

  def test_has_one
    assert companies(:first_firm).account?(Account.find(1))
    assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
    assert companies(:first_firm).has_account?, "37signals should have an account"
    assert Account.find(1).firm?(companies(:first_firm)), "37signals account should be able to backtrack"
    assert Account.find(1).has_firm?, "37signals account should be able to backtrack"

    assert !Account.find(2).has_firm?, "Unknown isn't linked"
    assert !Account.find(2).firm?(companies(:first_firm)), "Unknown isn't linked"
  end

  def test_has_many_dependence_on_account    
    num_accounts = Account.count
    companies(:first_firm).destroy
    assert_equal num_accounts - 1, Account.count
  end

  def test_find_in
    assert_equal Client.find(2).name, companies(:first_firm).find_in_clients(2).name
    assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).find_in_clients(6) }
  end

  def test_force_reload
    firm = Firm.new("name" => "A New Firm, Inc")
    firm.save
    firm.clients.each {|c|} # forcing to load all clients
    assert firm.clients.empty?, "New firm shouldn't have client objects"
    assert !firm.has_clients?, "New firm shouldn't have clients"
    assert_equal 0, firm.clients_count, "New firm should have 0 clients"

    client = Client.new("name" => "TheClient.com", "firm_id" => firm.id)
    client.save

    assert firm.clients.empty?, "New firm should have cached no client objects"
    assert !firm.has_clients?, "New firm should have cached a no-clients response"
    assert_equal 0, firm.clients_count, "New firm should have cached 0 clients count"

    assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
    assert firm.has_clients?(true), "New firm should have reloaded with a have-clients response"
    assert_equal 1, firm.clients_count(true), "New firm should have reloaded clients count"
  end

  def test_included_in_collection
    assert companies(:first_firm).clients.include?(Client.find(2))
  end

  def test_build_to_collection
    assert_equal 1, companies(:first_firm).clients_of_firm_count
    new_client = companies(:first_firm).build_to_clients_of_firm("name" => "Another Client")
    assert_equal "Another Client", new_client.name
    assert new_client.save

    assert new_client.firm?(companies(:first_firm))
    assert_equal 2, companies(:first_firm).clients_of_firm_count(true)
  end

  def test_create_in_collection
    assert_equal companies(:first_firm).create_in_clients_of_firm("name" => "Another Client"), companies(:first_firm).clients_of_firm(true).last
  end

  def test_has_and_belongs_to_many
    david = Developer.find(1)
    assert david.has_projects?
    assert_equal 2, david.projects_count

    active_record = Project.find(1)
    assert active_record.has_developers?
    assert_equal 3, active_record.developers_count
    assert active_record.developers.include?(david)
  end

  def test_has_and_belongs_to_many_removing
    david = Developer.find(1)
    active_record = Project.find(1)

    david.remove_projects(active_record)

    assert_equal 1, david.projects_count
    assert_equal 2, active_record.developers_count
  end

  def test_has_and_belongs_to_many_zero
    david = Developer.find(1)
    david.remove_projects(Project.find_all)

    assert_equal 0, david.projects_count
    assert !david.has_projects?
  end

  def test_has_and_belongs_to_many_adding
    jamis = Developer.find(2)
    action_controller = Project.find(2)

    jamis.add_projects(action_controller)

    assert_equal 2, jamis.projects_count
    assert_equal 2, action_controller.developers_count
  end

  def test_has_and_belongs_to_many_adding_from_the_project
    jamis = Developer.find(2)
    action_controller = Project.find(2)

    action_controller.add_developers(jamis)

    assert_equal 2, jamis.projects_count
    assert_equal 2, action_controller.developers_count
  end

  def test_has_and_belongs_to_many_adding_a_collection
    aredridel = Developer.new("name" => "Aredridel")
    aredridel.save

    aredridel.add_projects([ Project.find(1), Project.find(2) ])
    assert_equal 2, aredridel.projects_count
  end

  def test_belongs_to_counter
    topic = Topic.create("title" => "Apple", "content" => "hello world")
    assert_equal 0, topic.send(:read_attribute, "replies_count"), "No replies yet"

    reply = topic.create_in_replies("title" => "I'm saying no!", "content" => "over here")
    assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count"), "First reply created"

    reply.destroy
    assert_equal 0, Topic.find(topic.id).send(:read_attribute, "replies_count"), "First reply deleted"
  end

  def test_natural_assignment_of_has_one
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    apple.account = citibank
    assert_equal apple.id, citibank.firm_id
  end

  def test_natural_assignment_of_belongs_to
    apple = Firm.create("name" => "Apple")
    citibank = Account.create("credit_limit" => 10)
    citibank.firm = apple
    assert_equal apple.id, citibank.firm_id
  end

  def test_natural_assignment_of_has_many
    apple = Firm.create("name" => "Apple")
    natural = Client.create("name" => "Natural Company")
    apple.clients << natural
    assert_equal apple.id, natural.firm_id
    assert_equal Client.find(natural.id), Firm.find(apple.id).clients.find(natural.id)
    apple.clients.delete natural
    assert_raises(ActiveRecord::RecordNotFound) {
      Firm.find(apple.id).clients.find(natural.id)
    }
  end

  def test_natural_adding_of_has_and_belongs_to_many
    rails = Project.create("name" => "Rails")
    ap = Project.create("name" => "Action Pack")
    john = Developer.create("name" => "John")
    mike = Developer.create("name" => "Mike")
    rails.developers << john
    rails.developers << mike

    assert_equal Developer.find(john.id), Project.find(rails.id).developers.find(john.id)
    assert_equal Developer.find(mike.id), Project.find(rails.id).developers.find(mike.id)
    assert_equal Project.find(rails.id), Developer.find(mike.id).projects.find(rails.id)
    assert_equal Project.find(rails.id), Developer.find(john.id).projects.find(rails.id)
    ap.developers << john
    assert_equal Developer.find(john.id), Project.find(ap.id).developers.find(john.id)
    assert_equal Project.find(ap.id), Developer.find(john.id).projects.find(ap.id)

    ap.developers.delete john
    assert_raises(ActiveRecord::RecordNotFound) {
      Project.find(ap.id).developers.find(john.id)
    }
    assert_raises(ActiveRecord::RecordNotFound) {
      Developer.find(john.id).projects.find(ap.id)
    }
  end

  def test_storing_in_pstore
    require "pstore"
    require "tmpdir"
    apple = Firm.create("name" => "Apple")
    natural = Client.new("name" => "Natural Company")
    apple.clients << natural

    db = PStore.new(File.join(Dir.tmpdir, "ar-pstore-association-test"))
    db.transaction do
      db["apple"] = apple
    end

    db = PStore.new(File.join(Dir.tmpdir, "ar-pstore-association-test"))
    db.transaction do
      assert_equal "Natural Company", db["apple"].clients.first.name
    end
  end

  def test_has_many_find_all
    assert_equal 2, Firm.find_first.find_all_in_clients("#{QUOTED_TYPE} = 'Client'").length
    assert_equal 1, Firm.find_first.find_all_in_clients("name = 'Summit'").length
  end

  def test_has_one
    assert companies(:first_firm).account?(Account.find(1))
    assert companies(:first_firm).has_account?, "37signals should have an account"
    assert Account.find(1).firm?(companies(:first_firm)), "37signals account should be able to backtrack"
    assert Account.find(1).has_firm?, "37signals account should be able to backtrack"

    assert !Account.find(2).has_firm?, "Unknown isn't linked"
    assert !Account.find(2).firm?(companies(:first_firm)), "Unknown isn't linked"
  end

  def test_has_one_build
    firm = Firm.new("name" => "GlobalMegaCorp")
    assert firm.save

    account = firm.build_account("credit_limit" => 1000)
    assert account.save
    assert_equal account, firm.account
  end

  def test_has_one_failing_build_association
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save
    
    account = firm.build_account
    assert !account.save
    assert_equal "can't be empty", account.errors.on("credit_limit")
  end

  def test_has_one_create
    firm = Firm.new("name" => "GlobalMegaCorp")
    firm.save
    assert_equal firm.create_account("credit_limit" => 1000), firm.account
  end
end
require 'abstract_unit'
require 'fixtures/company'
require 'fixtures/topic'
require 'fixtures/entrant'
require 'fixtures/developer'

class DeprecatedFinderTest < Test::Unit::TestCase
  fixtures :companies, :topics, :entrants, :developers

  def test_find_all_with_limit
    entrants = Entrant.find_all nil, "id ASC", 2
    
    assert_equal(2, entrants.size)
    assert_equal(entrants(:first).name, entrants.first.name)
  end

  def test_find_all_with_prepared_limit_and_offset
    entrants = Entrant.find_all nil, "id ASC", [2, 1]

    assert_equal(2, entrants.size)
    assert_equal(entrants(:second).name, entrants.first.name)
  end

  def test_find_first
    first = Topic.find_first "title = 'The First Topic'"
    assert_equal(topics(:first).title, first.title)
  end
  
  def test_find_first_failing
    first = Topic.find_first "title = 'The First Topic!'"
    assert_nil(first)
  end
  
  def test_deprecated_find_on_conditions
    assert Topic.find_on_conditions(1, ["approved = ?", false])
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find_on_conditions(1, ["approved = ?", true]) }
  end
  
  def test_condition_interpolation
    assert_kind_of Firm, Company.find_first(["name = '%s'", "37signals"])
    assert_nil Company.find_first(["name = '%s'", "37signals!"])
    assert_nil Company.find_first(["name = '%s'", "37signals!' OR 1=1"])
    assert_kind_of Time, Topic.find_first(["id = %d", 1]).written_on
  end

  def test_bind_variables
    assert_kind_of Firm, Company.find_first(["name = ?", "37signals"])
    assert_nil Company.find_first(["name = ?", "37signals!"])
    assert_nil Company.find_first(["name = ?", "37signals!' OR 1=1"])
    assert_kind_of Time, Topic.find_first(["id = ?", 1]).written_on
    assert_raises(ActiveRecord::PreparedStatementInvalid) {
      Company.find_first(["id=? AND name = ?", 2])
    }
    assert_raises(ActiveRecord::PreparedStatementInvalid) {
	   Company.find_first(["id=?", 2, 3, 4])
    }
  end
  
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
    assert Company.find_first(["name = ?", "37signals' go'es agains"])
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
    assert Company.find_first(["name = :name", {:name => "37signals' go'es agains"}])
  end

  def test_named_bind_variables
    assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
    assert_equal '1 1', bind(':a :a', :a => 1)  # ' ruby-mode
  
    assert_kind_of Firm, Company.find_first(["name = :name", { :name => "37signals" }])
    assert_nil Company.find_first(["name = :name", { :name => "37signals!" }])
    assert_nil Company.find_first(["name = :name", { :name => "37signals!' OR 1=1" }])
    assert_kind_of Time, Topic.find_first(["id = :id", { :id => 1 }]).written_on
  end

  def test_count
    assert_equal(0, Entrant.count("id > 3"))
    assert_equal(1, Entrant.count(["id > ?", 2]))
    assert_equal(2, Entrant.count(["id > ?", 1]))
  end

  def test_count_by_sql
    assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
    assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
    assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
  end

  def test_find_all_with_limit
    first_five_developers = Developer.find_all nil, 'id ASC', 5
    assert_equal 5, first_five_developers.length
    assert_equal 'David', first_five_developers.first.name
    assert_equal 'fixture_5', first_five_developers.last.name
    
    no_developers = Developer.find_all nil, 'id ASC', 0
    assert_equal 0, no_developers.length
    
    assert_equal first_five_developers, Developer.find_all(nil, 'id ASC', [5])
    assert_equal no_developers, Developer.find_all(nil, 'id ASC', [0])
  end

  def test_find_all_with_limit_and_offset
    first_three_developers = Developer.find_all nil, 'id ASC', [3, 0]
    second_three_developers = Developer.find_all nil, 'id ASC', [3, 3]
    last_two_developers = Developer.find_all nil, 'id ASC', [2, 8]
    
    assert_equal 3, first_three_developers.length
    assert_equal 3, second_three_developers.length
    assert_equal 2, last_two_developers.length
    
    assert_equal 'David', first_three_developers.first.name
    assert_equal 'fixture_4', second_three_developers.first.name
    assert_equal 'fixture_9', last_two_developers.first.name
  end

  def test_find_all_by_one_attribute_with_options
    topics = Topic.find_all_by_content("Have a nice day", "id DESC")
    assert topics(:first), topics.last

    topics = Topic.find_all_by_content("Have a nice day", "id DESC")
    assert topics(:first), topics.first
  end

  protected
    def bind(statement, *vars)
      if vars.first.is_a?(Hash)
        ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
      else
        ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
      end
    end
end
require 'abstract_unit'
require 'fixtures/company'
require 'fixtures/topic'
require 'fixtures/reply'
require 'fixtures/entrant'
require 'fixtures/developer'
require 'fixtures/post'

class FinderTest < Test::Unit::TestCase
  fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :accounts

  def test_find
    assert_equal(topics(:first).title, Topic.find(1).title)
  end
  
  def test_exists
    assert (Topic.exists?(1))
    assert !(Topic.exists?(45))
    assert !(Topic.exists?("foo"))
    assert !(Topic.exists?([1,2]))
  end
  
  def test_find_by_array_of_one_id
    assert_kind_of(Array, Topic.find([ 1 ]))
    assert_equal(1, Topic.find([ 1 ]).length)
  end
  
  def test_find_by_ids
    assert_equal(2, Topic.find(1, 2).length)
    assert_equal(topics(:second).title, Topic.find([ 2 ]).first.title)
  end

  def test_find_an_empty_array
    assert_equal [], Topic.find([])
  end

  def test_find_by_ids_missing_one
    assert_raises(ActiveRecord::RecordNotFound) {
      Topic.find(1, 2, 45)
    }
  end
  
  def test_find_all_with_limit
    entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
    
    assert_equal(2, entrants.size)
    assert_equal(entrants(:first).name, entrants.first.name)
  end

  def test_find_all_with_prepared_limit_and_offset
    entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)

    assert_equal(2, entrants.size)
    assert_equal(entrants(:second).name, entrants.first.name)

    entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
    assert_equal(1, entrants.size)
    assert_equal(entrants(:third).name, entrants.first.name)
  end
  
  def test_find_all_with_limit_and_offset_and_multiple_orderings
    developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
    assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
  end
  
  def test_find_with_limit_and_condition
    developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
    assert_equal(1, developers.size)
    assert_equal("fixture_3", developers.first.name)
  end

  def test_find_with_entire_select_statement
    topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
    
    assert_equal(1, topics.size)
    assert_equal(topics(:second).title, topics.first.title)
  end
  
  def test_find_with_prepared_select_statement
    topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"]
    
    assert_equal(1, topics.size)
    assert_equal(topics(:second).title, topics.first.title)
  end
  
  def test_find_first
    first = Topic.find(:first, :conditions => "title = 'The First Topic'")
    assert_equal(topics(:first).title, first.title)
  end
  
  def test_find_first_failing
    first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
    assert_nil(first)
  end

  def test_unexisting_record_exception_handling
    assert_raises(ActiveRecord::RecordNotFound) {
      Topic.find(1).parent
    }
    
    Topic.find(2).topic
  end
  
  def test_find_only_some_columns
    topic = Topic.find(1, :select => "author_name")
    assert_raises(NoMethodError) { topic.title }
    assert_equal "David", topic.author_name
    assert !topic.attribute_present?("title")
    assert !topic.respond_to?("title")
    assert topic.attribute_present?("author_name")
    assert topic.respond_to?("author_name")
  end

  def test_find_on_conditions
    assert Topic.find(1, :conditions => ["approved = ?", false])
    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
  end
  
  def test_condition_interpolation
    assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
    assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
    assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
    assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
  end

  def test_bind_variables
    assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
    assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
    assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
    assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
    assert_raises(ActiveRecord::PreparedStatementInvalid) {
      Company.find(:first, :conditions => ["id=? AND name = ?", 2])
    }
    assert_raises(ActiveRecord::PreparedStatementInvalid) {
	   Company.find(:first, :conditions => ["id=?", 2, 3, 4])
    }
  end
  
  def test_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
    assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
  end

  def test_named_bind_variables_with_quotes
    Company.create("name" => "37signals' go'es agains")
    assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
  end

  def test_bind_arity
    assert_nothing_raised                                 { bind '' }
    assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
  
    assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
    assert_nothing_raised                                 { bind '?', 1 }
    assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1  }
  end
  
  def test_named_bind_variables
    assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
    assert_equal '1 1', bind(':a :a', :a => 1)  # ' ruby-mode
  
    assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
    assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
    assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
    assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
  end

  def test_bind_enumerable
    assert_equal '1,2,3', bind('?', [1, 2, 3])
    assert_equal %('a','b','c'), bind('?', %w(a b c))

    assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
    assert_equal %('a','b','c'), bind(':a', :a => %w(a b c)) # '

    require 'set'
    assert_equal '1,2,3', bind('?', Set.new([1, 2, 3]))
    assert_equal %('a','b','c'), bind('?', Set.new(%w(a b c)))

    assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3]))
    assert_equal %('a','b','c'), bind(':a', :a => Set.new(%w(a b c))) # '
  end

  def test_bind_string
    assert_equal "''", bind('?', '')
  end

  def test_string_sanitation
    assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
    assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
  end

  def test_count
    assert_equal(0, Entrant.count("id > 3"))
    assert_equal(1, Entrant.count(["id > ?", 2]))
    assert_equal(2, Entrant.count(["id > ?", 1]))
  end

  def test_count_by_sql
    assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
    assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
    assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
  end

  def test_find_by_one_attribute
    assert_equal topics(:first), Topic.find_by_title("The First Topic")
    assert_nil Topic.find_by_title("The First Topic!")
  end

  def test_find_by_one_attribute_with_order_option
    assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
    assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
  end

  def test_find_by_one_attribute_with_conditions
    assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
  end

  def test_find_by_one_attribute_with_several_options
    assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
  end
  
  def test_find_by_one_missing_attribute
    assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
  end

  def test_find_by_two_attributes
    assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
    assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
  end

  def test_find_all_by_one_attribute
    topics = Topic.find_all_by_content("Have a nice day")
    assert_equal 2, topics.size
    assert topics.include?(topics(:first))

    assert_equal [], Topic.find_all_by_title("The First Topic!!")
  end
  
  def test_find_all_by_one_attribute_with_options
    topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
    assert topics(:first), topics.last

    topics = Topic.find_all_by_content("Have a nice day", :order => "id")
    assert topics(:first), topics.first
  end

  def test_find_all_by_array_attribute
    assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic's of the day"]).size
  end

  def test_find_all_by_boolean_attribute
    topics = Topic.find_all_by_approved(false)
    assert_equal 1, topics.size
    assert topics.include?(topics(:first))

    topics = Topic.find_all_by_approved(true)
    assert_equal 1, topics.size
    assert topics.include?(topics(:second))
  end
  
  def test_find_by_nil_attribute
    topic = Topic.find_by_last_read nil
    assert_not_nil topic
    assert_nil topic.last_read
  end
  
  def test_find_all_by_nil_attribute
    topics = Topic.find_all_by_last_read nil
    assert_equal 1, topics.size
    assert_nil topics[0].last_read
  end
  
  def test_find_by_nil_and_not_nil_attributes
    topic = Topic.find_by_last_read_and_author_name nil, "Mary"
    assert_equal "Mary", topic.author_name
  end

  def test_find_all_by_nil_and_not_nil_attributes
    topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
    assert_equal 1, topics.size
    assert_equal "Mary", topics[0].author_name
  end

  def test_find_or_create_from_one_attribute
    number_of_companies = Company.count
    sig38 = Company.find_or_create_by_name("38signals")
    assert_equal number_of_companies + 1, Company.count
    assert_equal sig38, Company.find_or_create_by_name("38signals")
  end

  def test_find_or_create_from_two_attributes
    number_of_topics = Topic.count
    another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
    assert_equal number_of_topics + 1, Topic.count
    assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
  end

  def test_find_with_bad_sql
    assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
  end

  def test_find_with_invalid_params
    assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" }
    assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
  end

  def test_find_all_with_limit
    first_five_developers = Developer.find :all, :order => 'id ASC', :limit =>  5
    assert_equal 5, first_five_developers.length
    assert_equal 'David', first_five_developers.first.name
    assert_equal 'fixture_5', first_five_developers.last.name
    
    no_developers = Developer.find :all, :order => 'id ASC', :limit => 0
    assert_equal 0, no_developers.length
  end

  def test_find_all_with_limit_and_offset
    first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0
    second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3
    last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8
    
    assert_equal 3, first_three_developers.length
    assert_equal 3, second_three_developers.length
    assert_equal 2, last_two_developers.length
    
    assert_equal 'David', first_three_developers.first.name
    assert_equal 'fixture_4', second_three_developers.first.name
    assert_equal 'fixture_9', last_two_developers.first.name
  end

  def test_find_all_with_limit_and_offset_and_multiple_order_clauses
    first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
    second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
    last_posts = Post.find :all, :order => ' author_id, id  ', :limit => 3, :offset => 6

    assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
    assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
    assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
  end

  def test_find_all_with_join
    developers_on_project_one = Developer.find(
      :all, 
      :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', 
      :conditions => 'project_id=1'
    )
    assert_equal 3, developers_on_project_one.length
    developer_names = developers_on_project_one.map { |d| d.name }
    assert developer_names.include?('David')
    assert developer_names.include?('Jamis')
  end

  def test_find_by_id_with_conditions_with_or
    assert_nothing_raised do
      Post.find([1,2,3],
        :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'")
    end
  end

  def test_select_value
    assert_equal "37signals", Company.connection.select_value("SELECT name FROM companies WHERE id = 1")
    assert_nil Company.connection.select_value("SELECT name FROM companies WHERE id = -1")
    # make sure we didn't break count...
    assert_equal 0, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = 'Halliburton'")
    assert_equal 1, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = '37signals'")
  end

  def test_select_values
    assert_equal ["1","2","3","4","5","6","7","8"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
    assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
  end

  protected
    def bind(statement, *vars)
      if vars.first.is_a?(Hash)
        ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
      else
        ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
      end
    end
end
class Author < ActiveRecord::Base
  has_many :posts
  has_many :posts_with_comments, :include => :comments, :class_name => "Post"
  has_many :posts_with_categories, :include => :categories, :class_name => "Post"
  has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post"
  has_many :comments, :through => :posts
  has_many :funky_comments, :through => :posts, :source => :comments

  has_many :special_posts,        :class_name => "Post"
  has_many :hello_posts,          :class_name => "Post", :conditions=>"\#{aliased_table_name}.body = 'hello'"
  has_many :nonexistent_posts,    :class_name => "Post", :conditions=>"\#{aliased_table_name}.body = 'nonexistent'"
  has_many :posts_with_callbacks, :class_name => "Post", :before_add => :log_before_adding,
           :after_add     => :log_after_adding, 
           :before_remove => :log_before_removing,
           :after_remove  => :log_after_removing
  has_many :posts_with_proc_callbacks, :class_name => "Post",
           :before_add    => Proc.new {|o, r| o.post_log << "before_adding#{r.id}"},
           :after_add     => Proc.new {|o, r| o.post_log << "after_adding#{r.id}"},
           :before_remove => Proc.new {|o, r| o.post_log << "before_removing#{r.id}"},
           :after_remove  => Proc.new {|o, r| o.post_log << "after_removing#{r.id}"}
  has_many :posts_with_multiple_callbacks, :class_name => "Post",
           :before_add => [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id}"}],
           :after_add  => [:log_after_adding,  Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id}"}]
  has_many :unchangable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding

  has_many :categorizations
  has_many :categories, :through => :categorizations

  has_many :nothings, :through => :kateggorisatons, :class_name => 'Category'

  has_many :author_favorites
  has_many :favorite_authors, :through => :author_favorites, :order => 'name'

  has_many :tagging,  :through => :posts # through polymorphic has_one
  has_many :taggings, :through => :posts, :source => :taggings # through polymorphic has_many
  has_many :tags,     :through => :posts # through has_many :through
  has_many :post_categories, :through => :posts, :source => :categories

  belongs_to :author_address

  attr_accessor :post_log

  def after_initialize
    @post_log = []
  end

  private
    def log_before_adding(object)
      @post_log << "before_adding#{object.id}"
    end

    def log_after_adding(object)
      @post_log << "after_adding#{object.id}"
    end

    def log_before_removing(object)
      @post_log << "before_removing#{object.id}"
    end

    def log_after_removing(object)
      @post_log << "after_removing#{object.id}"
    end

    def raise_exception(object)
      raise Exception.new("You can't add a post")
    end
end

class AuthorAddress < ActiveRecord::Base
  has_one :author
end

class AuthorFavorite < ActiveRecord::Base
  belongs_to :author
  belongs_to :favorite_author, :class_name => "Author", :foreign_key => 'favorite_author_id'
endclass AutoId < ActiveRecord::Base
  def self.table_name () "auto_id_tests" end
  def self.primary_key () "auto_id" end
end
class Binary < ActiveRecord::Base
endclass Categorization < ActiveRecord::Base
  belongs_to :post
  belongs_to :category
  belongs_to :author
endclass Category < ActiveRecord::Base
  has_and_belongs_to_many :posts
  has_and_belongs_to_many :special_posts, :class_name => "Post"
  has_and_belongs_to_many :hello_posts, :class_name => "Post", :conditions => "\#{aliased_table_name}.body = 'hello'"
  has_and_belongs_to_many :nonexistent_posts, :class_name => "Post", :conditions=>"\#{aliased_table_name}.body = 'nonexistent'"
  
  def self.what_are_you
    'a category...'
  end
  
  has_many :categorizations
  has_many :authors, :through => :categorizations, :select => 'authors.*, categorizations.post_id'
end

class SpecialCategory < Category
  
  def self.what_are_you
    'a special category...'
  end  
  
end
class ColumnName < ActiveRecord::Base
  def self.table_name () "colnametests" end
endclass Comment < ActiveRecord::Base
  belongs_to :post
  
  def self.what_are_you
    'a comment...'
  end
  
  def self.search_by_type(q)
    self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q])
  end
end

class SpecialComment < Comment
  def self.what_are_you
    'a special comment...'
  end
end

class VerySpecialComment < Comment
  def self.what_are_you
    'a very special comment...'
  end
endclass Company < ActiveRecord::Base
  attr_protected :rating
  set_sequence_name :companies_nonstd_seq

  validates_presence_of :name

  has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account"
end


class Firm < Company
  has_many :clients, :order => "id", :dependent => :destroy, :counter_sql =>
      "SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
      "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
  has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
  has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
  has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
  has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
  has_many :limited_clients, :class_name => "Client", :order => "id", :limit => 1
  has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
  has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}'
  has_many :clients_using_counter_sql, :class_name => "Client",
           :finder_sql  => 'SELECT * FROM companies WHERE client_of = #{id}',
           :counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = #{id}'
  has_many :clients_using_zero_counter_sql, :class_name => "Client",
           :finder_sql  => 'SELECT * FROM companies WHERE client_of = #{id}',
           :counter_sql => 'SELECT 0 FROM companies WHERE client_of = #{id}'
  has_many :no_clients_using_counter_sql, :class_name => "Client",
           :finder_sql  => 'SELECT * FROM companies WHERE client_of = 1000',
           :counter_sql => 'SELECT COUNT(*) FROM companies WHERE client_of = 1000'

  has_one :account, :foreign_key => "firm_id", :dependent => :destroy
end

class DependentFirm < Company
  has_one :account, :foreign_key => "firm_id", :dependent => :nullify
  has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify
end


class Client < Company
  belongs_to :firm, :foreign_key => "client_of"
  belongs_to :firm_with_basic_id, :class_name => "Firm", :foreign_key => "firm_id"
  belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
  belongs_to :firm_with_condition, :class_name => "Firm", :foreign_key => "client_of", :conditions => ["1 = ?", 1]

  # Record destruction so we can test whether firm.clients.clear has
  # is calling client.destroy, deleting from the database, or setting
  # foreign keys to NULL.
  def self.destroyed_client_ids
    @destroyed_client_ids ||= Hash.new { |h,k| h[k] = [] }
  end

  before_destroy do |client|
    if client.firm
      Client.destroyed_client_ids[client.firm.id] << client.id
    end
    true
  end
  
  # Used to test that read and question methods are not generated for these attributes
  def ruby_type
    read_attribute :ruby_type
  end
  
  def rating?
    query_attribute :rating
  end
end


class SpecialClient < Client
end

class VerySpecialClient < SpecialClient
end

class Account < ActiveRecord::Base
  belongs_to :firm
  
  protected
    def validate
      errors.add_on_empty "credit_limit"
    end
endmodule MyApplication
  module Business
    class Company < ActiveRecord::Base
      attr_protected :rating
    end
    
    class Firm < Company
      has_many :clients, :order => "id", :dependent => :destroy
      has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
      has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
      has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
      has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}'

      has_one :account, :dependent => :destroy
    end

    class Client < Company
      belongs_to :firm, :foreign_key => "client_of"
      belongs_to :firm_with_other_name, :class_name => "Firm", :foreign_key => "client_of"
    end
    
    class Developer < ActiveRecord::Base
      has_and_belongs_to_many :projects

      protected
        def validate
          errors.add_on_boundary_breaking("name", 3..20)
      end
    end
    
    class Project < ActiveRecord::Base
      has_and_belongs_to_many :developers
    end

  end

  module Billing
    class Firm < ActiveRecord::Base
      self.table_name = 'companies'
    end

    module Nested
      class Firm < ActiveRecord::Base
        self.table_name = 'companies'
      end
    end

    class Account < ActiveRecord::Base
      belongs_to :firm, :class_name => 'MyApplication::Business::Firm'
      belongs_to :qualified_billing_firm, :class_name => 'MyApplication::Billing::Firm'
      belongs_to :unqualified_billing_firm, :class_name => 'Firm'
      belongs_to :nested_qualified_billing_firm, :class_name => 'MyApplication::Billing::Nested::Firm'
      belongs_to :nested_unqualified_billing_firm, :class_name => 'Nested::Firm'

      protected
        def validate
          errors.add_on_empty "credit_limit"
        end
    end
  end
end
class Computer < ActiveRecord::Base
  belongs_to :developer, :foreign_key=>'developer'
end
class Course < ActiveRecord::Base
  has_many :entrants
end
class Customer < ActiveRecord::Base
  composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ]
  composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
  composed_of :gps_location
end

class Address
  attr_reader :street, :city, :country

  def initialize(street, city, country)
    @street, @city, @country = street, city, country
  end
  
  def close_to?(other_address)
    city == other_address.city && country == other_address.country
  end

  def ==(other)
    other.is_a?(self.class) && other.street == street && other.city == city && other.country == country
  end  
end

class Money
  attr_reader :amount, :currency
  
  EXCHANGE_RATES = { "USD_TO_DKK" => 6, "DKK_TO_USD" => 0.6 }
  
  def initialize(amount, currency = "USD")
    @amount, @currency = amount, currency
  end
  
  def exchange_to(other_currency)
    Money.new((amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor, other_currency)
  end
end

class GpsLocation
  attr_reader :gps_location
  
  def initialize(gps_location)
    @gps_location = gps_location
  end
  
  def latitude
    gps_location.split("x").first
  end
  
  def longitude
    gps_location.split("x").last
  end

  def ==(other)
    self.latitude == other.latitude && self.longitude == other.longitude
  end
endActiveRecord::Schema.define do

  create_table :taggings, :force => true do |t|
    t.column :tag_id, :integer
    t.column :super_tag_id, :integer
    t.column :taggable_type, :string
    t.column :taggable_id, :integer
  end

  create_table :tags, :force => true do |t|
    t.column :name, :string
    t.column :taggings_count, :integer, :default => 0
  end

  create_table :categorizations, :force => true do |t|
    t.column :category_id, :integer
    t.column :post_id, :integer
    t.column :author_id, :integer
  end

  add_column :posts, :taggings_count, :integer, :default => 0
  add_column :authors, :author_address_id, :integer

  create_table :author_addresses, :force => true do |t|
    t.column :author_address_id, :integer
  end

  create_table :author_favorites, :force => true do |t|
    t.column :author_id, :integer
    t.column :favorite_author_id, :integer
  end
endclass Default < ActiveRecord::Base
end
module DeveloperProjectsAssociationExtension
  def find_most_recent
    find(:first, :order => "id DESC")
  end
end

class Developer < ActiveRecord::Base
  has_and_belongs_to_many :projects do
    def find_most_recent
      find(:first, :order => "id DESC")
    end
  end
  
  has_and_belongs_to_many :projects_extended_by_name, 
      :class_name => "Project", 
      :join_table => "developers_projects", 
      :association_foreign_key => "project_id",
      :extend => DeveloperProjectsAssociationExtension

  has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id'

  validates_inclusion_of :salary, :in => 50000..200000
  validates_length_of    :name, :within => 3..20
end

DeveloperSalary = Struct.new(:amount)
class DeveloperWithAggregate < ActiveRecord::Base
  self.table_name = 'developers'
  composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)]
end

class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
  self.table_name = 'developers'
  has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id'
  before_destroy :raise_if_projects_empty!

  def raise_if_projects_empty!
    raise if projects.empty?
  end
end
class Entrant < ActiveRecord::Base
  belongs_to :course
end
class Joke < ActiveRecord::Base
  set_table_name 'funny_jokes'
end
class Joke < ActiveRecord::Base
  set_table_name 'funny_jokes'
endclass Keyboard < ActiveRecord::Base
  set_primary_key 'key_number'
end
class LegacyThing < ActiveRecord::Base
  set_locking_column :version
end
class PeopleHaveLastNames < ActiveRecord::Migration
  def self.up
    add_column "people", "last_name", :string
  end
  
  def self.down
    remove_column "people", "last_name"
  end
endclass WeNeedReminders < ActiveRecord::Migration
  def self.up
    create_table("reminders") do |t|
      t.column :content, :text
      t.column :remind_at, :datetime
    end
  end
  
  def self.down
    drop_table "reminders"
  end
endclass InnocentJointable < ActiveRecord::Migration
  def self.up
    create_table("people_reminders", :id => false) do |t|
      t.column :reminder_id, :integer
      t.column :person_id, :integer
    end
  end

  def self.down
    drop_table "people_reminders"
  end
endclass PeopleHaveLastNames < ActiveRecord::Migration
  def self.up
    add_column "people", "last_name", :string
  end
  
  def self.down
    remove_column "people", "last_name"
  end
endclass WeNeedReminders < ActiveRecord::Migration
  def self.up
    create_table("reminders") do |t|
      t.column :content, :text
      t.column :remind_at, :datetime
    end
  end
  
  def self.down
    drop_table "reminders"
  end
endclass Foo < ActiveRecord::Migration
  def self.up
  end

  def self.down
  end
endclass InnocentJointable < ActiveRecord::Migration
  def self.up
    create_table("people_reminders", :id => false) do |t|
      t.column :reminder_id, :integer
      t.column :person_id, :integer
    end
  end

  def self.down
    drop_table "people_reminders"
  end
endclass Mixin < ActiveRecord::Base

end

class TreeMixin < Mixin 
    acts_as_tree :foreign_key => "parent_id", :order => "id"
end

class TreeMixinWithoutOrder < Mixin
    acts_as_tree :foreign_key => "parent_id"
end

class ListMixin < Mixin
  acts_as_list :column => "pos", :scope => :parent

  def self.table_name() "mixins" end
end

class ListMixinSub1 < ListMixin
end

class ListMixinSub2 < ListMixin
end


class ListWithStringScopeMixin < ActiveRecord::Base
  acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'

  def self.table_name() "mixins" end
end

class NestedSet < Mixin
  acts_as_nested_set :scope => "root_id IS NULL"
  
  def self.table_name() "mixins" end
end

class NestedSetWithStringScope < Mixin
  acts_as_nested_set :scope => 'root_id = #{root_id}'
  
  def self.table_name() "mixins" end
end

class NestedSetWithSymbolScope < Mixin
  acts_as_nested_set :scope => :root
  
  def self.table_name() "mixins" end
end
class Movie < ActiveRecord::Base
  def self.primary_key
    "movieid"
  end
end
class Order < ActiveRecord::Base
  belongs_to :billing, :class_name => 'Customer', :foreign_key => 'billing_customer_id' 
  belongs_to :shipping, :class_name => 'Customer', :foreign_key => 'shipping_customer_id' 
end
class Person < ActiveRecord::Base
  has_many :readers
  has_many :posts, :through => :readers
end
class Post < ActiveRecord::Base
  belongs_to :author do
    def greeting
      "hello"
    end
  end

  belongs_to :author_with_posts, :class_name => "Author", :include => :posts

  has_many   :comments, :order => "body" do
    def find_most_recent
      find(:first, :order => "id DESC")
    end
  end

  has_one  :very_special_comment
  has_one  :very_special_comment_with_post, :class_name => "VerySpecialComment", :include => :post
  has_many :special_comments

  has_and_belongs_to_many :categories
  has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id'

  has_many :taggings, :as => :taggable
  has_many :tags, :through => :taggings, :include => :tagging do
    def add_joins_and_select
      find :all, :select => 'tags.*, authors.id as author_id', :include => false,
        :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
    end
  end
  
  has_many :funky_tags, :through => :taggings, :source => :tag
  has_many :super_tags, :through => :taggings
  has_one :tagging, :as => :taggable

  has_many :invalid_taggings, :as => :taggable, :class_name => "Tagging", :conditions => 'taggings.id < 0'
  has_many :invalid_tags, :through => :invalid_taggings, :source => :tag

  has_many :categorizations, :foreign_key => :category_id
  has_many :authors, :through => :categorizations

  has_many :readers
  has_many :people, :through => :readers

  def self.what_are_you
    'a post...'
  end
end

class SpecialPost < Post; end;

class StiPost < Post
  self.abstract_class = true
  has_one :special_comment, :class_name => "SpecialComment"
end

class SubStiPost < StiPost
end
class Project < ActiveRecord::Base
  has_and_belongs_to_many :developers, :uniq => true
  has_and_belongs_to_many :limited_developers, :class_name => "Developer", :limit => 1
  has_and_belongs_to_many :developers_named_david, :class_name => "Developer", :conditions => "name = 'David'", :uniq => true
  has_and_belongs_to_many :salaried_developers, :class_name => "Developer", :conditions => "salary > 0"
  has_and_belongs_to_many :developers_with_finder_sql, :class_name => "Developer", :finder_sql => 'SELECT t.*, j.* FROM developers_projects j, developers t WHERE t.id = j.developer_id AND j.project_id = #{id}'
  has_and_belongs_to_many :developers_by_sql, :class_name => "Developer", :delete_sql => "DELETE FROM developers_projects WHERE project_id = \#{id} AND developer_id = \#{record.id}"
  has_and_belongs_to_many :developers_with_callbacks, :class_name => "Developer", :before_add => Proc.new {|o, r| o.developers_log << "before_adding#{r.id}"},
                            :after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id}"}, 
                            :before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
                            :after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}

  attr_accessor :developers_log

  def after_initialize
    @developers_log = []
  end

end

class SpecialProject < Project
  def hello_world
    "hello there!"
  end
end
class Reader < ActiveRecord::Base
  belongs_to :post
  belongs_to :person
end
require 'fixtures/topic'

class Reply < Topic
  belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
  has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id"

  validate :errors_on_empty_content
  validate_on_create :title_is_wrong_create
  
  attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read

  def validate
    errors.add("title", "Empty")   unless attribute_present? "title"
  end
  
  def errors_on_empty_content
    errors.add("content", "Empty") unless attribute_present? "content"
  end
  
  def validate_on_create
    if attribute_present?("title") && attribute_present?("content") && content == "Mismatch"
      errors.add("title", "is Content Mismatch") 
    end
  end

  def title_is_wrong_create
    errors.add("title", "is Wrong Create") if attribute_present?("title") && title == "Wrong Create"
  end

  def validate_on_update
    errors.add("title", "is Wrong Update") if attribute_present?("title") && title == "Wrong Update"
  end
end

class SillyReply < Reply
  belongs_to :reply, :foreign_key => "parent_id", :counter_cache => :replies_count
end
# used for OracleSynonymTest, see test/synonym_test_oci.rb
#
class Subject < ActiveRecord::Base
end
class Subscriber < ActiveRecord::Base
  set_primary_key 'nick'
end

class SpecialSubscriber < Subscriber
end
class Tag < ActiveRecord::Base
  has_many :taggings
  has_many :taggables, :through => :taggings
  has_one  :tagging
endclass Tagging < ActiveRecord::Base
  belongs_to :tag, :include => :tagging
  belongs_to :super_tag,   :class_name => 'Tag', :foreign_key => 'super_tag_id'
  belongs_to :invalid_tag, :class_name => 'Tag', :foreign_key => 'tag_id'
  belongs_to :taggable, :polymorphic => true, :counter_cache => true
endclass Task < ActiveRecord::Base

end
class Topic < ActiveRecord::Base
  has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
  serialize :content
  
  before_create  :default_written_on
  before_destroy :destroy_children

  def parent
    Topic.find(parent_id)
  end
  
  protected
    def default_written_on
      self.written_on = Time.now unless attribute_present?("written_on")
    end

    def destroy_children
      self.class.delete_all "parent_id = #{id}"
    end
endrequire 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/developer'
require 'fixtures/company'
require 'fixtures/task'
require 'fixtures/reply'
require 'fixtures/joke'
require 'fixtures/category'

class FixturesTest < Test::Unit::TestCase
  self.use_instantiated_fixtures = true
  self.use_transactional_fixtures = false

  fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes

  FIXTURES = %w( accounts companies customers
                 developers developers_projects entrants
                 movies projects subscribers topics tasks )
  MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-_\w]*/

  def test_clean_fixtures
    FIXTURES.each do |name|
      fixtures = nil
      assert_nothing_raised { fixtures = create_fixtures(name) }
      assert_kind_of(Fixtures, fixtures)
      fixtures.each { |name, fixture|
        fixture.each { |key, value|
          assert_match(MATCH_ATTRIBUTE_NAME, key)
        }
      }
    end
  end

  def test_multiple_clean_fixtures
    fixtures_array = nil
    assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) }
    assert_kind_of(Array, fixtures_array)
    fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) }
  end

  def test_attributes
    topics = create_fixtures("topics")
    assert_equal("The First Topic", topics["first"]["title"])
    assert_nil(topics["second"]["author_email_address"])
  end

  def test_inserts
    topics = create_fixtures("topics")
    firstRow = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
    assert_equal("The First Topic", firstRow["title"])

    secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'Mary'")
    assert_nil(secondRow["author_email_address"])
  end

  if ActiveRecord::Base.connection.supports_migrations?
    def test_inserts_with_pre_and_suffix
      ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
        t.column :title, :string
        t.column :author_name, :string
        t.column :author_email_address, :string
        t.column :written_on, :datetime
        t.column :bonus_time, :time
        t.column :last_read, :date
        t.column :content, :string
        t.column :approved, :boolean, :default => true
        t.column :replies_count, :integer, :default => 0
        t.column :parent_id, :integer
        t.column :type, :string, :limit => 50
      end

      # Store existing prefix/suffix
      old_prefix = ActiveRecord::Base.table_name_prefix
      old_suffix = ActiveRecord::Base.table_name_suffix

      # Set a prefix/suffix we can test against
      ActiveRecord::Base.table_name_prefix = 'prefix_'
      ActiveRecord::Base.table_name_suffix = '_suffix'

      topics = create_fixtures("topics")

      firstRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'")
      assert_equal("The First Topic", firstRow["title"])

      secondRow = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'")
      assert_nil(secondRow["author_email_address"])        
    ensure
      # Restore prefix/suffix to its previous values
      ActiveRecord::Base.table_name_prefix = old_prefix 
      ActiveRecord::Base.table_name_suffix = old_suffix 

      ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil
    end
  end

  def test_insert_with_datetime
    topics = create_fixtures("tasks")
    first = Task.find(1)
    assert first
  end


  def test_bad_format
    path = File.join(File.dirname(__FILE__), 'fixtures', 'bad_fixtures')
    Dir.entries(path).each do |file|
      next unless File.file?(file) and file !~ Fixtures::DEFAULT_FILTER_RE
      assert_raise(Fixture::FormatError) {
        Fixture.new(bad_fixtures_path, file)
      }
    end
  end

  def test_deprecated_yaml_extension
    assert_raise(Fixture::FormatError) {
      Fixtures.new(nil, 'bad_extension', 'BadExtension', File.join(File.dirname(__FILE__), 'fixtures'))
    }
  end

  def test_logger_level_invariant
    level = ActiveRecord::Base.logger.level
    create_fixtures('topics')
    assert_equal level, ActiveRecord::Base.logger.level
  end

  def test_instantiation
    topics = create_fixtures("topics")
    assert_kind_of Topic, topics["first"].find
  end

  def test_complete_instantiation
    assert_equal 2, @topics.size
    assert_equal "The First Topic", @first.title
  end

  def test_fixtures_from_root_yml_with_instantiation
    # assert_equal 2, @accounts.size
    assert_equal 50, @unknown.credit_limit
  end

  def test_erb_in_fixtures
    assert_equal 11, @developers.size
    assert_equal "fixture_5", @dev_5.name
  end

  def test_empty_yaml_fixture
    assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', File.dirname(__FILE__) + "/fixtures/naked/yml/accounts")
  end

  def test_empty_yaml_fixture_with_a_comment_in_it
    assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', File.dirname(__FILE__) + "/fixtures/naked/yml/companies")
  end

  def test_dirty_dirty_yaml_file
    assert_raises(Fixture::FormatError) do
      Fixtures.new( Account.connection, "courses", 'Course', File.dirname(__FILE__) + "/fixtures/naked/yml/courses")
    end
  end

  def test_empty_csv_fixtures
    assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', File.dirname(__FILE__) + "/fixtures/naked/csv/accounts")
  end

  def test_omap_fixtures
    assert_nothing_raised do
      fixtures = Fixtures.new(Account.connection, 'categories', 'Category', File.dirname(__FILE__) + '/fixtures/categories_ordered')

      i = 0
      fixtures.each do |name, fixture|
        assert_equal "fixture_no_#{i}", name
        assert_equal "Category #{i}", fixture['name']
        i += 1
      end
    end
  end


  def test_yml_file_in_subdirectory
    assert_equal(categories(:sub_special_1).name, "A special category in a subdir file")
    assert_equal(categories(:sub_special_1).class, SpecialCategory)
  end

  def test_subsubdir_file_with_arbitrary_name
    assert_equal(categories(:sub_special_3).name, "A special category in an arbitrarily named subsubdir file")
    assert_equal(categories(:sub_special_3).class, SpecialCategory)
  end


end

if Account.connection.respond_to?(:reset_pk_sequence!)
  class FixturesResetPkSequenceTest < Test::Unit::TestCase
    fixtures :accounts
    fixtures :companies

    def setup
      @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')]
    end

    def test_resets_to_min_pk_with_specified_pk_and_sequence
      @instances.each do |instance|
        model = instance.class
        model.delete_all
        model.connection.reset_pk_sequence!(model.table_name, model.primary_key, model.sequence_name)

        instance.save!
        assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed."
      end
    end

    def test_resets_to_min_pk_with_default_pk_and_sequence
      @instances.each do |instance|
        model = instance.class
        model.delete_all
        model.connection.reset_pk_sequence!(model.table_name)

        instance.save!
        assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed."
      end
    end

    def test_create_fixtures_resets_sequences
      @instances.each do |instance|
        max_id = create_fixtures(instance.class.table_name).inject(0) do |max_id, (name, fixture)|
          fixture_id = fixture['id'].to_i
          fixture_id > max_id ? fixture_id : max_id
        end

        # Clone the last fixture to check that it gets the next greatest id.
        instance.save!
        assert_equal max_id + 1, instance.id, "Sequence reset for #{instance.class.table_name} failed."
      end
    end
  end
end


class FixturesWithoutInstantiationTest < Test::Unit::TestCase
  self.use_instantiated_fixtures = false
  fixtures :topics, :developers, :accounts

  def test_without_complete_instantiation
    assert_nil @first
    assert_nil @topics
    assert_nil @developers
    assert_nil @accounts
  end

  def test_fixtures_from_root_yml_without_instantiation
    assert_nil @unknown
  end

  def test_accessor_methods
    assert_equal "The First Topic", topics(:first).title
    assert_equal "Jamis", developers(:jamis).name
    assert_equal 50, accounts(:signals37).credit_limit
  end
end


class FixturesWithoutInstanceInstantiationTest < Test::Unit::TestCase
  self.use_instantiated_fixtures = true
  self.use_instantiated_fixtures = :no_instances

  fixtures :topics, :developers, :accounts

  def test_without_instance_instantiation
    assert_nil @first
    assert_not_nil @topics
    assert_not_nil @developers
    assert_not_nil @accounts
  end
end


class TransactionalFixturesTest < Test::Unit::TestCase
  self.use_instantiated_fixtures = true
  self.use_transactional_fixtures = true

  fixtures :topics

  def test_destroy
    assert_not_nil @first
    @first.destroy
  end

  def test_destroy_just_kidding
    assert_not_nil @first
  end
end


class MultipleFixturesTest < Test::Unit::TestCase
  fixtures :topics
  fixtures :developers, :accounts

  def test_fixture_table_names
    assert_equal %w(topics developers accounts), fixture_table_names
  end
end


class OverlappingFixturesTest < Test::Unit::TestCase
  fixtures :topics, :developers
  fixtures :developers, :accounts

  def test_fixture_table_names
    assert_equal %w(topics developers accounts), fixture_table_names
  end
end


class ForeignKeyFixturesTest < Test::Unit::TestCase
  fixtures :fk_test_has_pk, :fk_test_has_fk

  # if foreign keys are implemented and fixtures
  # are not deleted in reverse order then this test
  # case will raise StatementInvalid

  def test_number1
    assert true
  end

  def test_number2
    assert true
  end
end

class SetTableNameFixturesTest < Test::Unit::TestCase
  set_fixture_class :funny_jokes => 'Joke'
  fixtures :funny_jokes
  
  def test_table_method
    assert_kind_of Joke, funny_jokes(:a_joke)
  end
end

class InvalidTableNameFixturesTest < Test::Unit::TestCase
  fixtures :funny_jokes

  def test_raises_error
    assert_raises FixtureClassNotFound do
      funny_jokes(:a_joke)
    end
  end
end
require 'abstract_unit'
require 'fixtures/company'
require 'fixtures/project'
require 'fixtures/subscriber'

class InheritanceTest < Test::Unit::TestCase
  fixtures :companies, :projects, :subscribers

  def test_a_bad_type_column
    #SQLServer need to turn Identity Insert On before manually inserting into the Identity column
    if current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter)
      Company.connection.execute "SET IDENTITY_INSERT companies ON"
    end
    Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"

    #We then need to turn it back Off before continuing.
    if current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter)
      Company.connection.execute "SET IDENTITY_INSERT companies OFF"
    end
    assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) }
  end

  def test_inheritance_find
    assert Company.find(1).kind_of?(Firm), "37signals should be a firm"
    assert Firm.find(1).kind_of?(Firm), "37signals should be a firm"
    assert Company.find(2).kind_of?(Client), "Summit should be a client"
    assert Client.find(2).kind_of?(Client), "Summit should be a client"
  end
  
  def test_alt_inheritance_find
    switch_to_alt_inheritance_column
    test_inheritance_find
  end

  def test_inheritance_find_all
    companies = Company.find(:all, :order => 'id')
    assert companies[0].kind_of?(Firm), "37signals should be a firm"
    assert companies[1].kind_of?(Client), "Summit should be a client"
  end
  
  def test_alt_inheritance_find_all
    switch_to_alt_inheritance_column
    test_inheritance_find_all
  end

  def test_inheritance_save
    firm = Firm.new
    firm.name = "Next Angle"
    firm.save
    
    next_angle = Company.find(firm.id)
    assert next_angle.kind_of?(Firm), "Next Angle should be a firm"
  end
  
  def test_alt_inheritance_save
    switch_to_alt_inheritance_column
    test_inheritance_save
  end

  def test_inheritance_condition
    assert_equal 8, Company.count
    assert_equal 2, Firm.count
    assert_equal 3, Client.count
  end
  
  def test_alt_inheritance_condition
    switch_to_alt_inheritance_column
    test_inheritance_condition
  end

  def test_finding_incorrect_type_data
    assert_raises(ActiveRecord::RecordNotFound) { Firm.find(2) }
    assert_nothing_raised   { Firm.find(1) }
  end
  
  def test_alt_finding_incorrect_type_data
    switch_to_alt_inheritance_column
    test_finding_incorrect_type_data
  end

  def test_update_all_within_inheritance
    Client.update_all "name = 'I am a client'"
    assert_equal "I am a client", Client.find(:all).first.name
    assert_equal "37signals", Firm.find(:all).first.name
  end
  
  def test_alt_update_all_within_inheritance
    switch_to_alt_inheritance_column
    test_update_all_within_inheritance
  end

  def test_destroy_all_within_inheritance
    Client.destroy_all
    assert_equal 0, Client.count
    assert_equal 2, Firm.count
  end
  
  def test_alt_destroy_all_within_inheritance
    switch_to_alt_inheritance_column
    test_destroy_all_within_inheritance
  end

  def test_find_first_within_inheritance
    assert_kind_of Firm, Company.find(:first, :conditions => "name = '37signals'")
    assert_kind_of Firm, Firm.find(:first, :conditions => "name = '37signals'")
    assert_nil Client.find(:first, :conditions => "name = '37signals'")
  end
  
  def test_alt_find_first_within_inheritance
    switch_to_alt_inheritance_column
    test_find_first_within_inheritance
  end

  def test_complex_inheritance
    very_special_client = VerySpecialClient.create("name" => "veryspecial")
    assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'")
    assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
    assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
    assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
    assert_equal 1, Client.find(:all, :conditions => "name = 'Summit'").size
    assert_equal very_special_client, Client.find(very_special_client.id)
  end

  def test_alt_complex_inheritance
    switch_to_alt_inheritance_column
    test_complex_inheritance
  end

  def test_inheritance_without_mapping
    assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132")
    assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save }
  end

  private
    def switch_to_alt_inheritance_column
      # we don't want misleading test results, so get rid of the values in the type column
      Company.find(:all, :order => 'id').each do |c|
        c['type'] = nil
        c.save
      end
    
      def Company.inheritance_column() "ruby_type" end
    end
end
require 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/developer'
require 'fixtures/reply'

class Topic; def after_find() end end
class Developer; def after_find() end end
class SpecialDeveloper < Developer; end

class TopicManualObserver
  include Singleton

  attr_reader :action, :object, :callbacks

  def initialize
    Topic.add_observer(self)
    @callbacks = []
  end

  def update(callback_method, object)
    @callbacks << { "callback_method" => callback_method, "object" => object }
  end

  def has_been_notified?
    !@callbacks.empty?
  end
end

class TopicaObserver < ActiveRecord::Observer
  def self.observed_class() Topic end
  
  attr_reader :topic
  
  def after_find(topic)
    @topic = topic
  end
end

class TopicObserver < ActiveRecord::Observer
  attr_reader :topic
  
  def after_find(topic)
    @topic = topic
  end
end

class MultiObserver < ActiveRecord::Observer
  attr_reader :record
  
  def self.observed_class() [ Topic, Developer ] end

  def after_find(record)
    @record = record
  end

end

class LifecycleTest < Test::Unit::TestCase
  fixtures :topics, :developers

  def test_before_destroy
    assert_equal 2, Topic.count
    Topic.find(1).destroy
    assert_equal 0, Topic.count
  end
  
  def test_after_save
    ActiveRecord::Base.observers = :topic_manual_observer

    topic = Topic.find(1)
    topic.title = "hello"
    topic.save
    
    assert TopicManualObserver.instance.has_been_notified?
    assert_equal :after_save, TopicManualObserver.instance.callbacks.last["callback_method"]
  end
  
  def test_observer_update_on_save
    ActiveRecord::Base.observers = TopicManualObserver

    topic = Topic.find(1)    
    assert TopicManualObserver.instance.has_been_notified?
    assert_equal :after_find, TopicManualObserver.instance.callbacks.first["callback_method"]
  end
  
  def test_auto_observer
    topic_observer = TopicaObserver.instance

    topic = Topic.find(1)    
    assert_equal topic_observer.topic.title, topic.title
  end
  
  def test_infered_auto_observer
    topic_observer = TopicObserver.instance

    topic = Topic.find(1)    
    assert_equal topic_observer.topic.title, topic.title
  end
  
  def test_observing_two_classes
    multi_observer = MultiObserver.instance

    topic = Topic.find(1)
    assert_equal multi_observer.record.title, topic.title

    developer = Developer.find(1)    
    assert_equal multi_observer.record.name, developer.name
  end
  
  def test_observing_subclasses
    multi_observer = MultiObserver.instance

    developer = SpecialDeveloper.find(1)
    assert_equal multi_observer.record.name, developer.name
  end
end
require 'abstract_unit'
require 'fixtures/person'
require 'fixtures/legacy_thing'

class LockingTest < Test::Unit::TestCase
  fixtures :people, :legacy_things

  def test_lock_existing
    p1 = Person.find(1)
    p2 = Person.find(1)
    
    p1.first_name = "Michael"
    p1.save
    
    assert_raises(ActiveRecord::StaleObjectError) {
      p2.first_name = "should fail"
      p2.save
    }
  end

  def test_lock_new
    p1 = Person.create({ "first_name"=>"anika"})
    p2 = Person.find(p1.id)
    assert_equal p1.id, p2.id
    p1.first_name = "Anika"
    p1.save
    
    assert_raises(ActiveRecord::StaleObjectError) {
      p2.first_name = "should fail"
      p2.save
    }
  end
  
  def test_lock_column_name_existing
    t1 = LegacyThing.find(1)
    t2 = LegacyThing.find(1)
    t1.tps_report_number = 400
    t1.save

    assert_raises(ActiveRecord::StaleObjectError) {
      t2.tps_report_number = 300
      t2.save
    }
  end 

end
require 'abstract_unit'
require 'fixtures/developer'
require 'fixtures/project'
require 'fixtures/comment'
require 'fixtures/post'
require 'fixtures/category'

class MethodScopingTest < Test::Unit::TestCase
  fixtures :developers, :projects, :comments, :posts
  
  def test_set_conditions
    Developer.with_scope(:find => { :conditions => 'just a test...' }) do
      assert_equal 'just a test...', Developer.send(:current_scoped_methods)[:find][:conditions]
    end
  end

  def test_scoped_find
    Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
      assert_nothing_raised { Developer.find(1) }
    end
  end
  
  def test_scoped_find_first
    Developer.with_scope(:find => { :conditions => "salary = 100000" }) do
      assert_equal Developer.find(10), Developer.find(:first, :order => 'name')
    end
  end
  
  def test_scoped_find_combines_conditions
    Developer.with_scope(:find => { :conditions => "salary = 9000" }) do
      assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
    end
  end
  
  def test_scoped_find_sanitizes_conditions
    Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
      assert_equal developers(:poor_jamis), Developer.find(:first)
    end  
  end
  
  def test_scoped_find_combines_and_sanitizes_conditions
    Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
      assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
    end  
  end
  
  def test_scoped_find_all
    Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
      assert_equal [developers(:david)], Developer.find(:all)
    end      
  end
  
  def test_scoped_count
    Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
      assert_equal 1, Developer.count
    end        

    Developer.with_scope(:find => { :conditions => 'salary = 100000' }) do
      assert_equal 8, Developer.count
      assert_equal 1, Developer.count("name LIKE 'fixture_1%'")
    end        
  end

  def test_scoped_find_include
    # with the include, will retrieve only developers for the given project
    scoped_developers = Developer.with_scope(:find => { :include => :projects }) do
      Developer.find(:all, :conditions => 'projects.id = 2')
    end
    assert scoped_developers.include?(developers(:david))
    assert !scoped_developers.include?(developers(:jamis))
    assert_equal 1, scoped_developers.size
  end
  
  def test_scoped_count_include
    # with the include, will retrieve only developers for the given project
    Developer.with_scope(:find => { :include => :projects }) do
      assert_equal 1, Developer.count('projects.id = 2')
    end
  end

  def test_scoped_create
    new_comment = nil

    VerySpecialComment.with_scope(:create => { :post_id => 1 }) do
      assert_equal({ :post_id => 1 }, VerySpecialComment.send(:current_scoped_methods)[:create])
      new_comment = VerySpecialComment.create :body => "Wonderful world"
    end

    assert Post.find(1).comments.include?(new_comment)
  end

  def test_immutable_scope
    options = { :conditions => "name = 'David'" }
    Developer.with_scope(:find => options) do
      assert_equal %w(David), Developer.find(:all).map { |d| d.name }
      options[:conditions] = "name != 'David'"
      assert_equal %w(David), Developer.find(:all).map { |d| d.name }
    end

    scope = { :find => { :conditions => "name = 'David'" }}
    Developer.with_scope(scope) do
      assert_equal %w(David), Developer.find(:all).map { |d| d.name }
      scope[:find][:conditions] = "name != 'David'"
      assert_equal %w(David), Developer.find(:all).map { |d| d.name }
    end
  end

  def test_scoped_with_duck_typing
    scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] })
    Developer.with_scope(scoping) do
       assert_equal %w(David), Developer.find(:all).map { |d| d.name }
    end
  end

  def test_ensure_that_method_scoping_is_correctly_restored
    scoped_methods = Developer.instance_eval('current_scoped_methods')

    begin
      Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
        raise "an exception"
      end
    rescue
    end
    assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
  end
end

class NestedScopingTest < Test::Unit::TestCase
  fixtures :developers, :projects, :comments, :posts

  def test_merge_options
    Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
      Developer.with_scope(:find => { :limit => 10 }) do
        merged_option = Developer.instance_eval('current_scoped_methods')[:find]
        assert_equal({ :conditions => 'salary = 80000', :limit => 10 }, merged_option)
      end
    end
  end

  def test_replace_options
    Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
      Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
        assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.instance_eval('current_scoped_methods'))
        assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.send(:scoped_methods)[-1])
      end
    end
  end

  def test_append_conditions
    Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
      Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
        appended_condition = Developer.instance_eval('current_scoped_methods')[:find][:conditions]
        assert_equal("( name = 'David' ) AND ( salary = 80000 )", appended_condition)
        assert_equal(1, Developer.count)
      end
      Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
        assert_equal(0, Developer.count)
      end
    end
  end

  def test_merge_and_append_options
    Developer.with_scope(:find => { :conditions => 'salary = 80000', :limit => 10 }) do
      Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
        merged_option = Developer.instance_eval('current_scoped_methods')[:find]
        assert_equal({ :conditions => "( salary = 80000 ) AND ( name = 'David' )", :limit => 10 }, merged_option)
      end
    end
  end

  def test_nested_scoped_find
    Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
      Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
        assert_nothing_raised { Developer.find(1) }
        assert_equal('David', Developer.find(:first).name)
      end
      assert_equal('Jamis', Developer.find(:first).name)
    end
  end

  def test_nested_scoped_find_include
    Developer.with_scope(:find => { :include => :projects }) do
      Developer.with_scope(:find => { :conditions => "projects.id = 2" }) do
        assert_nothing_raised { Developer.find(1) }
        assert_equal('David', Developer.find(:first).name)
      end
    end  
  end

  def test_nested_scoped_find_merged_include
    # :include's remain unique and don't "double up" when merging
    Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
      Developer.with_scope(:find => { :include => :projects }) do
        assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
        assert_equal('David', Developer.find(:first).name)
      end
    end  
    
    # the nested scope doesn't remove the first :include
    Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
      Developer.with_scope(:find => { :include => [] }) do
        assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
        assert_equal('David', Developer.find(:first).name)
      end
    end  
    
    # mixing array and symbol include's will merge correctly
    Developer.with_scope(:find => { :include => [:projects], :conditions => "projects.id = 2" }) do
      Developer.with_scope(:find => { :include => :projects }) do
        assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
        assert_equal('David', Developer.find(:first).name)
      end
    end  
  end
  
  def test_nested_scoped_find_replace_include
    Developer.with_scope(:find => { :include => :projects }) do
      Developer.with_exclusive_scope(:find => { :include => [] }) do
        assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:include].length
      end
    end  
  end

  def test_three_level_nested_exclusive_scoped_find
    Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
      assert_equal('Jamis', Developer.find(:first).name)

      Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
        assert_equal('David', Developer.find(:first).name)

        Developer.with_exclusive_scope(:find => { :conditions => "name = 'Maiha'" }) do
          assert_equal(nil, Developer.find(:first))
        end

        # ensure that scoping is restored
        assert_equal('David', Developer.find(:first).name)
      end

      # ensure that scoping is restored
      assert_equal('Jamis', Developer.find(:first).name)
    end
  end

  def test_merged_scoped_find
    poor_jamis = developers(:poor_jamis)
    Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
      Developer.with_scope(:find => { :offset => 1 }) do
        assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
      end
    end
  end

  def test_merged_scoped_find_sanitizes_conditions
    Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
      Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
        assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) }
      end
    end
  end

  def test_nested_scoped_find_combines_and_sanitizes_conditions
    Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
      Developer.with_exclusive_scope(:find => { :conditions => ['salary = ?', 9000] }) do
        assert_equal developers(:poor_jamis), Developer.find(:first)
        assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
      end
    end
  end

  def test_merged_scoped_find_combines_and_sanitizes_conditions
    Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
      Developer.with_scope(:find => { :conditions => ['salary > ?', 9000] }) do
        assert_equal %w(David), Developer.find(:all).map { |d| d.name }
      end
    end
  end

  def test_immutable_nested_scope
    options1 = { :conditions => "name = 'Jamis'" }
    options2 = { :conditions => "name = 'David'" }
    Developer.with_scope(:find => options1) do
      Developer.with_exclusive_scope(:find => options2) do
        assert_equal %w(David), Developer.find(:all).map { |d| d.name }
        options1[:conditions] = options2[:conditions] = nil
        assert_equal %w(David), Developer.find(:all).map { |d| d.name }
      end
    end
  end

  def test_immutable_merged_scope
    options1 = { :conditions => "name = 'Jamis'" }
    options2 = { :conditions => "salary > 10000" }
    Developer.with_scope(:find => options1) do
      Developer.with_scope(:find => options2) do
        assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
        options1[:conditions] = options2[:conditions] = nil
        assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
      end
    end
  end

  def test_ensure_that_method_scoping_is_correctly_restored
    Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
      scoped_methods = Developer.instance_eval('current_scoped_methods')
      begin
        Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
          raise "an exception"
        end
      rescue
      end
      assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
    end
  end
end

class HasManyScopingTest< Test::Unit::TestCase
  fixtures :comments, :posts
  
  def setup
    @welcome = Post.find(1)
  end
  
  def test_forwarding_of_static_methods
    assert_equal 'a comment...', Comment.what_are_you
    assert_equal 'a comment...', @welcome.comments.what_are_you
  end

  def test_forwarding_to_scoped
    assert_equal 4, Comment.search_by_type('Comment').size
    assert_equal 2, @welcome.comments.search_by_type('Comment').size
  end
  
  def test_forwarding_to_dynamic_finders
    assert_equal 4, Comment.find_all_by_type('Comment').size
    assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
  end

  def test_nested_scope
    Comment.with_scope(:find => { :conditions => '1=1' }) do
      assert_equal 'a comment...', @welcome.comments.what_are_you
    end
  end
end


class HasAndBelongsToManyScopingTest< Test::Unit::TestCase
  fixtures :posts, :categories, :categories_posts

  def setup
    @welcome = Post.find(1)
  end

  def test_forwarding_of_static_methods
    assert_equal 'a category...', Category.what_are_you
    assert_equal 'a category...', @welcome.categories.what_are_you
  end

  def test_forwarding_to_dynamic_finders
    assert_equal 4, Category.find_all_by_type('SpecialCategory').size
    assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
    assert_equal 2, @welcome.categories.find_all_by_type('Category').size
  end

  def test_nested_scope
    Category.with_scope(:find => { :conditions => '1=1' }) do
      assert_equal 'a comment...', @welcome.comments.what_are_you
    end
  end
end


=begin
# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case


class BelongsToScopingTest< Test::Unit::TestCase
  fixtures :comments, :posts

  def setup
    @greetings = Comment.find(1)
  end

  def test_forwarding_of_static_method
    assert_equal 'a post...', Post.what_are_you
    assert_equal 'a post...', @greetings.post.what_are_you
  end

  def test_forwarding_to_dynamic_finders
    assert_equal 4, Post.find_all_by_type('Post').size
    assert_equal 1, @greetings.post.find_all_by_type('Post').size
  end

end


class HasOneScopingTest< Test::Unit::TestCase
  fixtures :comments, :posts

  def setup
    @sti_comments = Post.find(4)
  end

  def test_forwarding_of_static_methods
    assert_equal 'a comment...', Comment.what_are_you
    assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
  end

  def test_forwarding_to_dynamic_finders
    assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
    assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
    assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
  end

end

=end
require 'abstract_unit'
require 'fixtures/person'
require File.dirname(__FILE__) + '/fixtures/migrations/1_people_have_last_names'
require File.dirname(__FILE__) + '/fixtures/migrations/2_we_need_reminders'

if ActiveRecord::Base.connection.supports_migrations? 
  class Reminder < ActiveRecord::Base; end

  class ActiveRecord::Migration
    class <<self
      attr_accessor :message_count
      def puts(text="")
        self.message_count ||= 0
        self.message_count += 1
      end
    end
  end

  class MigrationTest < Test::Unit::TestCase
    self.use_transactional_fixtures = false

    def setup
      ActiveRecord::Migration.verbose = true
      PeopleHaveLastNames.message_count = 0
    end

    def teardown
      ActiveRecord::Base.connection.initialize_schema_information
      ActiveRecord::Base.connection.update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0"

      Reminder.connection.drop_table("reminders") rescue nil
      Reminder.connection.drop_table("people_reminders") rescue nil
      Reminder.connection.drop_table("prefix_reminders_suffix") rescue nil
      Reminder.reset_column_information

      Person.connection.remove_column("people", "last_name") rescue nil
      Person.connection.remove_column("people", "key") rescue nil
      Person.connection.remove_column("people", "bio") rescue nil
      Person.connection.remove_column("people", "age") rescue nil
      Person.connection.remove_column("people", "height") rescue nil
      Person.connection.remove_column("people", "birthday") rescue nil
      Person.connection.remove_column("people", "favorite_day") rescue nil
      Person.connection.remove_column("people", "male") rescue nil
      Person.connection.remove_column("people", "administrator") rescue nil
      Person.reset_column_information
    end

    def test_add_index
      Person.connection.add_column "people", "last_name", :string        
      Person.connection.add_column "people", "administrator", :boolean
      Person.connection.add_column "people", "key", :string
      
      assert_nothing_raised { Person.connection.add_index("people", "last_name") }
      assert_nothing_raised { Person.connection.remove_index("people", "last_name") }

      assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
      assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
      
      # quoting
      assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key", :unique => true) }
      assert_nothing_raised { Person.connection.remove_index("people", :name => "key") }

      # Sybase adapter does not support indexes on :boolean columns
      unless current_adapter?(:SybaseAdapter)
        assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") }
        assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") }
      end
    end

    def test_create_table_adds_id
      Person.connection.create_table :testings do |t|
        t.column :foo, :string
      end

      assert_equal %w(foo id),
        Person.connection.columns(:testings).map { |c| c.name }.sort
    ensure
      Person.connection.drop_table :testings rescue nil
    end

    def test_create_table_with_not_null_column
      Person.connection.create_table :testings do |t|
        t.column :foo, :string, :null => false
      end

      assert_raises(ActiveRecord::StatementInvalid) do
        Person.connection.execute "insert into testings (foo) values (NULL)"
      end
    ensure
      Person.connection.drop_table :testings rescue nil
    end

    def test_create_table_with_defaults
      Person.connection.create_table :testings do |t|
        t.column :one, :string, :default => "hello"
        t.column :two, :boolean, :default => true
        t.column :three, :boolean, :default => false
        t.column :four, :integer, :default => 1
      end

      columns = Person.connection.columns(:testings)
      one = columns.detect { |c| c.name == "one" }
      two = columns.detect { |c| c.name == "two" }
      three = columns.detect { |c| c.name == "three" }
      four = columns.detect { |c| c.name == "four" }

      assert_equal "hello", one.default
      if current_adapter?(:OracleAdapter)
        # Oracle doesn't support native booleans
        assert_equal true, two.default == 1
        assert_equal false, three.default != 0
      else
        assert_equal true, two.default
        assert_equal false, three.default
      end
      assert_equal 1, four.default

    ensure
      Person.connection.drop_table :testings rescue nil
    end
  
    # SQL Server and Sybase will not allow you to add a NOT NULL column
    # to a table without specifying a default value, so the
    # following test must be skipped  
    unless current_adapter?(:SQLServerAdapter) || current_adapter?(:SybaseAdapter)
      def test_add_column_not_null_without_default
        Person.connection.create_table :testings do |t|
          t.column :foo, :string
        end
        Person.connection.add_column :testings, :bar, :string, :null => false
  
        assert_raises(ActiveRecord::StatementInvalid) do
          Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
        end
      ensure
        Person.connection.drop_table :testings rescue nil
      end
    end
    
    def test_add_column_not_null_with_default
      Person.connection.create_table :testings do |t|
        t.column :foo, :string
      end
      Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default"

      assert_raises(ActiveRecord::StatementInvalid) do
        Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
      end
    ensure
      Person.connection.drop_table :testings rescue nil
    end
  
    def test_native_types
      Person.delete_all
      Person.connection.add_column "people", "last_name", :string
      Person.connection.add_column "people", "bio", :text
      Person.connection.add_column "people", "age", :integer
      Person.connection.add_column "people", "height", :float
      Person.connection.add_column "people", "birthday", :datetime
      Person.connection.add_column "people", "favorite_day", :date
      Person.connection.add_column "people", "male", :boolean
      assert_nothing_raised { Person.create :first_name => 'bob', :last_name => 'bobsen', :bio => "I was born ....", :age => 18, :height => 1.78, :birthday => 18.years.ago, :favorite_day => 10.days.ago, :male => true }
      bob = Person.find(:first)
        
      assert_equal bob.first_name, 'bob'
      assert_equal bob.last_name, 'bobsen'
      assert_equal bob.bio, "I was born ...."
      assert_equal bob.age, 18
      assert_equal bob.male?, true
    
      assert_equal String, bob.first_name.class
      assert_equal String, bob.last_name.class
      assert_equal String, bob.bio.class
      assert_equal Fixnum, bob.age.class
      assert_equal Time, bob.birthday.class

      if current_adapter?(:SQLServerAdapter) || current_adapter?(:OracleAdapter) || current_adapter?(:SybaseAdapter)
        # SQL Server, Sybase, and Oracle don't differentiate between date/time
        assert_equal Time, bob.favorite_day.class
      else
        assert_equal Date, bob.favorite_day.class
      end

      assert_equal TrueClass, bob.male?.class
    end

    def test_add_remove_single_field_using_string_arguments
      assert !Person.column_methods_hash.include?(:last_name)

      ActiveRecord::Migration.add_column 'people', 'last_name', :string

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
    
      ActiveRecord::Migration.remove_column 'people', 'last_name'

      Person.reset_column_information
      assert !Person.column_methods_hash.include?(:last_name)
    end

    def test_add_remove_single_field_using_symbol_arguments
      assert !Person.column_methods_hash.include?(:last_name)

      ActiveRecord::Migration.add_column :people, :last_name, :string

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)

      ActiveRecord::Migration.remove_column :people, :last_name

      Person.reset_column_information
      assert !Person.column_methods_hash.include?(:last_name)
    end
    
    def test_add_rename
      Person.delete_all
           
      begin
        Person.connection.add_column "people", "girlfriend", :string      
        Person.create :girlfriend => 'bobette'      
      
        Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
      
        Person.reset_column_information      
        bob = Person.find(:first)
      
        assert_equal "bobette", bob.exgirlfriend
      ensure
        Person.connection.remove_column("people", "girlfriend") rescue nil
        Person.connection.remove_column("people", "exgirlfriend") rescue nil
      end
      
    end
    
    def test_rename_column_using_symbol_arguments
      begin
        Person.connection.rename_column :people, :first_name, :nick_name
        Person.reset_column_information
        assert Person.column_names.include?("nick_name")
      ensure
        Person.connection.remove_column("people","nick_name")
        Person.connection.add_column("people","first_name", :string)
      end
    end
    
    def test_rename_column
      begin
        Person.connection.rename_column "people", "first_name", "nick_name"
        Person.reset_column_information
        assert Person.column_names.include?("nick_name")
      ensure
        Person.connection.remove_column("people","nick_name")
        Person.connection.add_column("people","first_name", :string)
      end
    end
    
    def test_rename_table
      begin
        ActiveRecord::Base.connection.create_table :octopuses do |t|
          t.column :url, :string
        end
        ActiveRecord::Base.connection.rename_table :octopuses, :octopi

        assert_nothing_raised do
          if current_adapter?(:OracleAdapter)
            # Oracle requires the explicit sequence value for the pk
            ActiveRecord::Base.connection.execute "INSERT INTO octopi (id, url) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
          else
            ActiveRecord::Base.connection.execute "INSERT INTO octopi (url) VALUES ('http://www.foreverflying.com/octopus-black7.jpg')"
          end
        end

        assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")

      ensure
        ActiveRecord::Base.connection.drop_table :octopuses rescue nil
        ActiveRecord::Base.connection.drop_table :octopi rescue nil
      end
    end

    def test_change_column
      Person.connection.add_column 'people', 'age', :integer
      old_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
      assert old_columns.find { |c| c.name == 'age' and c.type == :integer }

      assert_nothing_raised { Person.connection.change_column "people", "age", :string }
      
      new_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
      assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer }
      assert new_columns.find { |c| c.name == 'age' and c.type == :string }
    end    

    def test_change_column_with_new_default
      Person.connection.add_column "people", "administrator", :boolean, :default => 1
      Person.reset_column_information            
      assert Person.new.administrator?
      
      assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => 0 }
      Person.reset_column_information            
      assert !Person.new.administrator?
    end    

    def test_add_table
      assert !Reminder.table_exists?
      
      WeNeedReminders.up
    
      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
      assert_equal "hello world", Reminder.find(:first).content
    
      WeNeedReminders.down
      assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
    end

    def test_migrator
      assert !Person.column_methods_hash.include?(:last_name)
      assert !Reminder.table_exists?

      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')

      assert_equal 3, ActiveRecord::Migrator.current_version
      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
      assert_equal "hello world", Reminder.find(:first).content

      ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/')

      assert_equal 0, ActiveRecord::Migrator.current_version
      Person.reset_column_information
      assert !Person.column_methods_hash.include?(:last_name)
      assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
    end

    def test_migrator_one_up
      assert !Person.column_methods_hash.include?(:last_name)
      assert !Reminder.table_exists?
      
      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
      assert !Reminder.table_exists?

      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 2)

      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
      assert_equal "hello world", Reminder.find(:first).content
    end
  
    def test_migrator_one_down
      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
    
      ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 1)

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
      assert !Reminder.table_exists?
    end
  
    def test_migrator_one_up_one_down
      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
      ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)

      assert !Person.column_methods_hash.include?(:last_name)
      assert !Reminder.table_exists?
    end
    
    def test_migrator_verbosity
      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
      assert PeopleHaveLastNames.message_count > 0
      PeopleHaveLastNames.message_count = 0

      ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
      assert PeopleHaveLastNames.message_count > 0
      PeopleHaveLastNames.message_count = 0
    end
    
    def test_migrator_verbosity_off
      PeopleHaveLastNames.verbose = false
      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
      assert PeopleHaveLastNames.message_count.zero?
      ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
      assert PeopleHaveLastNames.message_count.zero?
    end
    
    def test_migrator_going_down_due_to_version_target
      ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
      ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/', 0)

      assert !Person.column_methods_hash.include?(:last_name)
      assert !Reminder.table_exists?

      ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations/')

      Person.reset_column_information
      assert Person.column_methods_hash.include?(:last_name)
      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
      assert_equal "hello world", Reminder.find(:first).content
    end

    def test_schema_info_table_name
      ActiveRecord::Base.table_name_prefix = "prefix_"
      ActiveRecord::Base.table_name_suffix = "_suffix"
      Reminder.reset_table_name
      assert_equal "prefix_schema_info_suffix", ActiveRecord::Migrator.schema_info_table_name
      ActiveRecord::Base.table_name_prefix = ""
      ActiveRecord::Base.table_name_suffix = ""
      Reminder.reset_table_name
      assert_equal "schema_info", ActiveRecord::Migrator.schema_info_table_name
    ensure
      ActiveRecord::Base.table_name_prefix = ""
      ActiveRecord::Base.table_name_suffix = ""
    end
  
    def test_proper_table_name
      assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
      assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
      assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
      Reminder.reset_table_name
      assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
  
      # Use the model's own prefix/suffix if a model is given
      ActiveRecord::Base.table_name_prefix = "ARprefix_"
      ActiveRecord::Base.table_name_suffix = "_ARsuffix"
      Reminder.table_name_prefix = 'prefix_'
      Reminder.table_name_suffix = '_suffix'
      Reminder.reset_table_name
      assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
      Reminder.table_name_prefix = ''
      Reminder.table_name_suffix = ''
      Reminder.reset_table_name
      
      # Use AR::Base's prefix/suffix if string or symbol is given      
      ActiveRecord::Base.table_name_prefix = "prefix_"
      ActiveRecord::Base.table_name_suffix = "_suffix"
      Reminder.reset_table_name
      assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
      assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
      ActiveRecord::Base.table_name_prefix = ""
      ActiveRecord::Base.table_name_suffix = ""
      Reminder.reset_table_name
    end  

    def test_add_drop_table_with_prefix_and_suffix
      assert !Reminder.table_exists?
      ActiveRecord::Base.table_name_prefix = 'prefix_'
      ActiveRecord::Base.table_name_suffix = '_suffix'
      Reminder.reset_table_name
      Reminder.reset_sequence_name
      WeNeedReminders.up
      assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
      assert_equal "hello world", Reminder.find(:first).content

      WeNeedReminders.down
      assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
    ensure
      ActiveRecord::Base.table_name_prefix = ''
      ActiveRecord::Base.table_name_suffix = ''
      Reminder.reset_table_name
      Reminder.reset_sequence_name
    end

    def test_create_table_with_binary_column
      Person.connection.drop_table :binary_testings rescue nil
    
      assert_nothing_raised {
        Person.connection.create_table :binary_testings do |t|
          t.column "data", :binary, :default => "", :null => false
        end
      }
      
      columns = Person.connection.columns(:binary_testings)
      data_column = columns.detect { |c| c.name == "data" }

      if current_adapter?(:OracleAdapter)
        assert_equal "empty_blob()", data_column.default
      else
        assert_equal "", data_column.default
      end

      Person.connection.drop_table :binary_testings rescue nil
    end

    def test_migrator_with_duplicates
      assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
        ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_duplicate/', nil)
      end
    end
  end
end
require 'abstract_unit'
require 'active_record/acts/nested_set'
require 'fixtures/mixin'
require 'pp'

class MixinNestedSetTest < Test::Unit::TestCase
	fixtures :mixins
                             
	def test_mixing_in_methods
	  ns = NestedSet.new
		assert( ns.respond_to?( :all_children ) )
		assert_equal( ns.scope_condition, "root_id IS NULL" )
		
		check_method_mixins ns
	end
	
	def test_string_scope
	  ns = NestedSetWithStringScope.new
	  
	  ns.root_id = 1
	  assert_equal( ns.scope_condition, "root_id = 1" )
	  ns.root_id = 42
	  assert_equal( ns.scope_condition, "root_id = 42" )
	  check_method_mixins ns
  end
  
  def test_symbol_scope
    ns = NestedSetWithSymbolScope.new
    ns.root_id = 1
    assert_equal( ns.scope_condition, "root_id = 1" )
    ns.root_id = 42
    assert_equal( ns.scope_condition, "root_id = 42" )
    check_method_mixins ns
  end
  
  def check_method_mixins( obj )
    [:scope_condition, :left_col_name, :right_col_name, :parent_column, :root?, :add_child,
    :children_count, :full_set, :all_children, :direct_children].each { |symbol| assert( obj.respond_to?(symbol)) }
  end

  def set( id )
    NestedSet.find( 3000 + id )
  end
  
  def test_adding_children
    assert( set(1).unknown? )
    assert( set(2).unknown? )
    set(1).add_child set(2)
    
    # Did we maintain adding the parent_ids?
    assert( set(1).root? )
    assert( set(2).child? )
    assert( set(2).parent_id == set(1).id )
    
    # Check boundies
    assert_equal( set(1).lft, 1 )
    assert_equal( set(2).lft, 2 )
    assert_equal( set(2).rgt, 3 )
    assert_equal( set(1).rgt, 4 )
    
    # Check children cound
    assert_equal( set(1).children_count, 1 )
    
    set(1).add_child set(3)
    
    #check boundries
    assert_equal( set(1).lft, 1 )
    assert_equal( set(2).lft, 2 )
    assert_equal( set(2).rgt, 3 )
    assert_equal( set(3).lft, 4 )
    assert_equal( set(3).rgt, 5 )
    assert_equal( set(1).rgt, 6 )
    
    # How is the count looking?
    assert_equal( set(1).children_count, 2 )

    set(2).add_child set(4)

    # boundries
    assert_equal( set(1).lft, 1 )
    assert_equal( set(2).lft, 2 )
    assert_equal( set(4).lft, 3 )
    assert_equal( set(4).rgt, 4 )
    assert_equal( set(2).rgt, 5 )
    assert_equal( set(3).lft, 6 )
    assert_equal( set(3).rgt, 7 )
    assert_equal( set(1).rgt, 8 )
    
    # Children count
    assert_equal( set(1).children_count, 3 )
    assert_equal( set(2).children_count, 1 )
    assert_equal( set(3).children_count, 0 )
    assert_equal( set(4).children_count, 0 )
    
    set(2).add_child set(5)
    set(4).add_child set(6)
    
    assert_equal( set(2).children_count, 3 )


    # Children accessors
    assert_equal( set(1).full_set.length, 6 )
    assert_equal( set(2).full_set.length, 4 )
    assert_equal( set(4).full_set.length, 2 )
    
    assert_equal( set(1).all_children.length, 5 )
    assert_equal( set(6).all_children.length, 0 )
    
    assert_equal( set(1).direct_children.length, 2 )
    
  end

  def test_snipping_tree
    big_tree = NestedSetWithStringScope.find( 4001 )
    
    # Make sure we have the right one
    assert_equal( 3, big_tree.direct_children.length )
    assert_equal( 10, big_tree.full_set.length )
    
    NestedSetWithStringScope.find( 4005 ).destroy

    big_tree = NestedSetWithStringScope.find( 4001 )
    
    assert_equal( 7, big_tree.full_set.length )
    assert_equal( 2, big_tree.direct_children.length )
    
    assert_equal( 1, NestedSetWithStringScope.find(4001).lft )
    assert_equal( 2, NestedSetWithStringScope.find(4002).lft )
    assert_equal( 3, NestedSetWithStringScope.find(4003).lft )
    assert_equal( 4, NestedSetWithStringScope.find(4003).rgt )
    assert_equal( 5, NestedSetWithStringScope.find(4004).lft )
    assert_equal( 6, NestedSetWithStringScope.find(4004).rgt )
    assert_equal( 7, NestedSetWithStringScope.find(4002).rgt )
    assert_equal( 8, NestedSetWithStringScope.find(4008).lft )
    assert_equal( 9, NestedSetWithStringScope.find(4009).lft )
    assert_equal(10, NestedSetWithStringScope.find(4009).rgt )
    assert_equal(11, NestedSetWithStringScope.find(4010).lft )
    assert_equal(12, NestedSetWithStringScope.find(4010).rgt )
    assert_equal(13, NestedSetWithStringScope.find(4008).rgt )
    assert_equal(14, NestedSetWithStringScope.find(4001).rgt )
  end
  
  def test_deleting_root
    NestedSetWithStringScope.find(4001).destroy
    
    assert( NestedSetWithStringScope.count == 0 )
  end            
                               
  def test_common_usage
    mixins(:set_1).add_child( mixins(:set_2) )
    assert_equal( 1, mixins(:set_1).direct_children.length )

    mixins(:set_2).add_child( mixins(:set_3) )                      
    assert_equal( 1, mixins(:set_1).direct_children.length )     
    
    # Local cache is now out of date!
    # Problem: the update_alls update all objects up the tree
    mixins(:set_1).reload
    assert_equal( 2, mixins(:set_1).all_children.length )              
    
    assert_equal( 1, mixins(:set_1).lft )
    assert_equal( 2, mixins(:set_2).lft )
    assert_equal( 3, mixins(:set_3).lft )
    assert_equal( 4, mixins(:set_3).rgt )
    assert_equal( 5, mixins(:set_2).rgt )
    assert_equal( 6, mixins(:set_1).rgt )
          
    assert( mixins(:set_1).root? )
                  
    begin
      mixins(:set_4).add_child( mixins(:set_1) )
      fail
    rescue
    end
    
    assert_equal( 2, mixins(:set_1).all_children.length )
    
    mixins(:set_1).add_child mixins(:set_4)

    assert_equal( 3, mixins(:set_1).all_children.length )


  end
end
require 'abstract_unit'
require 'active_record/acts/tree'
require 'active_record/acts/list'
require 'active_record/acts/nested_set'
require 'fixtures/mixin'

class ListTest < Test::Unit::TestCase
  fixtures :mixins

  def test_reordering
    assert_equal [mixins(:list_1),
                  mixins(:list_2),
                  mixins(:list_3),
                  mixins(:list_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    mixins(:list_2).move_lower

    assert_equal [mixins(:list_1),
                  mixins(:list_3),
                  mixins(:list_2),
                  mixins(:list_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    mixins(:list_2).move_higher

    assert_equal [mixins(:list_1),
                  mixins(:list_2),
                  mixins(:list_3),
                  mixins(:list_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    mixins(:list_1).move_to_bottom

    assert_equal [mixins(:list_2),
                  mixins(:list_3),
                  mixins(:list_4),
                  mixins(:list_1)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    mixins(:list_1).move_to_top

    assert_equal [mixins(:list_1),
                  mixins(:list_2),
                  mixins(:list_3),
                  mixins(:list_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')


    mixins(:list_2).move_to_bottom

    assert_equal [mixins(:list_1),
                  mixins(:list_3),
                  mixins(:list_4),
                  mixins(:list_2)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    mixins(:list_4).move_to_top

    assert_equal [mixins(:list_4),
                  mixins(:list_1),
                  mixins(:list_3),
                  mixins(:list_2)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

  end

  def test_move_to_bottom_with_next_to_last_item
    assert_equal [mixins(:list_1),
                  mixins(:list_2),
                  mixins(:list_3),
                  mixins(:list_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    mixins(:list_3).move_to_bottom

    assert_equal [mixins(:list_1),
                  mixins(:list_2),
                  mixins(:list_4),
                  mixins(:list_3)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')
  end

  def test_next_prev
    assert_equal mixins(:list_2), mixins(:list_1).lower_item
    assert_nil mixins(:list_1).higher_item
    assert_equal mixins(:list_3), mixins(:list_4).higher_item
    assert_nil mixins(:list_4).lower_item
  end


  def test_injection
    item = ListMixin.new("parent_id"=>1)
    assert_equal "parent_id = 1", item.scope_condition
    assert_equal "pos", item.position_column
  end

  def test_insert
    new = ListMixin.create("parent_id"=>20)
    assert_equal 1, new.pos
    assert new.first?
    assert new.last?

    new = ListMixin.create("parent_id"=>20)
    assert_equal 2, new.pos
    assert !new.first?
    assert new.last?

    new = ListMixin.create("parent_id"=>20)
    assert_equal 3, new.pos
    assert !new.first?
    assert new.last?

    new = ListMixin.create("parent_id"=>0)
    assert_equal 1, new.pos
    assert new.first?
    assert new.last?
  end

  def test_insert_at
    new = ListMixin.create("parent_id" => 20)
    assert_equal 1, new.pos

    new = ListMixin.create("parent_id" => 20)
    assert_equal 2, new.pos

    new = ListMixin.create("parent_id" => 20)
    assert_equal 3, new.pos

    new4 = ListMixin.create("parent_id" => 20)
    assert_equal 4, new4.pos

    new4.insert_at(3)
    assert_equal 3, new4.pos

    new.reload
    assert_equal 4, new.pos

    new.insert_at(2)
    assert_equal 2, new.pos

    new4.reload
    assert_equal 4, new4.pos

    new5 = ListMixin.create("parent_id" => 20)
    assert_equal 5, new5.pos

    new5.insert_at(1)
    assert_equal 1, new5.pos

    new4.reload
    assert_equal 5, new4.pos
  end

  def test_delete_middle
    assert_equal [mixins(:list_1),
                  mixins(:list_2),
                  mixins(:list_3),
                  mixins(:list_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    mixins(:list_2).destroy

    assert_equal [mixins(:list_1, :reload),
                  mixins(:list_3, :reload),
                  mixins(:list_4, :reload)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    assert_equal 1, mixins(:list_1).pos
    assert_equal 2, mixins(:list_3).pos
    assert_equal 3, mixins(:list_4).pos

    mixins(:list_1).destroy

    assert_equal [mixins(:list_3, :reload),
                  mixins(:list_4, :reload)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos')

    assert_equal 1, mixins(:list_3).pos
    assert_equal 2, mixins(:list_4).pos

  end

  def test_with_string_based_scope
    new = ListWithStringScopeMixin.create("parent_id"=>500)
    assert_equal 1, new.pos
    assert new.first?
    assert new.last?
  end

  def test_nil_scope
    new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
    new2.move_higher
    assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
  end

end

class TreeTest < Test::Unit::TestCase
  fixtures :mixins

  def test_has_child
    assert_equal true, mixins(:tree_1).has_children?
    assert_equal true, mixins(:tree_2).has_children?
    assert_equal false, mixins(:tree_3).has_children?
    assert_equal false, mixins(:tree_4).has_children?
  end

  def test_children
    assert_equal mixins(:tree_1).children, [mixins(:tree_2), mixins(:tree_4)]
    assert_equal mixins(:tree_2).children, [mixins(:tree_3)]
    assert_equal mixins(:tree_3).children, []
    assert_equal mixins(:tree_4).children, []
  end

  def test_has_parent
    assert_equal false, mixins(:tree_1).has_parent?
    assert_equal true, mixins(:tree_2).has_parent?
    assert_equal true, mixins(:tree_3).has_parent?
    assert_equal true, mixins(:tree_4).has_parent?
  end

  def test_parent
    assert_equal mixins(:tree_2).parent, mixins(:tree_1)
    assert_equal mixins(:tree_2).parent, mixins(:tree_4).parent
    assert_nil mixins(:tree_1).parent
  end

  def test_delete
    assert_equal 6, TreeMixin.count
    mixins(:tree_1).destroy
    assert_equal 2, TreeMixin.count
    mixins(:tree2_1).destroy
    mixins(:tree3_1).destroy
    assert_equal 0, TreeMixin.count
  end

  def test_insert
    @extra = mixins(:tree_1).children.create

    assert @extra

    assert_equal @extra.parent, mixins(:tree_1)

    assert_equal 3, mixins(:tree_1).children.size
    assert mixins(:tree_1).children.include?(@extra)
    assert mixins(:tree_1).children.include?(mixins(:tree_2))
    assert mixins(:tree_1).children.include?(mixins(:tree_4))
  end

  def test_ancestors
    assert_equal [], mixins(:tree_1).ancestors
    assert_equal [mixins(:tree_1)], mixins(:tree_2).ancestors
    assert_equal [mixins(:tree_2), mixins(:tree_1)], mixins(:tree_3).ancestors
    assert_equal [mixins(:tree_1)], mixins(:tree_4).ancestors
    assert_equal [], mixins(:tree2_1).ancestors
    assert_equal [], mixins(:tree3_1).ancestors
  end

  def test_root
    assert_equal mixins(:tree_1), TreeMixin.root
    assert_equal mixins(:tree_1), mixins(:tree_1).root
    assert_equal mixins(:tree_1), mixins(:tree_2).root
    assert_equal mixins(:tree_1), mixins(:tree_3).root
    assert_equal mixins(:tree_1), mixins(:tree_4).root
    assert_equal mixins(:tree2_1), mixins(:tree2_1).root
    assert_equal mixins(:tree3_1), mixins(:tree3_1).root
  end

  def test_roots
    assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], TreeMixin.roots
  end

  def test_siblings
    assert_equal [mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree_1).siblings
    assert_equal [mixins(:tree_4)], mixins(:tree_2).siblings
    assert_equal [], mixins(:tree_3).siblings
    assert_equal [mixins(:tree_2)], mixins(:tree_4).siblings
    assert_equal [mixins(:tree_1), mixins(:tree3_1)], mixins(:tree2_1).siblings
    assert_equal [mixins(:tree_1), mixins(:tree2_1)], mixins(:tree3_1).siblings
  end

  def test_self_and_siblings
    assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree_1).self_and_siblings
    assert_equal [mixins(:tree_2), mixins(:tree_4)], mixins(:tree_2).self_and_siblings
    assert_equal [mixins(:tree_3)], mixins(:tree_3).self_and_siblings
    assert_equal [mixins(:tree_2), mixins(:tree_4)], mixins(:tree_4).self_and_siblings
    assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree2_1).self_and_siblings
    assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], mixins(:tree3_1).self_and_siblings
  end
end

class TreeTestWithoutOrder < Test::Unit::TestCase
  fixtures :mixins

  def test_root
    assert [mixins(:tree_without_order_1), mixins(:tree_without_order_2)].include?(TreeMixinWithoutOrder.root)
  end

  def test_roots
    assert_equal [], [mixins(:tree_without_order_1), mixins(:tree_without_order_2)] - TreeMixinWithoutOrder.roots
  end
end

class TouchTest < Test::Unit::TestCase
  fixtures :mixins

  def test_update
    stamped = Mixin.new

    assert_nil stamped.updated_at
    assert_nil stamped.created_at
    stamped.save
    assert_not_nil stamped.updated_at
    assert_not_nil stamped.created_at
  end

  def test_create
    @obj = Mixin.create
    assert_not_nil @obj.updated_at
    assert_not_nil @obj.created_at
  end

  def test_many_updates
    stamped = Mixin.new

    assert_nil stamped.updated_at
    assert_nil stamped.created_at
    stamped.save
    assert_not_nil stamped.created_at
    assert_not_nil stamped.updated_at

    old_updated_at = stamped.updated_at

    sleep 1
    stamped.save
    assert_not_equal stamped.created_at, stamped.updated_at
    assert_not_equal old_updated_at, stamped.updated_at

  end

  def test_create_turned_off
    Mixin.record_timestamps = false

    assert_nil mixins(:tree_1).updated_at
    mixins(:tree_1).save
    assert_nil mixins(:tree_1).updated_at

    Mixin.record_timestamps = true
  end

end


class ListSubTest < Test::Unit::TestCase
  fixtures :mixins

  def test_reordering
    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_2),
                  mixins(:list_sub_3),
                  mixins(:list_sub_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    mixins(:list_sub_2).move_lower

    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_3),
                  mixins(:list_sub_2),
                  mixins(:list_sub_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    mixins(:list_sub_2).move_higher

    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_2),
                  mixins(:list_sub_3),
                  mixins(:list_sub_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    mixins(:list_sub_1).move_to_bottom

    assert_equal [mixins(:list_sub_2),
                  mixins(:list_sub_3),
                  mixins(:list_sub_4),
                  mixins(:list_sub_1)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    mixins(:list_sub_1).move_to_top

    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_2),
                  mixins(:list_sub_3),
                  mixins(:list_sub_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')


    mixins(:list_sub_2).move_to_bottom

    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_3),
                  mixins(:list_sub_4),
                  mixins(:list_sub_2)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    mixins(:list_sub_4).move_to_top

    assert_equal [mixins(:list_sub_4),
                  mixins(:list_sub_1),
                  mixins(:list_sub_3),
                  mixins(:list_sub_2)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

  end

  def test_move_to_bottom_with_next_to_last_item
    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_2),
                  mixins(:list_sub_3),
                  mixins(:list_sub_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    mixins(:list_sub_3).move_to_bottom

    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_2),
                  mixins(:list_sub_4),
                  mixins(:list_sub_3)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')
  end

  def test_next_prev
    assert_equal mixins(:list_sub_2), mixins(:list_sub_1).lower_item
    assert_nil mixins(:list_sub_1).higher_item
    assert_equal mixins(:list_sub_3), mixins(:list_sub_4).higher_item
    assert_nil mixins(:list_sub_4).lower_item
  end


  def test_injection
    item = ListMixin.new("parent_id"=>1)
    assert_equal "parent_id = 1", item.scope_condition
    assert_equal "pos", item.position_column
  end


  def test_insert_at
    new = ListMixin.create("parent_id" => 20)
    assert_equal 1, new.pos

    new = ListMixinSub1.create("parent_id" => 20)
    assert_equal 2, new.pos

    new = ListMixinSub2.create("parent_id" => 20)
    assert_equal 3, new.pos

    new4 = ListMixin.create("parent_id" => 20)
    assert_equal 4, new4.pos

    new4.insert_at(3)
    assert_equal 3, new4.pos

    new.reload
    assert_equal 4, new.pos

    new.insert_at(2)
    assert_equal 2, new.pos

    new4.reload
    assert_equal 4, new4.pos

    new5 = ListMixinSub1.create("parent_id" => 20)
    assert_equal 5, new5.pos

    new5.insert_at(1)
    assert_equal 1, new5.pos

    new4.reload
    assert_equal 5, new4.pos
  end

  def test_delete_middle
    assert_equal [mixins(:list_sub_1),
                  mixins(:list_sub_2),
                  mixins(:list_sub_3),
                  mixins(:list_sub_4)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    mixins(:list_sub_2).destroy

    assert_equal [mixins(:list_sub_1, :reload),
                  mixins(:list_sub_3, :reload),
                  mixins(:list_sub_4, :reload)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    assert_equal 1, mixins(:list_sub_1).pos
    assert_equal 2, mixins(:list_sub_3).pos
    assert_equal 3, mixins(:list_sub_4).pos

    mixins(:list_sub_1).destroy

    assert_equal [mixins(:list_sub_3, :reload),
                  mixins(:list_sub_4, :reload)],
                  ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos')

    assert_equal 1, mixins(:list_sub_3).pos
    assert_equal 2, mixins(:list_sub_4).pos

  end

end

require 'abstract_unit'
require 'fixtures/company_in_module'

class ModulesTest < Test::Unit::TestCase
  fixtures :accounts, :companies, :projects, :developers

  def test_module_spanning_associations
    assert MyApplication::Business::Firm.find(:first).has_clients?, "Firm should have clients"
    firm = MyApplication::Business::Firm.find(:first)
    assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name"
    assert_equal 2, firm.clients_count, "Firm should have two clients"
  end

  def test_module_spanning_has_and_belongs_to_many_associations
    project = MyApplication::Business::Project.find(:first)
    project.developers << MyApplication::Business::Developer.create("name" => "John")
    assert "John", project.developers.last.name
  end
  
  def test_associations_spanning_cross_modules
    account = MyApplication::Billing::Account.find(:first, :order => 'id')
    assert_kind_of MyApplication::Business::Firm, account.firm
    assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm
    assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm
    assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_qualified_billing_firm
    assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_unqualified_billing_firm
  end
end
require 'abstract_unit'
require 'fixtures/entrant'

# So we can test whether Course.connection survives a reload.
require_dependency 'fixtures/course'

class MultipleDbTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false

  def setup
    @courses  = create_fixtures("courses") { Course.retrieve_connection }
    @entrants = create_fixtures("entrants")
  end

  def test_connected
    assert_not_nil Entrant.connection
    assert_not_nil Course.connection
  end

  def test_proper_connection
    assert_not_equal(Entrant.connection, Course.connection)
    assert_equal(Entrant.connection, Entrant.retrieve_connection)
    assert_equal(Course.connection, Course.retrieve_connection)
    assert_equal(ActiveRecord::Base.connection, Entrant.connection)
  end

  def test_find
    c1 = Course.find(1)
    assert_equal "Ruby Development", c1.name
    c2 = Course.find(2)
    assert_equal "Java Development", c2.name
    e1 = Entrant.find(1)
    assert_equal "Ruby Developer", e1.name
    e2 = Entrant.find(2)
    assert_equal "Ruby Guru", e2.name
    e3 = Entrant.find(3)
    assert_equal "Java Lover", e3.name
  end

  def test_associations
    c1 = Course.find(1)
    assert_equal 2, c1.entrants_count
    e1 = Entrant.find(1)
    assert_equal e1.course.id, c1.id
    c2 = Course.find(2)
    assert_equal 1, c2.entrants_count
    e3 = Entrant.find(3)
    assert_equal e3.course.id, c2.id
  end

  def test_course_connection_should_survive_dependency_reload
    assert Course.connection

    Dependencies.clear
    Object.send(:remove_const, :Course)
    require_dependency 'fixtures/course'

    assert Course.connection
  end
end
require "#{File.dirname(__FILE__)}/abstract_unit"
require 'fixtures/topic'
require 'fixtures/subscriber'
require 'fixtures/movie'
require 'fixtures/keyboard'

class PrimaryKeysTest < Test::Unit::TestCase
  fixtures :topics, :subscribers, :movies

  def test_integer_key
    topic = Topic.find(1)
    assert_equal(topics(:first).author_name, topic.author_name)
    topic = Topic.find(2)
    assert_equal(topics(:second).author_name, topic.author_name)

    topic = Topic.new
    topic.title = "New Topic"
    assert_equal(nil, topic.id)
    assert_nothing_raised { topic.save! }
    id = topic.id

    topicReloaded = Topic.find(id)
    assert_equal("New Topic", topicReloaded.title)
  end

  def test_customized_primary_key_auto_assigns_on_save
    Keyboard.delete_all
    keyboard = Keyboard.new(:name => 'HHKB')
    assert_nothing_raised { keyboard.save! }
    assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id
  end

  def test_customized_primary_key_can_be_get_before_saving
    keyboard = Keyboard.new
    assert_nil keyboard.id
    assert_nothing_raised { assert_nil keyboard.key_number }
  end

  def test_customized_string_primary_key_settable_before_save
    subscriber = Subscriber.new
    assert_nothing_raised { subscriber.id = 'webster123' }
    assert_equal 'webster123', subscriber.id
    assert_equal 'webster123', subscriber.nick
  end

  def test_string_key
    subscriber = Subscriber.find(subscribers(:first).nick)
    assert_equal(subscribers(:first).name, subscriber.name)
    subscriber = Subscriber.find(subscribers(:second).nick)
    assert_equal(subscribers(:second).name, subscriber.name)

    subscriber = Subscriber.new
    subscriber.id = "jdoe"
    assert_equal("jdoe", subscriber.id)
    subscriber.name = "John Doe"
    assert_nothing_raised { subscriber.save! }
    assert_equal("jdoe", subscriber.id)

    subscriberReloaded = Subscriber.find("jdoe")
    assert_equal("John Doe", subscriberReloaded.name)
  end

  def test_find_with_more_than_one_string_key
    assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length
  end
  
  def test_primary_key_prefix
    ActiveRecord::Base.primary_key_prefix_type = :table_name
    Topic.reset_primary_key
    assert_equal "topicid", Topic.primary_key

    ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
    Topic.reset_primary_key
    assert_equal "topic_id", Topic.primary_key

    ActiveRecord::Base.primary_key_prefix_type = nil
    Topic.reset_primary_key
    assert_equal "id", Topic.primary_key
  end
end
require 'abstract_unit'
require 'fixtures/post'
require 'fixtures/comment'
require 'fixtures/developer'
require 'fixtures/project'
require 'fixtures/reader'
require 'fixtures/person'

# Dummy class methods to test implicit association scoping.
def Comment.foo() find :first end
def Project.foo() find :first end


class ReadOnlyTest < Test::Unit::TestCase
  fixtures :posts, :comments, :developers, :projects, :developers_projects

  def test_cant_save_readonly_record
    dev = Developer.find(1)
    assert !dev.readonly?

    dev.readonly!
    assert dev.readonly?

    assert_nothing_raised do
      dev.name = 'Luscious forbidden fruit.'
      assert !dev.save
      dev.name = 'Forbidden.'
    end
    assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save  }
    assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
  end


  def test_find_with_readonly_option
    Developer.find(:all).each { |d| assert !d.readonly? }
    Developer.find(:all, :readonly => false).each { |d| assert !d.readonly? }
    Developer.find(:all, :readonly => true).each { |d| assert d.readonly? }
  end


  def test_find_with_joins_option_implies_readonly
    # Blank joins don't count.
    Developer.find(:all, :joins => '  ').each { |d| assert !d.readonly? }
    Developer.find(:all, :joins => '  ', :readonly => false).each { |d| assert !d.readonly? }

    # Others do.
    Developer.find(:all, :joins => ', projects').each { |d| assert d.readonly? }
    Developer.find(:all, :joins => ', projects', :readonly => false).each { |d| assert !d.readonly? }
  end


  def test_habtm_find_readonly
    dev = Developer.find(1)
    assert !dev.projects.empty?
    assert dev.projects.all?(&:readonly?)
    assert dev.projects.find(:all).all?(&:readonly?)
    assert dev.projects.find(:all, :readonly => true).all?(&:readonly?)
  end

  def test_has_many_find_readonly
    post = Post.find(1)
    assert !post.comments.empty?
    assert !post.comments.any?(&:readonly?)
    assert !post.comments.find(:all).any?(&:readonly?)
    assert post.comments.find(:all, :readonly => true).all?(&:readonly?)
  end

  def test_has_many_with_through_is_not_implicitly_marked_readonly
    assert people = Post.find(1).people
    assert !people.any?(&:readonly?)
  end

  def test_readonly_scoping
    Post.with_scope(:find => { :conditions => '1=1' }) do 
      assert !Post.find(1).readonly?
      assert Post.find(1, :readonly => true).readonly?
      assert !Post.find(1, :readonly => false).readonly?
    end

    Post.with_scope(:find => { :joins => '   ' }) do 
      assert !Post.find(1).readonly?
      assert Post.find(1, :readonly => true).readonly?
      assert !Post.find(1, :readonly => false).readonly?
    end

    # Oracle barfs on this because the join includes unqualified and
    # conflicting column names
    unless current_adapter?(:OracleAdapter)
      Post.with_scope(:find => { :joins => ', developers' }) do 
        assert Post.find(1).readonly?
        assert Post.find(1, :readonly => true).readonly?
        assert !Post.find(1, :readonly => false).readonly?
      end
    end

    Post.with_scope(:find => { :readonly => true }) do
      assert Post.find(1).readonly?
      assert Post.find(1, :readonly => true).readonly?
      assert !Post.find(1, :readonly => false).readonly?
    end
  end

  def test_association_collection_method_missing_scoping_not_readonly
    assert !Developer.find(1).projects.foo.readonly?
    assert !Post.find(1).comments.foo.readonly?
  end
end
require 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/customer'
require 'fixtures/company'
require 'fixtures/company_in_module'
require 'fixtures/subscriber'

class ReflectionTest < Test::Unit::TestCase
  fixtures :topics, :customers, :companies, :subscribers

  def setup
    @first = Topic.find(1)
  end

  def test_column_null_not_null
    subscriber = Subscriber.find(:first)
    assert subscriber.column_for_attribute("name").null
    assert !subscriber.column_for_attribute("nick").null
  end

  def test_read_attribute_names
    assert_equal(
      %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id type ).sort,
      @first.attribute_names
    )
  end

  def test_columns
    assert_equal 12, Topic.columns.length
  end

  def test_columns_are_returned_in_the_order_they_were_declared
    column_names = Topic.columns.map { |column| column.name }
    assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id type), column_names
  end

  def test_content_columns
    content_columns        = Topic.content_columns
    content_column_names   = content_columns.map {|column| column.name}
    assert_equal 8, content_columns.length
    assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved).sort, content_column_names.sort
  end

  def test_column_string_type_and_limit
    assert_equal :string, @first.column_for_attribute("title").type
    assert_equal 255, @first.column_for_attribute("title").limit
  end

  def test_human_name_for_column
    assert_equal "Author name", @first.column_for_attribute("author_name").human_name
  end

  def test_integer_columns
    assert_equal :integer, @first.column_for_attribute("id").type
  end

  def test_aggregation_reflection
    reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new(
      :composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
    )

    reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new(
      :composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
    )

    reflection_for_gps_location = ActiveRecord::Reflection::AggregateReflection.new(
      :composed_of, :gps_location, { }, Customer
    )

    assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
    assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
    assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)

    assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)

    assert_equal Address, Customer.reflect_on_aggregation(:address).klass
    
    assert_equal Money, Customer.reflect_on_aggregation(:balance).klass
  end

  def test_has_many_reflection
    reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)

    assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)

    assert_equal Client, Firm.reflect_on_association(:clients).klass
    assert_equal 'companies', Firm.reflect_on_association(:clients).table_name

    assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass
    assert_equal 'companies', Firm.reflect_on_association(:clients_of_firm).table_name
  end

  def test_has_one_reflection
    reflection_for_account = ActiveRecord::Reflection::AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
    assert_equal reflection_for_account, Firm.reflect_on_association(:account)

    assert_equal Account, Firm.reflect_on_association(:account).klass
    assert_equal 'accounts', Firm.reflect_on_association(:account).table_name
  end

  def test_association_reflection_in_modules
    assert_reflection MyApplication::Business::Firm,
      :clients_of_firm,
      :klass      => MyApplication::Business::Client,
      :class_name => 'Client',
      :table_name => 'companies'

    assert_reflection MyApplication::Billing::Account,
      :firm,
      :klass      => MyApplication::Business::Firm,
      :class_name => 'MyApplication::Business::Firm',
      :table_name => 'companies'

    assert_reflection MyApplication::Billing::Account,
      :qualified_billing_firm,
      :klass      => MyApplication::Billing::Firm,
      :class_name => 'MyApplication::Billing::Firm',
      :table_name => 'companies'

    assert_reflection MyApplication::Billing::Account,
      :unqualified_billing_firm,
      :klass      => MyApplication::Billing::Firm,
      :class_name => 'Firm',
      :table_name => 'companies'

    assert_reflection MyApplication::Billing::Account,
      :nested_qualified_billing_firm,
      :klass      => MyApplication::Billing::Nested::Firm,
      :class_name => 'MyApplication::Billing::Nested::Firm',
      :table_name => 'companies'

    assert_reflection MyApplication::Billing::Account,
      :nested_unqualified_billing_firm,
      :klass      => MyApplication::Billing::Nested::Firm,
      :class_name => 'Nested::Firm',
      :table_name => 'companies'
  end
  
  def test_reflection_of_all_associations
    assert_equal 13, Firm.reflect_on_all_associations.size
    assert_equal 11, Firm.reflect_on_all_associations(:has_many).size
    assert_equal 2, Firm.reflect_on_all_associations(:has_one).size
    assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
  end

  private
    def assert_reflection(klass, association, options)
      assert reflection = klass.reflect_on_association(association)
      options.each do |method, value|
        assert_equal(value, reflection.send(method))
      end
    end
end
require 'abstract_unit'
require "#{File.dirname(__FILE__)}/../lib/active_record/schema_dumper"
require 'stringio'

if ActiveRecord::Base.connection.respond_to?(:tables)

  class SchemaDumperTest < Test::Unit::TestCase
    def test_schema_dump
      stream = StringIO.new
      ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
      output = stream.string

      assert_match %r{create_table "accounts"}, output
      assert_match %r{create_table "authors"}, output
      assert_no_match %r{create_table "schema_info"}, output
    end
    
    def test_schema_dump_includes_not_null_columns
      stream = StringIO.new
      
      ActiveRecord::SchemaDumper.ignore_tables = [/^[^s]/]
      ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
      output = stream.string
      assert_match %r{:null => false}, output
    end

    def test_schema_dump_with_string_ignored_table
      stream = StringIO.new
      
      ActiveRecord::SchemaDumper.ignore_tables = ['accounts']      
      ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
      output = stream.string
      assert_no_match %r{create_table "accounts"}, output
      assert_match %r{create_table "authors"}, output
      assert_no_match %r{create_table "schema_info"}, output
    end


    def test_schema_dump_with_regexp_ignored_table
      stream = StringIO.new
      
      ActiveRecord::SchemaDumper.ignore_tables = [/^account/]      
      ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
      output = stream.string
      assert_no_match %r{create_table "accounts"}, output
      assert_match %r{create_table "authors"}, output
      assert_no_match %r{create_table "schema_info"}, output
    end


    def test_schema_dump_illegal_ignored_table_value
      stream = StringIO.new      
      ActiveRecord::SchemaDumper.ignore_tables = [5]      
      assert_raise(StandardError) do
        ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
      end
    end
  end

end
require 'abstract_unit'

class SchemaTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false

  SCHEMA_NAME = 'test_schema'
  TABLE_NAME = 'things'
  COLUMNS = [
    'id integer',
    'name character varying(50)',
    'moment timestamp without time zone default now()'
  ]

  def setup
    @connection = ActiveRecord::Base.connection
    @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
  end

  def teardown
    @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
  end

  def test_with_schema_prefixed_table_name
    assert_nothing_raised do
      assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}")
    end
  end

  def test_with_schema_search_path
    assert_nothing_raised do
      with_schema_search_path(SCHEMA_NAME) do
        assert_equal COLUMNS, columns(TABLE_NAME)
      end
    end
  end

  def test_raise_on_unquoted_schema_name
    assert_raise(ActiveRecord::StatementInvalid) do
      with_schema_search_path '$user,public'
    end
  end

  def test_without_schema_search_path
    assert_raise(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) }
  end

  def test_ignore_nil_schema_search_path
    assert_nothing_raised { with_schema_search_path nil }
  end

  private
    def columns(table_name)
      @connection.send(:column_definitions, table_name).map do |name, type, default|
        "#{name} #{type}" + (default ? " default #{default}" : '')
      end
    end

    def with_schema_search_path(schema_search_path)
      @connection.schema_search_path = schema_search_path
      yield if block_given?
    ensure
      @connection.schema_search_path = "'$user', public"
    end
end
require 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/subject'

# confirm that synonyms work just like tables; in this case
# the "subjects" table in Oracle (defined in oci.sql) is just
# a synonym to the "topics" table

class TestOracleSynonym < Test::Unit::TestCase

  def test_oracle_synonym
    topic = Topic.new
    subject = Subject.new
    assert_equal(topic.attributes, subject.attributes)
  end
  
end
require 'abstract_unit'
require 'fixtures/topic'

class ThreadedConnectionsTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false

  fixtures :topics

  def setup
    @connection = ActiveRecord::Base.remove_connection
    @connections = []
    @allow_concurrency = ActiveRecord::Base.allow_concurrency
  end
  
  def teardown
    # clear the connection cache
    ActiveRecord::Base.send(:clear_all_cached_connections!)
    # set allow_concurrency to saved value
    ActiveRecord::Base.allow_concurrency = @allow_concurrency
    # reestablish old connection
    ActiveRecord::Base.establish_connection(@connection)
  end
  
  def gather_connections(use_threaded_connections)
    ActiveRecord::Base.allow_concurrency = use_threaded_connections
    ActiveRecord::Base.establish_connection(@connection)
    
    5.times do
      Thread.new do
        Topic.find :first
        @connections << ActiveRecord::Base.active_connections.values.first
      end.join
    end
  end

  def test_threaded_connections
    gather_connections(true)
    assert_equal @connections.uniq.length, 5
  end

  def test_unthreaded_connections
    gather_connections(false)
    assert_equal @connections.uniq.length, 1
  end
end
require 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/developer'

class TransactionTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false

  fixtures :topics, :developers

  def setup
    # sqlite does not seem to return these in the right order, so we sort them
    # explicitly for sqlite's sake. sqlite3 does fine.
    @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
  end

  def test_successful
    Topic.transaction do
      @first.approved  = true
      @second.approved = false
      @first.save
      @second.save
    end

    assert Topic.find(1).approved?, "First should have been approved"
    assert !Topic.find(2).approved?, "Second should have been unapproved"
  end

  def transaction_with_return
    Topic.transaction do
      @first.approved  = true
      @second.approved = false
      @first.save
      @second.save
      return
    end
  end

  def test_successful_with_return
    class << Topic.connection
      alias :real_commit_db_transaction :commit_db_transaction
      def commit_db_transaction
        $committed = true
        real_commit_db_transaction
      end
    end

    $committed = false
    transaction_with_return
    assert $committed

    assert Topic.find(1).approved?, "First should have been approved"
    assert !Topic.find(2).approved?, "Second should have been unapproved"
  ensure
    class << Topic.connection
      alias :commit_db_transaction :real_commit_db_transaction rescue nil
    end
  end

  def test_successful_with_instance_method
    @first.transaction do
      @first.approved  = true
      @second.approved = false
      @first.save
      @second.save
    end

    assert Topic.find(1).approved?, "First should have been approved"
    assert !Topic.find(2).approved?, "Second should have been unapproved"
  end

  def test_failing_on_exception
    begin
      Topic.transaction do
        @first.approved  = true
        @second.approved = false
        @first.save
        @second.save
        raise "Bad things!"
      end
    rescue
      # caught it
    end

    assert @first.approved?, "First should still be changed in the objects"
    assert !@second.approved?, "Second should still be changed in the objects"
    
    assert !Topic.find(1).approved?, "First shouldn't have been approved"
    assert Topic.find(2).approved?, "Second should still be approved"
  end
  
  def test_failing_with_object_rollback
    assert !@first.approved?, "First should be unapproved initially"

    begin
      Topic.transaction(@first, @second) do
        @first.approved  = true
        @second.approved = false
        @first.save
        @second.save
        raise "Bad things!"
      end
    rescue
      # caught it
    end
    
    assert !@first.approved?, "First shouldn't have been approved"
    assert @second.approved?, "Second should still be approved"
  end
  
  def test_callback_rollback_in_save
    add_exception_raising_after_save_callback_to_topic

    begin
      @first.approved = true
      @first.save
      flunk
    rescue => e
      assert_equal "Make the transaction rollback", e.message
      assert !Topic.find(1).approved?
    ensure
      remove_exception_raising_after_save_callback_to_topic
    end
  end

  def test_nested_explicit_transactions
    Topic.transaction do
      Topic.transaction do
        @first.approved  = true
        @second.approved = false
        @first.save
        @second.save
      end
    end

    assert Topic.find(1).approved?, "First should have been approved"
    assert !Topic.find(2).approved?, "Second should have been unapproved"
  end

  # This will cause transactions to overlap and fail unless they are
  # performed on separate database connections.
  def test_transaction_per_thread
    assert_nothing_raised do
      threads = (1..20).map do
        Thread.new do
          Topic.transaction do
            topic = Topic.find(:first)
            topic.approved = !topic.approved?
            topic.save!
            topic.approved = !topic.approved?
            topic.save!
          end
        end
      end

      threads.each { |t| t.join }
    end
  end

  # Test for dirty reads among simultaneous transactions.
  def test_transaction_isolation__read_committed
    # Should be invariant.
    original_salary = Developer.find(1).salary
    temporary_salary = 200000

    assert_nothing_raised do
      threads = (1..20).map do
        Thread.new do
          Developer.transaction do
            # Expect original salary.
            dev = Developer.find(1)
            assert_equal original_salary, dev.salary

            dev.salary = temporary_salary
            dev.save!

            # Expect temporary salary.
            dev = Developer.find(1)
            assert_equal temporary_salary, dev.salary

            dev.salary = original_salary
            dev.save!

            # Expect original salary.
            dev = Developer.find(1)
            assert_equal original_salary, dev.salary
          end
        end
      end

      # Keep our eyes peeled.
      threads << Thread.new do
        10.times do
          sleep 0.05
          Developer.transaction do
            # Always expect original salary.
            assert_equal original_salary, Developer.find(1).salary
          end
        end
      end

      threads.each { |t| t.join }
    end

    assert_equal original_salary, Developer.find(1).salary
  end


  private
    def add_exception_raising_after_save_callback_to_topic
      Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
    end
    
    def remove_exception_raising_after_save_callback_to_topic
      Topic.class_eval { remove_method :after_save }
    end
end
require 'abstract_unit'

class TestRecord < ActiveRecord::Base
end

class TestUnconnectedAdaptor < Test::Unit::TestCase
  self.use_transactional_fixtures = false

  def setup
    @underlying = ActiveRecord::Base.connection
    @specification = ActiveRecord::Base.remove_connection
  end

  def teardown
    @underlying = nil
    ActiveRecord::Base.establish_connection(@specification)
  end

  def test_connection_no_longer_established
    assert_raise(ActiveRecord::ConnectionNotEstablished) do
      TestRecord.find(1)
    end

    assert_raise(ActiveRecord::ConnectionNotEstablished) do
      TestRecord.new.save
    end
  end

  def test_underlying_adapter_no_longer_active
    assert !@underlying.active?, "Removed adapter should no longer be active"
  end
end
require 'abstract_unit'
require 'fixtures/topic'
require 'fixtures/reply'
require 'fixtures/developer'

# The following methods in Topic are used in test_conditional_validation_*
class Topic
  def condition_is_true
    return true
  end

  def condition_is_true_but_its_not
    return false
  end
end

class ValidationsTest < Test::Unit::TestCase
  fixtures :topics, :developers

  def setup
    Topic.write_inheritable_attribute(:validate, nil)
    Topic.write_inheritable_attribute(:validate_on_create, nil)
    Topic.write_inheritable_attribute(:validate_on_update, nil)
  end

  def test_single_field_validation
    r = Reply.new
    r.title = "There's no content!"
    assert !r.save, "A reply without content shouldn't be saveable"

    r.content = "Messa content!"
    assert r.save, "A reply with content should be saveable"
  end

  def test_single_attr_validation_and_error_msg
    r = Reply.new
    r.title = "There's no content!"
    r.save
    assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
    assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
    assert_equal 1, r.errors.count
  end

  def test_double_attr_validation_and_error_msg
    r = Reply.new
    assert !r.save

    assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
    assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"

    assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
    assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"

    assert_equal 2, r.errors.count
  end

  def test_error_on_create
    r = Reply.new
    r.title = "Wrong Create"
    assert !r.save
    assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
    assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
  end

  def test_error_on_update
    r = Reply.new
    r.title = "Bad"
    r.content = "Good"
    assert r.save, "First save should be successful"

    r.title = "Wrong Update"
    assert !r.save, "Second save should fail"

    assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
    assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error"
  end

  def test_invalid_record_exception
    assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
    assert_raises(ActiveRecord::RecordInvalid) { Reply.new.save! }

    begin
      r = Reply.new
      r.save!
      flunk
    rescue ActiveRecord::RecordInvalid => invalid
      assert_equal r, invalid.record
    end
  end

  def test_single_error_per_attr_iteration
    r = Reply.new
    r.save

    errors = []
    r.errors.each { |attr, msg| errors << [attr, msg] }

    assert errors.include?(["title", "Empty"])
    assert errors.include?(["content", "Empty"])
  end

  def test_multiple_errors_per_attr_iteration_with_full_error_composition
    r = Reply.new
    r.title   = "Wrong Create"
    r.content = "Mismatch"
    r.save

    errors = []
    r.errors.each_full { |error| errors << error }

    assert_equal "Title is Wrong Create", errors[0]
    assert_equal "Title is Content Mismatch", errors[1]
    assert_equal 2, r.errors.count
  end

  def test_errors_on_base
    r = Reply.new
    r.content = "Mismatch"
    r.save
    r.errors.add_to_base "Reply is not dignifying"

    errors = []
    r.errors.each_full { |error| errors << error }

    assert_equal "Reply is not dignifying", r.errors.on_base

    assert errors.include?("Title Empty")
    assert errors.include?("Reply is not dignifying")
    assert_equal 2, r.errors.count
  end

  def test_create_without_validation
    reply = Reply.new
    assert !reply.save
    assert reply.save(false)
  end

  def test_validates_each
    perform = true
    hits = 0
    Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
      if perform
        record.errors.add attr, 'gotcha'
        hits += 1
      end
    end
    t = Topic.new("title" => "valid", "content" => "whatever")
    assert !t.save
    assert_equal 4, hits
    assert_equal %w(gotcha gotcha), t.errors.on(:title)
    assert_equal %w(gotcha gotcha), t.errors.on(:content)
  ensure
    perform = false
  end

  def test_errors_on_boundary_breaking
    developer = Developer.new("name" => "xs")
    assert !developer.save
    assert_equal "is too short (minimum is 3 characters)", developer.errors.on("name")

    developer.name = "All too very long for this boundary, it really is"
    assert !developer.save
    assert_equal "is too long (maximum is 20 characters)", developer.errors.on("name")

    developer.name = "Just right"
    assert developer.save
  end

  def test_title_confirmation_no_confirm
    Topic.validates_confirmation_of(:title)

    t = Topic.create("title" => "We should not be confirmed")
    assert t.save
  end

  def test_title_confirmation
    Topic.validates_confirmation_of(:title)

    t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
    assert !t.save

    t.title_confirmation = "We should be confirmed"
    assert t.save
  end

  def test_terms_of_service_agreement_no_acceptance
    Topic.validates_acceptance_of(:terms_of_service, :on => :create)

    t = Topic.create("title" => "We should not be confirmed")
    assert t.save
  end

  def test_terms_of_service_agreement
    Topic.validates_acceptance_of(:terms_of_service, :on => :create)

    t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
    assert !t.save
    assert_equal "must be accepted", t.errors.on(:terms_of_service)

    t.terms_of_service = "1"
    assert t.save
  end


  def test_eula
    Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)

    t = Topic.create("title" => "We should be confirmed","eula" => "")
    assert !t.save
    assert_equal "must be abided", t.errors.on(:eula)

    t.eula = "1"
    assert t.save
  end

  def test_terms_of_service_agreement_with_accept_value
    Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")

    t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
    assert !t.save
    assert_equal "must be accepted", t.errors.on(:terms_of_service)

    t.terms_of_service = "I agree."
    assert t.save
  end

  def test_validate_presences
    Topic.validates_presence_of(:title, :content)

    t = Topic.create
    assert !t.save
    assert_equal "can't be blank", t.errors.on(:title)
    assert_equal "can't be blank", t.errors.on(:content)

    t.title = "something"
    t.content  = "   "

    assert !t.save
    assert_equal "can't be blank", t.errors.on(:content)

    t.content = "like stuff"

    assert t.save
  end

  def test_validate_uniqueness
    Topic.validates_uniqueness_of(:title)

    t = Topic.new("title" => "I'm unique!")
    assert t.save, "Should save t as unique"

    t.content = "Remaining unique"
    assert t.save, "Should still save t as unique"

    t2 = Topic.new("title" => "I'm unique!")
    assert !t2.valid?, "Shouldn't be valid"
    assert !t2.save, "Shouldn't save t2 as unique"
    assert_equal "has already been taken", t2.errors.on(:title)

    t2.title = "Now Im really also unique"
    assert t2.save, "Should now save t2 as unique"
  end

  def test_validate_uniqueness_with_scope
    Reply.validates_uniqueness_of(:content, :scope => "parent_id")

    t = Topic.create("title" => "I'm unique!")

    r1 = t.replies.create "title" => "r1", "content" => "hello world"
    assert r1.valid?, "Saving r1"

    r2 = t.replies.create "title" => "r2", "content" => "hello world"
    assert !r2.valid?, "Saving r2 first time"

    r2.content = "something else"
    assert r2.save, "Saving r2 second time"

    t2 = Topic.create("title" => "I'm unique too!")
    r3 = t2.replies.create "title" => "r3", "content" => "hello world"
    assert r3.valid?, "Saving r3"
  end

  def test_validate_uniqueness_with_scope_array
    Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])

    t = Topic.create("title" => "The earth is actually flat!")
 
    r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
    assert r1.valid?, "Saving r1"
    
    r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
    assert !r2.valid?, "Saving r2. Double reply by same author." 
    
    r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
    assert r2.save, "Saving r2 the second time." 
    
    r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
    assert !r3.valid?, "Saving r3"
    
    r3.author_name = "jj"
    assert r3.save, "Saving r3 the second time."
    
    r3.author_name = "jeremy"
    assert !r3.save, "Saving r3 the third time."
  end

  def test_validate_format
    Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")

    t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
    assert !t.valid?, "Shouldn't be valid"
    assert !t.save, "Shouldn't save because it's invalid"
    assert_equal "is bad data", t.errors.on(:title)
    assert_nil t.errors.on(:content)

    t.title = "Validation macros rule!"

    assert t.save
    assert_nil t.errors.on(:title)

    assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
  end
  
  # testing ticket #3142
  def test_validate_format_numeric
    Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")

    t = Topic.create("title" => "72x", "content" => "6789")
    assert !t.valid?, "Shouldn't be valid"
    assert !t.save, "Shouldn't save because it's invalid"
    assert_equal "is bad data", t.errors.on(:title)
    assert_nil t.errors.on(:content)

    t.title = "-11"
    assert !t.valid?, "Shouldn't be valid"

    t.title = "03"
    assert !t.valid?, "Shouldn't be valid"

    t.title = "z44"
    assert !t.valid?, "Shouldn't be valid"

    t.title = "5v7"
    assert !t.valid?, "Shouldn't be valid"

    t.title = "1"

    assert t.save
    assert_nil t.errors.on(:title)
  end

  def test_validates_inclusion_of
    Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )

    assert !Topic.create("title" => "a!", "content" => "abc").valid?
    assert !Topic.create("title" => "a b", "content" => "abc").valid?
    assert !Topic.create("title" => nil, "content" => "def").valid?

    t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
    assert t.valid?
    t.title = "uhoh"
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "is not included in the list", t.errors["title"]

    assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
    assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }

    assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
    assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
    assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
  end

  def test_validates_inclusion_of_with_allow_nil
    Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )

    assert !Topic.create("title" => "a!", "content" => "abc").valid?
    assert !Topic.create("title" => "", "content" => "abc").valid?
    assert Topic.create("title" => nil, "content" => "abc").valid?
  end

  def test_numericality_with_allow_nil_and_getter_method
    Developer.validates_numericality_of( :salary, :allow_nil => true)
    developer = Developer.new("name" => "michael", "salary" => nil)
    developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
    assert developer.valid?
  end

  def test_validates_exclusion_of
    Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )

    assert Topic.create("title" => "something", "content" => "abc").valid?
    assert !Topic.create("title" => "monkey", "content" => "abc").valid?
  end

  def test_validates_length_of_using_minimum
    Topic.validates_length_of :title, :minimum => 5

    t = Topic.create("title" => "valid", "content" => "whatever")
    assert t.valid?

    t.title = "not"
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "is too short (minimum is 5 characters)", t.errors["title"]

    t.title = ""
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "is too short (minimum is 5 characters)", t.errors["title"]

    t.title = nil
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
  end

  def test_optionally_validates_length_of_using_minimum
    Topic.validates_length_of :title, :minimum => 5, :allow_nil => true

    t = Topic.create("title" => "valid", "content" => "whatever")
    assert t.valid?

    t.title = nil
    assert t.valid?
  end

  def test_validates_length_of_using_maximum
    Topic.validates_length_of :title, :maximum => 5

    t = Topic.create("title" => "valid", "content" => "whatever")
    assert t.valid?

    t.title = "notvalid"
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "is too long (maximum is 5 characters)", t.errors["title"]

    t.title = ""
    assert t.valid?

    t.title = nil
    assert !t.valid?
  end

  def test_optionally_validates_length_of_using_maximum
    Topic.validates_length_of :title, :maximum => 5, :allow_nil => true

    t = Topic.create("title" => "valid", "content" => "whatever")
    assert t.valid?

    t.title = nil
    assert t.valid?
  end

  def test_validates_length_of_using_within
    Topic.validates_length_of(:title, :content, :within => 3..5)

    t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
    assert !t.valid?
    assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
    assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)

    t.title = nil
    t.content = nil
    assert !t.valid?
    assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
    assert_equal "is too short (minimum is 3 characters)", t.errors.on(:content)

    t.title = "abe"
    t.content  = "mad"
    assert t.valid?
  end

  def test_optionally_validates_length_of_using_within
    Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true

    t = Topic.create('title' => 'abc', 'content' => 'abcd')
    assert t.valid?

    t.title = nil
    assert t.valid?
  end

  def test_optionally_validates_length_of_using_within_on_create
    Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: %d"

    t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
    assert !t.save
    assert t.errors.on(:title)
    assert_equal "my string is too long: 10", t.errors[:title]

    t.title = "butthisis"
    assert t.save

    t.title = "few"
    assert t.save

    t.content = "andthisislong"
    assert t.save

    t.content = t.title = "iamfine"
    assert t.save
  end

  def test_optionally_validates_length_of_using_within_on_update
    Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: %d"

    t = Topic.create("title" => "vali", "content" => "whatever")
    assert !t.save
    assert t.errors.on(:title)

    t.title = "not"
    assert !t.save
    assert t.errors.on(:title)
    assert_equal "my string is too short: 5", t.errors[:title]

    t.title = "valid"
    t.content = "andthisistoolong"
    assert !t.save
    assert t.errors.on(:content)

    t.content = "iamfine"
    assert t.save
  end

  def test_validates_length_of_using_is
    Topic.validates_length_of :title, :is => 5

    t = Topic.create("title" => "valid", "content" => "whatever")
    assert t.valid?

    t.title = "notvalid"
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]

    t.title = ""
    assert !t.valid?

    t.title = nil
    assert !t.valid?
  end

  def test_optionally_validates_length_of_using_is
    Topic.validates_length_of :title, :is => 5, :allow_nil => true

    t = Topic.create("title" => "valid", "content" => "whatever")
    assert t.valid?

    t.title = nil
    assert t.valid?
  end

  def test_validates_length_of_using_bignum
    bigmin = 2 ** 30
    bigmax = 2 ** 32
    bigrange = bigmin...bigmax
    assert_nothing_raised do
      Topic.validates_length_of :title, :is => bigmin + 5
      Topic.validates_length_of :title, :within => bigrange
      Topic.validates_length_of :title, :in => bigrange
      Topic.validates_length_of :title, :minimum => bigmin
      Topic.validates_length_of :title, :maximum => bigmax
    end
  end

  def test_validates_length_with_globaly_modified_error_message
    ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d'
    Topic.validates_length_of :title, :minimum => 10
    t = Topic.create(:title => 'too short')
    assert !t.valid?

    assert_equal 'tu est trops petit hombre 10', t.errors['title']
  end

  def test_validates_size_of_association
    assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
    t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
    assert !t.save
    assert t.errors.on(:replies)
    t.replies.create('title' => 'areply', 'content' => 'whateveragain')
    assert t.valid?
  end

  def test_validates_length_of_nasty_params
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
    assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
  end

  def test_validates_length_of_custom_errors_for_minimum_with_message
    Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d" )
    t = Topic.create("title" => "uhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "boo 5", t.errors["title"]
  end

  def test_validates_length_of_custom_errors_for_minimum_with_too_short
    Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d" )
    t = Topic.create("title" => "uhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "hoo 5", t.errors["title"]
  end

  def test_validates_length_of_custom_errors_for_maximum_with_message
    Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d" )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "boo 5", t.errors["title"]
  end

  def test_validates_length_of_custom_errors_for_maximum_with_too_long
    Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d" )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "hoo 5", t.errors["title"]
  end

  def test_validates_length_of_custom_errors_for_is_with_message
    Topic.validates_length_of( :title, :is=>5, :message=>"boo %d" )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "boo 5", t.errors["title"]
  end

  def test_validates_length_of_custom_errors_for_is_with_wrong_length
    Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d" )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "hoo 5", t.errors["title"]
  end

  def kcode_scope(kcode)
    orig_kcode = $KCODE
    $KCODE = kcode
    begin
      yield
    ensure
      $KCODE = orig_kcode
    end
  end

  def test_validates_length_of_using_minimum_utf8
    kcode_scope('UTF8') do
      Topic.validates_length_of :title, :minimum => 5

      t = Topic.create("title" => "一二三四五", "content" => "whatever")
      assert t.valid?

      t.title = "一二三四"
      assert !t.valid?
      assert t.errors.on(:title)
      assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
    end
  end

  def test_validates_length_of_using_maximum_utf8
    kcode_scope('UTF8') do
      Topic.validates_length_of :title, :maximum => 5

      t = Topic.create("title" => "一二三四五", "content" => "whatever")
      assert t.valid?
      
      t.title = "一二34五六"
      assert !t.valid?
      assert t.errors.on(:title)
      assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
    end
  end

  def test_validates_length_of_using_within_utf8
    kcode_scope('UTF8') do
      Topic.validates_length_of(:title, :content, :within => 3..5)

      t = Topic.new("title" => "一二", "content" => "12三四五六七")
      assert !t.valid?
      assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
      assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
      t.title = "一二三"
      t.content  = "12三"
      assert t.valid?
    end
  end

  def test_optionally_validates_length_of_using_within_utf8
    kcode_scope('UTF8') do
      Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true

      t = Topic.create('title' => '一二三', 'content' => '一二三四五')
      assert t.valid?

      t.title = nil
      assert t.valid?
    end
  end

  def test_optionally_validates_length_of_using_within_on_create_utf8
    kcode_scope('UTF8') do
      Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "長すぎます: %d"

      t = Topic.create("title" => "一二三四五六七八九十A", "content" => "whatever")
      assert !t.save
      assert t.errors.on(:title)
      assert_equal "長すぎます: 10", t.errors[:title]
      
      t.title = "一二三四五六七八九"
      assert t.save
      
      t.title = "一二3"
      assert t.save
      
      t.content = "一二三四五六七八九十"
      assert t.save
      
      t.content = t.title = "一二三四五六"
      assert t.save
    end
  end

  def test_optionally_validates_length_of_using_within_on_update_utf8
    kcode_scope('UTF8') do    
      Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "短すぎます: %d"

      t = Topic.create("title" => "一二三4", "content" => "whatever")
      assert !t.save
      assert t.errors.on(:title)
      
      t.title = "1二三4"
      assert !t.save
      assert t.errors.on(:title)
      assert_equal "短すぎます: 5", t.errors[:title]
      
      t.title = "valid"
      t.content = "一二三四五六七八九十A"
      assert !t.save
      assert t.errors.on(:content)
      
      t.content = "一二345"
      assert t.save
    end
  end

  def test_validates_length_of_using_is_utf8
    kcode_scope('UTF8') do
      Topic.validates_length_of :title, :is => 5

      t = Topic.create("title" => "一二345", "content" => "whatever")
      assert t.valid?

      t.title = "一二345六"
      assert !t.valid?
      assert t.errors.on(:title)
      assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
    end
  end

  def test_validates_size_of_association_utf8
    kcode_scope('UTF8') do
      assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
      t = Topic.new('title' => 'あいうえお', 'content' => 'かきくけこ')
      assert !t.save
      assert t.errors.on(:replies)
      t.replies.create('title' => 'あいうえお', 'content' => 'かきくけこ')
      assert t.valid?
    end
  end

  def test_validates_associated_many
    Topic.validates_associated( :replies )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    t.replies << [r = Reply.create("title" => "A reply"), r2 = Reply.create("title" => "Another reply")]
    assert !t.valid?
    assert t.errors.on(:replies)
    assert_equal 1, r.errors.count  # make sure all associated objects have been validated
    assert_equal 1, r2.errors.count
    r.content = r2.content = "non-empty"
    assert t.valid?
  end

  def test_validates_associated_one
    Reply.validates_associated( :topic )
    Topic.validates_presence_of( :content )
    r = Reply.create("title" => "A reply", "content" => "with content!")
    r.topic = Topic.create("title" => "uhohuhoh")
    assert !r.valid?
    assert r.errors.on(:topic)
    r.topic.content = "non-empty"
    assert r.valid?
  end

  def test_validate_block
    Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
    t = Topic.create("title" => "Title", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "will never be valid", t.errors["title"]
  end

  def test_invalid_validator
    Topic.validate 3
    assert_raise(ActiveRecord::ActiveRecordError) { t = Topic.create }
  end

  def test_throw_away_typing
    d = Developer.create "name" => "David", "salary" => "100,000"
    assert !d.valid?
    assert_equal 100, d.salary
    assert_equal "100,000", d.salary_before_type_cast
  end

  def test_validates_acceptance_of_with_custom_error_using_quotes
    Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.salary = "0"
    assert !d.valid?
    assert_equal d.errors.on(:salary).first, "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_confirmation_of_with_custom_error_using_quotes
    Developer.validates_confirmation_of :name, :message=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.name = "John"
    d.name_confirmation = "Johnny"
    assert !d.valid?
    assert_equal d.errors.on(:name), "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_format_of_with_custom_error_using_quotes
    Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.name = "John 32"
    assert !d.valid?
    assert_equal d.errors.on(:name), "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_inclusion_of_with_custom_error_using_quotes
    Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.salary = "90,000"
    assert !d.valid?
    assert_equal d.errors.on(:salary).first, "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_length_of_with_custom_too_long_using_quotes
    Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.name = "Jeffrey"
    assert !d.valid?
    assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_length_of_with_custom_too_short_using_quotes
    Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.name = "Joe"
    assert !d.valid?
    assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_length_of_with_custom_message_using_quotes
    Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.name = "Joe"
    assert !d.valid?
    assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_presence_of_with_custom_message_using_quotes
    Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.name = "Joe"
    assert !d.valid?
    assert_equal d.errors.on(:non_existent), "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_uniqueness_of_with_custom_message_using_quotes
    Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
    d = Developer.new
    d.name = "David"
    assert !d.valid?
    assert_equal d.errors.on(:name).first, "This string contains 'single' and \"double\" quotes"
  end

  def test_validates_associated_with_custom_message_using_quotes
    Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
    Topic.validates_presence_of :content
    r = Reply.create("title" => "A reply", "content" => "with content!")
    r.topic = Topic.create("title" => "uhohuhoh")
    assert !r.valid?
    assert_equal r.errors.on(:topic).first, "This string contains 'single' and \"double\" quotes"
  end

  def test_conditional_validation_using_method_true
    # When the method returns true
    Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "hoo 5", t.errors["title"]
  end

  def test_conditional_validation_using_method_false
    # When the method returns false
    Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert t.valid?
    assert !t.errors.on(:title)      
  end

  def test_conditional_validation_using_string_true
    # When the evaluated string returns true
    Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "a = 1; a == 1" )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "hoo 5", t.errors["title"]
  end

  def test_conditional_validation_using_string_false
    # When the evaluated string returns false
    Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "false")
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert t.valid?
    assert !t.errors.on(:title)
  end

  def test_conditional_validation_using_block_true
    # When the block returns true
    Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
      :if => Proc.new { |r| r.content.size > 4 } )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert !t.valid?
    assert t.errors.on(:title)
    assert_equal "hoo 5", t.errors["title"]
  end

  def test_conditional_validation_using_block_false
    # When the block returns false
    Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
      :if => Proc.new { |r| r.title != "uhohuhoh"} )
    t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
    assert t.valid?
    assert !t.errors.on(:title)
  end

  def test_validates_associated_missing
    Reply.validates_presence_of(:topic)
    r = Reply.create("title" => "A reply", "content" => "with content!")
    assert !r.valid?
    assert r.errors.on(:topic)
    
    r.topic = Topic.find :first
    assert r.valid?
  end
end


class ValidatesNumericalityTest
  NIL = [nil, "", " ", " \t \r \n"]
  FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1)
  INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
  FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
  INTEGERS = [0, 10, -10] + INTEGER_STRINGS
  JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12"]

  def setup
    Topic.write_inheritable_attribute(:validate, nil)
    Topic.write_inheritable_attribute(:validate_on_create, nil)
    Topic.write_inheritable_attribute(:validate_on_update, nil)
  end

  def test_default_validates_numericality_of
    Topic.validates_numericality_of :approved

    invalid!(NIL + JUNK)
    valid!(FLOATS + INTEGERS)
  end

  def test_validates_numericality_of_with_nil_allowed
    Topic.validates_numericality_of :approved, :allow_nil => true

    invalid!(JUNK)
    valid!(NIL + FLOATS + INTEGERS)
  end

  def test_validates_numericality_of_with_integer_only
    Topic.validates_numericality_of :approved, :only_integer => true

    invalid!(NIL + JUNK + FLOATS)
    valid!(INTEGERS)
  end

  def test_validates_numericality_of_with_integer_only_and_nil_allowed
    Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true

    invalid!(JUNK + FLOATS)
    valid!(NIL + INTEGERS)
  end

  private
    def invalid!(values)
      values.each do |value|
        topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
        assert !topic.valid?, "#{value} not rejected as a number"
        assert topic.errors.on(:approved)
      end
    end

    def valid!(values)
      values.each do |value|
        topic = Topic.create("title" => "numeric test", "content" => "whatever", "approved" => value)
        assert topic.valid?, "#{value} not accepted as a number"
      end
    end
end
begin
  require 'simplecc'
rescue LoadError
  class Continuation # :nodoc: # for RDoc
  end
  def Continuation.create(*args, &block) # :nodoc:
    cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
    result ||= args
    return *[cc, *result]
  end
end

class Binding; end # for RDoc
# This method returns the binding of the method that called your
# method. It will raise an Exception when you're not inside a method.
#
# It's used like this:
#   def inc_counter(amount = 1)
#     Binding.of_caller do |binding|
#       # Create a lambda that will increase the variable 'counter'
#       # in the caller of this method when called.
#       inc = eval("lambda { |arg| counter += arg }", binding)
#       # We can refer to amount from inside this block safely.
#       inc.call(amount)
#     end
#     # No other statements can go here. Put them inside the block.
#   end
#   counter = 0
#   2.times { inc_counter }
#   counter # => 2
#
# Binding.of_caller must be the last statement in the method.
# This means that you will have to put everything you want to
# do after the call to Binding.of_caller into the block of it.
# This should be no problem however, because Ruby has closures.
# If you don't do this an Exception will be raised. Because of
# the way that Binding.of_caller is implemented it has to be
# done this way.
def Binding.of_caller(&block)
  old_critical = Thread.critical
  Thread.critical = true
  count = 0
  cc, result, error, extra_data = Continuation.create(nil, nil)
  error.call if error

  tracer = lambda do |*args|
    type, context, extra_data = args[0], args[4], args
    if type == "return"
      count += 1
      # First this method and then calling one will return --
      # the trace event of the second event gets the context
      # of the method which called the method that called this
      # method.
      if count == 2
        # It would be nice if we could restore the trace_func
        # that was set before we swapped in our own one, but
        # this is impossible without overloading set_trace_func
        # in current Ruby.
        set_trace_func(nil)
        cc.call(eval("binding", context), nil, extra_data)
      end
    elsif type == "line" then
      nil
    elsif type == "c-return" and extra_data[3] == :set_trace_func then
      nil
    else
      set_trace_func(nil)
      error_msg = "Binding.of_caller used in non-method context or " +
        "trailing statements of method using it aren't in the block."
      cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
    end
  end

  unless result
    set_trace_func(tracer)
    return nil
  else
    Thread.critical = old_critical
    case block.arity
      when 1 then yield(result)
      else yield(result, extra_data)        
    end
  end
end
# The Breakpoint library provides the convenience of
# being able to inspect and modify state, diagnose
# bugs all via IRB by simply setting breakpoints in
# your applications by the call of a method.
#
# This library was written and is supported by me,
# Florian Gross. I can be reached at flgr@ccan.de
# and enjoy getting feedback about my libraries.
#
# The whole library (including breakpoint_client.rb
# and binding_of_caller.rb) is licensed under the
# same license that Ruby uses. (Which is currently
# either the GNU General Public License or a custom
# one that allows for commercial usage.) If you for
# some good reason need to use this under another
# license please contact me.

require 'irb'
require File.dirname(__FILE__) + '/binding_of_caller' unless defined? Binding.of_caller
require 'drb'
require 'drb/acl'

module Breakpoint
  id = %q$Id: breakpoint.rb 92 2005-02-04 22:35:53Z flgr $
  Version = id.split(" ")[2].to_i

  extend self

  # This will pop up an interactive ruby session at a
  # pre-defined break point in a Ruby application. In
  # this session you can examine the environment of
  # the break point.
  #
  # You can get a list of variables in the context using
  # local_variables via +local_variables+. You can then
  # examine their values by typing their names.
  #
  # You can have a look at the call stack via +caller+.
  #
  # The source code around the location where the breakpoint
  # was executed can be examined via +source_lines+. Its
  # argument specifies how much lines of context to display.
  # The default amount of context is 5 lines. Note that
  # the call to +source_lines+ can raise an exception when
  # it isn't able to read in the source code.
  #
  # breakpoints can also return a value. They will execute
  # a supplied block for getting a default return value.
  # A custom value can be returned from the session by doing
  # +throw(:debug_return, value)+.
  #
  # You can also give names to break points which will be
  # used in the message that is displayed upon execution 
  # of them.
  #
  # Here's a sample of how breakpoints should be placed:
  #
  #   class Person
  #     def initialize(name, age)
  #       @name, @age = name, age
  #       breakpoint("Person#initialize")
  #     end
  #
  #     attr_reader :age
  #     def name
  #       breakpoint("Person#name") { @name }
  #     end
  #   end
  #
  #   person = Person.new("Random Person", 23)
  #   puts "Name: #{person.name}"
  #
  # And here is a sample debug session:
  #
  #   Executing break point "Person#initialize" at file.rb:4 in `initialize'
  #   irb(#<Person:0x292fbe8>):001:0> local_variables
  #   => ["name", "age", "_", "__"]
  #   irb(#<Person:0x292fbe8>):002:0> [name, age]
  #   => ["Random Person", 23]
  #   irb(#<Person:0x292fbe8>):003:0> [@name, @age]
  #   => ["Random Person", 23]
  #   irb(#<Person:0x292fbe8>):004:0> self
  #   => #<Person:0x292fbe8 @age=23, @name="Random Person">
  #   irb(#<Person:0x292fbe8>):005:0> @age += 1; self
  #   => #<Person:0x292fbe8 @age=24, @name="Random Person">
  #   irb(#<Person:0x292fbe8>):006:0> exit
  #   Executing break point "Person#name" at file.rb:9 in `name'
  #   irb(#<Person:0x292fbe8>):001:0> throw(:debug_return, "Overriden name")
  #   Name: Overriden name
  #
  # Breakpoint sessions will automatically have a few
  # convenience methods available. See Breakpoint::CommandBundle
  # for a list of them.
  #
  # Breakpoints can also be used remotely over sockets.
  # This is implemented by running part of the IRB session
  # in the application and part of it in a special client.
  # You have to call Breakpoint.activate_drb to enable
  # support for remote breakpoints and then run
  # breakpoint_client.rb which is distributed with this
  # library. See the documentation of Breakpoint.activate_drb
  # for details.
  def breakpoint(id = nil, context = nil, &block)
    callstack = caller
    callstack.slice!(0, 3) if callstack.first["breakpoint"]
    file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures

    message = "Executing break point " + (id ? "#{id.inspect} " : "") +
              "at #{file}:#{line}" + (method ? " in `#{method}'" : "")

    if context then
      return handle_breakpoint(context, message, file, line, &block)
    end

    Binding.of_caller do |binding_context|
      handle_breakpoint(binding_context, message, file, line, &block)
    end
  end

  module CommandBundle #:nodoc:
    # Proxy to a Breakpoint client. Lets you directly execute code
    # in the context of the client.
    class Client #:nodoc:
      def initialize(eval_handler) # :nodoc:
        eval_handler.untaint
        @eval_handler = eval_handler
      end

      instance_methods.each do |method|
        next if method[/^__.+__$/]
        undef_method method
      end

      # Executes the specified code at the client.
      def eval(code)
        @eval_handler.call(code)
      end

      # Will execute the specified statement at the client.
      def method_missing(method, *args, &block)
        if args.empty? and not block
          result = eval "#{method}"
        else
          # This is a bit ugly. The alternative would be using an
          # eval context instead of an eval handler for executing
          # the code at the client. The problem with that approach
          # is that we would have to handle special expressions
          # like "self", "nil" or constants ourself which is hard.
          remote = eval %{
            result = lambda { |block, *args| #{method}(*args, &block) }
            def result.call_with_block(*args, &block)
              call(block, *args)
            end
            result
          }
          remote.call_with_block(*args, &block)
        end

        return result
      end
    end

    # Returns the source code surrounding the location where the
    # breakpoint was issued.
    def source_lines(context = 5, return_line_numbers = false)
      lines = File.readlines(@__bp_file).map { |line| line.chomp }

      break_line = @__bp_line
      start_line = [break_line - context, 1].max
      end_line = break_line + context

      result = lines[(start_line - 1) .. (end_line - 1)]

      if return_line_numbers then
        return [start_line, break_line, result]
      else
        return result
      end
    end

    # Lets an object that will forward method calls to the breakpoint
    # client. This is useful for outputting longer things at the client
    # and so on. You can for example do these things:
    #
    #   client.puts "Hello" # outputs "Hello" at client console
    #   # outputs "Hello" into the file temp.txt at the client
    #   client.File.open("temp.txt", "w") { |f| f.puts "Hello" } 
    def client()
      if Breakpoint.use_drb? then
        sleep(0.5) until Breakpoint.drb_service.eval_handler
        Client.new(Breakpoint.drb_service.eval_handler)
      else
        Client.new(lambda { |code| eval(code, TOPLEVEL_BINDING) })
      end
    end
  end

  def handle_breakpoint(context, message, file = "", line = "", &block) # :nodoc:
    catch(:debug_return) do |value|
      eval(%{
        @__bp_file = #{file.inspect}
        @__bp_line = #{line}
        extend Breakpoint::CommandBundle
        extend DRbUndumped if self
      }, context) rescue nil

      if not use_drb? then
        puts message
        IRB.start(nil, IRB::WorkSpace.new(context))
      else
        @drb_service.add_breakpoint(context, message)
      end

      block.call if block
    end
  end

  # These exceptions will be raised on failed asserts
  # if Breakpoint.asserts_cause_exceptions is set to
  # true.
  class FailedAssertError < RuntimeError #:nodoc:
  end

  # This asserts that the block evaluates to true.
  # If it doesn't evaluate to true a breakpoint will
  # automatically be created at that execution point.
  #
  # You can disable assert checking in production
  # code by setting Breakpoint.optimize_asserts to
  # true. (It will still be enabled when Ruby is run
  # via the -d argument.)
  #
  # Example:
  #   person_name = "Foobar"
  #   assert { not person_name.nil? }
  #
  # Note: If you want to use this method from an
  # unit test, you will have to call it by its full
  # name, Breakpoint.assert.
  def assert(context = nil, &condition)
    return if Breakpoint.optimize_asserts and not $DEBUG
    return if yield

    callstack = caller
    callstack.slice!(0, 3) if callstack.first["assert"]
    file, line, method = *callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures

    message = "Assert failed at #{file}:#{line}#{" in `#{method}'" if method}."

    if Breakpoint.asserts_cause_exceptions and not $DEBUG then
      raise(Breakpoint::FailedAssertError, message)
    end

    message += " Executing implicit breakpoint."

    if context then
      return handle_breakpoint(context, message, file, line)
    end

    Binding.of_caller do |context|
      handle_breakpoint(context, message, file, line)
    end
  end

  # Whether asserts should be ignored if not in debug mode.
  # Debug mode can be enabled by running ruby with the -d
  # switch or by setting $DEBUG to true.
  attr_accessor :optimize_asserts
  self.optimize_asserts = false

  # Whether an Exception should be raised on failed asserts
  # in non-$DEBUG code or not. By default this is disabled.
  attr_accessor :asserts_cause_exceptions
  self.asserts_cause_exceptions = false
  @use_drb = false

  attr_reader :drb_service # :nodoc:

  class DRbService # :nodoc:
    include DRbUndumped

    def initialize
      @handler = @eval_handler = @collision_handler = nil

      IRB.instance_eval { @CONF[:RC] = true }
      IRB.run_config
    end

    def collision
      sleep(0.5) until @collision_handler

      @collision_handler.untaint

      @collision_handler.call
    end

    def ping() end

    def add_breakpoint(context, message)
      workspace = IRB::WorkSpace.new(context)
      workspace.extend(DRbUndumped)

      sleep(0.5) until @handler

      @handler.untaint
      @handler.call(workspace, message)
    end

    attr_accessor :handler, :eval_handler, :collision_handler
  end

  # Will run Breakpoint in DRb mode. This will spawn a server
  # that can be attached to via the breakpoint-client command
  # whenever a breakpoint is executed. This is useful when you
  # are debugging CGI applications or other applications where
  # you can't access debug sessions via the standard input and
  # output of your application.
  #
  # You can specify an URI where the DRb server will run at.
  # This way you can specify the port the server runs on. The
  # default URI is druby://localhost:42531.
  #
  # Please note that breakpoints will be skipped silently in
  # case the DRb server can not spawned. (This can happen if
  # the port is already used by another instance of your
  # application on CGI or another application.)
  #
  # Also note that by default this will only allow access
  # from localhost. You can however specify a list of
  # allowed hosts or nil (to allow access from everywhere).
  # But that will still not protect you from somebody
  # reading the data as it goes through the net.
  #
  # A good approach for getting security and remote access
  # is setting up an SSH tunnel between the DRb service
  # and the client. This is usually done like this:
  #
  # $ ssh -L20000:127.0.0.1:20000 -R10000:127.0.0.1:10000 example.com
  # (This will connect port 20000 at the client side to port
  # 20000 at the server side, and port 10000 at the server
  # side to port 10000 at the client side.)
  #
  # After that do this on the server side: (the code being debugged)
  # Breakpoint.activate_drb("druby://127.0.0.1:20000", "localhost")
  #
  # And at the client side:
  # ruby breakpoint_client.rb -c druby://127.0.0.1:10000 -s druby://127.0.0.1:20000
  #
  # Running through such a SSH proxy will also let you use 
  # breakpoint.rb in case you are behind a firewall.
  #
  # Detailed information about running DRb through firewalls is
  # available at http://www.rubygarden.org/ruby?DrbTutorial
  def activate_drb(uri = nil, allowed_hosts = ['localhost', '127.0.0.1', '::1'],
    ignore_collisions = false)

    return false if @use_drb

    uri ||= 'druby://localhost:42531'

    if allowed_hosts then
      acl = ["deny", "all"]

      Array(allowed_hosts).each do |host|
        acl += ["allow", host]
      end

      DRb.install_acl(ACL.new(acl))
    end

    @use_drb = true
    @drb_service = DRbService.new
    did_collision = false
    begin
      @service = DRb.start_service(uri, @drb_service)
    rescue Errno::EADDRINUSE
      if ignore_collisions then
        nil
      else
        # The port is already occupied by another
        # Breakpoint service. We will try to tell
        # the old service that we want its port.
        # It will then forward that request to the
        # user and retry.
        unless did_collision then
          DRbObject.new(nil, uri).collision
          did_collision = true
        end
        sleep(10)
        retry
      end
    end

    return true
  end

  # Deactivates a running Breakpoint service.
  def deactivate_drb
    @service.stop_service unless @service.nil?
    @service = nil
    @use_drb = false
    @drb_service = nil
  end

  # Returns true when Breakpoints are used over DRb.
  # Breakpoint.activate_drb causes this to be true.
  def use_drb?
    @use_drb == true
  end
end

module IRB #:nodoc:
  class << self; remove_method :start; end
  def self.start(ap_path = nil, main_context = nil, workspace = nil)
    $0 = File::basename(ap_path, ".rb") if ap_path

    # suppress some warnings about redefined constants
    old_verbose, $VERBOSE = $VERBOSE, nil
    IRB.setup(ap_path)
    $VERBOSE = old_verbose

    if @CONF[:SCRIPT] then
      irb = Irb.new(main_context, @CONF[:SCRIPT])
    else
      irb = Irb.new(main_context)
    end

    if workspace then
      irb.context.workspace = workspace
    end

    @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
    @CONF[:MAIN_CONTEXT] = irb.context

    old_sigint = trap("SIGINT") do
      begin
        irb.signal_handle
      rescue RubyLex::TerminateLineInput
        # ignored
      end
    end
    
    catch(:IRB_EXIT) do
      irb.eval_input
    end
  ensure
    trap("SIGINT", old_sigint)
  end

  class << self
    alias :old_CurrentContext :CurrentContext
    remove_method :CurrentContext
  end
  def IRB.CurrentContext
    if old_CurrentContext.nil? and Breakpoint.use_drb? then
      result = Object.new
      def result.last_value; end
      return result
    else
      old_CurrentContext
    end
  end
  def IRB.parse_opts() end

  class Context #:nodoc:
    alias :old_evaluate :evaluate
    def evaluate(line, line_no)
      if line.chomp == "exit" then
        exit
      else
        old_evaluate(line, line_no)
      end
    end
  end

  class WorkSpace #:nodoc:
    alias :old_evaluate :evaluate

    def evaluate(*args)
      if Breakpoint.use_drb? then
        result = old_evaluate(*args)
        if args[0] != :no_proxy and
          not [true, false, nil].include?(result)
        then
          result.extend(DRbUndumped) rescue nil
        end
        return result
      else
        old_evaluate(*args)
      end
    end
  end

  module InputCompletor #:nodoc:
    def self.eval(code, context, *more)
      # Big hack, this assumes that InputCompletor
      # will only call eval() when it wants code
      # to be executed in the IRB context.
      IRB.conf[:MAIN_CONTEXT].workspace.evaluate(:no_proxy, code, *more)
    end
  end
end

module DRb # :nodoc:
  class DRbObject #:nodoc:
    undef :inspect if method_defined?(:inspect)
    undef :clone if method_defined?(:clone)
  end
end

# See Breakpoint.breakpoint
def breakpoint(id = nil, &block)
  Binding.of_caller do |context|
    Breakpoint.breakpoint(id, context, &block)
  end
end

# See Breakpoint.assert
def assert(&block)
  Binding.of_caller do |context|
    Breakpoint.assert(context, &block)
  end
end
module ActiveSupport
  module CachingTools #:nodoc:
    
    # Provide shortcuts to simply the creation of nested default hashes. This
    # pattern is useful, common practice, and unsightly when done manually.
    module HashCaching
      # Dynamically create a nested hash structure used to cache calls to +method_name+
      # The cache method is named +#{method_name}_cache+ unless :as => :alternate_name
      # is given.
      #
      # The hash structure is created using nested Hash.new. For example:
      # 
      #   def slow_method(a, b) a ** b end
      # 
      # can be cached using hash_cache :slow_method, which will define the method
      # slow_method_cache. We can then find the result of a ** b using:
      # 
      #   slow_method_cache[a][b]
      # 
      # The hash structure returned by slow_method_cache would look like this:
      # 
      #   Hash.new do |as, a|
      #     as[a] = Hash.new do |bs, b|
      #       bs[b] = slow_method(a, b)
      #     end
      #   end
      # 
      # The generated code is actually compressed onto a single line to maintain
      # sensible backtrace signatures.
      #
      def hash_cache(method_name, options = {})
        selector = options[:as] || "#{method_name}_cache"
        method = self.instance_method(method_name)
        
        args = []
        code = "def #{selector}(); @#{selector} ||= "
        
        (1..method.arity).each do |n|
          args << "v#{n}"
          code << "Hash.new {|h#{n}, v#{n}| h#{n}[v#{n}] = "
        end
        
        # Add the method call with arguments, followed by closing braces and end.
        code << "#{method_name}(#{args * ', '}) #{'}' * method.arity} end"
        
        # Extract the line number information from the caller. Exceptions arising
        # in the generated code should point to the +hash_cache :...+ line.
        if caller[0] && /^(.*):(\d+)$/ =~ caller[0]
          file, line_number = $1, $2.to_i
        else # We can't give good trackback info; fallback to this line:
          file, line_number = __FILE__, __LINE__
        end
        
        # We use eval rather than building proc's because it allows us to avoid
        # linking the Hash's to this method's binding. Experience has shown that
        # doing so can cause obtuse memory leaks.
        class_eval code, file, line_number
      end
    end
    
  end
end
require 'logger'
require File.dirname(__FILE__) + '/core_ext/class/attribute_accessors'

class Logger #:nodoc:
  cattr_accessor :silencer
  self.silencer = true

  # Silences the logger for the duration of the block.
  def silence(temporary_level = Logger::ERROR)
    if silencer
      begin
        old_logger_level, self.level = level, temporary_level
        yield self
      ensure
        self.level = old_logger_level
      end
    else
      yield self
    end
  end

  private
    alias old_format_message format_message

    # Ruby 1.8.3 transposed the msg and progname arguments to format_message.
    # We can't test RUBY_VERSION because some distributions don't keep Ruby
    # and its standard library in sync, leading to installations of Ruby 1.8.2
    # with Logger from 1.8.3 and vice versa.
    if method_defined?(:formatter=)
      def format_message(severity, timestamp, progname, msg)
        "#{msg}\n"
      end
    else
      def format_message(severity, timestamp, msg, progname)
        "#{msg}\n"
      end
    end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Array #:nodoc:
      module Conversions
        # Converts the array to comma-seperated sentence where the last element is joined by the connector word. Options:
        # * <tt>:connector</tt>: The word used to join the last element in arrays with more than two elements (default: "and")
        # * <tt>:skip_last_comma</tt>: Set to true to return "a, b, and c" instead of "a, b and c".
        def to_sentence(options = {})
          options.assert_valid_keys(:connector, :skip_last_comma)
          options.reverse_merge! :connector => 'and', :skip_last_comma => false
          
          case length
          	when 0
          		""
            when 1
              self[0]
            when 2
              "#{self[0]} #{options[:connector]} #{self[1]}"
            else
              "#{self[0...-1].join(', ')}#{options[:skip_last_comma] ? '' : ','} #{options[:connector]} #{self[-1]}"
          end
        end

        # When an array is given to url_for, it is converted to a slash separated string.
        def to_param
          join '/'
        end
        
        def to_xml(options = {})
          raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }

          options[:root]     ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
          options[:children] ||= options[:root].singularize
          options[:indent]   ||= 2
          options[:builder]  ||= Builder::XmlMarkup.new(:indent => options[:indent])

          root     = options.delete(:root)
          children = options.delete(:children)

          options[:builder].instruct! unless options.delete(:skip_instruct)
          options[:builder].tag!(root.to_s.dasherize) { each { |e| e.to_xml(options.merge({ :skip_instruct => true, :root => children })) } }
        end
      end
    end
  end
end
require File.dirname(__FILE__) + '/array/conversions'

class Array #:nodoc:
  include ActiveSupport::CoreExtensions::Array::Conversions

  # Iterate over an array in groups of a certain size, padding any remaining 
  # slots with specified value (<tt>nil</tt> by default).
  # 
  # E.g.
  # 
  #   %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
  #   ["1", "2", "3"]
  #   ["4", "5", "6"]
  #   ["7", nil, nil]
  def in_groups_of(number, fill_with = nil, &block)
    require 'enumerator'
    collection = dup
    collection << fill_with until collection.size.modulo(number).zero?
    collection.each_slice(number, &block)
  end
end
class Object #:nodoc:
  # "", "   ", nil, [], and {} are blank
  def blank?
    if respond_to?(:empty?) && respond_to?(:strip)
      empty? or strip.empty?
    elsif respond_to?(:empty?)
      empty?
    else
      !self
    end
  end
end

class NilClass #:nodoc:
  def blank?
    true
  end
end

class FalseClass #:nodoc:
  def blank?
    true
  end
end

class TrueClass #:nodoc:
  def blank?
    false
  end
end

class Array #:nodoc:
  alias_method :blank?, :empty?
end

class Hash #:nodoc:
  alias_method :blank?, :empty?
end

class String #:nodoc:
  def blank?
    empty? || strip.empty?
  end
end

class Numeric #:nodoc:
  def blank?
    false
  end
endmodule ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module CGI #:nodoc:
      module EscapeSkippingSlashes #:nodoc:
        def escape_skipping_slashes(str)
          str = str.join('/') if str.respond_to? :join
          str.gsub(/([^ \/a-zA-Z0-9_.-])/n) do
            "%#{$1.unpack('H2').first.upcase}"
          end.tr(' ', '+')
        end
      end
    end
  end
end
require File.dirname(__FILE__) + '/cgi/escape_skipping_slashes'

class CGI #:nodoc:
  extend(ActiveSupport::CoreExtensions::CGI::EscapeSkippingSlashes)
end
# Extends the class object with class and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
class Class # :nodoc:
  def cattr_reader(*syms)
    syms.flatten.each do |sym|
      class_eval(<<-EOS, __FILE__, __LINE__)
        unless defined? @@#{sym}
          @@#{sym} = nil
        end

        def self.#{sym}
          @@#{sym}
        end

        def #{sym}
          @@#{sym}
        end
      EOS
    end
  end

  def cattr_writer(*syms)
    syms.flatten.each do |sym|
      class_eval(<<-EOS, __FILE__, __LINE__)
        unless defined? @@#{sym}
          @@#{sym} = nil
        end

        def self.#{sym}=(obj)
          @@#{sym} = obj
        end

        def #{sym}=(obj)
          @@#{sym} = obj
        end
      EOS
    end
  end

  def cattr_accessor(*syms)
    cattr_reader(*syms)
    cattr_writer(*syms)
  end
end
# Retain for backward compatibility.  Methods are now included in Class.
module ClassInheritableAttributes # :nodoc:
end

# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
# to, for example, an array without those additions being shared with either their parent, siblings, or
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
class Class # :nodoc:
  def class_inheritable_reader(*syms)
    syms.each do |sym|
      class_eval <<-EOS
        def self.#{sym}
          read_inheritable_attribute(:#{sym})
        end

        def #{sym}
          self.class.#{sym}
        end
      EOS
    end
  end

  def class_inheritable_writer(*syms)
    syms.each do |sym|
      class_eval <<-EOS
        def self.#{sym}=(obj)
          write_inheritable_attribute(:#{sym}, obj)
        end

        def #{sym}=(obj)
          self.class.#{sym} = obj
        end
      EOS
    end
  end

  def class_inheritable_array_writer(*syms)
    syms.each do |sym|
      class_eval <<-EOS
        def self.#{sym}=(obj)
          write_inheritable_array(:#{sym}, obj)
        end

        def #{sym}=(obj)
          self.class.#{sym} = obj
        end
      EOS
    end
  end

  def class_inheritable_hash_writer(*syms)
    syms.each do |sym|
      class_eval <<-EOS
        def self.#{sym}=(obj)
          write_inheritable_hash(:#{sym}, obj)
        end

        def #{sym}=(obj)
          self.class.#{sym} = obj
        end
      EOS
    end
  end

  def class_inheritable_accessor(*syms)
    class_inheritable_reader(*syms)
    class_inheritable_writer(*syms)
  end

  def class_inheritable_array(*syms)
    class_inheritable_reader(*syms)
    class_inheritable_array_writer(*syms)
  end

  def class_inheritable_hash(*syms)
    class_inheritable_reader(*syms)
    class_inheritable_hash_writer(*syms)
  end

  def inheritable_attributes
    @inheritable_attributes ||= {}
  end
  
  def write_inheritable_attribute(key, value)
    inheritable_attributes[key] = value
  end
  
  def write_inheritable_array(key, elements)
    write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
    write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
  end

  def write_inheritable_hash(key, hash)
    write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
    write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
  end

  def read_inheritable_attribute(key)
    inheritable_attributes[key]
  end
  
  def reset_inheritable_attributes
    inheritable_attributes.clear
  end

  private 
    def inherited_with_inheritable_attributes(child)
      inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
      child.instance_variable_set('@inheritable_attributes', inheritable_attributes.dup)
    end

    alias inherited_without_inheritable_attributes inherited
    alias inherited inherited_with_inheritable_attributes
end
class Class #:nodoc:
  def remove_subclasses
    Object.remove_subclasses_of(self)
  end

  def subclasses
    Object.subclasses_of(self).map { |o| o.to_s }
  end

  def remove_class(*klasses)
    klasses.flatten.each do |klass|
      # Skip this class if there is nothing bound to this name
      next unless defined?(klass.name)
      
      basename = klass.to_s.split("::").last
      parent = klass.parent
      
      # Skip this class if it does not match the current one bound to this name
      next unless parent.const_defined?(basename) && klass = parent.const_get(basename)
      
      parent.send :remove_const, basename unless parent == klass
    end
  end
end
require File.dirname(__FILE__) + '/class/attribute_accessors'
require File.dirname(__FILE__) + '/class/inheritable_attributes'
require File.dirname(__FILE__) + '/class/removal'module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Date #:nodoc:
      # Getting dates in different convenient string representations and other objects
      module Conversions
        DATE_FORMATS = {
          :short => "%e %b",
          :long  => "%B %e, %Y"
        }
        
        def self.included(klass) #:nodoc:
          klass.send(:alias_method, :to_default_s, :to_s)
          klass.send(:alias_method, :to_s, :to_formatted_s)
        end
        
        def to_formatted_s(format = :default)
          DATE_FORMATS[format] ? strftime(DATE_FORMATS[format]).strip : to_default_s   
        end

        # To be able to keep Dates and Times interchangeable on conversions
        def to_date
          self
        end

        def to_time(form = :local)
          ::Time.send(form, year, month, day)
        end
        
        alias :xmlschema :to_s
      end
    end
  end
endrequire 'date'
require File.dirname(__FILE__) + '/date/conversions'

class Date#:nodoc:
  include ActiveSupport::CoreExtensions::Date::Conversions
end
module Enumerable #:nodoc:
  def first_match
    match = nil
    each do |items|
      break if match = yield(items)
    end
    match
  end

  # Collect an enumerable into sets, grouped by the result of a block. Useful,
  # for example, for grouping records by date.
  #
  # e.g. 
  #
  #   latest_transcripts.group_by(&:day).each do |day, transcripts| 
  #     p "#{day} -> #{transcripts.map(&:class) * ', '}"
  #   end
  #   "2006-03-01 -> Transcript"
  #   "2006-02-28 -> Transcript"
  #   "2006-02-27 -> Transcript, Transcript"
  #   "2006-02-26 -> Transcript, Transcript"
  #   "2006-02-25 -> Transcript"
  #   "2006-02-24 -> Transcript, Transcript"
  #   "2006-02-23 -> Transcript"
  def group_by
    inject({}) do |groups, element|
      (groups[yield(element)] ||= []) << element
      groups
    end
  end 
end
class Exception # :nodoc:
  def clean_message
    Pathname.clean_within message
  end
  
  TraceSubstitutions = []
  FrameworkRegexp = /generated|vendor|dispatch|ruby|script\/\w+/
  
  def clean_backtrace
    backtrace.collect do |line|
      Pathname.clean_within(TraceSubstitutions.inject(line) do |line, (regexp, sub)|
        line.gsub regexp, sub
      end)
    end
  end
  
  def application_backtrace
    before_application_frame = true
    
    trace = clean_backtrace.reject do |line|
      non_app_frame = (line =~ FrameworkRegexp)
      before_application_frame = false unless non_app_frame
      non_app_frame && ! before_application_frame
    end
    
    # If we didn't find any application frames, return an empty app trace.
    before_application_frame ? [] : trace
  end
  
  def framework_backtrace
    clean_backtrace.select {|line| line =~ FrameworkRegexp}
  end
endmodule ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Hash #:nodoc:
      module Conversions
        XML_TYPE_NAMES = {
          "Fixnum"     => "integer",
          "Date"       => "date",
          "Time"       => "datetime",
          "TrueClass"  => "boolean",
          "FalseClass" => "boolean"
        }
        
        XML_FORMATTING = {
          "date"     => Proc.new { |date| date.to_s(:db) },
          "datetime" => Proc.new { |time| time.xmlschema }
        }
        
        def to_xml(options = {})
          options[:indent] ||= 2
          options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]), :root => "hash" })
          options[:builder].instruct! unless options.delete(:skip_instruct)

          options[:builder].__send__(options[:root].to_s.dasherize) do
            each do |key, value|
              case value
                when ::Hash
                  value.to_xml(options.merge({ :root => key, :skip_instruct => true }))
                when ::Array
                  value.to_xml(options.merge({ :root => key, :children => key.to_s.singularize, :skip_instruct => true}))
                else
                  type_name = XML_TYPE_NAMES[value.class.to_s]

                  options[:builder].tag!(key.to_s.dasherize, 
                    XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value,
                    options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name }
                  )
              end
            end
          end
        end
      end
    end
  end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Hash #:nodoc:
      module Diff
        def diff(h2)
          self.dup.delete_if { |k, v| h2[k] == v }.merge(h2.dup.delete_if { |k, v| self.has_key?(k) })
        end
      end
    end
  end
end
# this class has dubious semantics and we only have it so that
# people can write params[:key] instead of params['key']

class HashWithIndifferentAccess < Hash
  def initialize(constructor = {})
    if constructor.is_a?(Hash)
      super()
      update(constructor)
    else
      super(constructor)
    end
  end
 
  def default(key)
    self[key.to_s] if key.is_a?(Symbol)
  end  

  alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
  alias_method :regular_update, :update unless method_defined?(:regular_update)
  
  def []=(key, value)
    regular_writer(convert_key(key), convert_value(value))
  end

  def update(other_hash)
    other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
    self
  end
  
  alias_method :merge!, :update

  def key?(key)
    super(convert_key(key))
  end

  alias_method :include?, :key?
  alias_method :has_key?, :key?
  alias_method :member?, :key?

  def fetch(key, *extras)
    super(convert_key(key), *extras)
  end

  def values_at(*indices)
    indices.collect {|key| self[convert_key(key)]}
  end

  def dup
    HashWithIndifferentAccess.new(self)
  end
  
  def merge(hash)
    self.dup.update(hash)
  end

  def delete(key)
    super(convert_key(key))
  end
    
  protected
    def convert_key(key)
      key.kind_of?(Symbol) ? key.to_s : key
    end
    def convert_value(value)
      value.is_a?(Hash) ? value.with_indifferent_access : value
    end
end

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Hash #:nodoc:
      module IndifferentAccess #:nodoc:
        def with_indifferent_access
          HashWithIndifferentAccess.new(self)
        end
      end
    end
  end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Hash #:nodoc:
      module Keys
        # Return a new hash with all keys converted to strings.
        def stringify_keys
          inject({}) do |options, (key, value)|
            options[key.to_s] = value
            options
          end
        end

        # Destructively convert all keys to strings.
        def stringify_keys!
          keys.each do |key|
            unless key.class.to_s == "String" # weird hack to make the tests run when string_ext_test.rb is also running
              self[key.to_s] = self[key]
              delete(key)
            end
          end
          self
        end

        # Return a new hash with all keys converted to symbols.
        def symbolize_keys
          inject({}) do |options, (key, value)|
            options[key.to_sym] = value
            options
          end
        end

        # Destructively convert all keys to symbols.
        def symbolize_keys!
          keys.each do |key|
            unless key.is_a?(Symbol)
              self[key.to_sym] = self[key]
              delete(key)
            end
          end
          self
        end

        alias_method :to_options,  :symbolize_keys
        alias_method :to_options!, :symbolize_keys!

        def assert_valid_keys(*valid_keys)
          unknown_keys = keys - [valid_keys].flatten
          raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
        end
      end
    end
  end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Hash #:nodoc:
      # Allows for reverse merging where its the keys in the calling hash that wins over those in the <tt>other_hash</tt>.
      # This is particularly useful for initializing an incoming option hash with default values:
      #
      #   def setup(options = {})
      #     options.reverse_merge! :size => 25, :velocity => 10
      #   end
      #
      # The default :size and :velocity is only set if the +options+ passed in doesn't already have those keys set.
      module ReverseMerge
        def reverse_merge(other_hash)
          other_hash.merge(self)
        end

        def reverse_merge!(other_hash)
          replace(reverse_merge(other_hash))
        end

        alias_method :reverse_update, :reverse_merge
      end
    end
  end
endrequire File.dirname(__FILE__) + '/hash/keys'
require File.dirname(__FILE__) + '/hash/indifferent_access'
require File.dirname(__FILE__) + '/hash/reverse_merge'
require File.dirname(__FILE__) + '/hash/conversions'
require File.dirname(__FILE__) + '/hash/diff'

class Hash #:nodoc:
  include ActiveSupport::CoreExtensions::Hash::Keys
  include ActiveSupport::CoreExtensions::Hash::IndifferentAccess
  include ActiveSupport::CoreExtensions::Hash::ReverseMerge
  include ActiveSupport::CoreExtensions::Hash::Conversions
  include ActiveSupport::CoreExtensions::Hash::Diff
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Integer #:nodoc:
      # For checking if a fixnum is even or odd. 
      # * 1.even? # => false
      # * 1.odd?  # => true
      # * 2.even? # => true
      # * 2.odd? # => false
      module EvenOdd
        def multiple_of?(number)
          self % number == 0
        end
        
        def even?
          multiple_of? 2
        end
        
        def odd?
          !even?
        end
      end
    end
  end
end
require File.dirname(__FILE__) + '/../../inflector' unless defined? Inflector
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Integer #:nodoc:
      module Inflections
        # 1.ordinalize  # => "1st"
        # 3.ordinalize  # => "3rd"
        # 10.ordinalize # => "10th"
        def ordinalize
          Inflector.ordinalize(self)
        end
      end
    end
  end
end
require File.dirname(__FILE__) + '/integer/even_odd'
require File.dirname(__FILE__) + '/integer/inflections'

class Integer #:nodoc:
  include ActiveSupport::CoreExtensions::Integer::EvenOdd
  include ActiveSupport::CoreExtensions::Integer::Inflections
end
class Object
  # Makes backticks behave (somewhat more) similarly on all platforms.
  # On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the
  # spawned shell prints a message to stderr and sets $?.  We emulate
  # Unix on the former but not the latter.
  def `(command) #:nodoc:
    super
  rescue Errno::ENOENT => e
    STDERR.puts "#$0: #{e}"
  end
endmodule Kernel
  # Turns the current script into a daemon process that detaches from the console.
  # It can be shut down with a TERM signal.
  def daemonize
    exit if fork                   # Parent exits, child continues.
    Process.setsid                 # Become session leader.
    exit if fork                   # Zap session leader. See [1].
    Dir.chdir "/"                  # Release old working directory.
    File.umask 0000                # Ensure sensible umask. Adjust as needed.
    STDIN.reopen "/dev/null"       # Free file descriptors and
    STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
    STDERR.reopen STDOUT           # STDOUT/ERR should better go to a logfile.
    trap("TERM") { exit }
  end
endmodule Kernel
  # Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards.
  #
  #   silence_warnings do
  #     value = noisy_call # no warning voiced
  #   end
  #
  #   noisy_call # warning voiced
  def silence_warnings
    old_verbose, $VERBOSE = $VERBOSE, nil
    yield
  ensure
    $VERBOSE = old_verbose
  end

  # Sets $VERBOSE to true for the duration of the block and back to its original value afterwards.
  def enable_warnings
    old_verbose, $VERBOSE = $VERBOSE, true
    yield
  ensure
    $VERBOSE = old_verbose
  end

  # For compatibility
  def silence_stderr #:nodoc:
    silence_stream(STDERR) { yield }
  end

  # Silences any stream for the duration of the block.
  #
  #   silence_stream(STDOUT) do
  #     puts 'This will never be seen'
  #   end
  #
  #   puts 'But this will'
  def silence_stream(stream)
    old_stream = stream.dup
    stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null')
    stream.sync = true
    yield
  ensure
    stream.reopen(old_stream)
  end

  def suppress(*exception_classes)
    begin yield
    rescue Exception => e
      raise unless exception_classes.any? { |cls| e.kind_of?(cls) }
    end
  end
endmodule Kernel
  # Require a library with fallback to RubyGems.  Warnings during library
  # loading are silenced to increase signal/noise for application warnings.
  def require_library_or_gem(library_name)
    silence_warnings do
      begin
        require library_name
      rescue LoadError => cannot_require
        # 1. Requiring the module is unsuccessful, maybe it's a gem and nobody required rubygems yet. Try.
        begin
          require 'rubygems'
        rescue LoadError => rubygems_not_installed
          raise cannot_require
        end
        # 2. Rubygems is installed and loaded. Try to load the library again
        begin
          require library_name
        rescue LoadError => gem_not_installed
          raise cannot_require
        end
      end
    end
  end
endrequire File.dirname(__FILE__) + '/kernel/daemonizing'
require File.dirname(__FILE__) + '/kernel/reporting'
require File.dirname(__FILE__) + '/kernel/agnostics'
require File.dirname(__FILE__) + '/kernel/requires'
class MissingSourceFile < LoadError #:nodoc:
  attr_reader :path
  def initialize(message, path)
    super(message)
    @path = path
  end

  def is_missing?(path)
    path.gsub(/\.rb$/, '') == self.path.gsub(/\.rb$/, '')
  end
  
  def self.from_message(message)
    REGEXPS.each do |regexp, capture|
      match = regexp.match(message)
      return MissingSourceFile.new(message, match[capture]) unless match.nil?
    end
    nil
  end
  
  REGEXPS = [
    [/^no such file to load -- (.+)$/i, 1],
    [/^Missing \w+ (file\s*)?([^\s]+.rb)$/i, 2],
    [/^Missing API definition file in (.+)$/i, 1]
  ]
end

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module LoadErrorExtensions #:nodoc:
      module LoadErrorClassMethods #:nodoc:
        def new(*args)
          (self == LoadError && MissingSourceFile.from_message(args.first)) || super
        end
      end
      ::LoadError.extend(LoadErrorClassMethods)
    end
  end
end
# Adds the 'around_level' method to Logger.

class Logger
  def self.define_around_helper(level)
    module_eval <<-end_eval
      def around_#{level}(before_message, after_message, &block)
        self.#{level}(before_message)
        return_value = block.call(self)
        self.#{level}(after_message)
        return return_value
      end
    end_eval
  end
  [:debug, :info, :error, :fatal].each {|level| define_around_helper(level) }

end# Extends the module object with module and instance accessors for class attributes, 
# just like the native attr* accessors for instance attributes.
class Module # :nodoc:
  def mattr_reader(*syms)
    syms.each do |sym|
      class_eval(<<-EOS, __FILE__, __LINE__)
        unless defined? @@#{sym}
          @@#{sym} = nil
        end
        
        def self.#{sym}
          @@#{sym}
        end

        def #{sym}
          @@#{sym}
        end
      EOS
    end
  end
  
  def mattr_writer(*syms)
    syms.each do |sym|
      class_eval(<<-EOS, __FILE__, __LINE__)
        unless defined? @@#{sym}
          @@#{sym} = nil
        end
        
        def self.#{sym}=(obj)
          @@#{sym} = obj
        end

        def #{sym}=(obj)
          @@#{sym} = obj
        end
      EOS
    end
  end
  
  def mattr_accessor(*syms)
    mattr_reader(*syms)
    mattr_writer(*syms)
  end
end
class Module
  def delegate(*methods)
    options = methods.pop
    unless options.is_a?(Hash) && to = options[:to]
      raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key"
    end

    methods.each do |method|
      module_eval(<<-EOS, "(__DELEGATION__)", 1)
        def #{method}(*args, &block)
          #{to}.__send__(#{method.inspect}, *args, &block)
        end
      EOS
    end
  end
endclass Module
  def included_in_classes
    classes = []
    ObjectSpace.each_object(Class) { |k| classes << k if k.included_modules.include?(self) }

    classes.reverse.inject([]) do |unique_classes, klass| 
      unique_classes << klass unless unique_classes.collect { |k| k.to_s }.include?(klass.to_s)
      unique_classes
    end
  end
endclass Module
  # Return the module which contains this one; if this is a root module, such as
  # +::MyModule+, then Object is returned.
  def parent
    parent_name = name.split('::')[0..-2] * '::'
    parent_name.empty? ? Object : parent_name.constantize
  end
  
  # Return all the parents of this module, ordered from nested outwards. The
  # receiver is not contained within the result.
  def parents
    parents = []
    parts = name.split('::')[0..-2]
    until parts.empty?
      parents << (parts * '::').constantize
      parts.pop
    end
    parents << Object unless parents.include? Object
    parents
  end
end
class Module
  def as_load_path
    if self == Object || self == Kernel
      ''
    elsif is_a? Class
      parent == self ? '' : parent.as_load_path
    else
      name.split('::').collect do |word|
        word.underscore
      end * '/'
    end
  end
endrequire File.dirname(__FILE__) + '/module/inclusion'
require File.dirname(__FILE__) + '/module/attribute_accessors'
require File.dirname(__FILE__) + '/module/delegation'
require File.dirname(__FILE__) + '/module/introspection'
require File.dirname(__FILE__) + '/module/loading'
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Numeric #:nodoc:
      # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
      module Bytes
        def bytes
          self
        end
        alias :byte :bytes

        def kilobytes
          self * 1024
        end
        alias :kilobyte :kilobytes

        def megabytes
          self * 1024.kilobytes
        end
        alias :megabyte :megabytes

        def gigabytes
          self * 1024.megabytes 
        end
        alias :gigabyte :gigabytes

        def terabytes
          self * 1024.gigabytes
        end
        alias :terabyte :terabytes
        
        def petabytes
          self * 1024.terabytes
        end
        alias :petabyte :petabytes
        
        def exabytes
          self * 1024.petabytes
        end
        alias :exabyte :exabytes
        
      end
    end
  end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Numeric #:nodoc:
      # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
      #
      # If you need precise date calculations that doesn't just treat months as 30 days, then have
      # a look at Time#advance.
      # 
      # Some of these methods are approximations, Ruby's core 
      # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and 
      # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
      # date and time arithmetic
      module Time
        def seconds
          self
        end
        alias :second :seconds

        def minutes
          self * 60
        end
        alias :minute :minutes  
        
        def hours
          self * 60.minutes
        end
        alias :hour :hours
        
        def days
          self * 24.hours
        end
        alias :day :days

        def weeks
          self * 7.days
        end
        alias :week :weeks
        
        def fortnights
          self * 2.weeks
        end
        alias :fortnight :fortnights
        
        def months
          self * 30.days
        end
        alias :month :months

        def years
          (self * 365.25.days).to_i
        end
        alias :year :years

        # Reads best without arguments:  10.minutes.ago
        def ago(time = ::Time.now)
          time - self
        end

        # Reads best with argument:  10.minutes.until(time)
        alias :until :ago

        # Reads best with argument:  10.minutes.since(time)
        def since(time = ::Time.now)
          time + self
        end

        # Reads best without arguments:  10.minutes.from_now
        alias :from_now :since
      end
    end
  end
end
require File.dirname(__FILE__) + '/numeric/time'
require File.dirname(__FILE__) + '/numeric/bytes'

class Numeric #:nodoc:
  include ActiveSupport::CoreExtensions::Numeric::Time 
  include ActiveSupport::CoreExtensions::Numeric::Bytes 
end
class Object #:nodoc:
  def remove_subclasses_of(*superclasses)
    Class.remove_class(*subclasses_of(*superclasses))
  end

  def subclasses_of(*superclasses)
    subclasses = []
    ObjectSpace.each_object(Class) do |k|
      next if # Exclude this class if
        (k.ancestors & superclasses).empty? || # It's not a subclass of our supers
        superclasses.include?(k) || # It *is* one of the supers
        eval("! defined?(::#{k})") || # It's not defined.
        eval("::#{k}").object_id != k.object_id
      subclasses << k
    end
    subclasses
  end
  
  def extended_by
    ancestors = class << self; ancestors end
    ancestors.select { |mod| mod.class == Module } - [ Object, Kernel ]
  end
  
  def copy_instance_variables_from(object, exclude = [])
    exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables
    
    instance_variables = object.instance_variables - exclude.map { |name| name.to_s }
    instance_variables.each { |name| instance_variable_set(name, object.instance_variable_get(name)) }
  end
  
  def extend_with_included_modules_from(object)
    object.extended_by.each { |mod| extend mod }
  end

  def instance_values
    instance_variables.inject({}) do |values, name|
      values[name[1..-1]] = instance_variable_get(name)
      values
    end
  end
  
  unless defined? instance_exec # 1.9
    def instance_exec(*arguments, &block)
      block.bind(self)[*arguments]
    end
  end
end
class Object #:nodoc:
  # A Ruby-ized realization of the K combinator, courtesy of Mikael Brockman.
  #
  #   def foo
  #     returning values = [] do
  #       values << 'bar'
  #       values << 'baz'
  #     end
  #   end
  #
  #   foo # => ['bar', 'baz']
  #
  #   def foo
  #     returning [] do |values|
  #       values << 'bar'
  #       values << 'baz'
  #     end
  #   end
  #
  #   foo # => ['bar', 'baz']
  #
  def returning(value)
    yield(value)
    value
  end

  def with_options(options)
    yield ActiveSupport::OptionMerger.new(self, options)
  end
  
  def to_json
    ActiveSupport::JSON.encode(self)
  end
endrequire File.dirname(__FILE__) + '/object/extending'
require File.dirname(__FILE__) + '/object/misc'module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Pathname #:nodoc:
      module CleanWithin
        # Clean the paths contained in the provided string.
        def clean_within(string)
          string.gsub(%r{[\w. ]+(/[\w. ]+)+(\.rb)?(\b|$)}) do |path|
            new(path).cleanpath
          end
        end
      end
    end
  end
end
require 'pathname'
require File.dirname(__FILE__) + '/pathname/clean_within'

class Pathname#:nodoc:
  extend ActiveSupport::CoreExtensions::Pathname::CleanWithin
end

class Proc #:nodoc:
  def bind(object)
    block, time = self, Time.now
    (class << object; self end).class_eval do
      method_name = "__bind_#{time.to_i}_#{time.usec}"
      define_method(method_name, &block)
      method = instance_method(method_name)
      remove_method(method_name)
      method
    end.bind(object)
  end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Range #:nodoc:
      # Getting dates in different convenient string representations and other objects
      module Conversions
        DATE_FORMATS = {
          :db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
        }
        
        def self.included(klass) #:nodoc:
          klass.send(:alias_method, :to_default_s, :to_s)
          klass.send(:alias_method, :to_s, :to_formatted_s)
        end
        
        def to_formatted_s(format = :default)
          DATE_FORMATS[format] ? DATE_FORMATS[format].call(first, last) : to_default_s   
        end
      end
    end
  end
endrequire File.dirname(__FILE__) + '/range/conversions'

class Range #:nodoc:
  include ActiveSupport::CoreExtensions::Range::Conversions
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module String #:nodoc:
      # Makes it easier to access parts of a string, such as specific characters and substrings.
      module Access
        # Returns the character at the +position+ treating the string as an array (where 0 is the first character).
        #
        # Examples: 
        #   "hello".at(0)  # => "h"
        #   "hello".at(4)  # => "o"
        #   "hello".at(10) # => nil
        def at(position)
          self[position, 1]
        end
        
        # Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character).
        #
        # Examples: 
        #   "hello".from(0)  # => "hello"
        #   "hello".from(2)  # => "llo"
        #   "hello".from(10) # => nil
        def from(position)
          self[position..-1]
        end
        
        # Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character).
        #
        # Examples: 
        #   "hello".to(0)  # => "h"
        #   "hello".to(2)  # => "hel"
        #   "hello".to(10) # => "hello"
        def to(position)
          self[0..position]
        end

        # Returns the first character of the string or the first +limit+ characters.
        #
        # Examples: 
        #   "hello".first     # => "h"
        #   "hello".first(2)  # => "he"
        #   "hello".first(10) # => "hello"
        def first(limit = 1)
          self[0..(limit - 1)]
        end
        
        # Returns the last character of the string or the last +limit+ characters.
        #
        # Examples: 
        #   "hello".last     # => "o"
        #   "hello".last(2)  # => "lo"
        #   "hello".last(10) # => "hello"
        def last(limit = 1)
          self[(-limit)..-1] || self
        end
      end
    end
  end
end
require 'parsedate'

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module String #:nodoc:
      # Converting strings to other objects
      module Conversions
        # Form can be either :utc (default) or :local.
        def to_time(form = :utc)
          ::Time.send(form, *ParseDate.parsedate(self))
        end

        def to_date
          ::Date.new(*ParseDate.parsedate(self)[0..2])
        end
      end
    end
  end
endrequire File.dirname(__FILE__) + '/../../inflector' unless defined? Inflector
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module String #:nodoc:
      # Makes it possible to do "posts".singularize that returns "post" and "MegaCoolClass".underscore that returns "mega_cool_class".
      module Inflections
        def pluralize
          Inflector.pluralize(self)
        end

        def singularize
          Inflector.singularize(self)
        end

        def camelize(first_letter = :upper)
          case first_letter
            when :upper then Inflector.camelize(self, true)
            when :lower then Inflector.camelize(self, false)
          end
        end
        alias_method :camelcase, :camelize

        def titleize
          Inflector.titleize(self)
        end
        alias_method :titlecase, :titleize

        def underscore
          Inflector.underscore(self)
        end

        def dasherize
          Inflector.dasherize(self)
        end

        def demodulize
          Inflector.demodulize(self)
        end

        def tableize
          Inflector.tableize(self)
        end

        def classify
          Inflector.classify(self)
        end
        
        # Capitalizes the first word and turns underscores into spaces and strips _id, so "employee_salary" becomes "Employee salary" 
        # and "author_id" becomes "Author".
        def humanize
          Inflector.humanize(self)
        end

        def foreign_key(separate_class_name_and_id_with_underscore = true)
          Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
        end

        def constantize
          Inflector.constantize(self)
        end
      end
    end
  end
end
require 'strscan'

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module String #:nodoc:
      # Custom string iterators
      module Iterators
        # Yields a single-character string for each character in the string.
        # When $KCODE = 'UTF8', multi-byte characters are yielded appropriately.
        def each_char
          scanner, char = StringScanner.new(self), /./mu
          loop { yield(scanner.scan(char) || break) }
        end
      end
    end
  end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module String #:nodoc:
      # Additional string tests.
      module StartsEndsWith
        # Does the string start with the specified +prefix+?
        def starts_with?(prefix)
          prefix = prefix.to_s
          self[0, prefix.length] == prefix
        end

        # Does the string end with the specified +suffix+?
        def ends_with?(suffix)
          suffix = suffix.to_s
          self[-suffix.length, suffix.length] == suffix      
        end
      end
    end
  end
end
require File.dirname(__FILE__) + '/string/inflections'
require File.dirname(__FILE__) + '/string/conversions'
require File.dirname(__FILE__) + '/string/access'
require File.dirname(__FILE__) + '/string/starts_ends_with'
require File.dirname(__FILE__) + '/string/iterators'

class String #:nodoc:
  include ActiveSupport::CoreExtensions::String::Access
  include ActiveSupport::CoreExtensions::String::Conversions
  include ActiveSupport::CoreExtensions::String::Inflections
  include ActiveSupport::CoreExtensions::String::StartsEndsWith
  include ActiveSupport::CoreExtensions::String::Iterators
end
class Symbol
  # Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
  #
  #   # The same as people.collect { |p| p.name }
  #   people.collect(&:name)
  #
  #   # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
  #   people.select(&:manager?).collect(&:salary)
  def to_proc
    Proc.new { |obj, *args| obj.send(self, *args) }
  end
end
module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Time #:nodoc:
      # Enables the use of time calculations within Time itself
      module Calculations
        def self.append_features(base) #:nodoc:
          super
          base.extend(ClassMethods)
        end

        module ClassMethods
          # Return the number of days in the given month. If a year is given,
          # February will return the correct number of days for leap years.
          # Otherwise, this method will always report February as having 28
          # days.
          def days_in_month(month, year=nil)
            if month == 2
              !year.nil? && (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) ?  29 : 28
            elsif month <= 7
              month % 2 == 0 ? 30 : 31
            else
              month % 2 == 0 ? 31 : 30
            end
          end
        end

        # Seconds since midnight: Time.now.seconds_since_midnight
        def seconds_since_midnight
          self.hour.hours + self.min.minutes + self.sec + (self.usec/1.0e+6)
        end
            
        # Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
        # (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and 
        # minute is passed, then sec and usec is set to 0.
        def change(options)
          ::Time.send(
            self.utc? ? :utc : :local, 
            options[:year]  || self.year, 
            options[:month] || self.month, 
            options[:mday]  || self.mday, 
            options[:hour]  || self.hour, 
            options[:min]   || (options[:hour] ? 0 : self.min),
            options[:sec]   || ((options[:hour] || options[:min]) ? 0 : self.sec),
            options[:usec]  || ((options[:hour] || options[:min] || options[:sec]) ? 0 : self.usec)
          )
        end
        
        # Uses Date to provide precise Time calculations for years, months, and days.  The +options+ parameter takes a hash with 
        # any of these keys: :months, :days, :years.
        def advance(options)
          d = ::Date.new(year + (options.delete(:years) || 0), month, day)
          d = d >> options.delete(:months) if options[:months]
          d = d +  options.delete(:days)   if options[:days]
          change(options.merge(:year => d.year, :month => d.month, :mday => d.day))
        end

        # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
        # Do not use this method in combination with x.months, use months_ago instead!
        def ago(seconds)
          seconds.until(self)
        end

        # Returns a new Time representing the time a number of seconds since the instance time, this is basically a wrapper around 
        #the Numeric extension. Do not use this method in combination with x.months, use months_since instead!
        def since(seconds)
          seconds.since(self)
        end
        alias :in :since

        # Returns a new Time representing the time a number of specified months ago
        def months_ago(months)
          months_since(-months)
        end

        def months_since(months)
          year, month, mday = self.year, self.month, self.mday

          month += months

          # in case months is negative
          while month < 1
            month += 12
            year -= 1
          end

          # in case months is positive
          while month > 12
            month -= 12
            year += 1
          end

          max = ::Time.days_in_month(month, year)
          mday = max if mday > max

          change(:year => year, :month => month, :mday => mday)
        end

        # Returns a new Time representing the time a number of specified years ago
        def years_ago(years)
          change(:year => self.year - years)
        end
        
        def years_since(years)
          change(:year => self.year + years)
        end

        # Short-hand for years_ago(1)
        def last_year
          years_ago(1)
        end
        
        # Short-hand for years_since(1)
        def next_year
          years_since(1)
        end


        # Short-hand for months_ago(1)
        def last_month
          months_ago(1)
        end

        # Short-hand for months_since(1)
        def next_month
          months_since(1)
        end
        
        # Returns a new Time representing the "start" of this week (Monday, 0:00)
        def beginning_of_week
          days_to_monday = self.wday!=0 ? self.wday-1 : 6
          (self - days_to_monday.days).midnight
        end
        alias :monday :beginning_of_week
        alias :at_beginning_of_week :beginning_of_week
        
        # Returns a new Time representing the start of the given day in next week (default is Monday).
        def next_week(day = :monday)
          days_into_week = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6}
          since(1.week).beginning_of_week.since(days_into_week[day].day).change(:hour => 0)
        end
        
        # Returns a new Time representing the start of the day (0:00)
        def beginning_of_day
          (self - self.seconds_since_midnight).change(:usec => 0)
        end
        alias :midnight :beginning_of_day
        alias :at_midnight :beginning_of_day
        alias :at_beginning_of_day :beginning_of_day
        
        # Returns a new Time representing the start of the month (1st of the month, 0:00)
        def beginning_of_month
          #self - ((self.mday-1).days + self.seconds_since_midnight)
          change(:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
        end
        alias :at_beginning_of_month :beginning_of_month
        
        # Returns a new Time representing the end of the month (last day of the month, 0:00)
        def end_of_month
          #self - ((self.mday-1).days + self.seconds_since_midnight)
          last_day = ::Time.days_in_month( self.month, self.year )
          change(:mday => last_day,:hour => 0, :min => 0, :sec => 0, :usec => 0)
        end
        alias :at_end_of_month :end_of_month
		  
        # Returns  a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
        def beginning_of_quarter
          beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
        end
        alias :at_beginning_of_quarter :beginning_of_quarter
        
        # Returns  a new Time representing the start of the year (1st of january, 0:00)
        def beginning_of_year
          change(:month => 1,:mday => 1,:hour => 0, :min => 0, :sec => 0, :usec => 0)
        end
        alias :at_beginning_of_year :beginning_of_year
        
        # Convenience method which returns a new Time representing the time 1 day ago
        def yesterday
          self.ago(1.day)
        end
        
        # Convenience method which returns a new Time representing the time 1 day since the instance time
        def tomorrow
          self.since(1.day)
        end
      end
    end
  end
end
require 'date'
require 'time'

module ActiveSupport #:nodoc:
  module CoreExtensions #:nodoc:
    module Time #:nodoc:
      # Getting times in different convenient string representations and other objects
      module Conversions
        DATE_FORMATS = {
          :db     => "%Y-%m-%d %H:%M:%S",
          :short  => "%d %b %H:%M",
          :long   => "%B %d, %Y %H:%M",
          :rfc822 => "%a, %d %b %Y %H:%M:%S %z"
        }

        def self.append_features(klass)
          super
          klass.send(:alias_method, :to_default_s, :to_s)
          klass.send(:alias_method, :to_s, :to_formatted_s)
        end
        
        def to_formatted_s(format = :default)
          DATE_FORMATS[format] ? strftime(DATE_FORMATS[format]).strip : to_default_s          
        end

        def to_date
          ::Date.new(year, month, day)
        end

        # To be able to keep Dates and Times interchangeable on conversions
        def to_time
          self
        end
      end
    end
  end
end
require File.dirname(__FILE__) + '/time/calculations'
require File.dirname(__FILE__) + '/time/conversions'

class Time#:nodoc:
  include ActiveSupport::CoreExtensions::Time::Calculations
  include ActiveSupport::CoreExtensions::Time::Conversions
end
Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each { |file| require(file) }
require 'set'
require File.dirname(__FILE__) + '/core_ext/module/attribute_accessors'
require File.dirname(__FILE__) + '/core_ext/load_error'
require File.dirname(__FILE__) + '/core_ext/kernel'

module Dependencies #:nodoc:
  extend self

  # Should we turn on Ruby warnings on the first load of dependent files?
  mattr_accessor :warnings_on_first_load
  self.warnings_on_first_load = false

  # All files ever loaded.
  mattr_accessor :history
  self.history = Set.new

  # All files currently loaded.
  mattr_accessor :loaded
  self.loaded = Set.new

  # Should we load files or require them?
  mattr_accessor :mechanism
  self.mechanism = :load

  def load?
    mechanism == :load
  end

  def depend_on(file_name, swallow_load_errors = false)
    require_or_load(file_name)
  rescue LoadError
    raise unless swallow_load_errors
  end

  def associate_with(file_name)
    depend_on(file_name, true)
  end

  def clear
    loaded.clear
  end

  def require_or_load(file_name)
    file_name = $1 if file_name =~ /^(.*)\.rb$/
    return if loaded.include?(file_name)

    # Record that we've seen this file *before* loading it to avoid an
    # infinite loop with mutual dependencies.
    loaded << file_name

    if load?
      begin
        # Enable warnings iff this file has not been loaded before and
        # warnings_on_first_load is set.
        if !warnings_on_first_load or history.include?(file_name)
          load "#{file_name}.rb"
        else
          enable_warnings { load "#{file_name}.rb" }
        end
      rescue
        loaded.delete file_name
        raise
      end
    else
      require file_name
    end

    # Record history *after* loading so first load gets warnings.
    history << file_name
  end
  
  class LoadingModule
    # Old style environment.rb referenced this method directly.  Please note, it doesn't
    # actualy *do* anything any more.
    def self.root(*args)
      if defined?(RAILS_DEFAULT_LOGGER)
        RAILS_DEFAULT_LOGGER.warn "Your environment.rb uses the old syntax, it may not continue to work in future releases."
        RAILS_DEFAULT_LOGGER.warn "For upgrade instructions please see: http://manuals.rubyonrails.com/read/book/19"
      end
    end
  end
end

Object.send(:define_method, :require_or_load)     { |file_name| Dependencies.require_or_load(file_name) } unless Object.respond_to?(:require_or_load)
Object.send(:define_method, :require_dependency)  { |file_name| Dependencies.depend_on(file_name) }       unless Object.respond_to?(:require_dependency)
Object.send(:define_method, :require_association) { |file_name| Dependencies.associate_with(file_name) }  unless Object.respond_to?(:require_association)

class Module #:nodoc:
  # Rename the original handler so we can chain it to the new one
  alias :rails_original_const_missing :const_missing
  
  # Use const_missing to autoload associations so we don't have to
  # require_association when using single-table inheritance.
  def const_missing(class_id)
    file_name = class_id.to_s.demodulize.underscore
    file_path = as_load_path.empty? ? file_name : "#{as_load_path}/#{file_name}"
    begin
      require_dependency(file_path)
      brief_name = self == Object ? '' : "#{name}::"
      raise NameError.new("uninitialized constant #{brief_name}#{class_id}") unless const_defined?(class_id)
      return const_get(class_id)
    rescue MissingSourceFile => e
      # Re-raise the error if it does not concern the file we were trying to load.
      raise unless e.is_missing? file_path
      
      # Look for a directory in the load path that we ought to load.
      if $LOAD_PATH.any? { |base| File.directory? "#{base}/#{file_path}" }
        mod = Module.new
        const_set class_id, mod # Create the new module
        return mod
      end
      
      # Attempt to access the name from the parent, unless we don't have a valid
      # parent, or the constant is already defined in the parent. If the latter
      # is the case, then we are being queried via self::class_id, and we should
      # avoid returning the constant from the parent if possible.
      if parent && parent != self && ! parents.any? { |p| p.const_defined?(class_id) }
        suppress(NameError) do
          return parent.send(:const_missing, class_id)
        end
      end
      
      raise NameError.new("uninitialized constant #{class_id}").copy_blame!(e)
    end
  end
end

class Class
  def const_missing(class_id)
    if [Object, Kernel].include?(self) || parent == self
      super
    else
      parent.send :const_missing, class_id
    end
  end
end

class Object #:nodoc:
  def load(file, *extras)
    super(file, *extras)
  rescue Object => exception
    exception.blame_file! file
    raise
  end

  def require(file, *extras)
    super(file, *extras)
  rescue Object => exception
    exception.blame_file! file
    raise
  end
end

# Add file-blaming to exceptions
class Exception #:nodoc:
  def blame_file!(file)
    (@blamed_files ||= []).unshift file
  end

  def blamed_files
    @blamed_files ||= []
  end

  def describe_blame
    return nil if blamed_files.empty?
    "This error occured while loading the following files:\n   #{blamed_files.join "\n   "}"
  end

  def copy_blame!(exc)
    @blamed_files = exc.blamed_files.clone
    self
  end
endInflector.inflections do |inflect|
  inflect.plural(/$/, 's')
  inflect.plural(/s$/i, 's')
  inflect.plural(/(ax|test)is$/i, '\1es')
  inflect.plural(/(octop|vir)us$/i, '\1i')
  inflect.plural(/(alias|status)$/i, '\1es')
  inflect.plural(/(bu)s$/i, '\1ses')
  inflect.plural(/(buffal|tomat)o$/i, '\1oes')
  inflect.plural(/([ti])um$/i, '\1a')
  inflect.plural(/sis$/i, 'ses')
  inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
  inflect.plural(/(hive)$/i, '\1s')
  inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
  inflect.plural(/([^aeiouy]|qu)ies$/i, '\1y')
  inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
  inflect.plural(/(matr|vert|ind)ix|ex$/i, '\1ices')
  inflect.plural(/([m|l])ouse$/i, '\1ice')
  inflect.plural(/^(ox)$/i, '\1en')
  inflect.plural(/(quiz)$/i, '\1zes')

  inflect.singular(/s$/i, '')
  inflect.singular(/(n)ews$/i, '\1ews')
  inflect.singular(/([ti])a$/i, '\1um')
  inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
  inflect.singular(/(^analy)ses$/i, '\1sis')
  inflect.singular(/([^f])ves$/i, '\1fe')
  inflect.singular(/(hive)s$/i, '\1')
  inflect.singular(/(tive)s$/i, '\1')
  inflect.singular(/([lr])ves$/i, '\1f')
  inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
  inflect.singular(/(s)eries$/i, '\1eries')
  inflect.singular(/(m)ovies$/i, '\1ovie')
  inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
  inflect.singular(/([m|l])ice$/i, '\1ouse')
  inflect.singular(/(bus)es$/i, '\1')
  inflect.singular(/(o)es$/i, '\1')
  inflect.singular(/(shoe)s$/i, '\1')
  inflect.singular(/(cris|ax|test)es$/i, '\1is')
  inflect.singular(/([octop|vir])i$/i, '\1us')
  inflect.singular(/(alias|status)es$/i, '\1')
  inflect.singular(/^(ox)en/i, '\1')
  inflect.singular(/(vert|ind)ices$/i, '\1ex')
  inflect.singular(/(matr)ices$/i, '\1ix')
  inflect.singular(/(quiz)zes$/i, '\1')

  inflect.irregular('person', 'people')
  inflect.irregular('man', 'men')
  inflect.irregular('child', 'children')
  inflect.irregular('sex', 'sexes')
  inflect.irregular('move', 'moves')

  inflect.uncountable(%w(equipment information rice money species series fish sheep))
end
require 'singleton' 

# The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
# and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
# in inflections.rb.
module Inflector 
  # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
  # inflection rules. Examples:
  #
  #   Inflector.inflections do |inflect|
  #     inflect.plural /^(ox)$/i, '\1\2en'
  #     inflect.singular /^(ox)en/i, '\1'
  #
  #     inflect.irregular 'octopus', 'octopi'
  #
  #     inflect.uncountable "equipment"
  #   end
  #
  # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
  # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
  # already have been loaded.
  class Inflections
    include Singleton
    
    attr_reader :plurals, :singulars, :uncountables
    
    def initialize
      @plurals, @singulars, @uncountables = [], [], []
    end
    
    # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. 
    # The replacement should always be a string that may include references to the matched data from the rule.
    def plural(rule, replacement)
      @plurals.insert(0, [rule, replacement])
    end
    
    # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. 
    # The replacement should always be a string that may include references to the matched data from the rule.
    def singular(rule, replacement)
      @singulars.insert(0, [rule, replacement])
    end

    # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
    # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
    # 
    # Examples:
    #   irregular 'octopus', 'octopi'
    #   irregular 'person', 'people'
    def irregular(singular, plural)
      plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
      singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
    end
    
    # Add uncountable words that shouldn't be attempted inflected.
    # 
    # Examples:
    #   uncountable "money"
    #   uncountable "money", "information"
    #   uncountable %w( money information rice )
    def uncountable(*words)
      (@uncountables << words).flatten!
    end
    
    # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
    # the options are: :plurals, :singulars, :uncountables
    #
    # Examples:
    #   clear :all
    #   clear :plurals
    def clear(scope = :all)
      case scope
        when :all
          @plurals, @singulars, @uncountables = [], [], []
        else
          instance_variable_set "@#{scope}", []
      end
    end
  end

  extend self

  def inflections
    if block_given?
      yield Inflections.instance
    else
      Inflections.instance
    end
  end

  def pluralize(word)
    result = word.to_s.dup

    if inflections.uncountables.include?(result.downcase)
      result
    else
      inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
      result
    end
  end

  def singularize(word)
    result = word.to_s.dup

    if inflections.uncountables.include?(result.downcase)
      result
    else
      inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
      result
    end
  end

  def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
    if first_letter_in_uppercase
      lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
    else
      lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
    end
  end

  def titleize(word)
    humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize }
  end
  
  def underscore(camel_cased_word)
    camel_cased_word.to_s.gsub(/::/, '/').
      gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
      gsub(/([a-z\d])([A-Z])/,'\1_\2').
      tr("-", "_").
      downcase
  end
  
  def dasherize(underscored_word)
    underscored_word.gsub(/_/, '-')
  end

  def humanize(lower_case_and_underscored_word)
    lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
  end

  def demodulize(class_name_in_module)
    class_name_in_module.to_s.gsub(/^.*::/, '')
  end

  def tableize(class_name)
    pluralize(underscore(class_name))
  end
  
  def classify(table_name)
    camelize(singularize(table_name))
  end

  def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
    underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
  end

  def constantize(camel_cased_word)
    raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!" unless
      /^(::)?([A-Z]\w*)(::[A-Z]\w*)*$/ =~ camel_cased_word
    
    camel_cased_word = "::#{camel_cased_word}" unless $1
    Object.module_eval(camel_cased_word, __FILE__, __LINE__)
  end

  def ordinalize(number)
    if (11..13).include?(number.to_i % 100)
      "#{number}th"
    else
      case number.to_i % 10
        when 1: "#{number}st"
        when 2: "#{number}nd"
        when 3: "#{number}rd"
        else    "#{number}th"
      end
    end
  end
end

require File.dirname(__FILE__) + '/inflections'
module ActiveSupport
  module JSON #:nodoc:
    module Encoders #:nodoc:
      define_encoder Object do |object|
        object.instance_values.to_json
      end
      
      define_encoder TrueClass do
        'true'
      end
      
      define_encoder FalseClass do
        'false'
      end
      
      define_encoder NilClass do
        'null'
      end
      
      define_encoder String do |string|
        returning value = '"' do
          string.each_char do |char|
            value << case
            when char == "\010":  '\b'
            when char == "\f":    '\f'
            when char == "\n":    '\n'
            when char == "\r":    '\r'
            when char == "\t":    '\t'
            when char == '"':     '\"'
            when char == '\\':    '\\\\'  
            when char.length > 1: "\\u#{'%04x' % char.unpack('U').first}"
            else;                 char
            end
          end
          value << '"'
        end
      end
      
      define_encoder Numeric do |numeric|
        numeric.to_s
      end
      
      define_encoder Symbol do |symbol|
        symbol.to_s.to_json
      end

      define_encoder Enumerable do |enumerable|
        "[#{enumerable.map { |value| value.to_json } * ', '}]"
      end
      
      define_encoder Hash do |hash|
        returning result = '{' do
          result << hash.map do |pair|
            pair.map { |value| value.to_json } * ': '
          end * ', '
          result << '}'
        end
      end

      define_encoder Regexp do |regexp|
        regexp.inspect
      end
    end
  end
end
module ActiveSupport
  module JSON #:nodoc:
    module Encoders
      mattr_accessor :encoders
      @@encoders = {}

      class << self        
        def define_encoder(klass, &block)
          encoders[klass] = block
        end
        
        def [](klass)
          klass.ancestors.each do |k|
            encoder = encoders[k]
            return encoder if encoder
          end
        end
      end
    end
  end
end

Dir[File.dirname(__FILE__) + '/encoders/*.rb'].each do |file|
  require file[0..-4]
end
require 'active_support/json/encoders'

module ActiveSupport
  module JSON #:nodoc:
    class CircularReferenceError < StandardError #:nodoc:
    end
    # returns the literal string as its JSON encoded form.  Useful for passing javascript variables into functions.
    #
    # page.call 'Element.show', ActiveSupport::JSON::Variable.new("$$(#items li)")
    class Variable < String #:nodoc:
      def to_json
        self
      end
    end

    class << self
      REFERENCE_STACK_VARIABLE = :json_reference_stack
      
      def encode(value)
        raise_on_circular_reference(value) do
          Encoders[value.class].call(value)
        end
      end
      
      protected
        def raise_on_circular_reference(value)
          stack = Thread.current[REFERENCE_STACK_VARIABLE] ||= []
          raise CircularReferenceError, 'object references itself' if
            stack.include? value
          stack << value
          yield
        ensure
          stack.pop
        end
    end
  end
end
module ActiveSupport
  class OptionMerger #:nodoc:
    instance_methods.each do |method| 
      undef_method(method) if method !~ /^(__|instance_eval)/
    end
    
    def initialize(context, options)
      @context, @options = context, options
    end
    
    private
      def method_missing(method, *arguments, &block)
        merge_argument_options! arguments
        @context.send(method, *arguments, &block)
      end
      
      def merge_argument_options!(arguments)
        arguments << if arguments.last.respond_to? :merge!
          arguments.pop.dup.merge!(@options)
        else
          @options.dup
        end  
      end
  end
end
class OrderedHash < Array #:nodoc:
  def []=(key, value)    
    if pair = find_pair(key)
      pair.pop
      pair << value
    else
      self << [key, value]
    end
  end
  
  def [](key)
    pair = find_pair(key)
    pair ? pair.last : nil
  end

  def keys
    self.collect { |i| i.first }
  end

  private
    def find_pair(key)
      self.each { |i| return i if i.first == key }
      return false
    end
end

class OrderedOptions < OrderedHash #:nodoc:
  def []=(key, value)
    super(key.to_sym, value)
  end
  
  def [](key)
    super(key.to_sym)
  end

  def method_missing(name, *args)
    if name.to_s =~ /(.*)=$/
      self[$1.to_sym] = args.first
    else
      self[name]
    end
  end
end# Classes that include this module will automatically be reloaded
# by the Rails dispatcher when Dependencies.mechanism = :load.
module Reloadable
  class << self
    def included(base) #nodoc:
      raise TypeError, "Only Classes can be Reloadable!" unless base.is_a? Class
      
      unless base.respond_to?(:reloadable?)
        class << base
          define_method(:reloadable?) { true }
        end
      end
    end
    
    def reloadable_classes
      included_in_classes.select { |klass| klass.reloadable? }
    end
  end
  
  # Captures the common pattern where a base class should not be reloaded,
  # but its subclasses should be.
  module Subclasses
    def self.included(base) #nodoc:
      base.send :include, Reloadable
      (class << base; self; end).send(:define_method, :reloadable?) do
         base != self
      end
    end
  end
end# A value object representing a time zone. A time zone is simply a named
# offset (in seconds) from GMT. Note that two time zone objects are only
# equivalent if they have both the same offset, and the same name.
#
# A TimeZone instance may be used to convert a Time value to the corresponding
# time zone.
#
# The class also includes #all, which returns a list of all TimeZone objects.
class TimeZone
  include Comparable

  attr_reader :name, :utc_offset

  # Create a new TimeZone object with the given name and offset. The offset is
  # the number of seconds that this time zone is offset from UTC (GMT). Seconds
  # were chosen as the offset unit because that is the unit that Ruby uses
  # to represent time zone offsets (see Time#utc_offset).
  def initialize(name, utc_offset)
    @name = name
    @utc_offset = utc_offset
  end

  # Returns the offset of this time zone as a formatted string, of the
  # format "+HH:MM". If the offset is zero, this returns the empty
  # string. If +colon+ is false, a colon will not be inserted into the
  # result.
  def formatted_offset( colon=true )
    return "" if utc_offset == 0
    sign = (utc_offset < 0 ? -1 : 1)
    hours = utc_offset.abs / 3600
    minutes = (utc_offset.abs % 3600) / 60
    "%+03d%s%02d" % [ hours * sign, colon ? ":" : "", minutes ]
  end

  # Compute and return the current time, in the time zone represented by
  # +self+.
  def now
    adjust(Time.now)
  end

  # Return the current date in this time zone.
  def today
    now.to_date
  end

  # Adjust the given time to the time zone represented by +self+.
  def adjust(time)
    time = time.to_time
    time + utc_offset - time.utc_offset
  end

  # Reinterprets the given time value as a time in the current time
  # zone, and then adjusts it to return the corresponding time in the
  # local time zone.
  def unadjust(time)
    time = Time.local(*time.to_time.to_a)
    time - utc_offset + time.utc_offset
  end

  # Compare this time zone to the parameter. The two are comapred first on
  # their offsets, and then by name.
  def <=>(zone)
    result = (utc_offset <=> zone.utc_offset)
    result = (name <=> zone.name) if result == 0
    result
  end

  # Returns a textual representation of this time zone.
  def to_s
    "(GMT#{formatted_offset}) #{name}"
  end

  @@zones = nil

  class << self
    # Create a new TimeZone instance with the given name and offset.
    def create(name, offset)
      zone = allocate
      zone.send :initialize, name, offset
      zone
    end

    # Return a TimeZone instance with the given name, or +nil+ if no
    # such TimeZone instance exists. (This exists to support the use of
    # this class with the #composed_of macro.)
    def new(name)
      self[name]
    end

    # Return an array of all TimeZone objects. There are multiple TimeZone
    # objects per time zone, in many cases, to make it easier for users to
    # find their own time zone.
    def all
      unless @@zones
        @@zones = []
        [[-43_200, "International Date Line West" ],
         [-39_600, "Midway Island", "Samoa" ],
         [-36_000, "Hawaii" ],
         [-32_400, "Alaska" ],
         [-28_800, "Pacific Time (US & Canada)", "Tijuana" ],
         [-25_200, "Mountain Time (US & Canada)", "Chihuahua", "La Paz",
                   "Mazatlan", "Arizona" ],
         [-21_600, "Central Time (US & Canada)", "Saskatchewan", "Guadalajara",
                   "Mexico City", "Monterrey", "Central America" ],
         [-18_000, "Eastern Time (US & Canada)", "Indiana (East)", "Bogota",
                   "Lima", "Quito" ],
         [-14_400, "Atlantic Time (Canada)", "Caracas", "La Paz", "Santiago" ],
         [-12_600, "Newfoundland" ],
         [-10_800, "Brasilia", "Buenos Aires", "Georgetown", "Greenland" ],
         [ -7_200, "Mid-Atlantic" ],
         [ -3_600, "Azores", "Cape Verde Is." ],
         [      0, "Dublin", "Edinburgh", "Lisbon", "London", "Casablanca",
                   "Monrovia" ],
         [  3_600, "Belgrade", "Bratislava", "Budapest", "Ljubljana", "Prague",
                   "Sarajevo", "Skopje", "Warsaw", "Zagreb", "Brussels",
                   "Copenhagen", "Madrid", "Paris", "Amsterdam", "Berlin",
                   "Bern", "Rome", "Stockholm", "Vienna",
                   "West Central Africa" ],
         [  7_200, "Bucharest", "Cairo", "Helsinki", "Kyev", "Riga", "Sofia",
                   "Tallinn", "Vilnius", "Athens", "Istanbul", "Minsk",
                   "Jerusalem", "Harare", "Pretoria" ],
         [ 10_800, "Moscow", "St. Petersburg", "Volgograd", "Kuwait", "Riyadh",
                   "Nairobi", "Baghdad" ],
         [ 12_600, "Tehran" ],
         [ 14_400, "Abu Dhabi", "Muscat", "Baku", "Tbilisi", "Yerevan" ],
         [ 16_200, "Kabul" ],
         [ 18_000, "Ekaterinburg", "Islamabad", "Karachi", "Tashkent" ],
         [ 19_800, "Chennai", "Kolkata", "Mumbai", "New Delhi" ],
         [ 20_700, "Kathmandu" ],
         [ 21_600, "Astana", "Dhaka", "Sri Jayawardenepura", "Almaty",
                   "Novosibirsk" ],
         [ 23_400, "Rangoon" ],
         [ 25_200, "Bangkok", "Hanoi", "Jakarta", "Krasnoyarsk" ],
         [ 28_800, "Beijing", "Chongqing", "Hong Kong", "Urumqi",
                   "Kuala Lumpur", "Singapore", "Taipei", "Perth", "Irkutsk",
                   "Ulaan Bataar" ],
         [ 32_400, "Seoul", "Osaka", "Sapporo", "Tokyo", "Yakutsk" ],
         [ 34_200, "Darwin", "Adelaide" ],
         [ 36_000, "Canberra", "Melbourne", "Sydney", "Brisbane", "Hobart",
                   "Vladivostok", "Guam", "Port Moresby" ],
         [ 39_600, "Magadan", "Solomon Is.", "New Caledonia" ],
         [ 43_200, "Fiji", "Kamchatka", "Marshall Is.", "Auckland",
                   "Wellington" ],
         [ 46_800, "Nuku'alofa" ]].
        each do |offset, *places|
          places.each { |place| @@zones << create(place, offset).freeze }
        end
        @@zones.sort!
      end
      @@zones
    end

    # Locate a specific time zone object. If the argument is a string, it
    # is interpreted to mean the name of the timezone to locate. If it is a
    # numeric value it is either the hour offset, or the second offset, of the
    # timezone to find. (The first one with that offset will be returned.)
    # Returns +nil+ if no such time zone is known to the system.
    def [](arg)
      case arg
        when String
          all.find { |z| z.name == arg }
        when Numeric
          arg *= 3600 if arg.abs <= 13
          all.find { |z| z.utc_offset == arg.to_i }
        else
          raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
      end
    end

    # A regular expression that matches the names of all time zones in
    # the USA.
    US_ZONES = /US|Arizona|Indiana|Hawaii|Alaska/

    # A convenience method for returning a collection of TimeZone objects
    # for time zones in the USA.
    def us_zones
      all.find_all { |z| z.name =~ US_ZONES }
    end
  end
end
#!/usr/bin/env ruby
#--
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++

module Builder #:nodoc:

  # BlankSlate provides an abstract base class with no predefined
  # methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
  # BlankSlate is useful as a base class when writing classes that
  # depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
  class BlankSlate #:nodoc:
    class << self
      def hide(name)
	      undef_method name if
	        instance_methods.include?(name.to_s) and
	        name !~ /^(__|instance_eval)/
      end
    end

    instance_methods.each { |m| hide(m) }
  end
end

# Since Ruby is very dynamic, methods added to the ancestors of
# BlankSlate <em>after BlankSlate is defined</em> will show up in the
# list of available BlankSlate methods.  We handle this by defining a hook in the Object and Kernel classes that will hide any defined 
module Kernel #:nodoc:
  class << self
    alias_method :blank_slate_method_added, :method_added
    def method_added(name)
      blank_slate_method_added(name)
      return if self != Kernel
      Builder::BlankSlate.hide(name)
    end
  end
end

class Object #:nodoc:
  class << self
    alias_method :blank_slate_method_added, :method_added
    def method_added(name)
      blank_slate_method_added(name)
      return  if self != Object
      Builder::BlankSlate.hide(name)
    end
  end
end
#!/usr/bin/env ruby

require 'builder/blankslate'

module Builder #:nodoc:

  # Generic error for builder
  class IllegalBlockError < RuntimeError #:nodoc:
  end

  # XmlBase is a base class for building XML builders.  See
  # Builder::XmlMarkup and Builder::XmlEvents for examples.
  class XmlBase < BlankSlate #:nodoc:

    # Create an XML markup builder.
    #
    # out::     Object receiving the markup.1  +out+ must respond to
    #           <tt><<</tt>.
    # indent::  Number of spaces used for indentation (0 implies no
    #           indentation and no line breaks).
    # initial:: Level of initial indentation.
    #
    def initialize(indent=0, initial=0)
      @indent = indent
      @level  = initial
    end
    
    # Create a tag named +sym+.  Other than the first argument which
    # is the tag name, the arguements are the same as the tags
    # implemented via <tt>method_missing</tt>.
    def tag!(sym, *args, &block)
      self.__send__(sym, *args, &block)
    end

    # Create XML markup based on the name of the method.  This method
    # is never invoked directly, but is called for each markup method
    # in the markup block.
    def method_missing(sym, *args, &block)
      text = nil
      attrs = nil
      sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol)
      args.each do |arg|
	case arg
	when Hash
	  attrs ||= {}
	  attrs.merge!(arg)
	else
	  text ||= ''
	  text << arg.to_s
	end
      end
      if block
	unless text.nil?
	  raise ArgumentError, "XmlMarkup cannot mix a text argument with a block"
	end
	_capture_outer_self(block) if @self.nil?
	_indent
	_start_tag(sym, attrs)
	_newline
	_nested_structures(block)
	_indent
	_end_tag(sym)
	_newline
      elsif text.nil?
	_indent
	_start_tag(sym, attrs, true)
	_newline
      else
	_indent
	_start_tag(sym, attrs)
	text! text
	_end_tag(sym)
	_newline
      end
      @target
    end

    # Append text to the output target.  Escape any markup.  May be
    # used within the markup brackets as:
    #
    #   builder.p { br; text! "HI" }   #=>  <p><br/>HI</p>
    def text!(text)
      _text(_escape(text))
    end
    
    # Append text to the output target without escaping any markup.
    # May be used within the markup brackets as:
    #
    #   builder.p { |x| x << "<br/>HI" }   #=>  <p><br/>HI</p>
    #
    # This is useful when using non-builder enabled software that
    # generates strings.  Just insert the string directly into the
    # builder without changing the inserted markup.
    #
    # It is also useful for stacking builder objects.  Builders only
    # use <tt><<</tt> to append to the target, so by supporting this
    # method/operation builders can use other builders as their
    # targets.
    def <<(text)
      _text(text)
    end
    
    # For some reason, nil? is sent to the XmlMarkup object.  If nil?
    # is not defined and method_missing is invoked, some strange kind
    # of recursion happens.  Since nil? won't ever be an XML tag, it
    # is pretty safe to define it here. (Note: this is an example of
    # cargo cult programming,
    # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).
    def nil?
      false
    end

    private
    
    def _escape(text)
      text.
	gsub(%r{&}, '&amp;').
	gsub(%r{<}, '&lt;').
	gsub(%r{>}, '&gt;')
    end

    def _capture_outer_self(block)
      @self = eval("self", block)
    end
    
    def _newline
      return if @indent == 0
      text! "\n"
    end
    
    def _indent
      return if @indent == 0 || @level == 0
      text!(" " * (@level * @indent))
    end
    
    def _nested_structures(block)
      @level += 1
      block.call(self)
    ensure
      @level -= 1
    end
  end
end
#!/usr/bin/env ruby

#--
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++

require 'builder/xmlmarkup'

module Builder

  # Create a series of SAX-like XML events (e.g. start_tag, end_tag)
  # from the markup code.  XmlEvent objects are used in a way similar
  # to XmlMarkup objects, except that a series of events are generated
  # and passed to a handler rather than generating character-based
  # markup.
  #
  # Usage:
  #   xe = Builder::XmlEvents.new(hander)
  #   xe.title("HI")    # Sends start_tag/end_tag/text messages to the handler.
  #
  # Indentation may also be selected by providing value for the
  # indentation size and initial indentation level.
  #
  #   xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level)
  #
  # == XML Event Handler
  #
  # The handler object must expect the following events.
  #
  # [<tt>start_tag(tag, attrs)</tt>]
  #     Announces that a new tag has been found.  +tag+ is the name of
  #     the tag and +attrs+ is a hash of attributes for the tag.
  #
  # [<tt>end_tag(tag)</tt>]
  #     Announces that an end tag for +tag+ has been found.
  #
  # [<tt>text(text)</tt>]
  #     Announces that a string of characters (+text+) has been found.
  #     A series of characters may be broken up into more than one
  #     +text+ call, so the client cannot assume that a single
  #     callback contains all the text data.
  #
  class XmlEvents < XmlMarkup #:nodoc:
    def text!(text)
      @target.text(text)
    end

    def _start_tag(sym, attrs, end_too=false)
      @target.start_tag(sym, attrs)
      _end_tag(sym) if end_too
    end

    def _end_tag(sym)
      @target.end_tag(sym)
    end
  end

end
#!/usr/bin/env ruby
#--
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++

# Provide a flexible and easy to use Builder for creating XML markup.
# See XmlBuilder for usage details.

require 'builder/xmlbase'

module Builder

  # Create XML markup easily.  All (well, almost all) methods sent to
  # an XmlMarkup object will be translated to the equivalent XML
  # markup.  Any method with a block will be treated as an XML markup
  # tag with nested markup in the block.
  #
  # Examples will demonstrate this easier than words.  In the
  # following, +xm+ is an +XmlMarkup+ object.
  #
  #   xm.em("emphasized")             # => <em>emphasized</em>
  #   xm.em { xmm.b("emp & bold") }   # => <em><b>emph &amp; bold</b></em>
  #   xm.a("A Link", "href"=>"http://onestepback.org")
  #                                   # => <a href="http://onestepback.org">A Link</a>
  #   xm.div { br }                    # => <div><br/></div>
  #   xm.target("name"=>"compile", "option"=>"fast")
  #                                   # => <target option="fast" name="compile"\>
  #                                   # NOTE: order of attributes is not specified.
  #
  #   xm.instruct!                   # <?xml version="1.0" encoding="UTF-8"?>
  #   xm.html {                      # <html>
  #     xm.head {                    #   <head>
  #       xm.title("History")        #     <title>History</title>
  #     }                            #   </head>
  #     xm.body {                    #   <body>
  #       xm.comment! "HI"           #     <!-- HI -->
  #       xm.h1("Header")            #     <h1>Header</h1>
  #       xm.p("paragraph")          #     <p>paragraph</p>
  #     }                            #   </body>
  #   }                              # </html>
  #
  # == Notes:
  #
  # * The order that attributes are inserted in markup tags is
  #   undefined. 
  #
  # * Sometimes you wish to insert text without enclosing tags.  Use
  #   the <tt>text!</tt> method to accomplish this.
  #
  #   Example:
  #
  #     xm.div {                          # <div>
  #       xm.text! "line"; xm.br          #   line<br/>
  #       xm.text! "another line"; xmbr   #    another line<br/>
  #     }                                 # </div>
  #
  # * The special XML characters <, >, and & are converted to &lt;,
  #   &gt; and &amp; automatically.  Use the <tt><<</tt> operation to
  #   insert text without modification.
  #
  # * Sometimes tags use special characters not allowed in ruby
  #   identifiers.  Use the <tt>tag!</tt> method to handle these
  #   cases.
  #
  #   Example:
  #
  #     xml.tag!("SOAP:Envelope") { ... }
  #
  #   will produce ...
  #
  #     <SOAP:Envelope> ... </SOAP:Envelope>"
  #
  #   <tt>tag!</tt> will also take text and attribute arguments (after
  #   the tag name) like normal markup methods.  (But see the next
  #   bullet item for a better way to handle XML namespaces).
  #   
  # * Direct support for XML namespaces is now available.  If the
  #   first argument to a tag call is a symbol, it will be joined to
  #   the tag to produce a namespace:tag combination.  It is easier to
  #   show this than describe it.
  #
  #     xml.SOAP :Envelope do ... end
  #
  #   Just put a space before the colon in a namespace to produce the
  #   right form for builder (e.g. "<tt>SOAP:Envelope</tt>" =>
  #   "<tt>xml.SOAP :Envelope</tt>")
  #
  # * XmlMarkup builds the markup in any object (called a _target_)
  #   that accepts the <tt><<</tt> method.  If no target is given,
  #   then XmlMarkup defaults to a string target.
  # 
  #   Examples:
  #
  #     xm = Builder::XmlMarkup.new
  #     result = xm.title("yada")
  #     # result is a string containing the markup.
  #
  #     buffer = ""
  #     xm = Builder::XmlMarkup.new(buffer)
  #     # The markup is appended to buffer (using <<)
  #
  #     xm = Builder::XmlMarkup.new(STDOUT)
  #     # The markup is written to STDOUT (using <<)
  #
  #     xm = Builder::XmlMarkup.new
  #     x2 = Builder::XmlMarkup.new(:target=>xm)
  #     # Markup written to +x2+ will be send to +xm+.
  #   
  # * Indentation is enabled by providing the number of spaces to
  #   indent for each level as a second argument to XmlBuilder.new.
  #   Initial indentation may be specified using a third parameter.
  #
  #   Example:
  #
  #     xm = Builder.new(:ident=>2)
  #     # xm will produce nicely formatted and indented XML.
  #  
  #     xm = Builder.new(:indent=>2, :margin=>4)
  #     # xm will produce nicely formatted and indented XML with 2
  #     # spaces per indent and an over all indentation level of 4.
  #
  #     builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2)
  #     builder.name { |b| b.first("Jim"); b.last("Weirich) }
  #     # prints:
  #     #     <name>
  #     #       <first>Jim</first>
  #     #       <last>Weirich</last>
  #     #     </name>
  #
  # * The instance_eval implementation which forces self to refer to
  #   the message receiver as self is now obsolete.  We now use normal
  #   block calls to execute the markup block.  This means that all
  #   markup methods must now be explicitly send to the xml builder.
  #   For instance, instead of
  #
  #      xml.div { strong("text") }
  #
  #   you need to write:
  #
  #      xml.div { xml.strong("text") }
  #
  #   Although more verbose, the subtle change in semantics within the
  #   block was found to be prone to error.  To make this change a
  #   little less cumbersome, the markup block now gets the markup
  #   object sent as an argument, allowing you to use a shorter alias
  #   within the block.
  #
  #   For example:
  #
  #     xml_builder = Builder::XmlMarkup.new
  #     xml_builder.div { |xml|
  #       xml.stong("text")
  #     }
  #
  class XmlMarkup < XmlBase

    # Create an XML markup builder.  Parameters are specified by an
    # option hash.
    #
    # :target=><em>target_object</em>::
    #    Object receiving the markup.  +out+ must respond to the
    #    <tt><<</tt> operator.  The default is a plain string target.
    # :indent=><em>indentation</em>::
    #    Number of spaces used for indentation.  The default is no
    #    indentation and no line breaks.
    # :margin=><em>initial_indentation_level</em>::
    #    Amount of initial indentation (specified in levels, not
    #    spaces).
    #    
    def initialize(options={})
      indent = options[:indent] || 0
      margin = options[:margin] || 0
      super(indent, margin)
      @target = options[:target] || ""
    end
    
    # Return the target of the builder.
    def target!
      @target
    end

    def comment!(comment_text)
      _ensure_no_block block_given?
      _special("<!-- ", " -->", comment_text, nil)
    end

    # Insert an XML declaration into the XML markup.
    #
    # For example:
    #
    #   xml.declare! :ELEMENT, :blah, "yada"
    #       # => <!ELEMENT blah "yada">
    def declare!(inst, *args, &block)
      _indent
      @target << "<!#{inst}"
      args.each do |arg|
	case arg
	when String
	  @target << %{ "#{arg}"}
	when Symbol
	  @target << " #{arg}"
	end
      end
      if block_given?
	@target << " ["
	_newline
	_nested_structures(block)
	@target << "]"
      end
      @target << ">"
      _newline
    end

    # Insert a processing instruction into the XML markup.  E.g.
    #
    # For example:
    #
    #    xml.instruct!
    #        #=> <?xml version="1.0" encoding="UTF-8"?>
    #    xml.instruct! :aaa, :bbb=>"ccc"
    #        #=> <?aaa bbb="ccc"?>
    #
    def instruct!(directive_tag=:xml, attrs={})
      _ensure_no_block block_given?
      if directive_tag == :xml
	a = { :version=>"1.0", :encoding=>"UTF-8" }
	attrs = a.merge attrs
      end
      _special(
	"<?#{directive_tag}",
	"?>",
	nil,
	attrs,
	[:version, :encoding, :standalone])
    end

    # Surrounds the given text with a CDATA tag
    #
    # For example:
    #
    #   xml.cdata! "blah blah blah"
    #       # => <![CDATA[blah blah blah]]>
    def cdata!(text)
      _ensure_no_block block_given?
      _special("<![CDATA[", "]]>", text, nil)
    end
    
    private

    # NOTE: All private methods of a builder object are prefixed when
    # a "_" character to avoid possible conflict with XML tag names.

    # Insert text directly in to the builder's target.
    def _text(text)
      @target << text
    end
    
    # Insert special instruction. 
    def _special(open, close, data=nil, attrs=nil, order=[])
      _indent
      @target << open
      @target << data if data
      _insert_attributes(attrs, order) if attrs
      @target << close
      _newline
    end

    # Start an XML tag.  If <tt>end_too</tt> is true, then the start
    # tag is also the end tag (e.g.  <br/>
    def _start_tag(sym, attrs, end_too=false)
      @target << "<#{sym}"
      _insert_attributes(attrs)
      @target << "/" if end_too
      @target << ">"
    end
    
    # Insert an ending tag.
    def _end_tag(sym)
      @target << "</#{sym}>"
    end

    # Insert the attributes (given in the hash).
    def _insert_attributes(attrs, order=[])
      return if attrs.nil?
      order.each do |k|
	v = attrs[k]
	@target << %{ #{k}="#{v}"} if v
      end
      attrs.each do |k, v|
	@target << %{ #{k}="#{v}"} unless order.member?(k)
      end
    end

    def _ensure_no_block(got_block)
      if got_block
	fail IllegalBlockError,
	  "Blocks are not allowed on XML instructions"
      end
    end

  end

end
#!/usr/bin/env ruby

#--
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++

require 'builder/xmlmarkup'
require 'builder/xmlevents'
module ActiveSupport
  module VERSION #:nodoc:
    MAJOR = 1
    MINOR = 3
    TINY  = 1

    STRING = [MAJOR, MINOR, TINY].join('.')
  end
end
# Extensions to nil which allow for more helpful error messages for 
# people who are new to rails.
#
# The aim is to ensure that when users pass nil to methods where that isn't
# appropriate, instead of NoMethodError and the name of some method used
# by the framework users will see a message explaining what type of object 
# was expected.

class NilClass
  WHINERS = [ ::ActiveRecord::Base, ::Array ]
  
  @@method_class_map = Hash.new
  
  WHINERS.each do |klass|
    methods = klass.public_instance_methods - public_instance_methods
    methods.each do |method|
      @@method_class_map[method.to_sym] = klass
    end
  end
  
  def id
    raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller
  end

  private
    def method_missing(method, *args, &block)
      raise_nil_warning_for @@method_class_map[method], method, caller
    end

    def raise_nil_warning_for(klass = nil, selector = nil, with_caller = nil)
      message = "You have a nil object when you didn't expect it!"
      message << "\nYou might have expected an instance of #{klass}." if klass
      message << "\nThe error occured while evaluating nil.#{selector}" if selector
      
      raise NoMethodError, message, with_caller || caller
    end
end

#--
# Copyright (c) 2005 David Heinemeier Hansson
#
# 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.
#++

$:.unshift(File.dirname(__FILE__))
$:.unshift(File.dirname(__FILE__) + "/active_support/vendor")

require 'builder'

require 'active_support/inflector'

require 'active_support/core_ext'
require 'active_support/clean_logger'
require 'active_support/dependencies'
require 'active_support/reloadable'

require 'active_support/ordered_options'
require 'active_support/option_merger'

require 'active_support/values/time_zone'

require 'active_support/json'