diff --git a/README b/README new file mode 100644 index 0000000..7d8965e --- /dev/null +++ b/README @@ -0,0 +1,183 @@ +== Welcome to Rails + +Rails is a web-application and persistence framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-built data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting started + +1. Start the web server: ruby script/server (run with --help for options) +2. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" +3. Follow the guidelines to start developing your application + + +== Web servers + +Rails uses the built-in web server in Ruby called WEBrick by default, so you don't +have to install or configure anything to play around. + +If you have lighttpd installed, though, it'll be used instead when running script/server. +It's considerably faster than WEBrick and suited for production use, but requires additional +installation and currently only works well on OS X/Unix (Windows users are encouraged +to start with WEBrick). We recommend version 1.4.11 and higher. You can download it from +http://www.lighttpd.net. + +If you want something that's halfway between WEBrick and lighttpd, we heartily recommend +Mongrel. It's a Ruby-based web server with a C-component (so it requires compilation) that +also works very well with Windows. See more at http://mongrel.rubyforge.org/. + +But of course its also possible to run Rails with the premiere open source web server Apache. +To get decent performance, though, you'll need to install FastCGI. For Apache 1.3, you want +to use mod_fastcgi. For Apache 2.0+, you want to use mod_fcgid. + +See http://wiki.rubyonrails.com/rails/pages/FastCGI for more information on FastCGI. + +== Example for Apache conf + + + ServerName rails + DocumentRoot /path/application/public/ + ErrorLog /path/application/log/server.log + + + Options ExecCGI FollowSymLinks + AllowOverride all + Allow from all + Order allow,deny + + + +NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI +should be on and ".cgi" should respond. All requests from 127.0.0.1 go +through CGI, so no Apache restart is necessary for changes. All other requests +go through FCGI (or mod_ruby), which requires a restart to show changes. + + +== Debugging Rails + +Have "tail -f" commands running on both the server.log, production.log, and +test.log files. Rails will automatically display debugging and runtime +information to these files. Debugging info will also be shown in the browser +on requests from 127.0.0.1. + + +== Breakpoints + +Breakpoint support is available through the script/breakpointer client. This +means that you can break out of execution at any point in the code, investigate +and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find_all + breakpoint "Breaking out from the list" + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the breakpointer window. Here you can do things like: + +Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a breakpoint" + => "hello from a breakpoint" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you press CTRL-D + + +== Console + +You can interact with the domain model by starting the console through script/console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like script/console production. + +To reload your controllers and models after launching the console run reload! + + + +== Description of contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblog_controller.rb for + automated URL mapping. All controllers should descend from + ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblog/index.rhtml for the WeblogController#index action. All views use eRuby + syntax. This directory can also be used to keep stylesheets, images, and so on + that can be symlinked to public. + +app/helpers + Holds view helpers that should be named like weblog_helper.rb. + +app/apis + Holds API classes for web services. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +components + Self-contained mini-applications that can bundle together controllers, models, and views. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + This directory is in the load path. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/app/controllers/application.rb b/app/controllers/application.rb new file mode 100644 index 0000000..e72e0a4 --- /dev/null +++ b/app/controllers/application.rb @@ -0,0 +1,6 @@ +# Filters added to this controller will be run for all controllers in the application. +# Likewise, all the methods added will be available for all controllers. +class ApplicationController < ActionController::Base + include ExceptionNotifiable + +end \ No newline at end of file diff --git a/app/controllers/appointments_controller.rb b/app/controllers/appointments_controller.rb new file mode 100644 index 0000000..73373d9 --- /dev/null +++ b/app/controllers/appointments_controller.rb @@ -0,0 +1,87 @@ +class AppointmentsController < ApplicationController + # Configuration + @@start_hour = 8 + @@end_hour = 18 + @@default_column_count = 5 + + before_filter :load_first_minute + + def index + begin + @date = Date.new(params[:year].to_i, params[:month].to_i, params[:day].to_i) + rescue ArgumentError + @date = Date.today + end + + @times = (@@start_hour..(@@end_hour - 1)).collect {|h| @date.to_time + h.hours} + + @providers = HealthcareProvider.find(:all, :order => 'name') + + num_columns = (@providers.length < @@default_column_count ? @providers.length : @@default_column_count) + @columns = (0..(num_columns - 1)).collect { |index| column_for_provider(@providers[index], @date, index) } + end + + def index_for_provider + @date = Date.new(params[:year].to_i, params[:month].to_i, params[:day].to_i) + @provider = HealthcareProvider.find(params[:provider_id]) + @column = column_for_provider(@provider, @date) + + render :layout => false + end + + def destroy + @appointment = Appointment.find(params[:id]) + @appointment.destroy + end + + def new + @date = Date.new(params[:year].to_i, params[:month].to_i, params[:day].to_i) + @minute = params[:minute].to_i + + @appointment = Appointment.new + @appointment.healthcare_provider = HealthcareProvider.find(params[:provider_id]) + @appointment.start_time = @date.to_time + @minute.minutes + + render :layout => false + end + + def create + @appointment = Appointment.new(params[:appointment]) + if @appointment.valid? + @saved = true + @appointment.create + end + end + + def edit + @appointment = Appointment.find(params[:id]) + render :layout => false + end + + def update + @appointment = Appointment.find(params[:id]) + @saved = @appointment.update_attributes(params[:appointment]) + end + + def update_in_place + @appointment = Appointment.find(params[:id]) + @saved = @appointment.update_attributes(params[:appointment]) + end + +private + + def column_for_provider(provider, date, index = nil) + { + :index => index, + :provider => provider, + :appointments => provider.appointments_on(date), + :slots => TimeSlot.range(@@start_hour * 60, @@end_hour * 60, provider.default_appointment_duration) + } + end + + def load_first_minute + @first_minute = @@start_hour.hours / 60 + true + end + +end diff --git a/app/controllers/patients_controller.rb b/app/controllers/patients_controller.rb new file mode 100644 index 0000000..e0f8d70 --- /dev/null +++ b/app/controllers/patients_controller.rb @@ -0,0 +1,5 @@ +class PatientsController < ApplicationController + + auto_complete_for :patient, :name + +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..d31bd65 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,4 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper + +end diff --git a/app/helpers/appointments_helper.rb b/app/helpers/appointments_helper.rb new file mode 100644 index 0000000..cd6e1c0 --- /dev/null +++ b/app/helpers/appointments_helper.rb @@ -0,0 +1,14 @@ +module AppointmentsHelper + + def format_time(time) + twelve_hour = time.hour % 12 + twelve_hour = 12 if twelve_hour == 0 + time.strftime("#{twelve_hour}:%M %p").downcase + end + + def day_url(date) + url_for :controller => 'appointments', :action => 'index', :year => date.year, :month => date.mon, :day => date.day + end + + +end diff --git a/app/helpers/patients_helper.rb b/app/helpers/patients_helper.rb new file mode 100644 index 0000000..e3835d0 --- /dev/null +++ b/app/helpers/patients_helper.rb @@ -0,0 +1,2 @@ +module PatientsHelper +end diff --git a/app/models/appointment.rb b/app/models/appointment.rb new file mode 100644 index 0000000..52e5ac1 --- /dev/null +++ b/app/models/appointment.rb @@ -0,0 +1,65 @@ +class Appointment < ActiveRecord::Base + belongs_to :patient + belongs_to :healthcare_provider + belongs_to :appointment_type + belongs_to :state, :class_name => 'AppointmentState', :foreign_key => 'appointment_state_id' + has_and_belongs_to_many :categories, :class_name => 'AppointmentCategory' + + validates_presence_of :state, :patient, :healthcare_provider, :start_time, :appointment_type + validate :provider_is_available + + before_validation :update_end_time + + def initialize(attributes = nil) + state = AppointmentState.default + super(attributes) + end + + def label + patient.nil? ? 'new' : patient.name + end + + def start_minute + unless start_time.nil? + start_time.hour * 60 + start_time.min + end + end + + alias_method :original_healthcare_provider=, :healthcare_provider= + + def healthcare_provider=(provider) + self.appointment_type = (provider.nil? ? nil : provider.default_appointment_type) + self.original_healthcare_provider = provider + end + + def duration + !duration_override.blank? ? duration_override : appointment_type.duration + end + + def patient_name + patient.name unless patient.nil? + end + + def patient_name=(value) + self.patient = Patient.find(:first, :conditions => ["name = ?", value]) + end + + def category_ids=(category_id_array) + self.categories = category_id_array.reject{ |category_id| category_id == '' }.collect { |category_id| AppointmentCategory.find(category_id.to_i) } + end + +protected + + def provider_is_available + if !healthcare_provider.nil? && !start_time.nil? && !duration.nil? && !healthcare_provider.is_available_between(start_time, end_time, id) + errors.add(:start_time, "and Duration clash with another appointment for this Healthcare Provider.") + end + end + + def update_end_time + unless start_time.nil? || duration.nil? + self.end_time = start_time + duration.minutes + end + end + +end diff --git a/app/models/appointment_category.rb b/app/models/appointment_category.rb new file mode 100644 index 0000000..106d840 --- /dev/null +++ b/app/models/appointment_category.rb @@ -0,0 +1,3 @@ +class AppointmentCategory < ActiveRecord::Base + validates_presence_of :name +end diff --git a/app/models/appointment_state.rb b/app/models/appointment_state.rb new file mode 100644 index 0000000..c4b2be1 --- /dev/null +++ b/app/models/appointment_state.rb @@ -0,0 +1,7 @@ +class AppointmentState < ActiveRecord::Base + + def self.default + AppointmentState.find(1) + end + +end diff --git a/app/models/appointment_type.rb b/app/models/appointment_type.rb new file mode 100644 index 0000000..774f994 --- /dev/null +++ b/app/models/appointment_type.rb @@ -0,0 +1,6 @@ +class AppointmentType < ActiveRecord::Base + belongs_to :healthcare_provider + validates_presence_of :healthcare_provider, :duration + + +end diff --git a/app/models/healthcare_provider.rb b/app/models/healthcare_provider.rb new file mode 100644 index 0000000..4daac1c --- /dev/null +++ b/app/models/healthcare_provider.rb @@ -0,0 +1,30 @@ +class HealthcareProvider < ActiveRecord::Base + has_many :appointments + has_many :appointment_types + belongs_to :default_appointment_type, :class_name => 'AppointmentType', :foreign_key => 'default_appointment_type_id' + + validates_presence_of :name, :default_appointment_type + + def appointments_on(date) + appointments.find(:all, :order => 'start_time ASC', :conditions => ["start_time >= ? and start_time < ?", date.to_time.beginning_of_day, date.to_time.beginning_of_day.tomorrow]) + end + + def create_default_appointment_types + appointment_types = [] + appointment_types << AppointmentType.create(:name => 'A', :duration => 5, :healthcare_provider => self) + appointment_types << AppointmentType.create(:name => 'B', :duration => 20, :healthcare_provider => self) + appointment_types << AppointmentType.create(:name => 'C', :duration => 40, :healthcare_provider => self) + appointment_types << AppointmentType.create(:name => 'D', :duration => 60, :healthcare_provider => self) + default_appointment_type = appointment_types[1] + save! + end + + def default_appointment_duration + default_appointment_type.duration + end + + def is_available_between(start_time, end_time, exception_id) + appointments.find(:first, :conditions => ["id != ? AND ((start_time >= ? AND start_time < ?) OR (end_time > ? AND end_time <= ?) OR (start_time <= ? AND end_time >= ?))", exception_id, start_time, end_time, start_time, end_time, start_time, end_time]).nil? + end + +end diff --git a/app/models/patient.rb b/app/models/patient.rb new file mode 100644 index 0000000..1854abf --- /dev/null +++ b/app/models/patient.rb @@ -0,0 +1,16 @@ +class Patient < ActiveRecord::Base + + validates_presence_of :first_name, :surname + before_save :update_name + + def name + first_name.to_s + ' ' + surname.to_s + end + +private + + def update_name + self.name = name + end + +end diff --git a/app/models/time_slot.rb b/app/models/time_slot.rb new file mode 100644 index 0000000..b12e15e --- /dev/null +++ b/app/models/time_slot.rb @@ -0,0 +1,31 @@ + +class TimeSlot + include Reloadable + + def self.range(start_minute, end_minute, duration) + results = [] + + slot = TimeSlot.new(start_minute, duration) + while slot.end_minute <= end_minute + results << slot + slot = TimeSlot.new(slot.end_minute, duration) + end + results + end + + def initialize(start_minute, duration) + @start_minute = start_minute + @duration = duration + end + + attr_accessor :start_minute, :duration + + def end_minute + start_minute + duration + end + + def start_time + Time.new.midnight + start_minute * 60 + end + +end \ No newline at end of file diff --git a/app/views/appointments/_appointment.rhtml b/app/views/appointments/_appointment.rhtml new file mode 100644 index 0000000..80f3358 --- /dev/null +++ b/app/views/appointments/_appointment.rhtml @@ -0,0 +1,43 @@ +
  • +
    + <%= link_to appointment.state.name, '#', :onclick => "Element.hide(this); Element.show('#{dom_id(appointment, 'state')}'); Event.stop(event);" %> + <% @appointment = appointment %> + + <% apply_behaviour '#' + dom_id(appointment, 'state') + ':click', "Event.stop(event)" %> + <% apply_behaviour '#' + dom_id(appointment, 'state') + ':change', + remote_function( + :url => {:controller => 'appointments', :action => 'update_in_place', :id => appointment}, + :with => "'appointment[appointment_state_id]=' + this.value" + ) + "; Event.stop(event);" + %> +
    + + <%= appointment.label %> + + +
  • +<% apply_behaviour "##{dom_id(appointment)}", "this.setAttribute('duration', #{appointment.duration}); setInitialAppointmentSize(this)" %> diff --git a/app/views/appointments/_appointments.rhtml b/app/views/appointments/_appointments.rhtml new file mode 100644 index 0000000..9095c54 --- /dev/null +++ b/app/views/appointments/_appointments.rhtml @@ -0,0 +1,6 @@ +
      + <% for appointment in appointments %> + <%= render :partial => 'appointment', :object => appointment, :locals => {:expanded => false} %> + <% end %> +
    +<% apply_behaviour 'li.appointment:click', "toggleAppointmentSize(this);" %> \ No newline at end of file diff --git a/app/views/appointments/_column.rhtml b/app/views/appointments/_column.rhtml new file mode 100644 index 0000000..b983b96 --- /dev/null +++ b/app/views/appointments/_column.rhtml @@ -0,0 +1,15 @@ +<% for slot in column[:slots] %> + <%= link_to_remote_redbox(format_time(slot.start_time), + { + :url => {:controller => 'appointments', :action => 'new', :year => @date.year, :month => @date.mon, :day => @date.day, :minute => slot.start_minute, :provider_id => column[:provider].to_param} + }, + { + :class => 'slot' + (slot == column[:slots].last ? ' last' : ''), + :id => "#{dom_id(column[:provider])}_slot_#{slot.start_minute}" + }) %> + <% apply_behaviour "##{dom_id(column[:provider])}_slot_#{slot.start_minute}", "this.setAttribute('duration', #{slot.duration}); setDefaultSlotSize(this)" %> +<% end %> + +
    + <%= render :partial => 'appointments', :object => column[:appointments], :locals => {:provider => column[:provider]} %> +
    diff --git a/app/views/appointments/_edit_form.rhtml b/app/views/appointments/_edit_form.rhtml new file mode 100644 index 0000000..eb421dd --- /dev/null +++ b/app/views/appointments/_edit_form.rhtml @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/app/views/appointments/_form.rhtml b/app/views/appointments/_form.rhtml new file mode 100644 index 0000000..9ac4081 --- /dev/null +++ b/app/views/appointments/_form.rhtml @@ -0,0 +1,54 @@ +<%= error_messages_for 'appointment' %> + +
    + + <%= text_field_with_auto_complete 'appointment', 'patient_name', {}, {:url => {:controller => 'patients', :action => 'auto_complete_for_patient_name' }, :with => "'patient[name]=' + $('appointment_patient_name').value"} %> +
    +
    + +
    + + <%= collection_select 'appointment', 'appointment_state_id', AppointmentState.find(:all, :order => 'name'), 'id', 'name', :class => 'input' %> +
    +
    + +
    + +
    + <%= associated_list 'appointment', 'categories' %> +
    +
    +
    + +
    + +
    + Consultation Type <%= collection_select 'appointment', 'appointment_type_id', @appointment.healthcare_provider.appointment_types, 'id', 'name' %> + or + <%= text_field 'appointment', 'duration_override', {:class => 'duration'} %> minutes +
    +
    +
    + +
    + + <%= collection_select 'appointment', 'healthcare_provider_id', HealthcareProvider.find(:all, :order => 'name'), 'id', 'name', {}, :class => 'input' %> +
    +
    + +
    + +
    + <%= datetime_select 'appointment', 'start_time' %> +
    +
    +
    + +
    + +
    + <%= text_area 'appointment', 'comment' %> +
    +
    +
    +<%= apply_behaviour '#appointment_patient_name', 'this.focus()', :external => false %> diff --git a/app/views/appointments/_new_form.rhtml b/app/views/appointments/_new_form.rhtml new file mode 100644 index 0000000..f16cdbd --- /dev/null +++ b/app/views/appointments/_new_form.rhtml @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/app/views/appointments/create.rjs b/app/views/appointments/create.rjs new file mode 100644 index 0000000..234571d --- /dev/null +++ b/app/views/appointments/create.rjs @@ -0,0 +1,10 @@ +if @saved + page.insert_html :bottom, "appointments_provider_#{@appointment.healthcare_provider.id}", :partial => 'appointment', :object => @appointment, :locals => {:expanded => false} + page << "$('#{dom_id(@appointment)}').setAttribute('duration', #{@appointment.duration});" + page << "setInitialAppointmentSize($('#{dom_id(@appointment)}'))" + page.hide dom_id(@appointment) + page.call "RedBox.close" + page.visual_effect :appear, dom_id(@appointment), :duration => 0.3, :queue => 'end' +else + page.replace 'appointment_form', :partial => 'new_form' +end \ No newline at end of file diff --git a/app/views/appointments/destroy.rjs b/app/views/appointments/destroy.rjs new file mode 100644 index 0000000..ee8743d --- /dev/null +++ b/app/views/appointments/destroy.rjs @@ -0,0 +1 @@ +page.visual_effect :fade, dom_id(@appointment) \ No newline at end of file diff --git a/app/views/appointments/edit.rhtml b/app/views/appointments/edit.rhtml new file mode 100644 index 0000000..2a2111b --- /dev/null +++ b/app/views/appointments/edit.rhtml @@ -0,0 +1 @@ +<%= render :partial => 'edit_form' %> \ No newline at end of file diff --git a/app/views/appointments/index.rhtml b/app/views/appointments/index.rhtml new file mode 100644 index 0000000..88d510a --- /dev/null +++ b/app/views/appointments/index.rhtml @@ -0,0 +1,82 @@ +<% require_bundle :dynarch_calendar %> +
    + +
    + <%= link_to " <<", day_url(@date.to_time.last_month.to_date), :class => 'last_month' %> + <%= link_to " >>", day_url(@date.to_time.next_month.to_date), :class => 'next_month' %> + <%= link_to " < ", day_url(@date - 1), :class => 'yesterday' %> + <%= link_to " > ", day_url(@date + 1), :class => 'tomorrow' %> +

    <%= @date.to_formatted_s :long %>

    +
    +
    + +
    +
    +
     
    + + <% for column in @columns %> + <% @provider = column[:provider] %> +
    + <%= collection_select('provider', 'id', @providers, 'id', 'name', {}, :id => "provider_selector_#{column[:index]}") %> + <% apply_behaviour "#provider_selector_#{column[:index]}:change", + remote_function( + :url => {:controller => 'appointments', :action => 'index_for_provider', :year => @date.year, :month => @date.mon, :day => @date.day}, + :with => "'provider_id=' + this.value", + :update => "column_#{column[:index]}", + :before => visual_effect(:fade, "provider_selector_#{column[:index]}", :duration => 0.2, :to => 0.2), + :complete => visual_effect(:appear, "provider_selector_#{column[:index]}", :duration => 0.2) + ) + %> +
    + <% end %> + +
    + +
    + <% for time in @times %> +
    +
    + <%= format_time time %> +
    +
    +   +
    +
    + <% end %> +
    + + <% index = 5 %> + <% for column in @columns %> +
    + <%= render :partial => 'column', :object => column %> +
    + <% index = index - 1 %> + <% end %> + +
    \ No newline at end of file diff --git a/app/views/appointments/index_for_provider.rhtml b/app/views/appointments/index_for_provider.rhtml new file mode 100644 index 0000000..cc83e6b --- /dev/null +++ b/app/views/appointments/index_for_provider.rhtml @@ -0,0 +1 @@ +<%= render :partial => 'column', :object => @column %> \ No newline at end of file diff --git a/app/views/appointments/new.rhtml b/app/views/appointments/new.rhtml new file mode 100644 index 0000000..bf599a7 --- /dev/null +++ b/app/views/appointments/new.rhtml @@ -0,0 +1 @@ +<%= render :partial => 'new_form' %> \ No newline at end of file diff --git a/app/views/appointments/update.rjs b/app/views/appointments/update.rjs new file mode 100644 index 0000000..d027968 --- /dev/null +++ b/app/views/appointments/update.rjs @@ -0,0 +1,8 @@ +if @saved + page.replace dom_id(@appointment), :partial => 'appointment', :object => @appointment, :locals => {:expanded => true} + page.call "RedBox.close" + page << "$('#{dom_id(@appointment)}').setAttribute('duration', #{@appointment.duration});" + page << "setInitialAppointmentSize($('#{dom_id(@appointment)}'))" +else + page.replace 'appointment_form', :partial => 'new_form' +end \ No newline at end of file diff --git a/app/views/appointments/update_in_place.rjs b/app/views/appointments/update_in_place.rjs new file mode 100644 index 0000000..ed85e96 --- /dev/null +++ b/app/views/appointments/update_in_place.rjs @@ -0,0 +1,5 @@ +if @saved + page.replace dom_id(@appointment), :partial => 'appointment', :object => @appointment, :locals => {:expanded => true} + page << "$('#{dom_id(@appointment)}').setAttribute('duration', #{@appointment.duration});" + page << "setInitialAppointmentSize($('#{dom_id(@appointment)}'))" +end \ No newline at end of file diff --git a/app/views/days/show.rhtml b/app/views/days/show.rhtml new file mode 100644 index 0000000..5b19477 --- /dev/null +++ b/app/views/days/show.rhtml @@ -0,0 +1 @@ +fish \ No newline at end of file diff --git a/app/views/layouts/application.rhtml b/app/views/layouts/application.rhtml new file mode 100644 index 0000000..3840e3d --- /dev/null +++ b/app/views/layouts/application.rhtml @@ -0,0 +1,29 @@ + + + + + + + <%= stylesheet_link_tag :defaults, 'redbox' %> + <%= stylesheet_link_tag 'associated_list' %> + <%= stylesheet_auto_link_tags %> + + <%= javascript_include_tag :defaults, 'more_effects', 'redbox', :unobtrusive %> + <%= javascript_auto_include_tags %> + + Appointments System + + + + +
    + + + <%= yield %> +
    + + + diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..9a094cb --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,44 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + + unless RUBY_PLATFORM =~ /mswin32/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath(true).to_s + end + + RAILS_ROOT = root_path +end + +unless defined?(Rails::Initializer) + if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + else + require 'rubygems' + + environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join + environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/ + rails_gem_version = $1 + + if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version + rails_gem = Gem.cache.search('rails', "=#{version}").first + + if rails_gem + require_gem "rails", "=#{version}" + require rails_gem.full_gem_path + '/lib/initializer' + else + STDERR.puts %(Cannot find gem for Rails =#{version}: + Install the missing gem with 'gem install -v=#{version} rails', or + change environment.rb to define RAILS_GEM_VERSION with your desired version. + ) + exit 1 + end + else + require_gem "rails" + require 'initializer' + end + end + + Rails::Initializer.run(:set_load_path) +end \ No newline at end of file diff --git a/config/database.example b/config/database.example new file mode 100644 index 0000000..954033d --- /dev/null +++ b/config/database.example @@ -0,0 +1,35 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# There is no gem for Windows. Install mysql.so from RubyForApache. +# http://rubyforge.org/projects/rubyforapache +# +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html +development: + adapter: mysql + database: appointments_development + username: root + password: + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: appointments_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: appointments_production + username: root + password: + host: localhost diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..bdb7b49 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,22 @@ + +development: + adapter: mysql + database: appointments_development + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: appointments_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: appointments_production + username: appointments + password: bootlace + host: localhost diff --git a/config/database.yml.site5 b/config/database.yml.site5 new file mode 100644 index 0000000..24d192b --- /dev/null +++ b/config/database.yml.site5 @@ -0,0 +1,15 @@ + +development: + adapter: mysql + database: geektrib_appointments + username: geektrib_hherb + password: bootlace + host: localhost + + +production: + adapter: mysql + database: geektrib_appointments + username: geektrib_hherb + password: bootlace + host: localhost diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..971409e --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,68 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '1.1.6' + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence those specified here + + # Skip frameworks you're not going to use (only works if using vendor/rails) + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # See Rails::Configuration for more options +end + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# Include your application configuration below +ExceptionNotifier.exception_recipients = %w(craig@craigambrose.com) + +AutoAdmin.config do |admin| + # This information is used by the theme to construct a useful + # header; the first parameter is the full URL of the main site, the + # second is the displayed name of the site, and the third (optional) + # parameter is the title for the administration site. + admin.set_site_info 'http://medical.craigambrose.com/', 'Medical Appointments' + + # "Primary Objects" are those for which lists should be directly + # accessible from the home page. + admin.primary_objects = %w(healthcare_provider patient appointment_type appointment_state appointment_category) + + admin.theme = :django # Optional; this is the default. +end \ No newline at end of file diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..0589aa9 --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false +config.action_view.debug_rjs = true + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..5a4e2b1 --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors if you bad email addresses should just be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..f0689b9 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Tell ActionMailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..5f96b72 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,17 @@ +ActionController::Routing::Routes.draw do |map| + + UJS::routes + + map.connect '', :controller => 'appointments', :action => 'index', :year => Date.today.year, :month => Date.today.month, :day => Date.today.day + + map.connection 'appointments/:action/:year/:month/:day', + :controller => 'appointments', + :requirements => { :action => /(index|index_for_provider|new)/, + :year => /(19|20)\d\d/, + :month => /[01]?\d/, + :day => /[0-3]?\d/} + + map.connection ':controller/:action/:id' + + +end diff --git a/db/migrate/001_create_providers.rb b/db/migrate/001_create_providers.rb new file mode 100644 index 0000000..c41af9b --- /dev/null +++ b/db/migrate/001_create_providers.rb @@ -0,0 +1,11 @@ +class CreateProviders < ActiveRecord::Migration + def self.up + create_table :providers do |t| + t.column :name, :string + end + end + + def self.down + drop_table :providers + end +end diff --git a/db/migrate/002_add_default_appointment_length_to_providers.rb b/db/migrate/002_add_default_appointment_length_to_providers.rb new file mode 100644 index 0000000..f7fc1ca --- /dev/null +++ b/db/migrate/002_add_default_appointment_length_to_providers.rb @@ -0,0 +1,9 @@ +class AddDefaultAppointmentLengthToProviders < ActiveRecord::Migration + def self.up + add_column :providers, :default_appointment_length, :integer, :null => false, :default => 30 + end + + def self.down + remove_column :providers, :default_appointment_length + end +end diff --git a/db/migrate/003_create_appointments.rb b/db/migrate/003_create_appointments.rb new file mode 100644 index 0000000..5ac7298 --- /dev/null +++ b/db/migrate/003_create_appointments.rb @@ -0,0 +1,14 @@ +class CreateAppointments < ActiveRecord::Migration + def self.up + create_table :appointments do |t| + t.column :patient_id, :integer + t.column :start_time, :datetime + t.column :duration, :integer + t.column :comment, :text + end + end + + def self.down + drop_table :appointments + end +end diff --git a/db/migrate/004_create_patients.rb b/db/migrate/004_create_patients.rb new file mode 100644 index 0000000..218152f --- /dev/null +++ b/db/migrate/004_create_patients.rb @@ -0,0 +1,12 @@ +class CreatePatients < ActiveRecord::Migration + def self.up + create_table :patients do |t| + t.column :first_name, :string + t.column :surname, :string + end + end + + def self.down + drop_table :patients + end +end diff --git a/db/migrate/005_add_provider_to_appointments.rb b/db/migrate/005_add_provider_to_appointments.rb new file mode 100644 index 0000000..64717ef --- /dev/null +++ b/db/migrate/005_add_provider_to_appointments.rb @@ -0,0 +1,9 @@ +class AddProviderToAppointments < ActiveRecord::Migration + def self.up + add_column :appointments, :provider_id, :integer + end + + def self.down + remove_column :appointments, :provider_id + end +end diff --git a/db/migrate/006_rename_provider_to_healthcare_provider.rb b/db/migrate/006_rename_provider_to_healthcare_provider.rb new file mode 100644 index 0000000..60eafcf --- /dev/null +++ b/db/migrate/006_rename_provider_to_healthcare_provider.rb @@ -0,0 +1,11 @@ +class RenameProviderToHealthcareProvider < ActiveRecord::Migration + def self.up + rename_table :providers, :healthcare_providers + rename_column :appointments, :provider_id, :healthcare_provider_id + end + + def self.down + rename_table :healthcare_providers, :providers + rename_column :appointments, :healthcare_provider_id, :provider_id + end +end diff --git a/db/migrate/007_create_appointment_types.rb b/db/migrate/007_create_appointment_types.rb new file mode 100644 index 0000000..9515230 --- /dev/null +++ b/db/migrate/007_create_appointment_types.rb @@ -0,0 +1,13 @@ +class CreateAppointmentTypes < ActiveRecord::Migration + def self.up + create_table :appointment_types do |t| + t.column :name, :string + t.column :duration, :integer + t.column :healthcare_provider_id, :integer + end + end + + def self.down + drop_table :appointment_types + end +end diff --git a/db/migrate/008_add_default_appointment_type_to_providers.rb b/db/migrate/008_add_default_appointment_type_to_providers.rb new file mode 100644 index 0000000..307f24d --- /dev/null +++ b/db/migrate/008_add_default_appointment_type_to_providers.rb @@ -0,0 +1,9 @@ +class AddDefaultAppointmentTypeToProviders < ActiveRecord::Migration + def self.up + add_column :healthcare_providers, :default_appointment_type_id, :integer + end + + def self.down + remove_column :healthcare_providers, :default_appointment_type_id + end +end diff --git a/db/migrate/009_add_appointment_type_to_appointment.rb b/db/migrate/009_add_appointment_type_to_appointment.rb new file mode 100644 index 0000000..3d587c9 --- /dev/null +++ b/db/migrate/009_add_appointment_type_to_appointment.rb @@ -0,0 +1,9 @@ +class AddAppointmentTypeToAppointment < ActiveRecord::Migration + def self.up + add_column :appointments, :appointment_type_id, :integer + end + + def self.down + remove_column :appointments, :appointment_type_id + end +end diff --git a/db/migrate/010_rename_appointment_duration.rb b/db/migrate/010_rename_appointment_duration.rb new file mode 100644 index 0000000..ab14672 --- /dev/null +++ b/db/migrate/010_rename_appointment_duration.rb @@ -0,0 +1,9 @@ +class RenameAppointmentDuration < ActiveRecord::Migration + def self.up + rename_column :appointments, :duration, :duration_override + end + + def self.down + rename_column :appointments, :duration_override, :duration + end +end diff --git a/db/migrate/011_add_end_time_to_appointments.rb b/db/migrate/011_add_end_time_to_appointments.rb new file mode 100644 index 0000000..6103ff7 --- /dev/null +++ b/db/migrate/011_add_end_time_to_appointments.rb @@ -0,0 +1,9 @@ +class AddEndTimeToAppointments < ActiveRecord::Migration + def self.up + add_column :appointments, :end_time, :datetime + end + + def self.down + remove_column :appointments, :end_time + end +end diff --git a/db/migrate/012_remove_default_appointment_duration.rb b/db/migrate/012_remove_default_appointment_duration.rb new file mode 100644 index 0000000..a56acd7 --- /dev/null +++ b/db/migrate/012_remove_default_appointment_duration.rb @@ -0,0 +1,9 @@ +class RemoveDefaultAppointmentDuration < ActiveRecord::Migration + def self.up + remove_column :healthcare_providers, :default_appointment_length + end + + def self.down + add_column :healthcare_providers, :default_appointment_length, :integer + end +end diff --git a/db/migrate/013_add_created_on_to_tables.rb b/db/migrate/013_add_created_on_to_tables.rb new file mode 100644 index 0000000..41a539b --- /dev/null +++ b/db/migrate/013_add_created_on_to_tables.rb @@ -0,0 +1,13 @@ +class AddCreatedOnToTables < ActiveRecord::Migration + def self.up + add_column :appointments, :created_on, :datetime + add_column :healthcare_providers, :created_on, :datetime + add_column :patients, :created_on, :datetime + end + + def self.down + remove_column :appointments, :created_on + remove_column :healthcare_providers, :created_on + remove_column :patients, :created_on + end +end diff --git a/db/migrate/014_create_appointment_states.rb b/db/migrate/014_create_appointment_states.rb new file mode 100644 index 0000000..aed4c54 --- /dev/null +++ b/db/migrate/014_create_appointment_states.rb @@ -0,0 +1,13 @@ +class CreateAppointmentStates < ActiveRecord::Migration + def self.up + create_table :appointment_states do |t| + t.column :name, :string + t.column :icon, :string + t.column :colour, :string + end + end + + def self.down + drop_table :appointment_states + end +end diff --git a/db/migrate/015_add_appointment_state_to_appointments.rb b/db/migrate/015_add_appointment_state_to_appointments.rb new file mode 100644 index 0000000..d85326a --- /dev/null +++ b/db/migrate/015_add_appointment_state_to_appointments.rb @@ -0,0 +1,9 @@ +class AddAppointmentStateToAppointments < ActiveRecord::Migration + def self.up + add_column :appointments, :appointment_state_id, :integer + end + + def self.down + remove_column :appointments, :appointment_state_id + end +end diff --git a/db/migrate/016_create_default_appointment_state.rb b/db/migrate/016_create_default_appointment_state.rb new file mode 100644 index 0000000..184cd03 --- /dev/null +++ b/db/migrate/016_create_default_appointment_state.rb @@ -0,0 +1,9 @@ +class CreateDefaultAppointmentState < ActiveRecord::Migration + def self.up + AppointmentState.create(:name => 'Booked', :icon => 'booked.png', :colour => '#FFCC99') + end + + def self.down + AppointmentState.destroy_all(:conditions => "name = 'Booked'") + end +end diff --git a/db/migrate/017_set_all_appointments_to_default_appointment_state.rb b/db/migrate/017_set_all_appointments_to_default_appointment_state.rb new file mode 100644 index 0000000..220cfbd --- /dev/null +++ b/db/migrate/017_set_all_appointments_to_default_appointment_state.rb @@ -0,0 +1,9 @@ +class SetAllAppointmentsToDefaultAppointmentState < ActiveRecord::Migration + def self.up + execute "UPDATE appointments SET appointment_state_id = 1" + end + + def self.down + execute "UPDATE appointments SET appointment_state_id = NULL" + end +end diff --git a/db/migrate/018_add_name_to_patient.rb b/db/migrate/018_add_name_to_patient.rb new file mode 100644 index 0000000..2f4f44c --- /dev/null +++ b/db/migrate/018_add_name_to_patient.rb @@ -0,0 +1,9 @@ +class AddNameToPatient < ActiveRecord::Migration + def self.up + add_column :patients, :name, :string + end + + def self.down + remove_column :patients, :name + end +end diff --git a/db/migrate/019_update_patient_names.rb b/db/migrate/019_update_patient_names.rb new file mode 100644 index 0000000..7468915 --- /dev/null +++ b/db/migrate/019_update_patient_names.rb @@ -0,0 +1,10 @@ +class UpdatePatientNames < ActiveRecord::Migration + def self.up + for patient in Patient.find(:all) + patient.save + end + end + + def self.down + end +end diff --git a/db/migrate/020_create_appointment_categories.rb b/db/migrate/020_create_appointment_categories.rb new file mode 100644 index 0000000..31a3e7f --- /dev/null +++ b/db/migrate/020_create_appointment_categories.rb @@ -0,0 +1,11 @@ +class CreateAppointmentCategories < ActiveRecord::Migration + def self.up + create_table :appointment_categories do |t| + t.column :name, :string + end + end + + def self.down + drop_table :appointment_categories + end +end diff --git a/db/migrate/021_create_appointment_categories_appointments.rb b/db/migrate/021_create_appointment_categories_appointments.rb new file mode 100644 index 0000000..e96e976 --- /dev/null +++ b/db/migrate/021_create_appointment_categories_appointments.rb @@ -0,0 +1,12 @@ +class CreateAppointmentCategoriesAppointments < ActiveRecord::Migration + def self.up + create_table :appointment_categories_appointments, :id => false do |t| + t.column :appointment_category_id, :integer + t.column :appointment_id, :integer + end + end + + def self.down + drop_table :appointment_categories_appointments + end +end diff --git a/db/migrate/022_save_all_appointments.rb b/db/migrate/022_save_all_appointments.rb new file mode 100644 index 0000000..1bca86f --- /dev/null +++ b/db/migrate/022_save_all_appointments.rb @@ -0,0 +1,10 @@ +class SaveAllAppointments < ActiveRecord::Migration + def self.up + for appointment in Appointment.find(:all) + appointment.save! + end + end + + def self.down + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..e5157aa --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,53 @@ +# 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(:version => 21) do + + create_table "appointment_categories", :force => true do |t| + t.column "name", :string + end + + create_table "appointment_categories_appointments", :id => false, :force => true do |t| + t.column "appointment_category_id", :integer + t.column "appointment_id", :integer + end + + create_table "appointment_states", :force => true do |t| + t.column "name", :string + t.column "icon", :string + t.column "colour", :string + end + + create_table "appointment_types", :force => true do |t| + t.column "name", :string + t.column "duration", :integer + t.column "healthcare_provider_id", :integer + end + + create_table "appointments", :force => true do |t| + t.column "patient_id", :integer + t.column "start_time", :datetime + t.column "duration_override", :integer + t.column "comment", :text + t.column "healthcare_provider_id", :integer + t.column "appointment_type_id", :integer + t.column "end_time", :datetime + t.column "created_on", :datetime + t.column "appointment_state_id", :integer + end + + create_table "healthcare_providers", :force => true do |t| + t.column "name", :string + t.column "default_appointment_type_id", :integer + t.column "created_on", :datetime + end + + create_table "patients", :force => true do |t| + t.column "first_name", :string + t.column "surname", :string + t.column "created_on", :datetime + t.column "name", :string + end + +end diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP new file mode 100644 index 0000000..ac6c149 --- /dev/null +++ b/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. \ No newline at end of file diff --git a/lib/tasks/capistrano.rake b/lib/tasks/capistrano.rake new file mode 100644 index 0000000..08642ac --- /dev/null +++ b/lib/tasks/capistrano.rake @@ -0,0 +1,90 @@ +# ============================================================================= +# A set of rake tasks for invoking the Capistrano automation utility. +# ============================================================================= + +# Invoke the given actions via Capistrano +def cap(*parameters) + begin + require 'rubygems' + rescue LoadError + # no rubygems to load, so we fail silently + end + + require 'capistrano/cli' + + Capistrano::CLI.new(parameters.map { |param| param.to_s }).execute! +end + +namespace :remote do + desc "Removes unused releases from the releases directory." + task(:cleanup) { cap :cleanup } + + desc "Used only for deploying when the spinner isn't running." + task(:cold_deploy) { cap :cold_deploy } + + desc "A macro-task that updates the code, fixes the symlink, and restarts the application servers." + task(:deploy) { cap :deploy } + + desc "Similar to deploy, but it runs the migrate task on the new release before updating the symlink." + task(:deploy_with_migrations) { cap :deploy_with_migrations } + + desc "Displays the diff between HEAD and what was last deployed." + task(:diff_from_last_deploy) { cap :diff_from_last_deploy } + + desc "Disable the web server by writing a \"maintenance.html\" file to the web servers." + task(:disable_web) { cap :disable_web } + + desc "Re-enable the web server by deleting any \"maintenance.html\" file." + task(:enable_web) { cap :enable_web } + + desc "A simple task for performing one-off commands that may not require a full task to be written for them." + task(:invoke) { cap :invoke } + + desc "Run the migrate rake task." + task(:migrate) { cap :migrate } + + desc "Restart the FCGI processes on the app server." + task(:restart) { cap :restart } + + desc "A macro-task that rolls back the code and restarts the application servers." + task(:rollback) { cap :rollback } + + desc "Rollback the latest checked-out version to the previous one by fixing the symlinks and deleting the current release from all servers." + task(:rollback_code) { cap :rollback_code } + + desc "Set up the expected application directory structure on all boxes" + task(:setup) { cap :setup } + + desc "Enumerate and describe every available task." + task(:show_tasks) { cap :show_tasks, '-q' } + + desc "Start the spinner daemon for the application (requires script/spin)." + task(:spinner) { cap :spinner } + + desc "Update the 'current' symlink to point to the latest version of the application's code." + task(:symlink) { cap :symlink } + + desc "Update all servers with the latest release of the source code." + task(:update_code) { cap :update_code } + + desc "Update the currently released version of the software directly via an SCM update operation" + task(:update_current) { cap :update_current } + + desc "Execute a specific action using capistrano" + task :exec do + unless ENV['ACTION'] + raise "Please specify an action (or comma separated list of actions) via the ACTION environment variable" + end + + actions = ENV['ACTION'].split(",") + actions.concat(ENV['PARAMS'].split(" ")) if ENV['PARAMS'] + + cap(*actions) + end +end + +desc "Push the latest revision into production (delegates to remote:deploy)" +task :deploy => "remote:deploy" + +desc "Rollback to the release before the current release in production (delegates to remote:rollback)" +task :rollback => "remote:rollback" diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..b742fc4 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +#RewriteBase /appointments + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

    Application error

    Rails application failed to start properly" \ No newline at end of file diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..0e18456 --- /dev/null +++ b/public/404.html @@ -0,0 +1,8 @@ + + + +

    File not found

    +

    Change this error message for pages not found in public/404.html

    + + \ No newline at end of file diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..ab95f74 --- /dev/null +++ b/public/500.html @@ -0,0 +1,8 @@ + + + +

    Application error

    +

    Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html

    + + \ No newline at end of file diff --git a/public/bundles/README b/public/bundles/README new file mode 100644 index 0000000..b248f46 --- /dev/null +++ b/public/bundles/README @@ -0,0 +1,5 @@ +Files in this directory are automatically generated from your Rails bundles. +They are copied from the directories of each bundle into this directory +each time Rails starts (server, console). Any edits you make will NOT +persist across the next server restart; instead you should edit the files within +the bundle directory itself (e.g. vendor/plugins/bundle_resource/bundles/*). diff --git a/public/bundles/dynarch_calendar/doc/html/field-button.jpg b/public/bundles/dynarch_calendar/doc/html/field-button.jpg new file mode 100644 index 0000000..ecbe9d8 Binary files /dev/null and b/public/bundles/dynarch_calendar/doc/html/field-button.jpg differ diff --git a/public/bundles/dynarch_calendar/doc/html/reference-Z-S.css b/public/bundles/dynarch_calendar/doc/html/reference-Z-S.css new file mode 100644 index 0000000..02a6f88 --- /dev/null +++ b/public/bundles/dynarch_calendar/doc/html/reference-Z-S.css @@ -0,0 +1,193 @@ + + body { + color: black; + /* background-color: #e5e5e5;*/ + background-color: #ffffff; + /*background-color: beige;*/ + margin-top: 2em; + margin-left: 8%; + margin-right: 8%; + } + + h1,h2,h3,h4,h5,h6 { + margin-top: .5em; + } + + .title { + font-size: 200%; + font-weight: normal; + } + + .partheading { + font-size: 100%; + } + + .chapterheading { + font-size: 100%; + } + + .beginsection { + font-size: 110%; + } + + .tiny { + font-size: 40%; + } + + .scriptsize { + font-size: 60%; + } + + .footnotesize { + font-size: 75%; + } + + .small { + font-size: 90%; + } + + .normalsize { + font-size: 100%; + } + + .large { + font-size: 120%; + } + + .largecap { + font-size: 150%; + } + + .largeup { + font-size: 200%; + } + + .huge { + font-size: 300%; + } + + .hugecap { + font-size: 350%; + } + + pre { + margin-left: 2em; + } + + blockquote { + margin-left: 2em; + } + + ol { + list-style-type: decimal; + } + + ol ol { + list-style-type: lower-alpha; + } + + ol ol ol { + list-style-type: lower-roman; + } + + ol ol ol ol { + list-style-type: upper-alpha; + } + + /* + .verbatim { + color: #4d0000; + } + */ + + tt i { + font-family: serif; + } + + .verbatim em { + font-family: serif; + } + + .scheme em { + font-family: serif; + color: black; + } + + .scheme { + color: brown; + } + + .scheme .keyword { + color: #990000; + font-weight: bold; + } + + .scheme .builtin { + color: #990000; + } + + .scheme .variable { + color: navy; + } + + .scheme .global { + color: purple; + } + + .scheme .selfeval { + color: green; + } + + .scheme .comment { + color: teal; + } + + .schemeresponse { + color: green; + } + + .navigation { + color: red; + text-align: right; + font-size: medium; + font-style: italic; + } + + .disable { + /* color: #e5e5e5; */ + color: gray; + } + + .smallcaps { + font-size: 75%; + } + + .smallprint { + color: gray; + font-size: 75%; + text-align: right; + } + + /* + .smallprint hr { + text-align: left; + width: 40%; + } + */ + + .footnoterule { + text-align: left; + width: 40%; + } + + .colophon { + color: gray; + font-size: 80%; + text-align: right; + } + + .colophon a { + color: gray; + } + + \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/doc/html/reference.css b/public/bundles/dynarch_calendar/doc/html/reference.css new file mode 100644 index 0000000..42e9283 --- /dev/null +++ b/public/bundles/dynarch_calendar/doc/html/reference.css @@ -0,0 +1,34 @@ +html { margin: 0px; padding: 0px; background-color: #08f; color: #444; font-family: georgia,serif; } +body { margin: 2em 8%; background-color: #fff; padding: 1em; border: 2px ridge #048; } + +a:link, a:visited { text-decoration: none; color: #00f; } +a:hover { color: #f00; text-decoration: underline; } +a:active { color: #f84; } + +h1, h2, h3, h4, h5, h6 { font-family: tahoma,verdana,sans-serif; } + +h2, h3 { font-weight: normal; } + +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover { text-decoration: none; } + +h1 { font-size: 170%; border: 2px ridge #048; letter-spacing: 2px; color: #000; margin-left: -2em; margin-right: -2em; +background-color: #fff; padding: 2px 1em; background-color: #def; } +h2 { font-size: 140%; color: #222; } +h3 { font-size: 120%; color: #444; } + +h1.title { font-size: 300%; font-family: georgia,serif; font-weight: normal; color: #846; letter-spacing: -1px; +border: none; +padding: none; +background-color: #fff; +border-bottom: 3px double #624; padding-bottom: 2px; margin-left: 8%; margin-right: 8%; } + +.colophon { padding-top: 2em; color: #999; font-size: 90%; font-family: georgia,"times new roman",serif; } +.colophon a:link, .colophon a:visited { color: #755; } +.colophon a:hover { color: #422; text-decoration: underline; } + +.footnote { font-size: 90%; font-style: italic; font-family: georgia,"times new roman",serif; margin: 0px 3em; } +.footnote sup { font-size: 120%; padding: 0px 0.3em; position: relative; top: -0.2em; } + +.small { font-size: 90%; } + +.verbatim { background-color: #eee; padding: 0.2em 1em; border: 1px solid #aaa; } diff --git a/public/bundles/dynarch_calendar/doc/html/reference.html b/public/bundles/dynarch_calendar/doc/html/reference.html new file mode 100644 index 0000000..4bf6380 --- /dev/null +++ b/public/bundles/dynarch_calendar/doc/html/reference.html @@ -0,0 +1,1738 @@ + + + + + +DHTML Calendar Widget + + + + + + +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    + + + + +

    +

    + + +

    +

    +

    +

    +

    +



    DHTML Calendar Widget

    +

    +
    +Mihai Bazon, <mihai_bazon@yahoo.com>
    +© Dynarch.com 2002-2005, www.dynarch.com

    March 7, 2005

    +

    +

    +calendar version: 1.0 ``It is happening again'' +

    +
    +

    +

    +$Id: reference.tex,v 1.23 2005/03/05 11:37:14 mishoo Exp $ +

    +
    +
    + +
    + +
    + +

    Contents

    +

    +

    +    1  Overview
    +        1.1  How does this thing work?
    +        1.2  Project files
    +        1.3  License
    +

    +

    +    2  Quick startup
    +        2.1  Installing a popup calendar
    +        2.2  Installing a flat calendar
    +        2.3  Calendar.setup in detail
    +

    +

    +    3  Recipes
    +        3.1  Popup calendars
    +            3.1.1  Simple text field with calendar attached to a button
    +            3.1.2  Simple field with calendar attached to an image
    +            3.1.3  Hidden field, plain text triggers
    +            3.1.4  2 Linked fields, no trigger buttons
    +        3.2  Flat calendars
    +        3.3  Highlight special dates
    +        3.4  Select multiple dates
    +

    +

    +    4  The Calendar object overview
    +        4.1  Creating a calendar
    +        4.2  Order does matter ;-)
    +        4.3  Caching the object
    +        4.4  Callback functions
    +

    +

    +    5  The Calendar object API reference
    +        5.1  Calendar constructor
    +        5.2  Useful member variables (properties)
    +        5.3  Public methods
    +            5.3.1  Calendar.create
    +            5.3.2  Calendar.callHandler
    +            5.3.3  Calendar.callCloseHandler
    +            5.3.4  Calendar.hide
    +            5.3.5  Calendar.setDateFormat
    +            5.3.6  Calendar.setTtDateFormat
    +            5.3.7  Calendar.setDisabledHandler
    +            5.3.8  Calendar.setDateStatusHandler
    +            5.3.9  Calendar.show
    +            5.3.10  Calendar.showAt
    +            5.3.11  Calendar.showAtElement
    +            5.3.12  Calendar.setDate
    +            5.3.13  Calendar.setFirstDayOfWeek
    +            5.3.14  Calendar.parseDate
    +            5.3.15  Calendar.setRange
    +

    +

    +    6  Side effects
    +

    +

    +    7  Credits
    +

    +

    +

    +

    +

    + +

    1  Overview

    +

    The DHTML Calendar widget1 +is an (HTML) user interface element that gives end-users a friendly way to +select date and time. It works in a web browser. The first versions only provided +support for popup calendars, while starting with version 0.9 it also supports +``flat'' display. A ``flat'' calendar is a calendar that stays visible in the +page all the time. In this mode it could be very useful for ``blog'' pages and +other pages that require the calendar to be always present.

    +

    +The calendar is compatible with most popular browsers nowadays. While it's +created using web standards and it should generally work with any compliant +browser, the following browsers were found to work: Mozilla/Firefox (the +development platform), Netscape 6.0 or better, all other Gecko-based browsers, +Internet Explorer 5.0 or better for Windows2, Opera 73, Konqueror 3.1.2 and Apple Safari for +MacOSX.

    +

    +You can find the latest info and version at the calendar homepage:

    +

    +

    +
    + +www.dynarch.com/projects/calendar +
    +

    +

    + +

    1.1  How does this thing work?

    +

    DHTML is not ``another kind of HTML''. It's merely a naming convention. DHTML +refers to the combination of HTML, CSS, JavaScript and DOM. DOM (Document +Object Model) is a set of interfaces that glues the other three together. In +other words, DOM allows dynamic modification of an HTML page through a program. +JavaScript is our programming language, since that's what browsers like. CSS +is a way to make it look good ;-). So all this soup is generically known as +DHTML.

    +

    +Using DOM calls, the program dynamically creates a <table> element +that contains a calendar for the given date and then inserts it in the document +body. Then it shows this table at a specified position. Usually the position +is related to some element in which the date needs to be displayed/entered, +such as an input field.

    +

    +By assigning a certain CSS class to the table we can control the look of the +calendar through an external CSS file; therefore, in order to change the +colors, backgrounds, rollover effects and other stuff, you can only change a +CSS file -- modification of the program itself is not necessary.

    +

    +

    + +

    1.2  Project files

    +

    Here's a description of the project files, excluding documentation and example +files.

    +

    +

    +

      +

      +
    • the main program file (calendar.js). This defines all the logic +behind the calendar widget.

      +

      +

      +
    • the CSS files (calendar-*.css). Loading one of them is +necessary in order to see the calendar as intended.

      +

      +

      +
    • the language definition files (lang/calendar-*.js). They are +plain JavaScript files that contain all texts that are displayed by the +calendar. Loading one of them is necessary.

      +

      +

      +
    • helper functions for quick setup of the calendar +(calendar-setup.js). You can do fine without it, but starting with +version 0.9.3 this is the recommended way to setup a calendar.

      +

      +

      +

    +

    +

    + +

    1.3  License

    +

    +
    + +© Dynarch.com 2002-2005, +www.dynarch.com +Author: Mihai Bazon +
    +

    +The calendar is released under the +GNU Lesser General Public License.

    +

    +

    + +

    2  Quick startup

    +

    +

    +Installing the calendar used to be quite a task until version 0.9.3. Starting +with 0.9.3 I have included the file calendar-setup.js whose goal is to +assist you to setup a popup or flat calendar in minutes. You are +encouraged to modify this file and not calendar.js if you need +extra customization, but you're on your own.

    +

    +First you have to include the needed scripts and style-sheet. Make sure you do +this in your document's <head> section, also make sure you put the +correct paths to the scripts.

    +

    +

    +
    <style type="text/css">@import url(calendar-win2k-1.css);</style>
    +<script type="text/javascript" src="calendar.js"></script>
    +<script type="text/javascript" src="lang/calendar-en.js"></script>
    +<script type="text/javascript" src="calendar-setup.js"></script>
    +

    +

    +

    + +

    2.1  Installing a popup calendar

    +

    +

    +Now suppose you have the following HTML:

    +

    +

    +
    <form ...>
    +  <input type="text" id="data" name="data" />
    +  <button id="trigger">...</button>
    +</form>
    +

    +

    +You want the button to popup a calendar widget when clicked? Just +insert the following code immediately after the HTML form:

    +

    +

    +
    <script type="text/javascript">
    +  Calendar.setup(
    +    {
    +      inputField  : "data",         // ID of the input field
    +      ifFormat    : "%m %d, %Y",    // the date format
    +      button      : "trigger"       // ID of the button
    +    }
    +  );
    +</script>
    +

    +

    +The Calendar.setup function, defined in calendar-setup.js +takes care of ``patching'' the button to display a calendar when clicked. The +calendar is by default in single-click mode and linked with the given input +field, so that when the end-user selects a date it will update the input field +with the date in the given format and close the calendar. If you are a +long-term user of the calendar you probably remember that for doing this you +needed to write a couple functions and add an ``onclick'' handler for the +button by hand.

    +

    +By looking at the example above we can see that the function +Calendar.setup receives only one parameter: a JavaScript object. +Further, that object can have lots of properties that tell to the setup +function how would we like to have the calendar. For instance, if we would +like a calendar that closes at double-click instead of single-click we would +also include the following: singleClick:false.

    +

    +For a list of all supported parameters please see the section +2.3.

    +

    +

    + +

    2.2  Installing a flat calendar

    +

    +

    +Here's how to configure a flat calendar, using the same Calendar.setup +function. First, you should have an empty element with an ID. This element +will act as a container for the calendar. It can be any block-level element, +such as DIV, TABLE, etc. We will use a DIV in this example.

    +

    +

    +
    <div id="calendar-container"></div>
    +

    +

    +Then there is the JavaScript code that sets up the calendar into the +``calendar-container'' DIV. The code can occur anywhere in HTML +after the DIV element.

    +

    +

    +
    <script type="text/javascript">
    +  function dateChanged(calendar) {
    +    // Beware that this function is called even if the end-user only
    +    // changed the month/year.  In order to determine if a date was
    +    // clicked you can use the dateClicked property of the calendar:
    +    if (calendar.dateClicked) {
    +      // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
    +      var y = calendar.date.getFullYear();
    +      var m = calendar.date.getMonth();     // integer, 0..11
    +      var d = calendar.date.getDate();      // integer, 1..31
    +      // redirect...
    +      window.location = "/" + y + "/" + m + "/" + d + "/index.php";
    +    }
    +  };
    +
    +  Calendar.setup(
    +    {
    +      flat         : "calendar-container", // ID of the parent element
    +      flatCallback : dateChanged           // our callback function
    +    }
    +  );
    +</script>
    +

    +

    +

    + +

    2.3  Calendar.setup in detail

    +

    +

    +Following there is the complete list of properties interpreted by +Calendar.setup. All of them have default values, so you can pass only those +which you would like to customize. Anyway, you must pass at least one +of inputField, displayArea or button, for a popup +calendar, or flat for a flat calendar. Otherwise you will get a +warning message saying that there's nothing to setup.

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    property type description default +
    inputField +string The ID of your input field. +null +
    displayArea +string This is the ID of a <span>, <div>, or any other element that you would like to use to display the current date. This is generally useful only if the input field is hidden, as an area to display the date. +null +
    button +string The ID of the calendar ``trigger''. This is an element (ordinarily a button or an image) that will dispatch a certain event (usually ``click'') to the function that creates and displays the calendar. +null +
    eventName +string The name of the event that will trigger the calendar. The name should be without the ``on'' prefix, such as ``click'' instead of ``onclick''. Virtually all users will want to let this have the default value (``click''). Anyway, it could be useful if, say, you want the calendar to appear when the input field is focused and have no trigger button (in this case use ``focus'' as the event name). +``click'' +
    ifFormat +string The format string that will be used to enter the date in the input field. This format will be honored even if the input field is hidden. +``%Y/%m/%d'' +
    daFormat +string Format of the date displayed in the displayArea (if specified). +``%Y/%m/%d'' +
    singleClick +boolean Wether the calendar is in ``single-click mode'' or ``double-click mode''. If true (the default) the calendar will be created in single-click mode. +true +
    disableFunc +function A function that receives a JS Date object. It should return +true if that date has to be disabled, false otherwise. +DEPRECATED (see below). +null +
    dateStatusFunc +function A function that receives a JS Date object and returns a boolean +or a string. This function allows one to set a certain CSS class to some +date, therefore making it look different. If it returns true then +the date will be disabled. If it returns false nothing special +happens with the given date. If it returns a string then that will be taken +as a CSS class and appended to the date element. If this string is +``disabled'' then the date is also disabled (therefore is like returning +true). For more information please also refer to section +5.3.8. +null +
    firstDay +integer Specifies which day is to be displayed as the first day of +week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday, ..., 6 +means Saturday. The end user can easily change this too, by clicking on the +day name in the calendar header. +0 +
    weekNumbers +boolean If ``true'' then the calendar will display week numbers. +true +
    align +string Alignment of the calendar, relative to the reference element. The +reference element is dynamically chosen like this: if a displayArea is +specified then it will be the reference element. Otherwise, the input field +is the reference element. For the meaning of the alignment characters +please section 5.3.11. +``Bl'' +
    range +array An array having exactly 2 elements, integers. (!) The first [0] element is the minimum year that is available, and the second [1] element is the maximum year that the calendar will allow. +[1900, 2999] +
    flat +string If you want a flat calendar, pass the ID of the parent object in +this property. If not, pass null here (or nothing at all as +null is the default value). +null +
    flatCallback +function You should provide this function if the calendar is flat. It +will be called when the date in the calendar is changed with a reference to +the calendar object. See section 2.2 for an example +of how to setup a flat calendar. +null +
    onSelect +function If you provide a function handler here then you have to manage +the ``click-on-date'' event by yourself. Look in the calendar-setup.js and +take as an example the onSelect handler that you can see there. +null +
    onClose +function This handler will be called when the calendar needs to close. +You don't need to provide one, but if you do it's your responsibility to +hide/destroy the calendar. You're on your own. Check the calendar-setup.js +file for an example. +null +
    onUpdate +function If you supply a function handler here, it will be called right +after the target field is updated with a new date. You can use this to +chain 2 calendars, for instance to setup a default date in the second just +after a date was selected in the first. +null +
    date +date This allows you to setup an initial date where the calendar will be +positioned to. If absent then the calendar will open to the today date. +null +
    showsTime +boolean If this is set to true then the calendar will also +allow time selection. +false +
    timeFormat +string Set this to ``12'' or ``24'' to configure the way that the +calendar will display time. +``24'' +
    electric +boolean Set this to ``false'' if you want the calendar to update the +field only when closed (by default it updates the field at each date change, +even if the calendar is not closed) true +
    position +array Specifies the [x, y] position, relative to page's top-left corner, +where the calendar will be displayed. If not passed then the position will +be computed based on the ``align'' parameter. Defaults to ``null'' (not +used). null +
    cache +boolean Set this to ``true'' if you want to cache the calendar object. +This means that a single calendar object will be used for all fields that +require a popup calendar false +
    showOthers +boolean If set to ``true'' then days belonging to months overlapping +with the currently displayed month will also be displayed in the calendar +(but in a ``faded-out'' color) false + +
    + +

    +

    + +

    3  Recipes

    +

    This section presents some common ways to setup a calendar using the +Calendar.setup function detailed in the previous section.

    +

    +We don't discuss here about loading the JS or CSS code -- so make sure you +add the proper <script> and <style> or <link> elements in your +HTML code. Also, when we present input fields, please note that they should +be embedded in some form in order for data to be actually sent to server; we +don't discuss these things here because they are not related to our +calendar.

    +

    +

    + +

    3.1  Popup calendars

    +

    These samples can be found in the file “simple-1.html” from the +calendar package.

    +

    +

    + +

    3.1.1  Simple text field with calendar attached to a button

    +

    +

    +This piece of code will create a calendar for a simple input field with a +button that will open the calendar when clicked.

    +

    +

    +
    <input type="text" name="date" id="f_date_b"
    +       /><button type="reset" id="f_trigger_b"
    +       >...</button>
    +<script type="text/javascript">
    +    Calendar.setup({
    +        inputField     :    "f_date_b",           //*
    +        ifFormat       :    "%m/%d/%Y %I:%M %p",
    +        showsTime      :    true,
    +        button         :    "f_trigger_b",        //*
    +        step           :    1
    +    });
    +</script>
    +

    +

    +Note that this code does more actually; the only required fields are +those marked with “//*” -- that is, the ID of the input field and the ID of +the button need to be passed to Calendar.setup in order for the +calendar to be properly assigned to this input field. As one can easily +guess from the argument names, the other arguments configure a certain date +format, instruct the calendar to also include a time selector and display +every year in the drop-down boxes (the “step” parameter) -- instead of showing +every other year as the default calendar does.

    +

    +

    + +

    3.1.2  Simple field with calendar attached to an image

    +

    Same as the above, but the element that triggers the calendar is this time +an image, not a button.

    +

    +

    +
    <input type="text" name="date" id="f_date_c" readonly="1" />
    +<img src="img.gif" id="f_trigger_c"
    +     style="cursor: pointer; border: 1px solid red;"
    +     title="Date selector"
    +     onmouseover="this.style.background='red';"
    +     onmouseout="this.style.background=''" />
    +<script type="text/javascript">
    +    Calendar.setup({
    +        inputField     :    "f_date_c",
    +        ifFormat       :    "%B %e, %Y",
    +        button         :    "f_trigger_c",
    +        align          :    "Tl",
    +        singleClick    :    false
    +    });
    +</script>
    +

    +

    +Note that the same 2 parameters are required as in the previous case; the +difference is that the 'button' parameter now gets the ID of the image +instead of the ID of the button. But the event is the same: at 'onclick' on +the element that is passed as 'button', the calendar will be shown.

    +

    +The above code additionally sets an alignment mode -- the parameters are +described in 5.3.11.

    +

    +

    + +

    3.1.3  Hidden field, plain text triggers

    +

    Sometimes, to assure that the date is well formatted, you might want not to +allow the end user to write a date manually. This can easily be achieved +with an input field by setting its readonly attribute, which is +defined by the HTML4 standard; however, here's an even nicer approach: our +calendar widget allows you to use a hidden field as the way to pass data to +server, and a “display area” to show the end user the selected date. The +“display area” can be any HTML element, such as a DIV or a SPAN or +whatever -- we will use a SPAN in our sample.

    +

    +

    +
    <input type="hidden" name="date" id="f_date_d" />
    +
    +<p>Your birthday:
    +   <span style="background-color: #ff8; cursor: default;"
    +         onmouseover="this.style.backgroundColor='#ff0';"
    +         onmouseout="this.style.backgroundColor='#ff8';"
    +         id="show_d"
    +   >Click to open date selector</span>.</p>
    +
    +<script type="text/javascript">
    +    Calendar.setup({
    +        inputField     :    "f_date_d",
    +        ifFormat       :    "%Y/%d/%m",
    +        displayArea    :    "show_d",
    +        daFormat       :    "%A, %B %d, %Y",
    +    });
    +</script>
    +

    +

    +The above code will configure a calendar attached to the hidden field and to +the SPAN having the id=“show_d”. When the SPAN element is clicked, the +calendar opens and allows the end user to chose a date. When the date is +chosen, the input field will be updated with the value in the format +“%Y/%d/%m”, and the SPAN element will display the date in a +friendlier format (defined by “daFormat”).

    +

    +Beware that using this approach will make your page unfunctional in browsers +that do not support JavaScript or our calendar.

    +

    +

    + +

    3.1.4  2 Linked fields, no trigger buttons

    +

    Supposing you want to create 2 fields that hold an interval of exactly one +week. The first is the starting date, and the second is the ending date. +You want the fields to be automatically updated when some date is clicked in +one or the other, in order to keep exactly one week difference between them.

    +

    +

    +
    <input type="text" name="date" id="f_date_a" />
    +<input type="text" name="date" id="f_calcdate" />
    +
    +<script type="text/javascript">
    +    function catcalc(cal) {
    +        var date = cal.date;
    +        var time = date.getTime()
    +        // use the _other_ field
    +        var field = document.getElementById("f_calcdate");
    +        if (field == cal.params.inputField) {
    +            field = document.getElementById("f_date_a");
    +            time -= Date.WEEK; // substract one week
    +        } else {
    +            time += Date.WEEK; // add one week
    +        }
    +        var date2 = new Date(time);
    +        field.value = date2.print("%Y-%m-%d %H:%M");
    +    }
    +    Calendar.setup({
    +        inputField     :    "f_date_a",
    +        ifFormat       :    "%Y-%m-%d %H:%M",
    +        showsTime      :    true,
    +        timeFormat     :    "24",
    +        onUpdate       :    catcalc
    +    });
    +    Calendar.setup({
    +        inputField     :    "f_calcdate",
    +        ifFormat       :    "%Y-%m-%d %H:%M",
    +        showsTime      :    true,
    +        timeFormat     :    "24",
    +        onUpdate       :    catcalc
    +    });
    +</script>
    +

    +

    +The above code will configure 2 input fields with calendars attached, as +usual. The first thing to note is that there's no trigger button -- in such +case, the calendar will popup when one clicks into the input field. Using +the onUpdate parameter, we pass a reference to a function of ours +that will get called after a date was selected. In that function we +determine what field was updated and we compute the date in the other input +field such that it keeps a one week difference between the two. Enjoy! :-)

    +

    +

    + +

    3.2  Flat calendars

    +

    This sample can be found in “simple-2.html”. It will configure a +flat calendar that is always displayed in the page, in the DIV having the +id=“calendar-container”. When a date is clicked our function hander gets +called (dateChanged) and it will compute an URL to jump to based on +the selected date, then use window.location to visit the new link.

    +

    +

    +
    <div style="float: right; margin-left: 1em; margin-bottom: 1em;"
    +id="calendar-container"></div>
    +
    +<script type="text/javascript">
    +  function dateChanged(calendar) {
    +    // Beware that this function is called even if the end-user only
    +    // changed the month/year.  In order to determine if a date was
    +    // clicked you can use the dateClicked property of the calendar:
    +    if (calendar.dateClicked) {
    +      // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
    +      var y = calendar.date.getFullYear();
    +      var m = calendar.date.getMonth();     // integer, 0..11
    +      var d = calendar.date.getDate();      // integer, 1..31
    +      // redirect...
    +      window.location = "/" + y + "/" + m + "/" + d + "/index.php";
    +    }
    +  };
    +
    +  Calendar.setup(
    +    {
    +      flat         : "calendar-container", // ID of the parent element
    +      flatCallback : dateChanged           // our callback function
    +    }
    +  );
    +</script>
    +

    +

    +

    + +

    3.3  Highlight special dates

    +

    So you want to display certain dates in a different color, or with bold +font, or whatever, right? Well, no problem -- our calendar can do this as +well. It doesn't matter if it's a flat or popup calendar -- we'll use a flat +one for this sample. The idea, however, is that you need to have the dates +in an array or a JavaScript object -- whatever is suitable for your way of +thinking -- and use it from a function that returns a value, telling the +calendar what kind of date is the passed one.

    +

    +Too much talking, here's the code ;-)

    +

    +

    +
    <!-- this goes into the <head> tag -->
    +<style type="text/css">
    +  .special { background-color: #000; color: #fff; }
    +</style>
    +
    +<!-- and the rest inside the <body> -->
    +<div style="float: right; margin-left: 1em; margin-bottom: 1em;"
    +id="calendar-container"></div>
    +
    +<script type="text/javascript">
    +  var SPECIAL_DAYS = {
    +    0 : [ 13, 24 ],		// special days in January
    +    2 : [ 1, 6, 8, 12, 18 ],	// special days in March
    +    8 : [ 21, 11 ]		// special days in September
    +  };
    +
    +  function dateIsSpecial(year, month, day) {
    +    var m = SPECIAL_DAYS[month];
    +    if (!m) return false;
    +    for (var i in m) if (m[i] == day) return true;
    +    return false;
    +  };
    +
    +  function dateChanged(calendar) {
    +    // Beware that this function is called even if the end-user only
    +    // changed the month/year.  In order to determine if a date was
    +    // clicked you can use the dateClicked property of the calendar:
    +    if (calendar.dateClicked) {
    +      // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
    +      var y = calendar.date.getFullYear();
    +      var m = calendar.date.getMonth();     // integer, 0..11
    +      var d = calendar.date.getDate();      // integer, 1..31
    +      // redirect...
    +      window.location = "/" + y + "/" + m + "/" + d + "/index.php";
    +    }
    +  };
    +
    +  function ourDateStatusFunc(date, y, m, d) {
    +    if (dateIsSpecial(y, m, d))
    +      return "special";
    +    else
    +      return false; // other dates are enabled
    +      // return true if you want to disable other dates
    +  };
    +
    +  Calendar.setup(
    +    {
    +      flat         : "calendar-container", // ID of the parent element
    +      flatCallback : dateChanged,          // our callback function
    +      dateStatusFunc : ourDateStatusFunc
    +    }
    +  );
    +</script>
    +

    +

    +So the above code creates a normal flat calendar, like in the previous +sample. We hook into it with the function “ourDateStatusFunc”, +which receives a date object as the first argument, and also the year, +month, date as the next 3 arguments (normally, you can extract year, month, +date from the first parameter too, but we pass them separately for +convenience, as it's very likely that they are going to be used in this +function).

    +

    +So, this function receives a date. It can return false if you want +no special action to be taken on that date, true if that date +should be disabled (unselectable), or a string if you want to assign a +special CSS class to that date. We return “special” for the dates that we +want to highlight -- and note that we defined a “special” look for them in +the CSS section.

    +

    +I used a simple approach here to define what dates are special. There's a +JavaScript object (the SPECIAL_DAYS global variable) which holds an array +of dates for each month. Month numbers start at zero (January). Months +that don't contain special dates can be absent from this object. Note that +the way to implement this is completely separated from the calendar +code -- therefore, feel free to use your imagination if you have better +ideas. :-)

    +

    +

    + +

    3.4  Select multiple dates

    +

    Starting version 1.0, the calendar is able to handle multiple dates +selection. You just need to pass the “multiple” parameter to +Calendar.setup and add some special code that interprets the +selection once the calendar is closed.

    +

    +

    +
    <a id="trigger" href="#">[open calendar...]</a>
    +<div id="output"></div>
    +<script type="text/javascript">//<![CDATA[
    +    // the default multiple dates selected,
    +    // first time the calendar is displayed
    +    var MA = [];
    +
    +    function closed(cal) {
    +
    +      // here we'll write the output; this is only for example.  You
    +      // will normally fill an input field or something with the dates.
    +      var el = document.getElementById("output");
    +
    +      // reset initial content.
    +      el.innerHTML = "";
    +
    +      // Reset the "MA", in case one triggers the calendar again.
    +      // CAREFUL!  You don't want to do "MA = [];".  We need to modify
    +      // the value of the current array, instead of creating a new one.
    +      // Calendar.setup is called only once! :-)  So be careful.
    +      MA.length = 0;
    +
    +      // walk the calendar's multiple dates selection hash
    +      for (var i in cal.multiple) {
    +        var d = cal.multiple[i];
    +        // sometimes the date is not actually selected,
    +        // so let's check
    +        if (d) {
    +          // OK, selected.  Fill an input field or something.
    +          el.innerHTML += d.print("%A, %Y %B %d") + "<br />";
    +          // and push it in the "MA", in case one triggers the calendar again.
    +          MA[MA.length] = d;
    +        }
    +      }
    +      cal.hide();
    +      return true;
    +    };
    +
    +    Calendar.setup({
    +      align      : "BR",
    +      showOthers : true,
    +      multiple   : MA, // pass the initial or computed array of multiple dates
    +      onClose    : closed,
    +      button     : "trigger"
    +    });
    +//]]></script>
    +

    +

    +The above code creates a popup calendar and passes to it an array of dates, +which is initially empty, in the “multiple” argument. When the calendar is +closed it will call our “closed” function handler; in this handler +we determine what dates were actually selected, inspecting the +“cal.multiple” property, we display them in a DIV element right +next to the <a> element that opens the calendar, and we reinitialize the +global array of selected dates (which will be used if the end user opens the +calendar again). I guess the code speaks for itself, right? :-)

    +

    +

    + +

    4  The Calendar object overview

    +

    +

    +Basically you should be able to setup the calendar with the function presented +in the previous section. However, if for some reason Calendar.setup +doesn't provide all the functionality that you need and you want to tweak into +the process of creating and configuring the calendar ``by hand'', then this +section is the way to go.

    +

    +The file calendar.js implements the functionality of the calendar. +All (well, almost all) functions and variables are embedded in the JavaScript +object ``Calendar''.

    +

    +You can instantiate a Calendar object by calling the constructor, like +this: var cal = new Calendar(...). We will discuss the parameters +later. After creating the object, the variable cal will contain a +reference to it. You can use this reference to access further options of the +calendar, for instance:

    +

    +

    +
    cal.weekNumbers = false; // do not display week numbers
    +cal.showsTime = true;    // include a time selector
    +cal.setDateFormat("%Y.%m.%d %H:%M"); // set this format: 2003.12.31 23:59
    +cal.setDisabledHandler(function(date, year, month, day) {
    +  // verify date and return true if it has to be disabled
    +  // ``date'' is a JS Date object, but if you only need the
    +  // year, month and/or day you can get them separately as
    +  // next 3 parameters, as you can see in the declaration
    +  if (year == 2004) {
    +    // disable all dates from 2004
    +    return true;
    +  }
    +  return false;
    +});
    +

    +

    +etc. Prior to version +0.9.3 this was the only way to configure it. The Calendar.setup +function, documented in section 2, basically does the same +things (actually more) in order to setup the calendar, based on the parameters +that you provided.

    +

    +

    + +

    4.1  Creating a calendar

    +

    The calendar is created by following some steps (even the function +Calendar.setup, described in section 2, does the +same). While you can skip optional (marked ``opt'') steps if you're happy with +the defaults, please respect the order below.

    +

    +

    +

      +

      +
    1. Instantiate a Calendar object. Details about this in +section 5.1.

      +

      +

      +
    2. opt   Set the weekNumbers property to false if you don't want +the calendar to display week numbers.

      +

      +

      +
    3. opt   Set the showsTime property to true if you +want the calendar to also provide a time selector.

      +

      +

      +
    4. opt   Set the time24 property to false if you want +the time selector to be in 12-hour format. Default is 24-hour format. This +property only has effect if you also set showsTime to +true.

      +

      +

      +
    5. opt   Set the range of years available for selection (see section +5.3.15). The default range is [1970..2050].

      +

      +

      +
    6. opt   Set the getDateStatus property. You should pass +here a function that receives a JavaScript Date object and returns +true if the given date should be disabled, false otherwise (details in +section 5.3.7).

      +

      +

      +
    7. opt   Set a date format. Your handler function, passed to the +calendar constructor, will be called when a date is selected with a reference +to the calendar and a date string in this format.

      +

      +

      +
    8. Create the HTML elements related to the calendar. This step +practically puts the calendar in your HTML page. You simply call +Calendar.create(). You can give an optional parameter if you wanna +create a flat calendar (details in section 5.3.1).

      +

      +

      +
    9. opt   Initialize the calendar to a certain date, for instance from +the input field.

      +

      +

      +
    10. Show the calendar (details in section 5.3.9).

      +

      +

      +

    +

    +

    + +

    4.2  Order does matter ;-)

    +

    As you could see in the previous section, there are more steps to be followed +in order to setup the calendar. This happens because there are two different +things that need to be accomplished: first there is the JavaScript object, that +is created with new Calendar(...). Secondly there are the HTML +elements that actually lets you see and manipulate the calendar.

    +

    +

    +[ Those that did UI4 programming, no matter in what +language and on what platform, may be familiar with this concept. First there +is the object in memory that lets you manipulate the UI element, and secondly +there is the UI element (known as ``control'', ``window'', ``widget'', etc.), +also in memory but you don't usually access it directly. ] +

    +By instantiating the calendar we create the JavaScript object. It lets us +configure some properties and it also knows how to create the UI element (the +HTML elements actually) that will eventually be what the end-user sees on +screen. Creation of the HTML element is accomplished by the function +Calendar.create. It knows how to create popup or flat calendars. +This function is described in section 5.3.1.

    +

    +Some properties need to be set prior to creating the HTML elements, because +otherwise they wouldn't have any effect. Such a property is +weekNumbers -- it has the default value ``true'', and if you don't +want the calendar to display the week numbers you have to set it to false. If, +however, you do that after calling Calendar.create the calendar +would still display the week numbers, because the HTML elements are already +created (including the <td>-s in the <table> element that +should contain the week numbers). For this reason the order of the steps above +is important.

    +

    +Another example is when you want to show the calendar. The ``create'' function +does create the HTML elements, but they are initially hidden (have the style +``display: none'') unless the calendar is a flat calendar that should be always +visible in the page. Obviously, the Calendar.show function should be +called after calling Calendar.create.

    +

    +

    + +

    4.3  Caching the object

    +

    Suppose the end-user has popped up a calendar and selects a date. The calendar +then closes. What really happens now?

    +

    +There are two approaches. The first (used in very old versions of the +calendar) was to drop completely the Calendar object and when the end-user pops +up the calendar again to create another one. This approach is bad for more +reasons:

    +

    +

    +

      +

      +
    • creating the JavaScript object and HTML elements is time-consuming

      +

      +

      +
    • we may loose some end-user preferences (i.e. he might prefer to have +Monday for the first day of week and probably already clicked it the first time +when the calendar was opened, but now he has to do it again)

      +

      +

      +

    +

    +The second approach, implemented by the Calendar.setup function, is to +cache the JavaScript object. It does this by checking the global variable +window.calendar and if it is not null it assumes it is the created +Calendar object. When the end-user closes the calendar, our code will only +call ``hide'' on it, therefore keeping the JavaScript object and the +HTML elements in place.

    +

    +CAVEAT:     Since time selection support was introduced, this +``object caching'' mechanism has the following drawback: if you once created +the calendar with the time selection support, then other items that may not +require this functionality will still get a calendar with the time selection +support enabled. And reciprocal. ;-) Hopefully this will be corrected in a +later version, but for now it doesn't seem such a big problem.

    +

    +

    + +

    4.4  Callback functions

    +

    You might rightfully wonder how is the calendar related to the input field? +Who tells it that it has to update that input field when a date is +selected, or that it has to jump to that URL when a date is clicked in +flat mode?

    +

    +All this magic is done through callback functions. The calendar doesn't know +anything about the existence of an input field, nor does it know where to +redirect the browser when a date is clicked in flat mode. It just calls your +callback when a particular event is happening, and you're responsible to handle +it from there. For a general purpose library I think this is the best model of +making a truly reusable thing.

    +

    +The calendar supports the following user callbacks:

    +

    +

    +

      +

      +
    • onSelect   -- this gets called when the end-user changes the date in the +calendar. Documented in section 5.1.

      +

      +

      +
    • onClose   -- this gets called when the calendar should close. It's +user's responsibility to close the calendar. Details in section +5.1.

      +

      +

      +
    • getDateStatus   -- this function gets called for any day in a month, +just before displaying the month. It is called with a JavaScript Date +object and should return true if that date should be disabled, false +if it's an ordinary date and no action should be taken, or it can return a +string in which case the returned value will be appended to the element's CSS +class (this way it provides a powerful way to make some dates ``special'', +i.e. highlight them differently). Details in section +5.3.8.

      +

      +

      +

    +

    +

    + +

    5  The Calendar object API reference

    +

    +

    +

    + +

    5.1  Calendar constructor

    +

    +

    +Synopsis:

    +

    +

    +
    var calendar = Calendar(firstDayOfWeek, date, onSelect, onClose);
    +

    +

    +Parameters are as follows:

    +

    +

    +

      +

      +
    • firstDayOfWeek   -- specifies which day is to be displayed as the first +day of week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday, +..., 6 means Saturday.

      +

      +

      +
    • date   -- a JavaScript Date object or null. If null +is passed then the calendar will default to today date. Otherwise it will +initialize on the given date.

      +

      +

      +
    • onSelect   -- your callback for the ``onChange'' event. See above.

      +

      +

      +
    • onClose   -- your callback for the ``onClose'' event. See above.

      +

      +

      +

    +

    +

    + +

    The onSelect event

    +

    +

    +Here is a typical implementation of this function:

    +

    +

    +
    function onSelect(calendar, date) {
    +  var input_field = document.getElementById("date");
    +  input_field.value = date;
    +};
    +

    +

    +date is in the format selected with calendar.setDateFormat +(see section 5.3.5). This code simply updates the +input field. If you want the calendar to be in single-click mode then you +should also close the calendar after you updated the input field, so we come to +the following version:

    +

    +

    +
    function onSelect(calendar, date) {
    +  var input_field = document.getElementById("date");
    +  input_field.value = date;
    +  if (calendar.dateClicked) {
    +    calendar.callCloseHandler(); // this calls "onClose" (see above)
    +  }
    +};
    +

    +

    +Note that we checked the member variable dateClicked and +only hide the calendar if it's true. If this variable is false it +means that no date was actually selected, but the user only changed the +month/year using the navigation buttons or the menus. We don't want to hide +the calendar in that case.

    +

    +

    + +

    The onClose event

    +

    +

    +This event is triggered when the calendar should close. It should hide or +destroy the calendar object -- the calendar itself just triggers the event, but +it won't close itself.

    +

    +A typical implementation of this function is the following:

    +

    +

    +
    function onClose(calendar) {
    +  calendar.hide();
    +  // or calendar.destroy();
    +};
    +

    +

    +

    + +

    5.2  Useful member variables (properties)

    +

    +

    +After creating the Calendar object you can access the following properties:

    +

    +

    +

      +

      +
    • date -- is a JavaScript Date object. It will always +reflect the date shown in the calendar (yes, even if the calendar is hidden).

      +

      +

      +
    • isPopup -- if this is true then the current Calendar object is +a popup calendar. Otherwise (false) we have a flat calendar. This variable is +set from Calendar.create and has no meaning before this function was +called.

      +

      +

      +
    • dateClicked -- particularly useful in the onSelect +handler, this variable tells us if a date was really clicked. That's because +the onSelect handler is called even if the end-user only changed the +month/year but did not select a date. We don't want to close the calendar in +that case.

      +

      +

      +
    • weekNumbers -- if true (default) then the calendar +displays week numbers. If you don't want week numbers you have to set this +variable to false before calling Calendar.create.

      +

      +

      +
    • showsTime - if you set this to true (it is +false by default) then the calendar will also include a time selector.

      +

      +

      +
    • time24 - if you set this to false then the time +selector will be in 12-hour format. It is in 24-hour format by default.

      +

      +

      +
    • firstDayOfWeek -- specifies the first day of week (0 to 6, pass +0 for Sunday, 1 for Monday, ..., 6 for Saturday). This variable is set from +constructor, but you still have a chance to modify it before calling +Calendar.create.

      +

      +

      +

    +

    +There are lots of other member variables, but one should access them only +through member functions so I won't document them here.

    +

    +

    + +

    5.3  Public methods

    +

    + +

    5.3.1  Calendar.create

    +

    +

    +This function creates the afferent HTML elements that are needed to display the +calendar. You should call it after setting the calendar properties. Synopsis: +

    +
    calendar.create(); // creates a popup calendar
    +  // -- or --
    +calendar.create(document.getElementById(parent_id)); // makes a flat calendar
    +

    +

    +It can create a popup calendar or a flat calendar. If the ``parent'' argument +is present (it should be a reference -- not ID -- to an HTML element) then +a flat calendar is created and it is inserted in the given element.

    +

    +At any moment, given a reference to a calendar object, we can inspect if it's a +popup or a flat calendar by checking the boolean member variable +isPopup:

    +

    +

    +
    if (calendar.isPopup) {
    +   // this is a popup calendar
    +} else {
    +   // this is a flat calendar
    +}
    +

    +

    +

    + +

    5.3.2  Calendar.callHandler

    +

    +

    +This function calls the first user callback (the +onSelect handler) with the required parameters.

    +

    +

    + +

    5.3.3  Calendar.callCloseHandler

    +

    +

    +This function calls the second user callback (the +onClose handler). It's useful when you want to have a +``single-click'' calendar -- just call this in your onSelect handler, +if a date was clicked.

    +

    +

    + +

    5.3.4  Calendar.hide

    +

    +

    +Call this function to hide the calendar. The calendar object and HTML elements +will not be destroyed, thus you can later call one of the show +functions on the same element.

    +

    +

    + +

    5.3.5  Calendar.setDateFormat

    +

    +

    +This function configures the format in which the calendar reports the date to +your ``onSelect'' handler. Call it like this:

    +

    +

    +
    calendar.setDateFormat("%y/%m/%d");
    +

    +

    +As you can see, it receives only one parameter, the required format. The magic +characters are the following:

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    %a abbreviated weekday name
    %A full weekday name
    %b abbreviated month name
    %B full month name
    %C century number
    %d the day of the month ( 00 .. 31 )
    %e the day of the month ( 0 .. 31 )
    %H hour ( 00 .. 23 )
    %I hour ( 01 .. 12 )
    %j day of the year ( 000 .. 366 )
    %k hour ( 0 .. 23 )
    %l hour ( 1 .. 12 )
    %m month ( 01 .. 12 )
    %M minute ( 00 .. 59 )
    %n a newline character
    %p ``PM'' or ``AM''
    %P ``pm'' or ``am''
    %S second ( 00 .. 59 )
    %s number of seconds since Epoch (since Jan 01 1970 00:00:00 UTC)
    %t a tab character
    %U, %W, %V the week number
    %u the day of the week ( 1 .. 7, 1 = MON )
    %w the day of the week ( 0 .. 6, 0 = SUN )
    %y year without the century ( 00 .. 99 )
    %Y year including the century ( ex. 1979 )
    %% a literal % character +

    +There are more algorithms for computing the week number. All +three specifiers currently implement the same one, as defined by ISO 8601: +``the week 01 is the week that has the Thursday in the current year, which is +equivalent to the week that contains the fourth day of January. Weeks start on +Monday.''

    +

    +

    + +

    5.3.6  Calendar.setTtDateFormat

    +

    +

    +Has the same prototype as Calendar.setDateFormat, but refers to the +format of the date displayed in the ``status bar'' when the mouse is over some +date.

    +

    +

    + +

    5.3.7  Calendar.setDisabledHandler

    +

    +

    +This function allows you to specify a callback function that checks if a +certain date must be disabled by the calendar. You are responsible to write +the callback function. Synopsis:

    +

    +

    +
    function disallowDate(date) {
    +  // date is a JS Date object
    +  if (  date.getFullYear() == 2003 &&
    +        date.getMonth()    == 6 /* July, it's zero-based */ &&
    +        date.getDate()     == 5  ) {
    +    return true; // disable July 5 2003
    +  }
    +  return false; // enable other dates
    +};
    +
    +calendar.setDisabledHandler(disallowDate);
    +

    +

    +If you change this function in ``real-time'', meaning, without creating a new +calendar, then you have to call calendar.refresh() to make it +redisplay the month and take into account the new disabledHandler. +Calendar.setup does this, so you have no such trouble with it.

    +

    +Note that disallowDate should be very fast, as it is called for each +date in the month. Thus, it gets called, say, 30 times before displaying the +calendar, and 30 times when the month is changed. Tests I've done so far show +that it's still good, but in the future I might switch it to a different design +(for instance, to call it once per month and to return an array of dates that +must be disabled).

    +

    +This function should be considered deprecated in the favor of +Calendar.setDateStatusHandler, described below.

    +

    +

    + +

    5.3.8  Calendar.setDateStatusHandler

    +

    +

    +This function obsoletes Calendar.setDisabledHandler. You call it with +a function parameter, but this function can return a boolean +or a string. If the return value is a boolean (true or +false) then it behaves just like setDisabledHandler, +therefore disabling the date if the return value is true.

    +

    +If the returned value is a string then the given date will gain an additional +CSS class, namely the returned value. You can use this to highlight some dates +in some way. Note that you are responsible for defining the CSS class that you +return. If you return the string ``disabled'' then that date will be disabled, +just as if you returned true.

    +

    +Here is a simple scenario that shows what you can do with this function. The +following should be present in some of your styles, or in the document head in +a STYLE tag (but put it after the place where the calendar styles were +loaded):

    +

    +

    +
    .special { background-color: #000; color: #fff; }
    +

    +

    +And you would use the following code before calling Calendar.create():

    +

    +

    +
    // this table holds your special days, so that we can automatize
    +// things a bit:
    +var SPECIAL_DAYS = {
    +    0 : [ 13, 24 ],             // special days in January
    +    2 : [ 1, 6, 8, 12, 18 ],    // special days in March
    +    8 : [ 21, 11 ],             // special days in September
    +   11 : [ 25, 28 ]              // special days in December
    +};
    +
    +// this function returns true if the passed date is special
    +function dateIsSpecial(year, month, day) {
    +    var m = SPECIAL_DAYS[month];
    +    if (!m) return false;
    +    for (var i in m) if (m[i] == day) return true;
    +    return false;
    +}
    +
    +// this is the actual date status handler.  Note that it receives the
    +// date object as well as separate values of year, month and date, for
    +// your confort.
    +function dateStatusHandler(date, y, m, d) {
    +    if (dateIsSpecial(y, m, d)) return ``special'';
    +    else return false;
    +    // return true above if you want to disable other dates
    +}
    +
    +// configure it to the calendar
    +calendar.setDateStatusHandler(dateStatusHandler);
    +

    +

    +The above code adds the ``special'' class name to some dates that are defined +in the SPECIAL_DAYS table. Other dates will simply be displayed as default, +enabled.

    +

    +

    + +

    5.3.9  Calendar.show

    +

    +

    +Call this function do show the calendar. It basically sets the CSS ``display'' +property to ``block''. It doesn't modify the calendar position.

    +

    +This function only makes sense when the calendar is in popup mode.

    +

    +

    + +

    5.3.10  Calendar.showAt

    +

    +

    +Call this to show the calendar at a certain (x, y) position. Prototype:

    +

    +

    +
    calendar.showAt(x, y);
    +

    +

    +The parameters are absolute coordinates relative to the top left +corner of the page, thus they are page coordinates not screen +coordinates.

    +

    +After setting the given coordinates it calls Calendar.show. This function only +makes sense when the calendar is in popup mode.

    +

    +

    + +

    5.3.11  Calendar.showAtElement

    +

    +

    +This function is useful if you want to display the calendar near some element. +You call it like this:

    +

    +

    +
    calendar.showAtElement(element, align);
    +

    +

    +where element is a reference to your element (for instance it can be the input +field that displays the date) and align is an optional parameter, of type string, +containing one or two characters. For instance, if you pass "Br" as +align, the calendar will appear below the element and with its right +margin continuing the element's right margin.

    +

    +As stated above, align may contain one or two characters. The first character +dictates the vertical alignment, relative to the element, and the second +character dictates the horizontal alignment. If the second character is +missing it will be assumed "l" (the left margin of the calendar will +be at the same horizontal position as the left margin of the element).

    +

    +The characters given for the align parameters are case sensitive. This +function only makes sense when the calendar is in popup mode. After computing +the position it uses Calendar.showAt to display the calendar there.

    +

    +

    + +

    Vertical alignment

    +

    The first character in ``align'' can take one of the following values:

    +

    +

    +

      +

      +
    • T -- completely above the reference element (bottom margin of +the calendar aligned to the top margin of the element).

      +

      +

      +
    • t -- above the element but may overlap it (bottom margin of the calendar aligned to +the bottom margin of the element).

      +

      +

      +
    • c -- the calendar displays vertically centered to the reference +element. It might overlap it (that depends on the horizontal alignment).

      +

      +

      +
    • b -- below the element but may overlap it (top margin of the calendar aligned to +the top margin of the element).

      +

      +

      +
    • B -- completely below the element (top margin of the calendar +aligned to the bottom margin of the element).

      +

      +

      +

    +

    +

    + +

    Horizontal alignment

    +

    The second character in ``align'' can take one of the following values:

    +

    +

    +

      +

      +
    • L -- completely to the left of the reference element (right +margin of the calendar aligned to the left margin of the element).

      +

      +

      +
    • l -- to the left of the element but may overlap it (left margin +of the calendar aligned to the left margin of the element).

      +

      +

      +
    • c -- horizontally centered to the element. Might overlap it, +depending on the vertical alignment.

      +

      +

      +
    • r -- to the right of the element but may overlap it (right +margin of the calendar aligned to the right margin of the element).

      +

      +

      +
    • R -- completely to the right of the element (left margin of the +calendar aligned to the right margin of the element).

      +

      +

      +

    +

    +

    + +

    Default values

    +

    If the ``align'' parameter is missing the calendar will choose +``Br''.

    +

    +

    + +

    5.3.12  Calendar.setDate

    +

    +

    +Receives a JavaScript Date object. Sets the given date in the +calendar. If the calendar is visible the new date is displayed immediately.

    +

    +

    +
    calendar.setDate(new Date()); // go today
    +

    +

    +

    + +

    5.3.13  Calendar.setFirstDayOfWeek

    +

    +

    +Changes the first day of week. The parameter has to be a numeric value ranging +from 0 to 6. Pass 0 for Sunday, 1 for Monday, ..., 6 for Saturday.

    +

    +

    +
    calendar.setFirstDayOfWeek(5); // start weeks on Friday
    +

    +

    +

    + +

    5.3.14  Calendar.parseDate

    +

    +

    +Use this function to parse a date given as string and to move the calendar to +that date.

    +

    +The algorithm tries to parse the date according to the format that was +previously set with Calendar.setDateFormat; if that fails, it still +tries to get some valid date out of it (it doesn't read your thoughts, though).

    +

    +

    +
    calendar.parseDate("2003/07/06");
    +

    +

    +

    + +

    5.3.15  Calendar.setRange

    +

    +

    +Sets the range of years that are allowed in the calendar. Synopsis:

    +

    +

    +
    calendar.setRange(1970, 2050);
    +

    +

    +

    + +

    6  Side effects

    +

    The calendar code was intentionally embedded in an object to make it have as +less as possible side effects. However, there are some -- not harmful, after +all. Here is a list of side effects; you can count they already happened after +calendar.js was loaded.

    +

    +

    +

      +

      +
    1. The global variable window.calendar will be set to null. This +variable is used by the calendar code, especially when doing drag & drop for +moving the calendar. In the future I might get rid of it, but for now it +didn't harm anyone.

      +

      +

      +
    2. The JavaScript Date object is modified. We add some properties +and functions that are very useful to our calendar. It made more sense to add +them directly to the Date object than to the calendar itself. +Complete list:

      +

      +

      +

        +

        +
      1. Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); +

        +
      2. Date.SECOND = 1000 /* milliseconds */; +

        +
      3. Date.MINUTE = 60 * Date.SECOND; +

        +
      4. Date.HOUR = 60 * Date.MINUTE; +

        +
      5. Date.DAY = 24 * Date.HOUR; +

        +
      6. Date.WEEK = 7 * Date.DAY;

        +

        +

        +
      7. Date.prototype.getMonthDays(month) -- returns the number of days +of the given month, or of the current date object if no month was given.

        +

        +

        +
      8. Date.prototype.getWeekNumber() -- returns the week number of the +date in the current object.

        +

        +

        +
      9. Date.prototype.equalsTo(other_date) -- compare the current date +object with other_date and returns true if the dates are +equal. It ignores time.

        +

        +

        +
      10. Date.prototype.print(format) -- returns a string with the +current date object represented in the given format. It implements the format +specified in section 5.3.5.

        +

        +

        +

      +

      +

      +

    +

    +

    + +

    7  Credits

    +

    The following people either sponsored, donated money to the project or bought +commercial licenses (listed in reverse chronological order). Your name could +be here too! If you wish to sponsor the project (for instance request a +feature and pay me for implementing it) or donate some money please +please contact me at mihai_bazon@yahoo.com.

    +

    +

    +

    +

    +

    +
    + +Thank you!
    + -- mihai_bazon@yahoo.com +
    +

    +

    +

    +

    1 +by the term ``widget'' I understand a single element of user interface. +But that's in Linux world. For those that did lots of Windows +programming the term ``control'' might be more familiar +

    +

    2 people report that the calendar does +not work with IE5/Mac. However, this browser was discontinued and we +believe that supporting it doesn't worth the efforts, given the fact that +it has the worst, buggiest implementation for DOM I've ever seen.

    +

    3 under Opera 7 the calendar still lacks some functionality, such as +keyboard navigation; also Opera doesn't seem to allow disabling text +selection when one drags the mouse on the page; despite all that, the +calendar is still highly functional under Opera 7 and looks as good as +in other supported browsers.

    +

    4 user interface

    +
    +
    +Last modified: Saturday, March 5th, 2005
    +HTML conversion by TeX2page 2004-09-11
    +
    + + diff --git a/public/bundles/dynarch_calendar/doc/reference.pdf b/public/bundles/dynarch_calendar/doc/reference.pdf new file mode 100644 index 0000000..a09497f Binary files /dev/null and b/public/bundles/dynarch_calendar/doc/reference.pdf differ diff --git a/public/bundles/dynarch_calendar/images/calendar_icon1.gif b/public/bundles/dynarch_calendar/images/calendar_icon1.gif new file mode 100755 index 0000000..cb9aee7 Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon1.gif differ diff --git a/public/bundles/dynarch_calendar/images/calendar_icon2.gif b/public/bundles/dynarch_calendar/images/calendar_icon2.gif new file mode 100644 index 0000000..8680b8f Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon2.gif differ diff --git a/public/bundles/dynarch_calendar/images/calendar_icon3.gif b/public/bundles/dynarch_calendar/images/calendar_icon3.gif new file mode 100644 index 0000000..a549652 Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon3.gif differ diff --git a/public/bundles/dynarch_calendar/images/calendar_icon4.gif b/public/bundles/dynarch_calendar/images/calendar_icon4.gif new file mode 100755 index 0000000..d7538cb Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon4.gif differ diff --git a/public/bundles/dynarch_calendar/images/menuarrow.gif b/public/bundles/dynarch_calendar/images/menuarrow.gif new file mode 100755 index 0000000..ed2dee0 Binary files /dev/null and b/public/bundles/dynarch_calendar/images/menuarrow.gif differ diff --git a/public/bundles/dynarch_calendar/images/menuarrow2.gif b/public/bundles/dynarch_calendar/images/menuarrow2.gif new file mode 100644 index 0000000..40c0aad Binary files /dev/null and b/public/bundles/dynarch_calendar/images/menuarrow2.gif differ diff --git a/public/bundles/dynarch_calendar/images/pixel.gif b/public/bundles/dynarch_calendar/images/pixel.gif new file mode 100755 index 0000000..b740647 Binary files /dev/null and b/public/bundles/dynarch_calendar/images/pixel.gif differ diff --git a/public/bundles/dynarch_calendar/javascripts/calendar-en.js b/public/bundles/dynarch_calendar/javascripts/calendar-en.js new file mode 100755 index 0000000..0dbde79 --- /dev/null +++ b/public/bundles/dynarch_calendar/javascripts/calendar-en.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + +// full month names +Calendar._MN = new Array +("January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "About the calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; +Calendar._TT["GO_TODAY"] = "Go Today"; +Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Select date"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; +Calendar._TT["PART_TODAY"] = " (today)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Display %s first"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; +Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Time:"; diff --git a/public/bundles/dynarch_calendar/javascripts/calendar-setup.js b/public/bundles/dynarch_calendar/javascripts/calendar-setup.js new file mode 100755 index 0000000..f2b4854 --- /dev/null +++ b/public/bundles/dynarch_calendar/javascripts/calendar-setup.js @@ -0,0 +1,200 @@ +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * --------------------------------------------------------------------------- + * + * The DHTML Calendar + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + * + * This file defines helper functions for setting up the calendar. They are + * intended to help non-programmers get a working calendar on their site + * quickly. This script should not be seen as part of the calendar. It just + * shows you what one can do with the calendar, while in the same time + * providing a quick and simple method for setting it up. If you need + * exhaustive customization of the calendar creation process feel free to + * modify this code to suit your needs (this is recommended and much better + * than modifying calendar.js itself). + */ + +// $Id: calendar-setup.js,v 1.25 2005/03/07 09:51:33 mishoo Exp $ + +/** + * This function "patches" an input field (or other element) to use a calendar + * widget for date selection. + * + * The "params" is a single object that can have the following properties: + * + * prop. name | description + * ------------------------------------------------------------------------------------------------- + * inputField | the ID of an input field to store the date + * displayArea | the ID of a DIV or other element to show the date + * button | ID of a button or other element that will trigger the calendar + * eventName | event that will trigger the calendar, without the "on" prefix (default: "click") + * ifFormat | date format that will be stored in the input field + * daFormat | the date format that will be used to display the date in displayArea + * singleClick | (true/false) wether the calendar is in single click mode or not (default: true) + * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc. + * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation + * range | array with 2 elements. Default: [1900, 2999] -- the range of years available + * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers + * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID + * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar) + * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar + * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay) + * onClose | function that gets called when the calendar is closed. [default] + * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar. + * date | the date that the calendar will be initially displayed to + * showsTime | default: false; if true the calendar will include a time selector + * timeFormat | the time format; can be "12" or "24", default is "12" + * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close + * step | configures the step of the years in drop-down boxes; default: 2 + * position | configures the calendar absolute position; default: null + * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible + * showOthers | if "true" (but default: "false") it will show days from other months too + * + * None of them is required, they all have default values. However, if you + * pass none of "inputField", "displayArea" or "button" you'll get a warning + * saying "nothing to setup". + */ +Calendar.setup = function (params) { + function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } }; + + param_default("inputField", null); + param_default("displayArea", null); + param_default("button", null); + param_default("eventName", "click"); + param_default("ifFormat", "%Y/%m/%d"); + param_default("daFormat", "%Y/%m/%d"); + param_default("singleClick", true); + param_default("disableFunc", null); + param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined + param_default("dateText", null); + param_default("firstDay", null); + param_default("align", "Br"); + param_default("range", [1900, 2999]); + param_default("weekNumbers", true); + param_default("flat", null); + param_default("flatCallback", null); + param_default("onSelect", null); + param_default("onClose", null); + param_default("onUpdate", null); + param_default("date", null); + param_default("showsTime", false); + param_default("timeFormat", "24"); + param_default("electric", true); + param_default("step", 2); + param_default("position", null); + param_default("cache", false); + param_default("showOthers", false); + param_default("multiple", null); + + var tmp = ["inputField", "displayArea", "button"]; + for (var i in tmp) { + if (typeof params[tmp[i]] == "string") { + params[tmp[i]] = document.getElementById(params[tmp[i]]); + } + } + if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) { + alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code"); + return false; + } + + function onSelect(cal) { + var p = cal.params; + var update = (cal.dateClicked || p.electric); + if (update && p.inputField) { + p.inputField.value = cal.date.print(p.ifFormat); + if (typeof p.inputField.onchange == "function") + p.inputField.onchange(); + } + if (update && p.displayArea) + p.displayArea.innerHTML = cal.date.print(p.daFormat); + if (update && typeof p.onUpdate == "function") + p.onUpdate(cal); + if (update && p.flat) { + if (typeof p.flatCallback == "function") + p.flatCallback(cal); + } + if (update && p.singleClick && cal.dateClicked) + cal.callCloseHandler(); + }; + + if (params.flat != null) { + if (typeof params.flat == "string") + params.flat = document.getElementById(params.flat); + if (!params.flat) { + alert("Calendar.setup:\n Flat specified but can't find parent."); + return false; + } + var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect); + cal.showsOtherMonths = params.showOthers; + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.params = params; + cal.weekNumbers = params.weekNumbers; + cal.setRange(params.range[0], params.range[1]); + cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + if (params.ifFormat) { + cal.setDateFormat(params.ifFormat); + } + if (params.inputField && typeof params.inputField.value == "string") { + cal.parseDate(params.inputField.value); + } + cal.create(params.flat); + cal.show(); + return false; + } + + var triggerEl = params.button || params.displayArea || params.inputField; + triggerEl["on" + params.eventName] = function() { + var dateEl = params.inputField || params.displayArea; + var dateFmt = params.inputField ? params.ifFormat : params.daFormat; + var mustCreate = false; + var cal = window.calendar; + if (dateEl) + params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt); + if (!(cal && params.cache)) { + window.calendar = cal = new Calendar(params.firstDay, + params.date, + params.onSelect || onSelect, + params.onClose || function(cal) { cal.hide(); }); + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.weekNumbers = params.weekNumbers; + mustCreate = true; + } else { + if (params.date) + cal.setDate(params.date); + cal.hide(); + } + if (params.multiple) { + cal.multiple = {}; + for (var i = params.multiple.length; --i >= 0;) { + var d = params.multiple[i]; + var ds = d.print("%Y%m%d"); + cal.multiple[ds] = d; + } + } + cal.showsOtherMonths = params.showOthers; + cal.yearStep = params.step; + cal.setRange(params.range[0], params.range[1]); + cal.params = params; + cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + cal.setDateFormat(dateFmt); + if (mustCreate) + cal.create(); + cal.refresh(); + if (!params.position) + cal.showAtElement(params.button || params.displayArea || params.inputField, params.align); + else + cal.showAt(params.position[0], params.position[1]); + return false; + }; + + return cal; +}; diff --git a/public/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js b/public/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js new file mode 100755 index 0000000..91c927f --- /dev/null +++ b/public/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js @@ -0,0 +1,21 @@ +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * --------------------------------------------------------------------------- + * + * The DHTML Calendar + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + * + * This file defines helper functions for setting up the calendar. They are + * intended to help non-programmers get a working calendar on their site + * quickly. This script should not be seen as part of the calendar. It just + * shows you what one can do with the calendar, while in the same time + * providing a quick and simple method for setting it up. If you need + * exhaustive customization of the calendar creation process feel free to + * modify this code to suit your needs (this is recommended and much better + * than modifying calendar.js itself). + */ + Calendar.setup=function(params){function param_default(pname,def){if(typeof params[pname]=="undefined"){params[pname]=def;}};param_default("inputField",null);param_default("displayArea",null);param_default("button",null);param_default("eventName","click");param_default("ifFormat","%Y/%m/%d");param_default("daFormat","%Y/%m/%d");param_default("singleClick",true);param_default("disableFunc",null);param_default("dateStatusFunc",params["disableFunc"]);param_default("dateText",null);param_default("firstDay",null);param_default("align","Br");param_default("range",[1900,2999]);param_default("weekNumbers",true);param_default("flat",null);param_default("flatCallback",null);param_default("onSelect",null);param_default("onClose",null);param_default("onUpdate",null);param_default("date",null);param_default("showsTime",false);param_default("timeFormat","24");param_default("electric",true);param_default("step",2);param_default("position",null);param_default("cache",false);param_default("showOthers",false);param_default("multiple",null);var tmp=["inputField","displayArea","button"];for(var i in tmp){if(typeof params[tmp[i]]=="string"){params[tmp[i]]=document.getElementById(params[tmp[i]]);}}if(!(params.flat||params.multiple||params.inputField||params.displayArea||params.button)){alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");return false;}function onSelect(cal){var p=cal.params;var update=(cal.dateClicked||p.electric);if(update&&p.inputField){p.inputField.value=cal.date.print(p.ifFormat);if(typeof p.inputField.onchange=="function")p.inputField.onchange();}if(update&&p.displayArea)p.displayArea.innerHTML=cal.date.print(p.daFormat);if(update&&typeof p.onUpdate=="function")p.onUpdate(cal);if(update&&p.flat){if(typeof p.flatCallback=="function")p.flatCallback(cal);}if(update&&p.singleClick&&cal.dateClicked)cal.callCloseHandler();};if(params.flat!=null){if(typeof params.flat=="string")params.flat=document.getElementById(params.flat);if(!params.flat){alert("Calendar.setup:\n Flat specified but can't find parent.");return false;}var cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect);cal.showsOtherMonths=params.showOthers;cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.params=params;cal.weekNumbers=params.weekNumbers;cal.setRange(params.range[0],params.range[1]);cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;if(params.ifFormat){cal.setDateFormat(params.ifFormat);}if(params.inputField&&typeof params.inputField.value=="string"){cal.parseDate(params.inputField.value);}cal.create(params.flat);cal.show();return false;}var triggerEl=params.button||params.displayArea||params.inputField;triggerEl["on"+params.eventName]=function(){var dateEl=params.inputField||params.displayArea;var dateFmt=params.inputField?params.ifFormat:params.daFormat;var mustCreate=false;var cal=window.calendar;if(dateEl)params.date=Date.parseDate(dateEl.value||dateEl.innerHTML,dateFmt);if(!(cal&¶ms.cache)){window.calendar=cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect,params.onClose||function(cal){cal.hide();});cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.weekNumbers=params.weekNumbers;mustCreate=true;}else{if(params.date)cal.setDate(params.date);cal.hide();}if(params.multiple){cal.multiple={};for(var i=params.multiple.length;--i>=0;){var d=params.multiple[i];var ds=d.print("%Y%m%d");cal.multiple[ds]=d;}}cal.showsOtherMonths=params.showOthers;cal.yearStep=params.step;cal.setRange(params.range[0],params.range[1]);cal.params=params;cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;cal.setDateFormat(dateFmt);if(mustCreate)cal.create();cal.refresh();if(!params.position)cal.showAtElement(params.button||params.displayArea||params.inputField,params.align);else cal.showAt(params.position[0],params.position[1]);return false;};return cal;}; \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/javascripts/calendar.js b/public/bundles/dynarch_calendar/javascripts/calendar.js new file mode 100755 index 0000000..9088e0e --- /dev/null +++ b/public/bundles/dynarch_calendar/javascripts/calendar.js @@ -0,0 +1,1806 @@ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- + * + * The DHTML Calendar, version 1.0 "It is happening again" + * + * Details and latest version at: + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + */ + +// $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $ + +/** The Calendar object constructor. */ +Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { + // member variables + this.activeDiv = null; + this.currentDateEl = null; + this.getDateStatus = null; + this.getDateToolTip = null; + this.getDateText = null; + this.timeout = null; + this.onSelected = onSelected || null; + this.onClose = onClose || null; + this.dragging = false; + this.hidden = false; + this.minYear = 1970; + this.maxYear = 2050; + this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; + this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; + this.isPopup = true; + this.weekNumbers = true; + this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. + this.showsOtherMonths = false; + this.dateStr = dateStr; + this.ar_days = null; + this.showsTime = false; + this.time24 = true; + this.yearStep = 2; + this.hiliteToday = true; + this.multiple = null; + // HTML elements + this.table = null; + this.element = null; + this.tbody = null; + this.firstdayname = null; + // Combo boxes + this.monthsCombo = null; + this.yearsCombo = null; + this.hilitedMonth = null; + this.activeMonth = null; + this.hilitedYear = null; + this.activeYear = null; + // Information + this.dateClicked = false; + + // one-time initializations + if (typeof Calendar._SDN == "undefined") { + // table of short day names + if (typeof Calendar._SDN_len == "undefined") + Calendar._SDN_len = 3; + var ar = new Array(); + for (var i = 8; i > 0;) { + ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); + } + Calendar._SDN = ar; + // table of short month names + if (typeof Calendar._SMN_len == "undefined") + Calendar._SMN_len = 3; + ar = new Array(); + for (var i = 12; i > 0;) { + ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); + } + Calendar._SMN = ar; + } +}; + +// ** constants + +/// "static", needed for event handlers. +Calendar._C = null; + +/// detect a special case of "web browser" +Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && + !/opera/i.test(navigator.userAgent) ); + +Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) ); + +/// detect Opera browser +Calendar.is_opera = /opera/i.test(navigator.userAgent); + +/// detect KHTML-based browsers +Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); + +// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate +// library, at some point. + +Calendar.getAbsolutePos = function(el) { + var SL = 0, ST = 0; + var is_div = /^div$/i.test(el.tagName); + if (is_div && el.scrollLeft) + SL = el.scrollLeft; + if (is_div && el.scrollTop) + ST = el.scrollTop; + var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; + if (el.offsetParent) { + var tmp = this.getAbsolutePos(el.offsetParent); + r.x += tmp.x; + r.y += tmp.y; + } + return r; +}; + +Calendar.isRelated = function (el, evt) { + var related = evt.relatedTarget; + if (!related) { + var type = evt.type; + if (type == "mouseover") { + related = evt.fromElement; + } else if (type == "mouseout") { + related = evt.toElement; + } + } + while (related) { + if (related == el) { + return true; + } + related = related.parentNode; + } + return false; +}; + +Calendar.removeClass = function(el, className) { + if (!(el && el.className)) { + return; + } + var cls = el.className.split(" "); + var ar = new Array(); + for (var i = cls.length; i > 0;) { + if (cls[--i] != className) { + ar[ar.length] = cls[i]; + } + } + el.className = ar.join(" "); +}; + +Calendar.addClass = function(el, className) { + Calendar.removeClass(el, className); + el.className += " " + className; +}; + +// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. +Calendar.getElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; + while (f.nodeType != 1 || /^div$/i.test(f.tagName)) + f = f.parentNode; + return f; +}; + +Calendar.getTargetElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.target; + while (f.nodeType != 1) + f = f.parentNode; + return f; +}; + +Calendar.stopEvent = function(ev) { + ev || (ev = window.event); + if (Calendar.is_ie) { + ev.cancelBubble = true; + ev.returnValue = false; + } else { + ev.preventDefault(); + ev.stopPropagation(); + } + return false; +}; + +Calendar.addEvent = function(el, evname, func) { + if (el.attachEvent) { // IE + el.attachEvent("on" + evname, func); + } else if (el.addEventListener) { // Gecko / W3C + el.addEventListener(evname, func, true); + } else { + el["on" + evname] = func; + } +}; + +Calendar.removeEvent = function(el, evname, func) { + if (el.detachEvent) { // IE + el.detachEvent("on" + evname, func); + } else if (el.removeEventListener) { // Gecko / W3C + el.removeEventListener(evname, func, true); + } else { + el["on" + evname] = null; + } +}; + +Calendar.createElement = function(type, parent) { + var el = null; + if (document.createElementNS) { + // use the XHTML namespace; IE won't normally get here unless + // _they_ "fix" the DOM2 implementation. + el = document.createElementNS("http://www.w3.org/1999/xhtml", type); + } else { + el = document.createElement(type); + } + if (typeof parent != "undefined") { + parent.appendChild(el); + } + return el; +}; + +// END: UTILITY FUNCTIONS + +// BEGIN: CALENDAR STATIC FUNCTIONS + +/** Internal -- adds a set of events to make some element behave like a button. */ +Calendar._add_evs = function(el) { + with (Calendar) { + addEvent(el, "mouseover", dayMouseOver); + addEvent(el, "mousedown", dayMouseDown); + addEvent(el, "mouseout", dayMouseOut); + if (is_ie) { + addEvent(el, "dblclick", dayMouseDblClick); + el.setAttribute("unselectable", true); + } + } +}; + +Calendar.findMonth = function(el) { + if (typeof el.month != "undefined") { + return el; + } else if (typeof el.parentNode.month != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.findYear = function(el) { + if (typeof el.year != "undefined") { + return el; + } else if (typeof el.parentNode.year != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.showMonthsCombo = function () { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var mc = cal.monthsCombo; + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + if (cal.activeMonth) { + Calendar.removeClass(cal.activeMonth, "active"); + } + var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; + Calendar.addClass(mon, "active"); + cal.activeMonth = mon; + var s = mc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var mcw = mc.offsetWidth; + if (typeof mcw == "undefined") + // Konqueror brain-dead techniques + mcw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; +}; + +Calendar.showYearsCombo = function (fwd) { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var yc = cal.yearsCombo; + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + if (cal.activeYear) { + Calendar.removeClass(cal.activeYear, "active"); + } + cal.activeYear = null; + var Y = cal.date.getFullYear() + (fwd ? 1 : -1); + var yr = yc.firstChild; + var show = false; + for (var i = 12; i > 0; --i) { + if (Y >= cal.minYear && Y <= cal.maxYear) { + yr.innerHTML = Y; + yr.year = Y; + yr.style.display = "block"; + show = true; + } else { + yr.style.display = "none"; + } + yr = yr.nextSibling; + Y += fwd ? cal.yearStep : -cal.yearStep; + } + if (show) { + var s = yc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var ycw = yc.offsetWidth; + if (typeof ycw == "undefined") + // Konqueror brain-dead techniques + ycw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; + } +}; + +// event handlers + +Calendar.tableMouseUp = function(ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + if (cal.timeout) { + clearTimeout(cal.timeout); + } + var el = cal.activeDiv; + if (!el) { + return false; + } + var target = Calendar.getTargetElement(ev); + ev || (ev = window.event); + Calendar.removeClass(el, "active"); + if (target == el || target.parentNode == el) { + Calendar.cellClick(el, ev); + } + var mon = Calendar.findMonth(target); + var date = null; + if (mon) { + date = new Date(cal.date); + if (mon.month != date.getMonth()) { + date.setMonth(mon.month); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } else { + var year = Calendar.findYear(target); + if (year) { + date = new Date(cal.date); + if (year.year != date.getFullYear()) { + date.setFullYear(year.year); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } + } + with (Calendar) { + removeEvent(document, "mouseup", tableMouseUp); + removeEvent(document, "mouseover", tableMouseOver); + removeEvent(document, "mousemove", tableMouseOver); + cal._hideCombos(); + _C = null; + return stopEvent(ev); + } +}; + +Calendar.tableMouseOver = function (ev) { + var cal = Calendar._C; + if (!cal) { + return; + } + var el = cal.activeDiv; + var target = Calendar.getTargetElement(ev); + if (target == el || target.parentNode == el) { + Calendar.addClass(el, "hilite active"); + Calendar.addClass(el.parentNode, "rowhilite"); + } else { + if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) + Calendar.removeClass(el, "active"); + Calendar.removeClass(el, "hilite"); + Calendar.removeClass(el.parentNode, "rowhilite"); + } + ev || (ev = window.event); + if (el.navtype == 50 && target != el) { + var pos = Calendar.getAbsolutePos(el); + var w = el.offsetWidth; + var x = ev.clientX; + var dx; + var decrease = true; + if (x > pos.x + w) { + dx = x - pos.x - w; + decrease = false; + } else + dx = pos.x - x; + + if (dx < 0) dx = 0; + var range = el._range; + var current = el._current; + var count = Math.floor(dx / 10) % range.length; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + while (count-- > 0) + if (decrease) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + + cal.onUpdateTime(); + } + var mon = Calendar.findMonth(target); + if (mon) { + if (mon.month != cal.date.getMonth()) { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + Calendar.addClass(mon, "hilite"); + cal.hilitedMonth = mon; + } else if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + } else { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + var year = Calendar.findYear(target); + if (year) { + if (year.year != cal.date.getFullYear()) { + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + Calendar.addClass(year, "hilite"); + cal.hilitedYear = year; + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.tableMouseDown = function (ev) { + if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { + return Calendar.stopEvent(ev); + } +}; + +Calendar.calDragIt = function (ev) { + var cal = Calendar._C; + if (!(cal && cal.dragging)) { + return false; + } + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posX = ev.pageX; + posY = ev.pageY; + } + cal.hideShowCovered(); + var st = cal.element.style; + st.left = (posX - cal.xOffs) + "px"; + st.top = (posY - cal.yOffs) + "px"; + return Calendar.stopEvent(ev); +}; + +Calendar.calDragEnd = function (ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + cal.dragging = false; + with (Calendar) { + removeEvent(document, "mousemove", calDragIt); + removeEvent(document, "mouseup", calDragEnd); + tableMouseUp(ev); + } + cal.hideShowCovered(); +}; + +Calendar.dayMouseDown = function(ev) { + var el = Calendar.getElement(ev); + if (el.disabled) { + return false; + } + var cal = el.calendar; + cal.activeDiv = el; + Calendar._C = cal; + if (el.navtype != 300) with (Calendar) { + if (el.navtype == 50) { + el._current = el.innerHTML; + addEvent(document, "mousemove", tableMouseOver); + } else + addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); + addClass(el, "hilite active"); + addEvent(document, "mouseup", tableMouseUp); + } else if (cal.isPopup) { + cal._dragStart(ev); + } + if (el.navtype == -1 || el.navtype == 1) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); + } else if (el.navtype == -2 || el.navtype == 2) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); + } else { + cal.timeout = null; + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseDblClick = function(ev) { + Calendar.cellClick(Calendar.getElement(ev), ev || window.event); + if (Calendar.is_ie) { + document.selection.empty(); + } +}; + +Calendar.dayMouseOver = function(ev) { + var el = Calendar.getElement(ev); + if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { + return false; + } + if (el.ttip) { + if (el.ttip.substr(0, 1) == "_") { + el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); + } + el.calendar.tooltips.innerHTML = el.ttip; + } + if (el.navtype != 300) { + Calendar.addClass(el, "hilite"); + if (el.caldate) { + Calendar.addClass(el.parentNode, "rowhilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseOut = function(ev) { + with (Calendar) { + var el = getElement(ev); + if (isRelated(el, ev) || _C || el.disabled) + return false; + removeClass(el, "hilite"); + if (el.caldate) + removeClass(el.parentNode, "rowhilite"); + if (el.calendar) + el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; + return stopEvent(ev); + } +}; + +/** + * A generic "click" handler :) handles all types of buttons defined in this + * calendar. + */ +Calendar.cellClick = function(el, ev) { + var cal = el.calendar; + var closing = false; + var newdate = false; + var date = null; + if (typeof el.navtype == "undefined") { + if (cal.currentDateEl) { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } + } + cal.date.setDateOnly(el.caldate); + date = cal.date; + var other_month = !(cal.dateClicked = !el.otherMonth); + if (!other_month && !cal.currentDateEl) + cal._toggleMultipleDate(new Date(date)); + else + newdate = !el.disabled; + // a date was clicked + if (other_month) + cal._init(cal.firstDayOfWeek, date); + } else { + if (el.navtype == 200) { + Calendar.removeClass(el, "hilite"); + cal.callCloseHandler(); + return; + } + date = new Date(cal.date); + if (el.navtype == 0) + date.setDateOnly(new Date()); // TODAY + // unless "today" was clicked, we assume no date was clicked so + // the selected handler will know not to close the calenar when + // in single-click mode. + // cal.dateClicked = (el.navtype == 0); + cal.dateClicked = false; + var year = date.getFullYear(); + var mon = date.getMonth(); + function setMonth(m) { + var day = date.getDate(); + var max = date.getMonthDays(m); + if (day > max) { + date.setDate(max); + } + date.setMonth(m); + }; + switch (el.navtype) { + case 400: + Calendar.removeClass(el, "hilite"); + var text = Calendar._TT["ABOUT"]; + if (typeof text != "undefined") { + text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; + } else { + // FIXME: this should be removed as soon as lang files get updated! + text = "Help and about box text is not translated into this language.\n" + + "If you know this language and you feel generous please update\n" + + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + + "and send it back to to get it into the distribution ;-)\n\n" + + "Thank you!\n" + + "http://dynarch.com/mishoo/calendar.epl\n"; + } + alert(text); + return; + case -2: + if (year > cal.minYear) { + date.setFullYear(year - 1); + } + break; + case -1: + if (mon > 0) { + setMonth(mon - 1); + } else if (year-- > cal.minYear) { + date.setFullYear(year); + setMonth(11); + } + break; + case 1: + if (mon < 11) { + setMonth(mon + 1); + } else if (year < cal.maxYear) { + date.setFullYear(year + 1); + setMonth(0); + } + break; + case 2: + if (year < cal.maxYear) { + date.setFullYear(year + 1); + } + break; + case 100: + cal.setFirstDayOfWeek(el.fdow); + return; + case 50: + var range = el._range; + var current = el.innerHTML; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + if (ev && ev.shiftKey) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + cal.onUpdateTime(); + return; + case 0: + // TODAY will bring us here + if ((typeof cal.getDateStatus == "function") && + cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { + return false; + } + break; + } + if (!date.equalsTo(cal.date)) { + cal.setDate(date); + newdate = true; + } else if (el.navtype == 0) + newdate = closing = true; + } + if (newdate) { + ev && cal.callHandler(); + } + if (closing) { + Calendar.removeClass(el, "hilite"); + ev && cal.callCloseHandler(); + } +}; + +// END: CALENDAR STATIC FUNCTIONS + +// BEGIN: CALENDAR OBJECT FUNCTIONS + +/** + * This function creates the calendar inside the given parent. If _par is + * null than it creates a popup calendar inside the BODY element. If _par is + * an element, be it BODY, then it creates a non-popup calendar (still + * hidden). Some properties need to be set before calling this function. + */ +Calendar.prototype.create = function (_par) { + var parent = null; + if (! _par) { + // default parent is the document body, in which case we create + // a popup calendar. + parent = document.getElementsByTagName("body")[0]; + this.isPopup = true; + } else { + parent = _par; + this.isPopup = false; + } + this.date = this.dateStr ? new Date(this.dateStr) : new Date(); + + var table = Calendar.createElement("table"); + this.table = table; + table.cellSpacing = 0; + table.cellPadding = 0; + table.calendar = this; + Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); + + var div = Calendar.createElement("div"); + this.element = div; + div.className = "calendar"; + if (this.isPopup) { + div.style.position = "absolute"; + div.style.display = "none"; + } + div.appendChild(table); + + var thead = Calendar.createElement("thead", table); + var cell = null; + var row = null; + + var cal = this; + var hh = function (text, cs, navtype) { + cell = Calendar.createElement("td", row); + cell.colSpan = cs; + cell.className = "button"; + if (navtype != 0 && Math.abs(navtype) <= 2) + cell.className += " nav"; + Calendar._add_evs(cell); + cell.calendar = cal; + cell.navtype = navtype; + cell.innerHTML = "
    " + text + "
    "; + return cell; + }; + + row = Calendar.createElement("tr", thead); + var title_length = 6; + (this.isPopup) && --title_length; + (this.weekNumbers) && ++title_length; + + hh("?", 1, 400).ttip = Calendar._TT["INFO"]; + this.title = hh("", title_length, 300); + this.title.className = "title"; + if (this.isPopup) { + this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; + this.title.style.cursor = "move"; + hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; + } + + row = Calendar.createElement("tr", thead); + row.className = "headrow"; + + this._nav_py = hh("«", 1, -2); + this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; + + this._nav_pm = hh("‹", 1, -1); + this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; + + this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); + this._nav_now.ttip = Calendar._TT["GO_TODAY"]; + + this._nav_nm = hh("›", 1, 1); + this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; + + this._nav_ny = hh("»", 1, 2); + this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; + + // day names + row = Calendar.createElement("tr", thead); + row.className = "daynames"; + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + cell.className = "name wn"; + cell.innerHTML = Calendar._TT["WK"]; + } + for (var i = 7; i > 0; --i) { + cell = Calendar.createElement("td", row); + if (!i) { + cell.navtype = 100; + cell.calendar = this; + Calendar._add_evs(cell); + } + } + this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; + this._displayWeekdays(); + + var tbody = Calendar.createElement("tbody", table); + this.tbody = tbody; + + for (i = 6; i > 0; --i) { + row = Calendar.createElement("tr", tbody); + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + } + for (var j = 7; j > 0; --j) { + cell = Calendar.createElement("td", row); + cell.calendar = this; + Calendar._add_evs(cell); + } + } + + if (this.showsTime) { + row = Calendar.createElement("tr", tbody); + row.className = "time"; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + cell.innerHTML = Calendar._TT["TIME"] || " "; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = this.weekNumbers ? 4 : 3; + + (function(){ + function makeTimePart(className, init, range_start, range_end) { + var part = Calendar.createElement("span", cell); + part.className = className; + part.innerHTML = init; + part.calendar = cal; + part.ttip = Calendar._TT["TIME_PART"]; + part.navtype = 50; + part._range = []; + if (typeof range_start != "number") + part._range = range_start; + else { + for (var i = range_start; i <= range_end; ++i) { + var txt; + if (i < 10 && range_end >= 10) txt = '0' + i; + else txt = '' + i; + part._range[part._range.length] = txt; + } + } + Calendar._add_evs(part); + return part; + }; + var hrs = cal.date.getHours(); + var mins = cal.date.getMinutes(); + var t12 = !cal.time24; + var pm = (hrs > 12); + if (t12 && pm) hrs -= 12; + var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); + var span = Calendar.createElement("span", cell); + span.innerHTML = ":"; + span.className = "colon"; + var M = makeTimePart("minute", mins, 0, 59); + var AP = null; + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + if (t12) + AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); + else + cell.innerHTML = " "; + + cal.onSetTime = function() { + var pm, hrs = this.date.getHours(), + mins = this.date.getMinutes(); + if (t12) { + pm = (hrs >= 12); + if (pm) hrs -= 12; + if (hrs == 0) hrs = 12; + AP.innerHTML = pm ? "pm" : "am"; + } + H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; + M.innerHTML = (mins < 10) ? ("0" + mins) : mins; + }; + + cal.onUpdateTime = function() { + var date = this.date; + var h = parseInt(H.innerHTML, 10); + if (t12) { + if (/pm/i.test(AP.innerHTML) && h < 12) + h += 12; + else if (/am/i.test(AP.innerHTML) && h == 12) + h = 0; + } + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + date.setHours(h); + date.setMinutes(parseInt(M.innerHTML, 10)); + date.setFullYear(y); + date.setMonth(m); + date.setDate(d); + this.dateClicked = false; + this.callHandler(); + }; + })(); + } else { + this.onSetTime = this.onUpdateTime = function() {}; + } + + var tfoot = Calendar.createElement("tfoot", table); + + row = Calendar.createElement("tr", tfoot); + row.className = "footrow"; + + cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); + cell.className = "ttip"; + if (this.isPopup) { + cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; + cell.style.cursor = "move"; + } + this.tooltips = cell; + + div = Calendar.createElement("div", this.element); + this.monthsCombo = div; + div.className = "combo"; + for (i = 0; i < Calendar._MN.length; ++i) { + var mn = Calendar.createElement("div"); + mn.className = Calendar.is_ie ? "label-IEfix" : "label"; + mn.month = i; + mn.innerHTML = Calendar._SMN[i]; + div.appendChild(mn); + } + + div = Calendar.createElement("div", this.element); + this.yearsCombo = div; + div.className = "combo"; + for (i = 12; i > 0; --i) { + var yr = Calendar.createElement("div"); + yr.className = Calendar.is_ie ? "label-IEfix" : "label"; + div.appendChild(yr); + } + + this._init(this.firstDayOfWeek, this.date); + parent.appendChild(this.element); +}; + +/** keyboard navigation, only for popup calendars */ +Calendar._keyEvent = function(ev) { + var cal = window._dynarch_popupCalendar; + if (!cal || cal.multiple) + return false; + (Calendar.is_ie) && (ev = window.event); + var act = (Calendar.is_ie || ev.type == "keypress"), + K = ev.keyCode; + if (ev.ctrlKey) { + switch (K) { + case 37: // KEY left + act && Calendar.cellClick(cal._nav_pm); + break; + case 38: // KEY up + act && Calendar.cellClick(cal._nav_py); + break; + case 39: // KEY right + act && Calendar.cellClick(cal._nav_nm); + break; + case 40: // KEY down + act && Calendar.cellClick(cal._nav_ny); + break; + default: + return false; + } + } else switch (K) { + case 32: // KEY space (now) + Calendar.cellClick(cal._nav_now); + break; + case 27: // KEY esc + act && cal.callCloseHandler(); + break; + case 37: // KEY left + case 38: // KEY up + case 39: // KEY right + case 40: // KEY down + if (act) { + var prev, x, y, ne, el, step; + prev = K == 37 || K == 38; + step = (K == 37 || K == 39) ? 1 : 7; + function setVars() { + el = cal.currentDateEl; + var p = el.pos; + x = p & 15; + y = p >> 4; + ne = cal.ar_days[y][x]; + };setVars(); + function prevMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() - step); + cal.setDate(date); + }; + function nextMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() + step); + cal.setDate(date); + }; + while (1) { + switch (K) { + case 37: // KEY left + if (--x >= 0) + ne = cal.ar_days[y][x]; + else { + x = 6; + K = 38; + continue; + } + break; + case 38: // KEY up + if (--y >= 0) + ne = cal.ar_days[y][x]; + else { + prevMonth(); + setVars(); + } + break; + case 39: // KEY right + if (++x < 7) + ne = cal.ar_days[y][x]; + else { + x = 0; + K = 40; + continue; + } + break; + case 40: // KEY down + if (++y < cal.ar_days.length) + ne = cal.ar_days[y][x]; + else { + nextMonth(); + setVars(); + } + break; + } + break; + } + if (ne) { + if (!ne.disabled) + Calendar.cellClick(ne); + else if (prev) + prevMonth(); + else + nextMonth(); + } + } + break; + case 13: // KEY enter + if (act) + Calendar.cellClick(cal.currentDateEl, ev); + break; + default: + return false; + } + return Calendar.stopEvent(ev); +}; + +/** + * (RE)Initializes the calendar to the given date and firstDayOfWeek + */ +Calendar.prototype._init = function (firstDayOfWeek, date) { + var today = new Date(), + TY = today.getFullYear(), + TM = today.getMonth(), + TD = today.getDate(); + this.table.style.visibility = "hidden"; + var year = date.getFullYear(); + if (year < this.minYear) { + year = this.minYear; + date.setFullYear(year); + } else if (year > this.maxYear) { + year = this.maxYear; + date.setFullYear(year); + } + this.firstDayOfWeek = firstDayOfWeek; + this.date = new Date(date); + var month = date.getMonth(); + var mday = date.getDate(); + var no_days = date.getMonthDays(); + + // calendar voodoo for computing the first day that would actually be + // displayed in the calendar, even if it's from the previous month. + // WARNING: this is magic. ;-) + date.setDate(1); + var day1 = (date.getDay() - this.firstDayOfWeek) % 7; + if (day1 < 0) + day1 += 7; + date.setDate(-day1); + date.setDate(date.getDate() + 1); + + var row = this.tbody.firstChild; + var MN = Calendar._SMN[month]; + var ar_days = this.ar_days = new Array(); + var weekend = Calendar._TT["WEEKEND"]; + var dates = this.multiple ? (this.datesCells = {}) : null; + for (var i = 0; i < 6; ++i, row = row.nextSibling) { + var cell = row.firstChild; + if (this.weekNumbers) { + cell.className = "day wn"; + cell.innerHTML = date.getWeekNumber(); + cell = cell.nextSibling; + } + row.className = "daysrow"; + var hasdays = false, iday, dpos = ar_days[i] = []; + for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { + iday = date.getDate(); + var wday = date.getDay(); + cell.className = "day"; + cell.pos = i << 4 | j; + dpos[j] = cell; + var current_month = (date.getMonth() == month); + if (!current_month) { + if (this.showsOtherMonths) { + cell.className += " othermonth"; + cell.otherMonth = true; + } else { + cell.className = "emptycell"; + cell.innerHTML = " "; + cell.disabled = true; + continue; + } + } else { + cell.otherMonth = false; + hasdays = true; + } + cell.disabled = false; + cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; + if (dates) + dates[date.print("%Y%m%d")] = cell; + if (this.getDateStatus) { + var status = this.getDateStatus(date, year, month, iday); + if (this.getDateToolTip) { + var toolTip = this.getDateToolTip(date, year, month, iday); + if (toolTip) + cell.title = toolTip; + } + if (status === true) { + cell.className += " disabled"; + cell.disabled = true; + } else { + if (/disabled/i.test(status)) + cell.disabled = true; + cell.className += " " + status; + } + } + if (!cell.disabled) { + cell.caldate = new Date(date); + cell.ttip = "_"; + if (!this.multiple && current_month + && iday == mday && this.hiliteToday) { + cell.className += " selected"; + this.currentDateEl = cell; + } + if (date.getFullYear() == TY && + date.getMonth() == TM && + iday == TD) { + cell.className += " today"; + cell.ttip += Calendar._TT["PART_TODAY"]; + } + if (weekend.indexOf(wday.toString()) != -1) + cell.className += cell.otherMonth ? " oweekend" : " weekend"; + } + } + if (!(hasdays || this.showsOtherMonths)) + row.className = "emptyrow"; + } + this.title.innerHTML = Calendar._MN[month] + ", " + year; + this.onSetTime(); + this.table.style.visibility = "visible"; + this._initMultipleDates(); + // PROFILE + // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; +}; + +Calendar.prototype._initMultipleDates = function() { + if (this.multiple) { + for (var i in this.multiple) { + var cell = this.datesCells[i]; + var d = this.multiple[i]; + if (!d) + continue; + if (cell) + cell.className += " selected"; + } + } +}; + +Calendar.prototype._toggleMultipleDate = function(date) { + if (this.multiple) { + var ds = date.print("%Y%m%d"); + var cell = this.datesCells[ds]; + if (cell) { + var d = this.multiple[ds]; + if (!d) { + Calendar.addClass(cell, "selected"); + this.multiple[ds] = date; + } else { + Calendar.removeClass(cell, "selected"); + delete this.multiple[ds]; + } + } + } +}; + +Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { + this.getDateToolTip = unaryFunction; +}; + +/** + * Calls _init function above for going to a certain date (but only if the + * date is different than the currently selected one). + */ +Calendar.prototype.setDate = function (date) { + if (!date.equalsTo(this.date)) { + this._init(this.firstDayOfWeek, date); + } +}; + +/** + * Refreshes the calendar. Useful if the "disabledHandler" function is + * dynamic, meaning that the list of disabled date can change at runtime. + * Just * call this function if you think that the list of disabled dates + * should * change. + */ +Calendar.prototype.refresh = function () { + this._init(this.firstDayOfWeek, this.date); +}; + +/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */ +Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { + this._init(firstDayOfWeek, this.date); + this._displayWeekdays(); +}; + +/** + * Allows customization of what dates are enabled. The "unaryFunction" + * parameter must be a function object that receives the date (as a JS Date + * object) and returns a boolean value. If the returned value is true then + * the passed date will be marked as disabled. + */ +Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { + this.getDateStatus = unaryFunction; +}; + +/** Customization of allowed year range for the calendar. */ +Calendar.prototype.setRange = function (a, z) { + this.minYear = a; + this.maxYear = z; +}; + +/** Calls the first user handler (selectedHandler). */ +Calendar.prototype.callHandler = function () { + if (this.onSelected) { + this.onSelected(this, this.date.print(this.dateFormat)); + } +}; + +/** Calls the second user handler (closeHandler). */ +Calendar.prototype.callCloseHandler = function () { + if (this.onClose) { + this.onClose(this); + } + this.hideShowCovered(); +}; + +/** Removes the calendar object from the DOM tree and destroys it. */ +Calendar.prototype.destroy = function () { + var el = this.element.parentNode; + el.removeChild(this.element); + Calendar._C = null; + window._dynarch_popupCalendar = null; +}; + +/** + * Moves the calendar element to a different section in the DOM tree (changes + * its parent). + */ +Calendar.prototype.reparent = function (new_parent) { + var el = this.element; + el.parentNode.removeChild(el); + new_parent.appendChild(el); +}; + +// This gets called when the user presses a mouse button anywhere in the +// document, if the calendar is shown. If the click was outside the open +// calendar this function closes it. +Calendar._checkCalendar = function(ev) { + var calendar = window._dynarch_popupCalendar; + if (!calendar) { + return false; + } + var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); + for (; el != null && el != calendar.element; el = el.parentNode); + if (el == null) { + // calls closeHandler which should hide the calendar. + window._dynarch_popupCalendar.callCloseHandler(); + return Calendar.stopEvent(ev); + } +}; + +/** Shows the calendar. */ +Calendar.prototype.show = function () { + var rows = this.table.getElementsByTagName("tr"); + for (var i = rows.length; i > 0;) { + var row = rows[--i]; + Calendar.removeClass(row, "rowhilite"); + var cells = row.getElementsByTagName("td"); + for (var j = cells.length; j > 0;) { + var cell = cells[--j]; + Calendar.removeClass(cell, "hilite"); + Calendar.removeClass(cell, "active"); + } + } + this.element.style.display = "block"; + this.hidden = false; + if (this.isPopup) { + window._dynarch_popupCalendar = this; + Calendar.addEvent(document, "keydown", Calendar._keyEvent); + Calendar.addEvent(document, "keypress", Calendar._keyEvent); + Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); + } + this.hideShowCovered(); +}; + +/** + * Hides the calendar. Also removes any "hilite" from the class of any TD + * element. + */ +Calendar.prototype.hide = function () { + if (this.isPopup) { + Calendar.removeEvent(document, "keydown", Calendar._keyEvent); + Calendar.removeEvent(document, "keypress", Calendar._keyEvent); + Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); + } + this.element.style.display = "none"; + this.hidden = true; + this.hideShowCovered(); +}; + +/** + * Shows the calendar at a given absolute position (beware that, depending on + * the calendar element style -- position property -- this might be relative + * to the parent's containing rectangle). + */ +Calendar.prototype.showAt = function (x, y) { + var s = this.element.style; + s.left = x + "px"; + s.top = y + "px"; + this.show(); +}; + +/** Shows the calendar near a given element. */ +Calendar.prototype.showAtElement = function (el, opts) { + var self = this; + var p = Calendar.getAbsolutePos(el); + if (!opts || typeof opts != "string") { + this.showAt(p.x, p.y + el.offsetHeight); + return true; + } + function fixPosition(box) { + if (box.x < 0) + box.x = 0; + if (box.y < 0) + box.y = 0; + var cp = document.createElement("div"); + var s = cp.style; + s.position = "absolute"; + s.right = s.bottom = s.width = s.height = "0px"; + document.body.appendChild(cp); + var br = Calendar.getAbsolutePos(cp); + document.body.removeChild(cp); + if (Calendar.is_ie) { + br.y += document.body.scrollTop; + br.x += document.body.scrollLeft; + } else { + br.y += window.scrollY; + br.x += window.scrollX; + } + var tmp = box.x + box.width - br.x; + if (tmp > 0) box.x -= tmp; + tmp = box.y + box.height - br.y; + if (tmp > 0) box.y -= tmp; + }; + this.element.style.display = "block"; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + var w = self.element.offsetWidth; + var h = self.element.offsetHeight; + self.element.style.display = "none"; + var valign = opts.substr(0, 1); + var halign = "l"; + if (opts.length > 1) { + halign = opts.substr(1, 1); + } + // vertical alignment + switch (valign) { + case "T": p.y -= h; break; + case "B": p.y += el.offsetHeight; break; + case "C": p.y += (el.offsetHeight - h) / 2; break; + case "t": p.y += el.offsetHeight - h; break; + case "b": break; // already there + } + // horizontal alignment + switch (halign) { + case "L": p.x -= w; break; + case "R": p.x += el.offsetWidth; break; + case "C": p.x += (el.offsetWidth - w) / 2; break; + case "l": p.x += el.offsetWidth - w; break; + case "r": break; // already there + } + p.width = w; + p.height = h + 40; + self.monthsCombo.style.display = "none"; + fixPosition(p); + self.showAt(p.x, p.y); + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); +}; + +/** Customizes the date format. */ +Calendar.prototype.setDateFormat = function (str) { + this.dateFormat = str; +}; + +/** Customizes the tooltip date format. */ +Calendar.prototype.setTtDateFormat = function (str) { + this.ttDateFormat = str; +}; + +/** + * Tries to identify the date represented in a string. If successful it also + * calls this.setDate which moves the calendar to the given date. + */ +Calendar.prototype.parseDate = function(str, fmt) { + if (!fmt) + fmt = this.dateFormat; + this.setDate(Date.parseDate(str, fmt)); +}; + +Calendar.prototype.hideShowCovered = function () { + if (!Calendar.is_ie && !Calendar.is_opera) + return; + function getVisib(obj){ + var value = obj.style.visibility; + if (!value) { + if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C + if (!Calendar.is_khtml) + value = document.defaultView. + getComputedStyle(obj, "").getPropertyValue("visibility"); + else + value = ''; + } else if (obj.currentStyle) { // IE + value = obj.currentStyle.visibility; + } else + value = ''; + } + return value; + }; + + var tags = new Array("applet", "iframe", "select"); + var el = this.element; + + var p = Calendar.getAbsolutePos(el); + var EX1 = p.x; + var EX2 = el.offsetWidth + EX1; + var EY1 = p.y; + var EY2 = el.offsetHeight + EY1; + + for (var k = tags.length; k > 0; ) { + var ar = document.getElementsByTagName(tags[--k]); + var cc = null; + + for (var i = ar.length; i > 0;) { + cc = ar[--i]; + + p = Calendar.getAbsolutePos(cc); + var CX1 = p.x; + var CX2 = cc.offsetWidth + CX1; + var CY1 = p.y; + var CY2 = cc.offsetHeight + CY1; + + if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = cc.__msh_save_visibility; + } else { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = "hidden"; + } + } + } +}; + +/** Internal function; it displays the bar with the names of the weekday. */ +Calendar.prototype._displayWeekdays = function () { + var fdow = this.firstDayOfWeek; + var cell = this.firstdayname; + var weekend = Calendar._TT["WEEKEND"]; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + var realday = (i + fdow) % 7; + if (i) { + cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); + cell.navtype = 100; + cell.calendar = this; + cell.fdow = realday; + Calendar._add_evs(cell); + } + if (weekend.indexOf(realday.toString()) != -1) { + Calendar.addClass(cell, "weekend"); + } + cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; + cell = cell.nextSibling; + } +}; + +/** Internal function. Hides all combo boxes that might be displayed. */ +Calendar.prototype._hideCombos = function () { + this.monthsCombo.style.display = "none"; + this.yearsCombo.style.display = "none"; +}; + +/** Internal function. Starts dragging the element. */ +Calendar.prototype._dragStart = function (ev) { + if (this.dragging) { + return; + } + this.dragging = true; + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posY = ev.clientY + window.scrollY; + posX = ev.clientX + window.scrollX; + } + var st = this.element.style; + this.xOffs = posX - parseInt(st.left); + this.yOffs = posY - parseInt(st.top); + with (Calendar) { + addEvent(document, "mousemove", calDragIt); + addEvent(document, "mouseup", calDragEnd); + } +}; + +// BEGIN: DATE OBJECT PATCHES + +/** Adds the number of days array to the Date object. */ +Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); + +/** Constants used for time computations */ +Date.SECOND = 1000 /* milliseconds */; +Date.MINUTE = 60 * Date.SECOND; +Date.HOUR = 60 * Date.MINUTE; +Date.DAY = 24 * Date.HOUR; +Date.WEEK = 7 * Date.DAY; + +Date.parseDate = function(str, fmt) { + var today = new Date(); + var y = 0; + var m = -1; + var d = 0; + var a = str.split(/\W+/); + var b = fmt.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%b": + case "%B": + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } + } + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + else if (/am/i.test(a[i]) && hr >= 12) + hr -= 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (isNaN(y)) y = today.getFullYear(); + if (isNaN(m)) m = today.getMonth(); + if (isNaN(d)) d = today.getDate(); + if (isNaN(hr)) hr = today.getHours(); + if (isNaN(min)) min = today.getMinutes(); + if (y != 0 && m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + y = 0; m = -1; d = 0; + for (i = 0; i < a.length; ++i) { + if (a[i].search(/[a-zA-Z]+/) != -1) { + var t = -1; + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } + } + if (t != -1) { + if (m != -1) { + d = m+1; + } + m = t; + } + } else if (parseInt(a[i], 10) <= 12 && m == -1) { + m = a[i]-1; + } else if (parseInt(a[i], 10) > 31 && y == 0) { + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + } else if (d == 0) { + d = a[i]; + } + } + if (y == 0) + y = today.getFullYear(); + if (m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + return today; +}; + +/** Returns the number of days in the current month */ +Date.prototype.getMonthDays = function(month) { + var year = this.getFullYear(); + if (typeof month == "undefined") { + month = this.getMonth(); + } + if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { + return 29; + } else { + return Date._MD[month]; + } +}; + +/** Returns the number of day in the year. */ +Date.prototype.getDayOfYear = function() { + var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0); + var time = now - then; + return Math.floor(time / Date.DAY); +}; + +/** Returns the number of the week in year, as defined in ISO 8601. */ +Date.prototype.getWeekNumber = function() { + var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var DoW = d.getDay(); + d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu + var ms = d.valueOf(); // GMT + d.setMonth(0); + d.setDate(4); // Thu in Week 1 + return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; +}; + +/** Checks date and time equality */ +Date.prototype.equalsTo = function(date) { + return ((this.getFullYear() == date.getFullYear()) && + (this.getMonth() == date.getMonth()) && + (this.getDate() == date.getDate()) && + (this.getHours() == date.getHours()) && + (this.getMinutes() == date.getMinutes())); +}; + +/** Set only the year, month, date parts (keep existing time) */ +Date.prototype.setDateOnly = function(date) { + var tmp = new Date(date); + this.setDate(1); + this.setFullYear(tmp.getFullYear()); + this.setMonth(tmp.getMonth()); + this.setDate(tmp.getDate()); +}; + +/** Prints the date in a string according to the given format. */ +Date.prototype.print = function (str) { + var m = this.getMonth(); + var d = this.getDate(); + var y = this.getFullYear(); + var wn = this.getWeekNumber(); + var w = this.getDay(); + var s = {}; + var hr = this.getHours(); + var pm = (hr >= 12); + var ir = (pm) ? (hr - 12) : hr; + var dy = this.getDayOfYear(); + if (ir == 0) + ir = 12; + var min = this.getMinutes(); + var sec = this.getSeconds(); + s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] + s["%A"] = Calendar._DN[w]; // full weekday name + s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] + s["%B"] = Calendar._MN[m]; // full month name + // FIXME: %c : preferred date and time representation for the current locale + s["%C"] = 1 + Math.floor(y / 100); // the century number + s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + // FIXME: %D : american date style: %m/%d/%y + // FIXME: %E, %F, %G, %g, %h (man strftime) + s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) + s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) + s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) + s["%k"] = hr; // hour, range 0 to 23 (24h format) + s["%l"] = ir; // hour, range 1 to 12 (12h format) + s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 + s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 + s["%n"] = "\n"; // a newline character + s["%p"] = pm ? "PM" : "AM"; + s["%P"] = pm ? "pm" : "am"; + // FIXME: %r : the time in am/pm notation %I:%M:%S %p + // FIXME: %R : the time in 24-hour notation %H:%M + s["%s"] = Math.floor(this.getTime() / 1000); + s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 + s["%t"] = "\t"; // a tab character + // FIXME: %T : the time in 24-hour notation (%H:%M:%S) + s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; + s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) + s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) + // FIXME: %x : preferred date representation for the current locale without the time + // FIXME: %X : preferred time representation for the current locale without the date + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + s["%%"] = "%"; // a literal '%' character + + var re = /%./g; + if (!Calendar.is_ie5 && !Calendar.is_khtml) + return str.replace(re, function (par) { return s[par] || par; }); + + var a = str.match(re); + for (var i = 0; i < a.length; i++) { + var tmp = s[a[i]]; + if (tmp) { + re = new RegExp(a[i], 'g'); + str = str.replace(re, tmp); + } + } + + return str; +}; + +Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear; +Date.prototype.setFullYear = function(y) { + var d = new Date(this); + d.__msh_oldSetFullYear(y); + if (d.getMonth() != this.getMonth()) + this.setDate(28); + this.__msh_oldSetFullYear(y); +}; + +// END: DATE OBJECT PATCHES + + +// global object that remembers the calendar +window._dynarch_popupCalendar = null; diff --git a/public/bundles/dynarch_calendar/javascripts/calendar_stripped.js b/public/bundles/dynarch_calendar/javascripts/calendar_stripped.js new file mode 100755 index 0000000..4fe03f1 --- /dev/null +++ b/public/bundles/dynarch_calendar/javascripts/calendar_stripped.js @@ -0,0 +1,14 @@ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- + * + * The DHTML Calendar, version 1.0 "It is happening again" + * + * Details and latest version at: + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + */ + Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.getDateToolTip=null;this.getDateText=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled)return false;removeClass(el,"hilite");if(el.caldate)removeClass(el.parentNode,"rowhilite");if(el.calendar)el.calendar.tooltips.innerHTML=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new Date());cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;cell.innerHTML="
    "+text+"
    ";return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("×",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("«",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("‹",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("›",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("»",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.innerHTML=Calendar._TT["WK"];}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=Calendar._TT["TIME"]||" ";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.innerHTML=init;part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.innerHTML=":";span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML=" ";cal.onSetTime=function(){var pm,hrs=this.date.getHours(),mins=this.date.getMinutes();if(t12){pm=(hrs>=12);if(pm)hrs-=12;if(hrs==0)hrs=12;AP.innerHTML=pm?"pm":"am";}H.innerHTML=(hrs<10)?("0"+hrs):hrs;M.innerHTML=(mins<10)?("0"+mins):mins;};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.innerHTML,10);if(t12){if(/pm/i.test(AP.innerHTML)&&h<12)h+=12;else if(/am/i.test(AP.innerHTML)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.innerHTML,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){var cal=window._dynarch_popupCalendar;if(!cal||cal.multiple)return false;(Calendar.is_ie)&&(ev=window.event);var act=(Calendar.is_ie||ev.type=="keypress"),K=ev.keyCode;if(ev.ctrlKey){switch(K){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(K){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var prev,x,y,ne,el,step;prev=K==37||K==38;step=(K==37||K==39)?1:7;function setVars(){el=cal.currentDateEl;var p=el.pos;x=p&15;y=p>>4;ne=cal.ar_days[y][x];};setVars();function prevMonth(){var date=new Date(cal.date);date.setDate(date.getDate()-step);cal.setDate(date);};function nextMonth(){var date=new Date(cal.date);date.setDate(date.getDate()+step);cal.setDate(date);};while(1){switch(K){case 37:if(--x>=0)ne=cal.ar_days[y][x];else{x=6;K=38;continue;}break;case 38:if(--y>=0)ne=cal.ar_days[y][x];else{prevMonth();setVars();}break;case 39:if(++x<7)ne=cal.ar_days[y][x];else{x=0;K=40;continue;}break;case 40:if(++ythis.maxYear){year=this.maxYear;date.setFullYear(year);}this.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=this.ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];var dates=this.multiple?(this.datesCells={}):null;for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.innerHTML=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false,iday,dpos=ar_days[i]=[];for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(iday+1)){iday=date.getDate();var wday=date.getDay();cell.className="day";cell.pos=i<<4|j;dpos[j]=cell;var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML=" ";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.innerHTML=this.getDateText?this.getDateText(date,iday):iday;if(dates)dates[date.print("%Y%m%d")]=cell;if(this.getDateStatus){var status=this.getDateStatus(date,year,month,iday);if(this.getDateToolTip){var toolTip=this.getDateToolTip(date,year,month,iday);if(toolTip)cell.title=toolTip;}if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){cell.caldate=new Date(date);cell.ttip="_";if(!this.multiple&¤t_month&&iday==mday&&this.hiliteToday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==TY&&date.getMonth()==TM&&iday==TD){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1)cell.className+=cell.otherMonth?" oweekend":" weekend";}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.title.innerHTML=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";this._initMultipleDates();};Calendar.prototype._initMultipleDates=function(){if(this.multiple){for(var i in this.multiple){var cell=this.datesCells[i];var d=this.multiple[i];if(!d)continue;if(cell)cell.className+=" selected";}}};Calendar.prototype._toggleMultipleDate=function(date){if(this.multiple){var ds=date.print("%Y%m%d");var cell=this.datesCells[ds];if(cell){var d=this.multiple[ds];if(!d){Calendar.addClass(cell,"selected");this.multiple[ds]=date;}else{Calendar.removeClass(cell,"selected");delete this.multiple[ds];}}}};Calendar.prototype.setDateToolTipHandler=function(unaryFunction){this.getDateToolTip=unaryFunction;};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window._dynarch_popupCalendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){var calendar=window._dynarch_popupCalendar;if(!calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window._dynarch_popupCalendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window._dynarch_popupCalendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "l":p.x+=el.offsetWidth-w;break;case "r":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){if(!fmt)fmt=this.dateFormat;this.setDate(Date.parseDate(str,fmt));};Calendar.prototype.hideShowCovered=function(){if(!Calendar.is_ie&&!Calendar.is_opera)return;function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=this.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(this.hidden||(CX1>EX2)||(CX2EY2)||(CY229)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;else if(/am/i.test(a[i])&&hr>=12)hr-=12;break;case "%M":min=parseInt(a[i],10);break;}}if(isNaN(y))y=today.getFullYear();if(isNaN(m))m=today.getMonth();if(isNaN(d))d=today.getDate();if(isNaN(hr))hr=today.getHours();if(isNaN(min))min=today.getMinutes();if(y!=0&&m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);y=0;m=-1;d=0;for(i=0;i31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0)y=today.getFullYear();if(m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);return today;};Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.setDateOnly=function(date){var tmp=new Date(date);this.setDate(1);this.setFullYear(tmp.getFullYear());this.setMonth(tmp.getMonth());this.setDate(tmp.getDate());};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=/%./g;if(!Calendar.is_ie5&&!Calendar.is_khtml)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i" + initial_display + "\n"; + dynarch_contents += "Calendar Icon\n"; + + date = Date.parseDate(initial_date, ifFormat); + + // Use a hidden, nameless field to store the date from the dynarch calendar + dynarch_contents += "\n"; + + // Use three hidden, named fields to pass the dates along in a Rails-compatible format + dynarch_contents += "\n"; + dynarch_contents += "\n"; + dynarch_contents += "\n"; + + // Replace the default Rails select boxes with our dynarch contents + //alert("Contents for " + tag_id + '_container' + ": \n" + dynarch_contents); + container = $(tag_id + '_container'); + container.innerHTML = dynarch_contents; + + Calendar.setup({ + inputField : tag_id + '_input_field', + ifFormat : ifFormat, + displayArea : tag_id + '_display', + daFormat : daFormat, + button : tag_id + '_button', + align : "Tl", + singleClick : true, + cache : true + //showsTime : true + }); +} + +function update_multiparams(tag_id, ifFormat) { + date_string = $(tag_id + '_input_field').value; + date = Date.parseDate(date_string, ifFormat); + $(tag_id + '_year').value = date.getFullYear(); + $(tag_id + '_month').value = date.getMonth() + 1; + $(tag_id + '_day').value = date.getDate(); +} + +function dynarch_tag_name(object_name, method_name, index) { + tag_name = object_name.replace('[]', ''); + if (index) tag_name += '[' + index + ']'; + tag_name += '[' + method_name + ']'; + return tag_name; +} + +function dynarch_tag_id(object_name, method_name, index) { + tag_id = object_name.replace('[]', ''); + if (index) tag_id += '_' + index; + tag_id += '_' + method_name; + return tag_id; +} \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/lang/calendar-af.js b/public/bundles/dynarch_calendar/lang/calendar-af.js new file mode 100644 index 0000000..aeda581 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-af.js @@ -0,0 +1,39 @@ +// ** I18N Afrikaans +Calendar._DN = new Array +("Sondag", + "Maandag", + "Dinsdag", + "Woensdag", + "Donderdag", + "Vrydag", + "Saterdag", + "Sondag"); +Calendar._MN = new Array +("Januarie", + "Februarie", + "Maart", + "April", + "Mei", + "Junie", + "Julie", + "Augustus", + "September", + "Oktober", + "November", + "Desember"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Verander eerste dag van die week"; +Calendar._TT["PREV_YEAR"] = "Vorige jaar (hou vir keuselys)"; +Calendar._TT["PREV_MONTH"] = "Vorige maand (hou vir keuselys)"; +Calendar._TT["GO_TODAY"] = "Gaan na vandag"; +Calendar._TT["NEXT_MONTH"] = "Volgende maand (hou vir keuselys)"; +Calendar._TT["NEXT_YEAR"] = "Volgende jaar (hou vir keuselys)"; +Calendar._TT["SEL_DATE"] = "Kies datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Sleep om te skuif"; +Calendar._TT["PART_TODAY"] = " (vandag)"; +Calendar._TT["MON_FIRST"] = "Vertoon Maandag eerste"; +Calendar._TT["SUN_FIRST"] = "Display Sunday first"; +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-al.js b/public/bundles/dynarch_calendar/lang/calendar-al.js new file mode 100644 index 0000000..4f701cf --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-al.js @@ -0,0 +1,101 @@ +// Calendar ALBANIAN language +//author Rigels Gordani rige@hotmail.com + +// ditet +Calendar._DN = new Array +("E Diele", +"E Hene", +"E Marte", +"E Merkure", +"E Enjte", +"E Premte", +"E Shtune", +"E Diele"); + +//ditet shkurt +Calendar._SDN = new Array +("Die", +"Hen", +"Mar", +"Mer", +"Enj", +"Pre", +"Sht", +"Die"); + +// muajt +Calendar._MN = new Array +("Janar", +"Shkurt", +"Mars", +"Prill", +"Maj", +"Qeshor", +"Korrik", +"Gusht", +"Shtator", +"Tetor", +"Nentor", +"Dhjetor"); + +// muajte shkurt +Calendar._SMN = new Array +("Jan", +"Shk", +"Mar", +"Pri", +"Maj", +"Qes", +"Kor", +"Gus", +"Sht", +"Tet", +"Nen", +"Dhj"); + +// ndihmesa +Calendar._TT = {}; +Calendar._TT["INFO"] = "Per kalendarin"; + +Calendar._TT["ABOUT"] = +"Zgjedhes i ores/dates ne DHTML \n" + +"\n\n" +"Zgjedhja e Dates:\n" + +"- Perdor butonat \xab, \xbb per te zgjedhur vitin\n" + +"- Perdor butonat" + String.fromCharCode(0x2039) + ", " + +String.fromCharCode(0x203a) + +" per te zgjedhur muajin\n" + +"- Mbani shtypur butonin e mousit per nje zgjedje me te shpejte."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Zgjedhja e kohes:\n" + +"- Kliko tek ndonje nga pjeset e ores per ta rritur ate\n" + +"- ose kliko me Shift per ta zvogeluar ate\n" + +"- ose cliko dhe terhiq per zgjedhje me te shpejte."; + +Calendar._TT["PREV_YEAR"] = "Viti i shkuar (prit per menune)"; +Calendar._TT["PREV_MONTH"] = "Muaji i shkuar (prit per menune)"; +Calendar._TT["GO_TODAY"] = "Sot"; +Calendar._TT["NEXT_MONTH"] = "Muaji i ardhshem (prit per menune)"; +Calendar._TT["NEXT_YEAR"] = "Viti i ardhshem (prit per menune)"; +Calendar._TT["SEL_DATE"] = "Zgjidh daten"; +Calendar._TT["DRAG_TO_MOVE"] = "Terhiqe per te levizur"; +Calendar._TT["PART_TODAY"] = " (sot)"; + +// "%s" eshte dita e pare e javes +// %s do te zevendesohet me emrin e dite +Calendar._TT["DAY_FIRST"] = "Trego te %s te paren"; + + +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Mbyll"; +Calendar._TT["TODAY"] = "Sot"; +Calendar._TT["TIME_PART"] = "Kliko me (Shift-)ose terhiqe per te ndryshuar +vleren"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Java"; +Calendar._TT["TIME"] = "Koha:"; + diff --git a/public/bundles/dynarch_calendar/lang/calendar-bg.js b/public/bundles/dynarch_calendar/lang/calendar-bg.js new file mode 100644 index 0000000..5eb73ec --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-bg.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar BG language +// Author: Mihai Bazon, +// Translator: Valentin Sheiretsky, +// Encoding: Windows-1251 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Íĺäĺë˙", + "Ďîíĺäĺëíčę", + "Âňîđíčę", + "Ńđ˙äŕ", + "×ĺňâúđňúę", + "Ďĺňúę", + "Ńúáîňŕ", + "Íĺäĺë˙"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Íĺä", + "Ďîí", + "Âňî", + "Ńđ˙", + "×ĺň", + "Ďĺň", + "Ńúá", + "Íĺä"); + +// full month names +Calendar._MN = new Array +("ßíóŕđč", + "Ôĺâđóŕđč", + "Ěŕđň", + "Ŕďđčë", + "Ěŕé", + "Ţíč", + "Ţëč", + "Ŕâăóńň", + "Ńĺďňĺěâđč", + "Îęňîěâđč", + "Íîĺěâđč", + "Äĺęĺěâđč"); + +// short month names +Calendar._SMN = new Array +("ßíó", + "Ôĺâ", + "Ěŕđ", + "Ŕďđ", + "Ěŕé", + "Ţíč", + "Ţëč", + "Ŕâă", + "Ńĺď", + "Îęň", + "Íîĺ", + "Äĺę"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Číôîđěŕöč˙ çŕ ęŕëĺíäŕđŕ"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Ďđĺäíŕ ăîäčíŕ (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["PREV_MONTH"] = "Ďđĺäĺí ěĺńĺö (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["GO_TODAY"] = "Čçáĺđĺňĺ äíĺń"; +Calendar._TT["NEXT_MONTH"] = "Ńëĺäâŕů ěĺńĺö (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["NEXT_YEAR"] = "Ńëĺäâŕůŕ ăîäčíŕ (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["SEL_DATE"] = "Čçáĺđĺňĺ äŕňŕ"; +Calendar._TT["DRAG_TO_MOVE"] = "Ďđĺěĺńňâŕíĺ"; +Calendar._TT["PART_TODAY"] = " (äíĺń)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "%s ęŕňî ďúđâč äĺí"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Çŕňâîđĺňĺ"; +Calendar._TT["TODAY"] = "Äíĺń"; +Calendar._TT["TIME_PART"] = "(Shift-)Click čëč drag çŕ äŕ ďđîěĺíčňĺ ńňîéíîńňňŕ"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A - %e %B %Y"; + +Calendar._TT["WK"] = "Ńĺäě"; +Calendar._TT["TIME"] = "×ŕń:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-big5-utf8.js b/public/bundles/dynarch_calendar/lang/calendar-big5-utf8.js new file mode 100644 index 0000000..14e0d5d --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-big5-utf8.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar big5-utf8 language +// Author: Gary Fu, +// Encoding: utf8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("ćźćśźć—Ą", + "ćźćśźä¸€", + "ćźćśźäşŚ", + "ćźćśźä¸‰", + "ćźćśźĺ››", + "ćźćśźäş”", + "ćźćśźĺ…­", + "ćźćśźć—Ą"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("ć—Ą", + "一", + "二", + "三", + "ĺ››", + "äş”", + "ĺ…­", + "ć—Ą"); + +// full month names +Calendar._MN = new Array +("一ćś", + "二ćś", + "三ćś", + "ĺ››ćś", + "äş”ćś", + "ĺ…­ćś", + "ä¸ćś", + "ĺ…«ćś", + "äąťćś", + "ĺŤćś", + "ĺŤä¸€ćś", + "ĺŤäşŚćś"); + +// short month names +Calendar._SMN = new Array +("一ćś", + "二ćś", + "三ćś", + "ĺ››ćś", + "äş”ćś", + "ĺ…­ćś", + "ä¸ćś", + "ĺ…«ćś", + "äąťćś", + "ĺŤćś", + "ĺŤä¸€ćś", + "ĺŤäşŚćś"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "é—ść–Ľ"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"ć—Ąćśźé¸ć“‡ć–ąćł•:\n" + +"- 使用 \xab, \xbb 按é•ĺŹŻé¸ć“‡ĺą´ä»˝\n" + +"- 使用 " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " 按é•ĺŹŻé¸ć“‡ćśä»˝\n" + +"- 按住上面的按é•ĺŹŻä»ĄĺŠ ĺż«é¸ĺŹ–"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"時間é¸ć“‡ć–ąćł•:\n" + +"- 點擊任何的時間é¨ä»˝ĺŹŻĺ˘žĺŠ ĺ…¶ĺ€Ľ\n" + +"- ĺŚć™‚按Shift鍵再點擊可減少其值\n" + +"- 點擊並拖曳可加快改變的值"; + +Calendar._TT["PREV_YEAR"] = "上一年 (按住é¸ĺ–®)"; +Calendar._TT["PREV_MONTH"] = "下一年 (按住é¸ĺ–®)"; +Calendar._TT["GO_TODAY"] = "ĺ°ä»Šć—Ą"; +Calendar._TT["NEXT_MONTH"] = "ä¸Šä¸€ćś (按住é¸ĺ–®)"; +Calendar._TT["NEXT_YEAR"] = "ä¸‹ä¸€ćś (按住é¸ĺ–®)"; +Calendar._TT["SEL_DATE"] = "é¸ć“‡ć—Ąćśź"; +Calendar._TT["DRAG_TO_MOVE"] = "拖曳"; +Calendar._TT["PART_TODAY"] = " (今日)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "ĺ°‡ %s 顯示在前"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "é—śé–‰"; +Calendar._TT["TODAY"] = "今日"; +Calendar._TT["TIME_PART"] = "點擊or拖曳可改變時間(ĺŚć™‚按Shift為減)"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "週"; +Calendar._TT["TIME"] = "Time:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-big5.js b/public/bundles/dynarch_calendar/lang/calendar-big5.js new file mode 100644 index 0000000..a589358 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-big5.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar big5 language +// Author: Gary Fu, +// Encoding: big5 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("¬P´Á¤é", + "¬P´Á¤@", + "¬P´Á¤G", + "¬P´Á¤T", + "¬P´ÁĄ|", + "¬P´Á¤­", + "¬P´Á¤»", + "¬P´Á¤é"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("¤é", + "¤@", + "¤G", + "¤T", + "Ą|", + "¤­", + "¤»", + "¤é"); + +// full month names +Calendar._MN = new Array +("¤@¤ë", + "¤G¤ë", + "¤T¤ë", + "Ą|¤ë", + "¤­¤ë", + "¤»¤ë", + "¤C¤ë", + "¤K¤ë", + "¤E¤ë", + "¤Q¤ë", + "¤Q¤@¤ë", + "¤Q¤G¤ë"); + +// short month names +Calendar._SMN = new Array +("¤@¤ë", + "¤G¤ë", + "¤T¤ë", + "Ą|¤ë", + "¤­¤ë", + "¤»¤ë", + "¤C¤ë", + "¤K¤ë", + "¤E¤ë", + "¤Q¤ë", + "¤Q¤@¤ë", + "¤Q¤G¤ë"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Ăö©ó"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"¤é´ÁżďľÜ¤čŞk:\n" + +"- ¨ĎĄÎ \xab, \xbb «ö¶sĄiżďľÜ¦~Ą÷\n" + +"- ¨ĎĄÎ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " «ö¶sĄiżďľÜ¤ëĄ÷\n" + +"- «ö¦í¤W­±Şş«ö¶sĄiĄHĄ[§Öżď¨ú"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"®É¶ˇżďľÜ¤čŞk:\n" + +"- ÂIŔ»Ąô¦óŞş®É¶ˇłˇĄ÷ĄiĽWĄ[¨ä­Č\n" + +"- ¦P®É«öShiftÁä¦AÂIŔ»Ąi´î¤Ö¨ä­Č\n" + +"- ÂIŔ»¨Ă©ě¦˛ĄiĄ[§Ö§ďĹÜŞş­Č"; + +Calendar._TT["PREV_YEAR"] = "¤W¤@¦~ («ö¦íżďłć)"; +Calendar._TT["PREV_MONTH"] = "¤U¤@¦~ («ö¦íżďłć)"; +Calendar._TT["GO_TODAY"] = "¨ě¤µ¤é"; +Calendar._TT["NEXT_MONTH"] = "¤W¤@¤ë («ö¦íżďłć)"; +Calendar._TT["NEXT_YEAR"] = "¤U¤@¤ë («ö¦íżďłć)"; +Calendar._TT["SEL_DATE"] = "żďľÜ¤é´Á"; +Calendar._TT["DRAG_TO_MOVE"] = "©ě¦˛"; +Calendar._TT["PART_TODAY"] = " (¤µ¤é)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "±N %s ĹăĄÜ¦b«e"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Ăöł¬"; +Calendar._TT["TODAY"] = "¤µ¤é"; +Calendar._TT["TIME_PART"] = "ÂIŔ»or©ě¦˛Ąi§ďĹܮɶˇ(¦P®É«öShift¬°´î)"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "¶g"; +Calendar._TT["TIME"] = "Time:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-br.js b/public/bundles/dynarch_calendar/lang/calendar-br.js new file mode 100644 index 0000000..bfb0747 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-br.js @@ -0,0 +1,108 @@ +// ** I18N + +// Calendar pt-BR language +// Author: Fernando Dourado, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Segunda", + "Terça", + "Quarta", + "Quinta", + "Sexta", + "Sabádo", + "Domingo"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +// [No changes using default values] + +// full month names +Calendar._MN = new Array +("Janeiro", + "Fevereiro", + "Março", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro"); + +// short month names +// [No changes using default values] + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Sobre o calendário"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Translate to portuguese Brazil (pt-BR) by Fernando Dourado (fernando.dourado@ig.com.br)\n" + +"Tradução para o portuguĂŞs Brasil (pt-BR) por Fernando Dourado (fernando.dourado@ig.com.br)" + +"\n\n" + +"Selecionar data:\n" + +"- Use as teclas \xab, \xbb para selecionar o ano\n" + +"- Use as teclas " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mĂŞs\n" + +"- Clique e segure com o mouse em qualquer botĂŁo para selecionar rapidamente."; + +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selecionar hora:\n" + +"- Clique em qualquer uma das partes da hora para aumentar\n" + +"- ou Shift-clique para diminuir\n" + +"- ou clique e arraste para selecionar rapidamente."; + +Calendar._TT["PREV_YEAR"] = "Ano anterior (clique e segure para menu)"; +Calendar._TT["PREV_MONTH"] = "MĂŞs anterior (clique e segure para menu)"; +Calendar._TT["GO_TODAY"] = "Ir para a data atual"; +Calendar._TT["NEXT_MONTH"] = "PrĂłximo mĂŞs (clique e segure para menu)"; +Calendar._TT["NEXT_YEAR"] = "PrĂłximo ano (clique e segure para menu)"; +Calendar._TT["SEL_DATE"] = "Selecione uma data"; +Calendar._TT["DRAG_TO_MOVE"] = "Clique e segure para mover"; +Calendar._TT["PART_TODAY"] = " (hoje)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Exibir %s primeiro"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Fechar"; +Calendar._TT["TODAY"] = "Hoje"; +Calendar._TT["TIME_PART"] = "(Shift-)Clique ou arraste para mudar o valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%d de %B de %Y"; + +Calendar._TT["WK"] = "sem"; +Calendar._TT["TIME"] = "Hora:"; + diff --git a/public/bundles/dynarch_calendar/lang/calendar-ca.js b/public/bundles/dynarch_calendar/lang/calendar-ca.js new file mode 100644 index 0000000..a2121bc --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-ca.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar CA language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Diumenge", + "Dilluns", + "Dimarts", + "Dimecres", + "Dijous", + "Divendres", + "Dissabte", + "Diumenge"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Diu", + "Dil", + "Dmt", + "Dmc", + "Dij", + "Div", + "Dis", + "Diu"); + +// full month names +Calendar._MN = new Array +("Gener", + "Febrer", + "Març", + "Abril", + "Maig", + "Juny", + "Juliol", + "Agost", + "Setembre", + "Octubre", + "Novembre", + "Desembre"); + +// short month names +Calendar._SMN = new Array +("Gen", + "Feb", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Oct", + "Nov", + "Des"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Sobre el calendari"; + +Calendar._TT["ABOUT"] = +"DHTML Selector de Data/Hora\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Sel.lecció de Dates:\n" + +"- Fes servir els botons \xab, \xbb per sel.leccionar l'any\n" + +"- Fes servir els botons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per se.lecciconar el mes\n" + +"- Manté el ratolí apretat en qualsevol dels anteriors per sel.lecció rŕpida."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- claca en qualsevol de les parts de la hora per augmentar-les\n" + +"- o Shift-click per decrementar-la\n" + +"- or click and arrastra per sel.lecció rŕpida."; + +Calendar._TT["PREV_YEAR"] = "Any anterior (Mantenir per menu)"; +Calendar._TT["PREV_MONTH"] = "Mes anterior (Mantenir per menu)"; +Calendar._TT["GO_TODAY"] = "Anar a avui"; +Calendar._TT["NEXT_MONTH"] = "Mes següent (Mantenir per menu)"; +Calendar._TT["NEXT_YEAR"] = "Any següent (Mantenir per menu)"; +Calendar._TT["SEL_DATE"] = "Sel.leccionar data"; +Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar per moure"; +Calendar._TT["PART_TODAY"] = " (avui)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostra %s primer"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Tanca"; +Calendar._TT["TODAY"] = "Avui"; +Calendar._TT["TIME_PART"] = "(Shift-)Click a arrastra per canviar el valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "st"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-cs-utf8.js b/public/bundles/dynarch_calendar/lang/calendar-cs-utf8.js new file mode 100644 index 0000000..f6bbbeb --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-cs-utf8.js @@ -0,0 +1,65 @@ +/* + calendar-cs-win.js + language: Czech + encoding: windows-1250 + author: Lubos Jerabek (xnet@seznam.cz) + Jan Uhlir (espinosa@centrum.cz) +*/ + +// ** I18N +Calendar._DN = new Array('NedÄ›le','PondÄ›lĂ­','ĂšterĂ˝','StĹ™eda','ÄŚtvrtek','Pátek','Sobota','NedÄ›le'); +Calendar._SDN = new Array('Ne','Po','Ăšt','St','ÄŚt','Pá','So','Ne'); +Calendar._MN = new Array('Leden','Ăšnor','BĹ™ezen','Duben','KvÄ›ten','ÄŚerven','ÄŚervenec','Srpen','Září','ĹĂ­jen','Listopad','Prosinec'); +Calendar._SMN = new Array('Led','Ăšno','BĹ™e','Dub','KvÄ›','ÄŚrv','ÄŚvc','Srp','Zář','ĹĂ­j','Lis','Pro'); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O komponentÄ› kalendář"; +Calendar._TT["TOGGLE"] = "ZmÄ›na prvnĂ­ho dne v tĂ˝dnu"; +Calendar._TT["PREV_YEAR"] = "PĹ™edchozĂ­ rok (pĹ™idrĹľ pro menu)"; +Calendar._TT["PREV_MONTH"] = "PĹ™edchozĂ­ mÄ›sĂ­c (pĹ™idrĹľ pro menu)"; +Calendar._TT["GO_TODAY"] = "DnešnĂ­ datum"; +Calendar._TT["NEXT_MONTH"] = "Další mÄ›sĂ­c (pĹ™idrĹľ pro menu)"; +Calendar._TT["NEXT_YEAR"] = "Další rok (pĹ™idrĹľ pro menu)"; +Calendar._TT["SEL_DATE"] = "Vyber datum"; +Calendar._TT["DRAG_TO_MOVE"] = "ChyĹĄ a táhni, pro pĹ™esun"; +Calendar._TT["PART_TODAY"] = " (dnes)"; +Calendar._TT["MON_FIRST"] = "UkaĹľ jako prvnĂ­ PondÄ›lĂ­"; +//Calendar._TT["SUN_FIRST"] = "UkaĹľ jako prvnĂ­ NedÄ›li"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"VĂ˝bÄ›r datumu:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- PouĹľijte tlaÄŤĂ­tka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " k vĂ˝bÄ›ru mÄ›sĂ­ce\n" + +"- PodrĹľte tlaÄŤĂ­tko myši na jakĂ©mkoliv z tÄ›ch tlaÄŤĂ­tek pro rychlejší vĂ˝bÄ›r."; + +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"VĂ˝bÄ›r ÄŤasu:\n" + +"- KliknÄ›te na jakoukoliv z částĂ­ vĂ˝bÄ›ru ÄŤasu pro zvýšenĂ­.\n" + +"- nebo Shift-click pro snĂ­ĹľenĂ­\n" + +"- nebo kliknÄ›te a táhnÄ›te pro rychlejší vĂ˝bÄ›r."; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Zobraz %s prvnĂ­"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Zavřít"; +Calendar._TT["TODAY"] = "Dnes"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikni nebo táhni pro zmÄ›nu hodnoty"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "ÄŚas:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-cs-win.js b/public/bundles/dynarch_calendar/lang/calendar-cs-win.js new file mode 100644 index 0000000..140dff3 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-cs-win.js @@ -0,0 +1,65 @@ +/* + calendar-cs-win.js + language: Czech + encoding: windows-1250 + author: Lubos Jerabek (xnet@seznam.cz) + Jan Uhlir (espinosa@centrum.cz) +*/ + +// ** I18N +Calendar._DN = new Array('Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota','Neděle'); +Calendar._SDN = new Array('Ne','Po','Út','St','Čt','Pá','So','Ne'); +Calendar._MN = new Array('Leden','Únor','Březen','Duben','Květen','Červen','Červenec','Srpen','Září','Říjen','Listopad','Prosinec'); +Calendar._SMN = new Array('Led','Úno','Bře','Dub','Kvě','Črv','Čvc','Srp','Zář','Říj','Lis','Pro'); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O komponentě kalendář"; +Calendar._TT["TOGGLE"] = "Změna prvního dne v týdnu"; +Calendar._TT["PREV_YEAR"] = "Předchozí rok (přidrž pro menu)"; +Calendar._TT["PREV_MONTH"] = "Předchozí měsíc (přidrž pro menu)"; +Calendar._TT["GO_TODAY"] = "Dnešní datum"; +Calendar._TT["NEXT_MONTH"] = "Další měsíc (přidrž pro menu)"; +Calendar._TT["NEXT_YEAR"] = "Další rok (přidrž pro menu)"; +Calendar._TT["SEL_DATE"] = "Vyber datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Chyť a táhni, pro přesun"; +Calendar._TT["PART_TODAY"] = " (dnes)"; +Calendar._TT["MON_FIRST"] = "Ukaž jako první Pondělí"; +//Calendar._TT["SUN_FIRST"] = "Ukaž jako první Neděli"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Výběr datumu:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Použijte tlačítka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " k výběru měsíce\n" + +"- Podržte tlačítko myši na jakémkoliv z těch tlačítek pro rychlejší výběr."; + +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Výběr času:\n" + +"- Klikněte na jakoukoliv z částí výběru času pro zvýšení.\n" + +"- nebo Shift-click pro snížení\n" + +"- nebo klikněte a táhněte pro rychlejší výběr."; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Zobraz %s první"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Zavřít"; +Calendar._TT["TODAY"] = "Dnes"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikni nebo táhni pro změnu hodnoty"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Čas:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-da.js b/public/bundles/dynarch_calendar/lang/calendar-da.js new file mode 100644 index 0000000..a99b598 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-da.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar DA language +// Author: Michael Thingmand Henriksen, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Søndag", +"Mandag", +"Tirsdag", +"Onsdag", +"Torsdag", +"Fredag", +"Lørdag", +"Søndag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Søn", +"Man", +"Tir", +"Ons", +"Tor", +"Fre", +"Lør", +"Søn"); + +// full month names +Calendar._MN = new Array +("Januar", +"Februar", +"Marts", +"April", +"Maj", +"Juni", +"Juli", +"August", +"September", +"Oktober", +"November", +"December"); + +// short month names +Calendar._SMN = new Array +("Jan", +"Feb", +"Mar", +"Apr", +"Maj", +"Jun", +"Jul", +"Aug", +"Sep", +"Okt", +"Nov", +"Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Om Kalenderen"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For den seneste version besøg: http://www.dynarch.com/projects/calendar/\n"; + +"Distribueret under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detajler." + +"\n\n" + +"Valg af dato:\n" + +"- Brug \xab, \xbb knapperne for at vælge ĂĄr\n" + +"- Brug " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knapperne for at vælge mĂĄned\n" + +"- Hold knappen pĂĄ musen nede pĂĄ knapperne ovenfor for hurtigere valg."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Valg af tid:\n" + +"- Klik pĂĄ en vilkĂĄrlig del for større værdi\n" + +"- eller Shift-klik for for mindre værdi\n" + +"- eller klik og træk for hurtigere valg."; + +Calendar._TT["PREV_YEAR"] = "Ét ĂĄr tilbage (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Én mĂĄned tilbage (hold for menu)"; +Calendar._TT["GO_TODAY"] = "GĂĄ til i dag"; +Calendar._TT["NEXT_MONTH"] = "Én mĂĄned frem (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Ét ĂĄr frem (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Vælg dag"; +Calendar._TT["DRAG_TO_MOVE"] = "Træk vinduet"; +Calendar._TT["PART_TODAY"] = " (i dag)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Vis %s først"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Luk"; +Calendar._TT["TODAY"] = "I dag"; +Calendar._TT["TIME_PART"] = "(Shift-)klik eller træk for at ændre værdi"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Uge"; +Calendar._TT["TIME"] = "Tid:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-de.js b/public/bundles/dynarch_calendar/lang/calendar-de.js new file mode 100644 index 0000000..4bc1137 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-de.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar DE language +// Author: Jack (tR), +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sonntag", + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag", + "Sonntag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("So", + "Mo", + "Di", + "Mi", + "Do", + "Fr", + "Sa", + "So"); + +// full month names +Calendar._MN = new Array +("Januar", + "Februar", + "M\u00e4rz", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Dezember"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "M\u00e4r", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Dez"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "\u00DCber dieses Kalendarmodul"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Datum ausw\u00e4hlen:\n" + +"- Benutzen Sie die \xab, \xbb Buttons um das Jahr zu w\u00e4hlen\n" + +"- Benutzen Sie die " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " Buttons um den Monat zu w\u00e4hlen\n" + +"- F\u00fcr eine Schnellauswahl halten Sie die Maustaste \u00fcber diesen Buttons fest."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Zeit ausw\u00e4hlen:\n" + +"- Klicken Sie auf die Teile der Uhrzeit, um diese zu erh\u00F6hen\n" + +"- oder klicken Sie mit festgehaltener Shift-Taste um diese zu verringern\n" + +"- oder klicken und festhalten f\u00fcr Schnellauswahl."; + +Calendar._TT["TOGGLE"] = "Ersten Tag der Woche w\u00e4hlen"; +Calendar._TT["PREV_YEAR"] = "Voriges Jahr (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["PREV_MONTH"] = "Voriger Monat (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["GO_TODAY"] = "Heute ausw\u00e4hlen"; +Calendar._TT["NEXT_MONTH"] = "N\u00e4chst. Monat (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["NEXT_YEAR"] = "N\u00e4chst. Jahr (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["SEL_DATE"] = "Datum ausw\u00e4hlen"; +Calendar._TT["DRAG_TO_MOVE"] = "Zum Bewegen festhalten"; +Calendar._TT["PART_TODAY"] = " (Heute)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Woche beginnt mit %s "; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Schlie\u00dfen"; +Calendar._TT["TODAY"] = "Heute"; +Calendar._TT["TIME_PART"] = "(Shift-)Klick oder Festhalten und Ziehen um den Wert zu \u00e4ndern"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Zeit:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-du.js b/public/bundles/dynarch_calendar/lang/calendar-du.js new file mode 100644 index 0000000..2200448 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-du.js @@ -0,0 +1,45 @@ +// ** I18N +Calendar._DN = new Array +("Zondag", + "Maandag", + "Dinsdag", + "Woensdag", + "Donderdag", + "Vrijdag", + "Zaterdag", + "Zondag"); +Calendar._MN = new Array +("Januari", + "Februari", + "Maart", + "April", + "Mei", + "Juni", + "Juli", + "Augustus", + "September", + "Oktober", + "November", + "December"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Toggle startdag van de week"; +Calendar._TT["PREV_YEAR"] = "Vorig jaar (indrukken voor menu)"; +Calendar._TT["PREV_MONTH"] = "Vorige month (indrukken voor menu)"; +Calendar._TT["GO_TODAY"] = "Naar Vandaag"; +Calendar._TT["NEXT_MONTH"] = "Volgende Maand (indrukken voor menu)"; +Calendar._TT["NEXT_YEAR"] = "Volgend jaar (indrukken voor menu)"; +Calendar._TT["SEL_DATE"] = "Selecteer datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Sleep om te verplaatsen"; +Calendar._TT["PART_TODAY"] = " (vandaag)"; +Calendar._TT["MON_FIRST"] = "Toon Maandag eerst"; +Calendar._TT["SUN_FIRST"] = "Toon Zondag eerst"; +Calendar._TT["CLOSE"] = "Sluiten"; +Calendar._TT["TODAY"] = "Vandaag"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd"; +Calendar._TT["TT_DATE_FORMAT"] = "D, M d"; + +Calendar._TT["WK"] = "wk"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-el.js b/public/bundles/dynarch_calendar/lang/calendar-el.js new file mode 100644 index 0000000..43a9b2c --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-el.js @@ -0,0 +1,89 @@ +// ** I18N +Calendar._DN = new Array +("ΚυĎιακή", + "ΔευτέĎα", + "ΤĎίτη", + "ΤετάĎτη", + "Πέμπτη", + "ΠαĎαĎκευή", + "Σάββατο", + "ΚυĎιακή"); + +Calendar._SDN = new Array +("Κυ", + "Δε", + "TĎ", + "Τε", + "Πε", + "Πα", + "Σα", + "Κυ"); + +Calendar._MN = new Array +("ΙανουάĎιος", + "ΦεβĎουάĎιος", + "ΜάĎτιος", + "ΑπĎίλιος", + "Μάϊος", + "Ιούνιος", + "Ιούλιος", + "ΑύγουĎτος", + "ΣεπτέμβĎιος", + "ΟκτώβĎιος", + "ΝοέμβĎιος", + "ΔεκέμβĎιος"); + +Calendar._SMN = new Array +("Ιαν", + "Φεβ", + "ΜαĎ", + "ΑπĎ", + "Μαι", + "Ιουν", + "Ιουλ", + "Αυγ", + "Σεπ", + "Οκτ", + "Νοε", + "Δεκ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Για Ď„Îż ημεĎολόγιο"; + +Calendar._TT["ABOUT"] = +"Επιλογέας ημεĎομηνίας/ĎŽĎας Ďε DHTML\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Για τελευταία έκδοĎη: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Επιλογή ημεĎομηνίας:\n" + +"- ΧĎηĎιμοποιείĎτε τα κουμπιά \xab, \xbb για επιλογή έτους\n" + +"- ΧĎηĎιμοποιείĎτε τα κουμπιά " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " για επιλογή μήνα\n" + +"- ΚĎατήĎτε κουμπί ποντικού πατημένο Ďτα παĎαπάνω κουμπιά για πιο ÎłĎήγοĎη επιλογή."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Επιλογή ĎŽĎας:\n" + +"- Κάντε κλικ Ďε ένα από τα μέĎη της ĎŽĎας για αύξηĎη\n" + +"- ή Shift-κλικ για μείωĎη\n" + +"- ή κλικ και μετακίνηĎη για πιο ÎłĎήγοĎη επιλογή."; +Calendar._TT["TOGGLE"] = "ΜπάĎα Ď€Ďώτης ημέĎας της εβδομάδας"; +Calendar._TT["PREV_YEAR"] = "ΠĎοηγ. έτος (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["PREV_MONTH"] = "ΠĎοηγ. μήνας (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["GO_TODAY"] = "ΣήμεĎα"; +Calendar._TT["NEXT_MONTH"] = "Επόμενος μήνας (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["NEXT_YEAR"] = "Επόμενο έτος (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["SEL_DATE"] = "Επιλέξτε ημεĎομηνία"; +Calendar._TT["DRAG_TO_MOVE"] = "ÎŁĎŤĎτε για να μετακινήĎετε"; +Calendar._TT["PART_TODAY"] = " (ĎήμεĎα)"; +Calendar._TT["MON_FIRST"] = "ΕμφάνιĎη ΔευτέĎας Ď€Ďώτα"; +Calendar._TT["SUN_FIRST"] = "ΕμφάνιĎη ΚυĎιακής Ď€Ďώτα"; +Calendar._TT["CLOSE"] = "ΚλείĎιμο"; +Calendar._TT["TODAY"] = "ΣήμεĎα"; +Calendar._TT["TIME_PART"] = "(Shift-)κλικ ή μετακίνηĎη για αλλαγή"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y"; +Calendar._TT["TT_DATE_FORMAT"] = "D, d M"; + +Calendar._TT["WK"] = "εβδ"; + diff --git a/public/bundles/dynarch_calendar/lang/calendar-en.js b/public/bundles/dynarch_calendar/lang/calendar-en.js new file mode 100644 index 0000000..0dbde79 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-en.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + +// full month names +Calendar._MN = new Array +("January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "About the calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; +Calendar._TT["GO_TODAY"] = "Go Today"; +Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Select date"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; +Calendar._TT["PART_TODAY"] = " (today)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Display %s first"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; +Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Time:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-es.js b/public/bundles/dynarch_calendar/lang/calendar-es.js new file mode 100644 index 0000000..19c1b30 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-es.js @@ -0,0 +1,129 @@ +// ** I18N + +// Calendar ES (spanish) language +// Author: Mihai Bazon, +// Updater: Servilio Afre Puentes +// Updated: 2004-06-03 +// Encoding: utf-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Lunes", + "Martes", + "Miércoles", + "Jueves", + "Viernes", + "Sábado", + "Domingo"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dom", + "Lun", + "Mar", + "Mié", + "Jue", + "Vie", + "Sáb", + "Dom"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 1; + +// full month names +Calendar._MN = new Array +("Enero", + "Febrero", + "Marzo", + "Abril", + "Mayo", + "Junio", + "Julio", + "Agosto", + "Septiembre", + "Octubre", + "Noviembre", + "Diciembre"); + +// short month names +Calendar._SMN = new Array +("Ene", + "Feb", + "Mar", + "Abr", + "May", + "Jun", + "Jul", + "Ago", + "Sep", + "Oct", + "Nov", + "Dic"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Acerca del calendario"; + +Calendar._TT["ABOUT"] = +"Selector DHTML de Fecha/Hora\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Para conseguir la última versión visite: http://www.dynarch.com/projects/calendar/\n" + +"Distribuido bajo licencia GNU LGPL. Visite http://gnu.org/licenses/lgpl.html para más detalles." + +"\n\n" + +"Selección de fecha:\n" + +"- Use los botones \xab, \xbb para seleccionar el ańo\n" + +"- Use los botones " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar el mes\n" + +"- Mantenga pulsado el ratón en cualquiera de estos botones para una selección rápida."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selección de hora:\n" + +"- Pulse en cualquiera de las partes de la hora para incrementarla\n" + +"- o pulse las mayúsculas mientras hace clic para decrementarla\n" + +"- o haga clic y arrastre el ratón para una selección más rápida."; + +Calendar._TT["PREV_YEAR"] = "Ańo anterior (mantener para menú)"; +Calendar._TT["PREV_MONTH"] = "Mes anterior (mantener para menú)"; +Calendar._TT["GO_TODAY"] = "Ir a hoy"; +Calendar._TT["NEXT_MONTH"] = "Mes siguiente (mantener para menú)"; +Calendar._TT["NEXT_YEAR"] = "Ańo siguiente (mantener para menú)"; +Calendar._TT["SEL_DATE"] = "Seleccionar fecha"; +Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar para mover"; +Calendar._TT["PART_TODAY"] = " (hoy)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Hacer %s primer día de la semana"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Cerrar"; +Calendar._TT["TODAY"] = "Hoy"; +Calendar._TT["TIME_PART"] = "(Mayúscula-)Clic o arrastre para cambiar valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y"; + +Calendar._TT["WK"] = "sem"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-fi.js b/public/bundles/dynarch_calendar/lang/calendar-fi.js new file mode 100644 index 0000000..328eabb --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-fi.js @@ -0,0 +1,98 @@ +// ** I18N + +// Calendar FI language (Finnish, Suomi) +// Author: Jarno Käyhkö, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// full day names +Calendar._DN = new Array +("Sunnuntai", + "Maanantai", + "Tiistai", + "Keskiviikko", + "Torstai", + "Perjantai", + "Lauantai", + "Sunnuntai"); + +// short day names +Calendar._SDN = new Array +("Su", + "Ma", + "Ti", + "Ke", + "To", + "Pe", + "La", + "Su"); + +// full month names +Calendar._MN = new Array +("Tammikuu", + "Helmikuu", + "Maaliskuu", + "Huhtikuu", + "Toukokuu", + "Kesäkuu", + "Heinäkuu", + "Elokuu", + "Syyskuu", + "Lokakuu", + "Marraskuu", + "Joulukuu"); + +// short month names +Calendar._SMN = new Array +("Tam", + "Hel", + "Maa", + "Huh", + "Tou", + "Kes", + "Hei", + "Elo", + "Syy", + "Lok", + "Mar", + "Jou"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Tietoja kalenterista"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Uusin versio osoitteessa: http://www.dynarch.com/projects/calendar/\n" + +"Julkaistu GNU LGPL lisenssin alaisuudessa. Lisätietoja osoitteessa http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"Päivämäärä valinta:\n" + +"- Käytä \xab, \xbb painikkeita valitaksesi vuosi\n" + +"- Käytä " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " painikkeita valitaksesi kuukausi\n" + +"- Pitämällä hiiren painiketta minkä tahansa yllä olevan painikkeen kohdalla, saat näkyviin valikon nopeampaan siirtymiseen."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Ajan valinta:\n" + +"- Klikkaa kellonajan numeroita lisätäksesi aikaa\n" + +"- tai pitämällä Shift-näppäintä pohjassa saat aikaa taaksepäin\n" + +"- tai klikkaa ja pidä hiiren painike pohjassa sekä liikuta hiirtä muuttaaksesi aikaa nopeasti eteen- ja taaksepäin."; + +Calendar._TT["PREV_YEAR"] = "Edell. vuosi (paina hetki, näet valikon)"; +Calendar._TT["PREV_MONTH"] = "Edell. kuukausi (paina hetki, näet valikon)"; +Calendar._TT["GO_TODAY"] = "Siirry tähän päivään"; +Calendar._TT["NEXT_MONTH"] = "Seur. kuukausi (paina hetki, näet valikon)"; +Calendar._TT["NEXT_YEAR"] = "Seur. vuosi (paina hetki, näet valikon)"; +Calendar._TT["SEL_DATE"] = "Valitse päivämäärä"; +Calendar._TT["DRAG_TO_MOVE"] = "Siirrä kalenterin paikkaa"; +Calendar._TT["PART_TODAY"] = " (tänään)"; +Calendar._TT["MON_FIRST"] = "Näytä maanantai ensimmäisenä"; +Calendar._TT["SUN_FIRST"] = "Näytä sunnuntai ensimmäisenä"; +Calendar._TT["CLOSE"] = "Sulje"; +Calendar._TT["TODAY"] = "Tänään"; +Calendar._TT["TIME_PART"] = "(Shift-) Klikkaa tai liikuta muuttaaksesi aikaa"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%d.%m.%Y"; + +Calendar._TT["WK"] = "Vko"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-fr.js b/public/bundles/dynarch_calendar/lang/calendar-fr.js new file mode 100644 index 0000000..2a9e0b2 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-fr.js @@ -0,0 +1,125 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// Translator: David Duret, from previous french version + +// full day names +Calendar._DN = new Array +("Dimanche", + "Lundi", + "Mardi", + "Mercredi", + "Jeudi", + "Vendredi", + "Samedi", + "Dimanche"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dim", + "Lun", + "Mar", + "Mar", + "Jeu", + "Ven", + "Sam", + "Dim"); + +// full month names +Calendar._MN = new Array +("Janvier", + "Février", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Aoűt", + "Septembre", + "Octobre", + "Novembre", + "Décembre"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Fev", + "Mar", + "Avr", + "Mai", + "Juin", + "Juil", + "Aout", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "A propos du calendrier"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Heure Selecteur\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Pour la derniere version visitez : http://www.dynarch.com/projects/calendar/\n" + +"Distribué par GNU LGPL. Voir http://gnu.org/licenses/lgpl.html pour les details." + +"\n\n" + +"Selection de la date :\n" + +"- Utiliser les bouttons \xab, \xbb pour selectionner l\'annee\n" + +"- Utiliser les bouttons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pour selectionner les mois\n" + +"- Garder la souris sur n'importe quels boutons pour une selection plus rapide"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selection de l\'heure :\n" + +"- Cliquer sur heures ou minutes pour incrementer\n" + +"- ou Maj-clic pour decrementer\n" + +"- ou clic et glisser-deplacer pour une selection plus rapide"; + +Calendar._TT["PREV_YEAR"] = "Année préc. (maintenir pour menu)"; +Calendar._TT["PREV_MONTH"] = "Mois préc. (maintenir pour menu)"; +Calendar._TT["GO_TODAY"] = "Atteindre la date du jour"; +Calendar._TT["NEXT_MONTH"] = "Mois suiv. (maintenir pour menu)"; +Calendar._TT["NEXT_YEAR"] = "Année suiv. (maintenir pour menu)"; +Calendar._TT["SEL_DATE"] = "Sélectionner une date"; +Calendar._TT["DRAG_TO_MOVE"] = "Déplacer"; +Calendar._TT["PART_TODAY"] = " (Aujourd'hui)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Afficher %s en premier"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Fermer"; +Calendar._TT["TODAY"] = "Aujourd'hui"; +Calendar._TT["TIME_PART"] = "(Maj-)Clic ou glisser pour modifier la valeur"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Sem."; +Calendar._TT["TIME"] = "Heure :"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-he-utf8.js b/public/bundles/dynarch_calendar/lang/calendar-he-utf8.js new file mode 100644 index 0000000..7861217 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-he-utf8.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar EN language +// Author: Idan Sofer, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("ר×שון", + "שני", + "שלישי", + "רביעי", + "חמישי", + "שישי", + "שבת", + "ר×שון"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("×", + "ב", + "×’", + "ד", + "×”", + "ו", + "ש", + "×"); + +// full month names +Calendar._MN = new Array +("ינו×ר", + "פברו×ר", + "מרץ", + "×פריל", + "מ××™", + "יוני", + "יולי", + "×וגוס×", + "ספ×מבר", + "×וק×ובר", + "נובמבר", + "דצמבר"); + +// short month names +Calendar._SMN = new Array +("×™× ×", + "פבר", + "מרץ", + "×פר", + "מ××™", + "יונ", + "יול", + "×וג", + "ספ×", + "×וק", + "נוב", + "דצמ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "×ודות השנתון"; + +Calendar._TT["ABOUT"] = +"בחרן ת×ריך/שעה DHTML\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"×”×’×™×¨×ˇ× ×”×חרונה זמינה ב: http://www.dynarch.com/projects/calendar/\n" + +"מופץ תחת זיכיון ×” GNU LGPL. עיין ב http://gnu.org/licenses/lgpl.html לפר×ים נוספים." + +"\n\n" + +בחירת ת×ריך:\n" + +"- השתמש בכפתורים \xab, \xbb לבחירת שנה\n" + +"- השתמש בכפתורים " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " לבחירת חודש\n" + +"- החזק העכבר לחוץ מעל הכפתורים המוזכרים לעיל לבחירה מהירה יותר."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"בחירת זמן:\n" + +"- לחץ על כל ×חד מחלקי הזמן כדי להוסיף\n" + +"- ×ו shift בשילוב עם לחיצה כדי להחסיר\n" + +"- ×ו לחץ וגרור לפעולה מהירה יותר."; + +Calendar._TT["PREV_YEAR"] = "שנה קודמת - החזק לקבלת תפרי×"; +Calendar._TT["PREV_MONTH"] = "חודש קודם - החזק לקבלת תפרי×"; +Calendar._TT["GO_TODAY"] = "עבור להיום"; +Calendar._TT["NEXT_MONTH"] = "חודש ×”×‘× - החזק לתפרי×"; +Calendar._TT["NEXT_YEAR"] = "שנה הב××” - החזק לתפרי×"; +Calendar._TT["SEL_DATE"] = "בחר ת×ריך"; +Calendar._TT["DRAG_TO_MOVE"] = "גרור להזזה"; +Calendar._TT["PART_TODAY"] = " )היום("; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "הצג %s קודם"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "6"; + +Calendar._TT["CLOSE"] = "סגור"; +Calendar._TT["TODAY"] = "היום"; +Calendar._TT["TIME_PART"] = "(שיפ×-)לחץ וגרור כדי לשנות ערך"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "שעה::"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-hr-utf8.js b/public/bundles/dynarch_calendar/lang/calendar-hr-utf8.js new file mode 100644 index 0000000..d569cfd --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-hr-utf8.js @@ -0,0 +1,49 @@ +/* Croatian language file for the DHTML Calendar version 0.9.2 +* Author Krunoslav Zubrinic , June 2003. +* Feel free to use this script under the terms of the GNU Lesser General +* Public License, as long as you do not remove or alter this notice. +*/ +Calendar._DN = new Array +("Nedjelja", + "Ponedjeljak", + "Utorak", + "Srijeda", + "ÄŚetvrtak", + "Petak", + "Subota", + "Nedjelja"); +Calendar._MN = new Array +("SijeÄŤanj", + "VeljaÄŤa", + "OĹľujak", + "Travanj", + "Svibanj", + "Lipanj", + "Srpanj", + "Kolovoz", + "Rujan", + "Listopad", + "Studeni", + "Prosinac"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Promjeni dan s kojim poÄŤinje tjedan"; +Calendar._TT["PREV_YEAR"] = "Prethodna godina (dugi pritisak za meni)"; +Calendar._TT["PREV_MONTH"] = "Prethodni mjesec (dugi pritisak za meni)"; +Calendar._TT["GO_TODAY"] = "Idi na tekući dan"; +Calendar._TT["NEXT_MONTH"] = "Slijedeći mjesec (dugi pritisak za meni)"; +Calendar._TT["NEXT_YEAR"] = "Slijedeća godina (dugi pritisak za meni)"; +Calendar._TT["SEL_DATE"] = "Izaberite datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Pritisni i povuci za promjenu pozicije"; +Calendar._TT["PART_TODAY"] = " (today)"; +Calendar._TT["MON_FIRST"] = "PrikaĹľi ponedjeljak kao prvi dan"; +Calendar._TT["SUN_FIRST"] = "PrikaĹľi nedjelju kao prvi dan"; +Calendar._TT["CLOSE"] = "Zatvori"; +Calendar._TT["TODAY"] = "Danas"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y"; +Calendar._TT["TT_DATE_FORMAT"] = "DD, dd.mm.y"; + +Calendar._TT["WK"] = "Tje"; \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/lang/calendar-hr.js b/public/bundles/dynarch_calendar/lang/calendar-hr.js new file mode 100644 index 0000000..6c27f60 Binary files /dev/null and b/public/bundles/dynarch_calendar/lang/calendar-hr.js differ diff --git a/public/bundles/dynarch_calendar/lang/calendar-hu.js b/public/bundles/dynarch_calendar/lang/calendar-hu.js new file mode 100644 index 0000000..f5bf057 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-hu.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar HU language +// Author: ??? +// Modifier: KARASZI Istvan, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Vasárnap", + "Hétfő", + "Kedd", + "Szerda", + "Csütörtök", + "Péntek", + "Szombat", + "Vasárnap"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("v", + "h", + "k", + "sze", + "cs", + "p", + "szo", + "v"); + +// full month names +Calendar._MN = new Array +("január", + "február", + "március", + "április", + "május", + "június", + "július", + "augusztus", + "szeptember", + "október", + "november", + "december"); + +// short month names +Calendar._SMN = new Array +("jan", + "feb", + "már", + "ápr", + "máj", + "jún", + "júl", + "aug", + "sze", + "okt", + "nov", + "dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "A kalendáriumról"; + +Calendar._TT["ABOUT"] = +"DHTML dátum/idő kiválasztó\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"a legfrissebb verzió megtalálható: http://www.dynarch.com/projects/calendar/\n" + +"GNU LGPL alatt terjesztve. Lásd a http://gnu.org/licenses/lgpl.html oldalt a részletekhez." + +"\n\n" + +"Dátum választás:\n" + +"- használja a \xab, \xbb gombokat az év kiválasztásához\n" + +"- használja a " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gombokat a hónap kiválasztásához\n" + +"- tartsa lenyomva az egérgombot a gyors választáshoz."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Idő választás:\n" + +"- kattintva növelheti az időt\n" + +"- shift-tel kattintva csökkentheti\n" + +"- lenyomva tartva és húzva gyorsabban kiválaszthatja."; + +Calendar._TT["PREV_YEAR"] = "Előző év (tartsa nyomva a menühöz)"; +Calendar._TT["PREV_MONTH"] = "Előző hónap (tartsa nyomva a menühöz)"; +Calendar._TT["GO_TODAY"] = "Mai napra ugrás"; +Calendar._TT["NEXT_MONTH"] = "Köv. hónap (tartsa nyomva a menühöz)"; +Calendar._TT["NEXT_YEAR"] = "Köv. év (tartsa nyomva a menühöz)"; +Calendar._TT["SEL_DATE"] = "Válasszon dátumot"; +Calendar._TT["DRAG_TO_MOVE"] = "Húzza a mozgatáshoz"; +Calendar._TT["PART_TODAY"] = " (ma)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "%s legyen a hét első napja"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Bezár"; +Calendar._TT["TODAY"] = "Ma"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikk vagy húzás az érték változtatásához"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%b %e, %a"; + +Calendar._TT["WK"] = "hét"; +Calendar._TT["TIME"] = "idő:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-it.js b/public/bundles/dynarch_calendar/lang/calendar-it.js new file mode 100644 index 0000000..7f84cde --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-it.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Translator: Fabio Di Bernardini, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domenica", + "Lunedì", + "Martedì", + "Mercoledì", + "Giovedì", + "Venerdì", + "Sabato", + "Domenica"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dom", + "Lun", + "Mar", + "Mer", + "Gio", + "Ven", + "Sab", + "Dom"); + +// full month names +Calendar._MN = new Array +("Gennaio", + "Febbraio", + "Marzo", + "Aprile", + "Maggio", + "Giugno", + "Luglio", + "Augosto", + "Settembre", + "Ottobre", + "Novembre", + "Dicembre"); + +// short month names +Calendar._SMN = new Array +("Gen", + "Feb", + "Mar", + "Apr", + "Mag", + "Giu", + "Lug", + "Ago", + "Set", + "Ott", + "Nov", + "Dic"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Informazioni sul calendario"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Per gli aggiornamenti: http://www.dynarch.com/projects/calendar/\n" + +"Distribuito sotto licenza GNU LGPL. Vedi http://gnu.org/licenses/lgpl.html per i dettagli." + +"\n\n" + +"Selezione data:\n" + +"- Usa \xab, \xbb per selezionare l'anno\n" + +"- Usa " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per i mesi\n" + +"- Tieni premuto a lungo il mouse per accedere alle funzioni di selezione veloce."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selezione orario:\n" + +"- Clicca sul numero per incrementarlo\n" + +"- o Shift+click per decrementarlo\n" + +"- o click e sinistra o destra per variarlo."; + +Calendar._TT["PREV_YEAR"] = "Anno prec.(clicca a lungo per il menĂą)"; +Calendar._TT["PREV_MONTH"] = "Mese prec. (clicca a lungo per il menĂą)"; +Calendar._TT["GO_TODAY"] = "Oggi"; +Calendar._TT["NEXT_MONTH"] = "Pross. mese (clicca a lungo per il menĂą)"; +Calendar._TT["NEXT_YEAR"] = "Pross. anno (clicca a lungo per il menĂą)"; +Calendar._TT["SEL_DATE"] = "Seleziona data"; +Calendar._TT["DRAG_TO_MOVE"] = "Trascina per spostarlo"; +Calendar._TT["PART_TODAY"] = " (oggi)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostra prima %s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Chiudi"; +Calendar._TT["TODAY"] = "Oggi"; +Calendar._TT["TIME_PART"] = "(Shift-)Click o trascina per cambiare il valore"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a:%b:%e"; + +Calendar._TT["WK"] = "set"; +Calendar._TT["TIME"] = "Ora:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-jp.js b/public/bundles/dynarch_calendar/lang/calendar-jp.js new file mode 100644 index 0000000..3bca7eb --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-jp.js @@ -0,0 +1,45 @@ +// ** I18N +Calendar._DN = new Array +("“ú", + "ŚŽ", + "‰Î", + "…", + "–Ř", + "‹ŕ", + "“y", + "“ú"); +Calendar._MN = new Array +("1ŚŽ", + "2ŚŽ", + "3ŚŽ", + "4ŚŽ", + "5ŚŽ", + "6ŚŽ", + "7ŚŽ", + "8ŚŽ", + "9ŚŽ", + "10ŚŽ", + "11ŚŽ", + "12ŚŽ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "ŹT‚ĚŤĹŹ‰‚Ě—j“ú‚đŘ‚č‘Ö‚¦"; +Calendar._TT["PREV_YEAR"] = "‘O”N"; +Calendar._TT["PREV_MONTH"] = "‘OŚŽ"; +Calendar._TT["GO_TODAY"] = "Ťˇ“ú"; +Calendar._TT["NEXT_MONTH"] = "—‚ŚŽ"; +Calendar._TT["NEXT_YEAR"] = "—‚”N"; +Calendar._TT["SEL_DATE"] = "“ú•t‘I‘đ"; +Calendar._TT["DRAG_TO_MOVE"] = "EB“hE‚ĚÚ“®"; +Calendar._TT["PART_TODAY"] = " (Ťˇ“ú)"; +Calendar._TT["MON_FIRST"] = "ŚŽ—j“ú‚đć“Ş‚É"; +Calendar._TT["SUN_FIRST"] = "“ú—j“ú‚đć“Ş‚É"; +Calendar._TT["CLOSE"] = "•Â‚¶‚é"; +Calendar._TT["TODAY"] = "Ťˇ“ú"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd"; +Calendar._TT["TT_DATE_FORMAT"] = "%mŚŽ %d“ú (%a)"; + +Calendar._TT["WK"] = "ŹT"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-ko-utf8.js b/public/bundles/dynarch_calendar/lang/calendar-ko-utf8.js new file mode 100644 index 0000000..035dd74 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-ko-utf8.js @@ -0,0 +1,120 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Translation: Yourim Yi +// Encoding: EUC-KR +// lang : ko +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names + +Calendar._DN = new Array +("일요일", + "월요일", + "화요일", + "ě요일", + "목요일", + "ę¸ěš”일", + "토요일", + "일요일"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("일", + "ě›”", + "í™”", + "ě", + "목", + "ę¸", + "토", + "일"); + +// full month names +Calendar._MN = new Array +("1ě›”", + "2ě›”", + "3ě›”", + "4ě›”", + "5ě›”", + "6ě›”", + "7ě›”", + "8ě›”", + "9ě›”", + "10ě›”", + "11ě›”", + "12ě›”"); + +// short month names +Calendar._SMN = new Array +("1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "calendar ě— ëŚ€í•´ě„ś"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"\n"+ +"최신 버전을 받으시려면 http://www.dynarch.com/projects/calendar/ ě— ë°©ë¬¸í•ě„¸ěš”\n" + +"\n"+ +"GNU LGPL 라이센스로 배포ë©ë‹ë‹¤. \n"+ +"ëťĽěť´ě„ĽěŠ¤ě— ëŚ€í•ś ěžě„¸í•ś 내용은 http://gnu.org/licenses/lgpl.html ěť„ 읽으세요." + +"\n\n" + +"날짜 ě„ íť:\n" + +"- 연도를 ě„ íťí•ë ¤ë©´ \xab, \xbb 버튼을 사용합ë‹ë‹¤\n" + +"- 달을 ě„ íťí•ë ¤ë©´ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " 버튼을 ë„르세요\n" + +"- 계속 ë„르고 ěžěśĽë©´ ěś„ 값들을 빠르게 ě„ íťí•ě‹¤ ě ěžěŠµë‹ë‹¤."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"ě‹śę°„ ě„ íť:\n" + +"- ë§ěš°ěŠ¤ëˇś ë„르면 ě‹śę°„ěť´ 증가합ë‹ë‹¤\n" + +"- Shift 키와 í•¨ę» ë„르면 ę°ě†Śí•©ë‹ë‹¤\n" + +"- ë„른 ěíśě—ě„ś ë§ěš°ěŠ¤ëĄĽ 움ě§ěť´ë©´ 좀 더 빠르게 ę°’ěť´ 변합ë‹ë‹¤.\n"; + +Calendar._TT["PREV_YEAR"] = "지난 í•´ (길게 ë„르면 목록)"; +Calendar._TT["PREV_MONTH"] = "지난 달 (길게 ë„르면 목록)"; +Calendar._TT["GO_TODAY"] = "ě¤ëŠ 날짜로"; +Calendar._TT["NEXT_MONTH"] = "다음 달 (길게 ë„르면 목록)"; +Calendar._TT["NEXT_YEAR"] = "다음 í•´ (길게 ë„르면 목록)"; +Calendar._TT["SEL_DATE"] = "날짜를 ě„ íťí•ě„¸ěš”"; +Calendar._TT["DRAG_TO_MOVE"] = "ë§ěš°ěŠ¤ ë“śëžę·¸ëˇś 이동 í•ě„¸ěš”"; +Calendar._TT["PART_TODAY"] = " (ě¤ëŠ)"; +Calendar._TT["MON_FIRST"] = "월요일을 í•ś ěŁĽěť ě‹śěž‘ 요일로"; +Calendar._TT["SUN_FIRST"] = "일요일을 í•ś ěŁĽěť ě‹śěž‘ 요일로"; +Calendar._TT["CLOSE"] = "닫기"; +Calendar._TT["TODAY"] = "ě¤ëŠ"; +Calendar._TT["TIME_PART"] = "(Shift-)í´ë¦­ ë는 ë“śëžę·¸ í•ě„¸ěš”"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%b/%e [%a]"; + +Calendar._TT["WK"] = "주"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-ko.js b/public/bundles/dynarch_calendar/lang/calendar-ko.js new file mode 100644 index 0000000..8cddf58 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-ko.js @@ -0,0 +1,120 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Translation: Yourim Yi +// Encoding: EUC-KR +// lang : ko +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names + +Calendar._DN = new Array +("ŔĎżäŔĎ", + "żůżäŔĎ", + "Č­żäŔĎ", + "ĽöżäŔĎ", + "¸ńżäŔĎ", + "±ÝżäŔĎ", + "ĹäżäŔĎ", + "ŔĎżäŔĎ"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("ŔĎ", + "żů", + "Č­", + "Ľö", + "¸ń", + "±Ý", + "Ĺä", + "ŔĎ"); + +// full month names +Calendar._MN = new Array +("1żů", + "2żů", + "3żů", + "4żů", + "5żů", + "6żů", + "7żů", + "8żů", + "9żů", + "10żů", + "11żů", + "12żů"); + +// short month names +Calendar._SMN = new Array +("1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "calendar żˇ ´ëÇŘĽ­"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"\n"+ +"ĂֽŠąöŔüŔ» ąŢŔ¸˝Ă·Á¸é http://www.dynarch.com/projects/calendar/ żˇ ąćą®ÇĎĽĽżä\n" + +"\n"+ +"GNU LGPL ¶óŔĚĽľ˝ş·Î ąčĆ÷µË´Ď´Ů. \n"+ +"¶óŔĚĽľ˝şżˇ ´ëÇŃ ŔÚĽĽÇŃ ł»żëŔş http://gnu.org/licenses/lgpl.html Ŕ» ŔĐŔ¸ĽĽżä." + +"\n\n" + +"łŻÂĄ Ľ±ĹĂ:\n" + +"- ż¬µµ¸¦ Ľ±ĹĂÇĎ·Á¸é \xab, \xbb ąöĆ°Ŕ» »çżëÇŐ´Ď´Ů\n" + +"- ´ŢŔ» Ľ±ĹĂÇĎ·Á¸é " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ąöĆ°Ŕ» ´©¸ŁĽĽżä\n" + +"- °čĽÓ ´©¸Ł°í ŔÖŔ¸¸é Ŕ§ °ŞµéŔ» şü¸Ł°Ô Ľ±ĹĂÇĎ˝Ç Ľö ŔÖ˝Ŕ´Ď´Ů."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"˝Ă°Ł Ľ±ĹĂ:\n" + +"- ¸¶żě˝ş·Î ´©¸Ł¸é ˝Ă°ŁŔĚ Áő°ˇÇŐ´Ď´Ů\n" + +"- Shift Ĺ°żÍ ÇÔ˛˛ ´©¸Ł¸é °¨ĽŇÇŐ´Ď´Ů\n" + +"- ´©¸Ą »óĹÂżˇĽ­ ¸¶żě˝ş¸¦ żňÁ÷Ŕ̸é Á» ´ő şü¸Ł°Ô °ŞŔĚ şŻÇŐ´Ď´Ů.\n"; + +Calendar._TT["PREV_YEAR"] = "Áöł­ ÇŘ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["PREV_MONTH"] = "Áöł­ ´Ţ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["GO_TODAY"] = "żŔ´Ă łŻÂĄ·Î"; +Calendar._TT["NEXT_MONTH"] = "´ŮŔ˝ ´Ţ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["NEXT_YEAR"] = "´ŮŔ˝ ÇŘ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["SEL_DATE"] = "łŻÂĄ¸¦ Ľ±ĹĂÇĎĽĽżä"; +Calendar._TT["DRAG_TO_MOVE"] = "¸¶żě˝ş µĺ·ˇ±×·Î Ŕ̵ż ÇĎĽĽżä"; +Calendar._TT["PART_TODAY"] = " (żŔ´Ă)"; +Calendar._TT["MON_FIRST"] = "żůżäŔĎŔ» ÇŃ ÁÖŔÇ ˝ĂŔŰ żäŔĎ·Î"; +Calendar._TT["SUN_FIRST"] = "ŔĎżäŔĎŔ» ÇŃ ÁÖŔÇ ˝ĂŔŰ żäŔĎ·Î"; +Calendar._TT["CLOSE"] = "´Ý±â"; +Calendar._TT["TODAY"] = "żŔ´Ă"; +Calendar._TT["TIME_PART"] = "(Shift-)Ŭ¸Ż ¶Ç´Â µĺ·ˇ±× ÇĎĽĽżä"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%b/%e [%a]"; + +Calendar._TT["WK"] = "ÁÖ"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-lt-utf8.js b/public/bundles/dynarch_calendar/lang/calendar-lt-utf8.js new file mode 100644 index 0000000..d39653b --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-lt-utf8.js @@ -0,0 +1,114 @@ +// ** I18N + +// Calendar LT language +// Author: Martynas Majeris, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sekmadienis", + "Pirmadienis", + "Antradienis", + "TreÄŤiadienis", + "Ketvirtadienis", + "Pentadienis", + "Ĺ eštadienis", + "Sekmadienis"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sek", + "Pir", + "Ant", + "Tre", + "Ket", + "Pen", + "Ĺ eš", + "Sek"); + +// full month names +Calendar._MN = new Array +("Sausis", + "Vasaris", + "Kovas", + "Balandis", + "Gegužė", + "BirĹľelis", + "Liepa", + "RugpjĹ«tis", + "RugsÄ—jis", + "Spalis", + "Lapkritis", + "Gruodis"); + +// short month names +Calendar._SMN = new Array +("Sau", + "Vas", + "Kov", + "Bal", + "Geg", + "Bir", + "Lie", + "Rgp", + "Rgs", + "Spa", + "Lap", + "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Apie kalendoriĹł"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"NaujausiÄ… versijÄ… rasite: http://www.dynarch.com/projects/calendar/\n" + +"Platinamas pagal GNU LGPL licencijÄ…. Aplankykite http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"Datos pasirinkimas:\n" + +"- MetĹł pasirinkimas: \xab, \xbb\n" + +"- MÄ—nesio pasirinkimas: " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "\n" + +"- Nuspauskite ir laikykite pelÄ—s klavišą greitesniam pasirinkimui."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Laiko pasirinkimas:\n" + +"- Spustelkite ant valandĹł arba minuÄŤiĹł - skaiÄŤius padidÄ—s vienetu.\n" + +"- Jei spausite kartu su Shift, skaiÄŤius sumažės.\n" + +"- Greitam pasirinkimui spustelkite ir pajudinkite pelÄ™."; + +Calendar._TT["PREV_YEAR"] = "Ankstesni metai (laikykite, jei norite meniu)"; +Calendar._TT["PREV_MONTH"] = "Ankstesnis mÄ—nuo (laikykite, jei norite meniu)"; +Calendar._TT["GO_TODAY"] = "Pasirinkti šiandienÄ…"; +Calendar._TT["NEXT_MONTH"] = "Kitas mÄ—nuo (laikykite, jei norite meniu)"; +Calendar._TT["NEXT_YEAR"] = "Kiti metai (laikykite, jei norite meniu)"; +Calendar._TT["SEL_DATE"] = "Pasirinkite datÄ…"; +Calendar._TT["DRAG_TO_MOVE"] = "Tempkite"; +Calendar._TT["PART_TODAY"] = " (šiandien)"; +Calendar._TT["MON_FIRST"] = "Pirma savaitÄ—s diena - pirmadienis"; +Calendar._TT["SUN_FIRST"] = "Pirma savaitÄ—s diena - sekmadienis"; +Calendar._TT["CLOSE"] = "UĹľdaryti"; +Calendar._TT["TODAY"] = "Ĺ iandien"; +Calendar._TT["TIME_PART"] = "Spustelkite arba tempkite jei norite pakeisti"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %Y-%m-%d"; + +Calendar._TT["WK"] = "sav"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-lt.js b/public/bundles/dynarch_calendar/lang/calendar-lt.js new file mode 100644 index 0000000..43b93d6 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-lt.js @@ -0,0 +1,114 @@ +// ** I18N + +// Calendar LT language +// Author: Martynas Majeris, +// Encoding: Windows-1257 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sekmadienis", + "Pirmadienis", + "Antradienis", + "Trečiadienis", + "Ketvirtadienis", + "Pentadienis", + "Đeđtadienis", + "Sekmadienis"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sek", + "Pir", + "Ant", + "Tre", + "Ket", + "Pen", + "Đeđ", + "Sek"); + +// full month names +Calendar._MN = new Array +("Sausis", + "Vasaris", + "Kovas", + "Balandis", + "Geguţë", + "Birţelis", + "Liepa", + "Rugpjűtis", + "Rugsëjis", + "Spalis", + "Lapkritis", + "Gruodis"); + +// short month names +Calendar._SMN = new Array +("Sau", + "Vas", + "Kov", + "Bal", + "Geg", + "Bir", + "Lie", + "Rgp", + "Rgs", + "Spa", + "Lap", + "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Apie kalendoriř"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Naujausiŕ versijŕ rasite: http://www.dynarch.com/projects/calendar/\n" + +"Platinamas pagal GNU LGPL licencijŕ. Aplankykite http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"Datos pasirinkimas:\n" + +"- Metř pasirinkimas: \xab, \xbb\n" + +"- Mënesio pasirinkimas: " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "\n" + +"- Nuspauskite ir laikykite pelës klaviđŕ greitesniam pasirinkimui."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Laiko pasirinkimas:\n" + +"- Spustelkite ant valandř arba minučiř - skaičus padidës vienetu.\n" + +"- Jei spausite kartu su Shift, skaičius sumaţës.\n" + +"- Greitam pasirinkimui spustelkite ir pajudinkite pelć."; + +Calendar._TT["PREV_YEAR"] = "Ankstesni metai (laikykite, jei norite meniu)"; +Calendar._TT["PREV_MONTH"] = "Ankstesnis mënuo (laikykite, jei norite meniu)"; +Calendar._TT["GO_TODAY"] = "Pasirinkti điandienŕ"; +Calendar._TT["NEXT_MONTH"] = "Kitas mënuo (laikykite, jei norite meniu)"; +Calendar._TT["NEXT_YEAR"] = "Kiti metai (laikykite, jei norite meniu)"; +Calendar._TT["SEL_DATE"] = "Pasirinkite datŕ"; +Calendar._TT["DRAG_TO_MOVE"] = "Tempkite"; +Calendar._TT["PART_TODAY"] = " (điandien)"; +Calendar._TT["MON_FIRST"] = "Pirma savaitës diena - pirmadienis"; +Calendar._TT["SUN_FIRST"] = "Pirma savaitës diena - sekmadienis"; +Calendar._TT["CLOSE"] = "Uţdaryti"; +Calendar._TT["TODAY"] = "Điandien"; +Calendar._TT["TIME_PART"] = "Spustelkite arba tempkite jei norite pakeisti"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %Y-%m-%d"; + +Calendar._TT["WK"] = "sav"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-lv.js b/public/bundles/dynarch_calendar/lang/calendar-lv.js new file mode 100644 index 0000000..407699d --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-lv.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar LV language +// Author: Juris Valdovskis, +// Encoding: cp1257 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Svçtdiena", + "Pirmdiena", + "Otrdiena", + "Tređdiena", + "Ceturdiena", + "Piektdiena", + "Sestdiena", + "Svçtdiena"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sv", + "Pr", + "Ot", + "Tr", + "Ce", + "Pk", + "Se", + "Sv"); + +// full month names +Calendar._MN = new Array +("Janvâris", + "Februâris", + "Marts", + "Aprîlis", + "Maijs", + "Jűnijs", + "Jűlijs", + "Augusts", + "Septembris", + "Oktobris", + "Novembris", + "Decembris"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Mai", + "Jűn", + "Jűl", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Par kalendâru"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Datuma izvçle:\n" + +"- Izmanto \xab, \xbb pogas, lai izvçlçtos gadu\n" + +"- Izmanto " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "pogas, lai izvçlçtos mçnesi\n" + +"- Turi nospiestu peles pogu uz jebkuru no augstâk minçtajâm pogâm, lai paâtrinâtu izvçli."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Laika izvçle:\n" + +"- Uzklikđíini uz jebkuru no laika daďâm, lai palielinâtu to\n" + +"- vai Shift-klikđíis, lai samazinâtu to\n" + +"- vai noklikđíini un velc uz attiecîgo virzienu lai mainîtu âtrâk."; + +Calendar._TT["PREV_YEAR"] = "Iepr. gads (turi izvçlnei)"; +Calendar._TT["PREV_MONTH"] = "Iepr. mçnesis (turi izvçlnei)"; +Calendar._TT["GO_TODAY"] = "Đodien"; +Calendar._TT["NEXT_MONTH"] = "Nâkođais mçnesis (turi izvçlnei)"; +Calendar._TT["NEXT_YEAR"] = "Nâkođais gads (turi izvçlnei)"; +Calendar._TT["SEL_DATE"] = "Izvçlies datumu"; +Calendar._TT["DRAG_TO_MOVE"] = "Velc, lai pârvietotu"; +Calendar._TT["PART_TODAY"] = " (đodien)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Attçlot %s kâ pirmo"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "1,7"; + +Calendar._TT["CLOSE"] = "Aizvçrt"; +Calendar._TT["TODAY"] = "Đodien"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikđíis vai pârvieto, lai mainîtu"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Laiks:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-nl.js b/public/bundles/dynarch_calendar/lang/calendar-nl.js new file mode 100644 index 0000000..a1dea94 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-nl.js @@ -0,0 +1,73 @@ +// ** I18N +Calendar._DN = new Array +("Zondag", + "Maandag", + "Dinsdag", + "Woensdag", + "Donderdag", + "Vrijdag", + "Zaterdag", + "Zondag"); + +Calendar._SDN_len = 2; + +Calendar._MN = new Array +("Januari", + "Februari", + "Maart", + "April", + "Mei", + "Juni", + "Juli", + "Augustus", + "September", + "Oktober", + "November", + "December"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Info"; + +Calendar._TT["ABOUT"] = +"DHTML Datum/Tijd Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + +"Ga voor de meest recente versie naar: http://www.dynarch.com/projects/calendar/\n" + +"Verspreid onder de GNU LGPL. Zie http://gnu.org/licenses/lgpl.html voor details." + +"\n\n" + +"Datum selectie:\n" + +"- Gebruik de \xab \xbb knoppen om een jaar te selecteren\n" + +"- Gebruik de " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knoppen om een maand te selecteren\n" + +"- Houd de muis ingedrukt op de genoemde knoppen voor een snellere selectie."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Tijd selectie:\n" + +"- Klik op een willekeurig onderdeel van het tijd gedeelte om het te verhogen\n" + +"- of Shift-klik om het te verlagen\n" + +"- of klik en sleep voor een snellere selectie."; + +//Calendar._TT["TOGGLE"] = "Selecteer de eerste week-dag"; +Calendar._TT["PREV_YEAR"] = "Vorig jaar (ingedrukt voor menu)"; +Calendar._TT["PREV_MONTH"] = "Vorige maand (ingedrukt voor menu)"; +Calendar._TT["GO_TODAY"] = "Ga naar Vandaag"; +Calendar._TT["NEXT_MONTH"] = "Volgende maand (ingedrukt voor menu)"; +Calendar._TT["NEXT_YEAR"] = "Volgend jaar (ingedrukt voor menu)"; +Calendar._TT["SEL_DATE"] = "Selecteer datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Klik en sleep om te verplaatsen"; +Calendar._TT["PART_TODAY"] = " (vandaag)"; +//Calendar._TT["MON_FIRST"] = "Toon Maandag eerst"; +//Calendar._TT["SUN_FIRST"] = "Toon Zondag eerst"; + +Calendar._TT["DAY_FIRST"] = "Toon %s eerst"; + +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Sluiten"; +Calendar._TT["TODAY"] = "(vandaag)"; +Calendar._TT["TIME_PART"] = "(Shift-)Klik of sleep om de waarde te veranderen"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b %Y"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Tijd:"; \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/lang/calendar-no.js b/public/bundles/dynarch_calendar/lang/calendar-no.js new file mode 100644 index 0000000..d9297d1 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-no.js @@ -0,0 +1,114 @@ +// ** I18N + +// Calendar NO language +// Author: Daniel Holmen, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Søndag", + "Mandag", + "Tirsdag", + "Onsdag", + "Torsdag", + "Fredag", + "Lørdag", + "Søndag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Søn", + "Man", + "Tir", + "Ons", + "Tor", + "Fre", + "Lør", + "Søn"); + +// full month names +Calendar._MN = new Array +("Januar", + "Februar", + "Mars", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Desember"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Mai", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Des"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Om kalenderen"; + +Calendar._TT["ABOUT"] = +"DHTML Dato-/Tidsvelger\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For nyeste versjon, gĂĄ til: http://www.dynarch.com/projects/calendar/\n" + +"Distribuert under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detaljer." + +"\n\n" + +"Datovalg:\n" + +"- Bruk knappene \xab og \xbb for ĂĄ velge ĂĄr\n" + +"- Bruk knappene " + String.fromCharCode(0x2039) + " og " + String.fromCharCode(0x203a) + " for ĂĄ velge mĂĄned\n" + +"- Hold inne musknappen eller knappene over for raskere valg."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Tidsvalg:\n" + +"- Klikk pĂĄ en av tidsdelene for ĂĄ øke den\n" + +"- eller Shift-klikk for ĂĄ senke verdien\n" + +"- eller klikk-og-dra for raskere valg.."; + +Calendar._TT["PREV_YEAR"] = "Forrige. ĂĄr (hold for meny)"; +Calendar._TT["PREV_MONTH"] = "Forrige. mĂĄned (hold for meny)"; +Calendar._TT["GO_TODAY"] = "GĂĄ til idag"; +Calendar._TT["NEXT_MONTH"] = "Neste mĂĄned (hold for meny)"; +Calendar._TT["NEXT_YEAR"] = "Neste ĂĄr (hold for meny)"; +Calendar._TT["SEL_DATE"] = "Velg dato"; +Calendar._TT["DRAG_TO_MOVE"] = "Dra for ĂĄ flytte"; +Calendar._TT["PART_TODAY"] = " (idag)"; +Calendar._TT["MON_FIRST"] = "Vis mandag først"; +Calendar._TT["SUN_FIRST"] = "Vis søndag først"; +Calendar._TT["CLOSE"] = "Lukk"; +Calendar._TT["TODAY"] = "Idag"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikk eller dra for ĂĄ endre verdi"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "uke"; \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/lang/calendar-pl-utf8.js b/public/bundles/dynarch_calendar/lang/calendar-pl-utf8.js new file mode 100644 index 0000000..6b8ca67 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-pl-utf8.js @@ -0,0 +1,93 @@ +// ** I18N + +// Calendar PL language +// Author: Dariusz Pietrzak, +// Author: Janusz Piwowarski, +// Encoding: utf-8 +// Distributed under the same terms as the calendar itself. + +Calendar._DN = new Array +("Niedziela", + "PoniedziaĹ‚ek", + "Wtorek", + "Ĺšroda", + "Czwartek", + "PiÄ…tek", + "Sobota", + "Niedziela"); +Calendar._SDN = new Array +("Nie", + "Pn", + "Wt", + "Ĺšr", + "Cz", + "Pt", + "So", + "Nie"); +Calendar._MN = new Array +("StyczeĹ„", + "Luty", + "Marzec", + "KwiecieĹ„", + "Maj", + "Czerwiec", + "Lipiec", + "SierpieĹ„", + "WrzesieĹ„", + "PaĹşdziernik", + "Listopad", + "GrudzieĹ„"); +Calendar._SMN = new Array +("Sty", + "Lut", + "Mar", + "Kwi", + "Maj", + "Cze", + "Lip", + "Sie", + "Wrz", + "PaĹş", + "Lis", + "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O kalendarzu"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Aby pobrać najnowszÄ… wersjÄ™, odwiedĹş: http://www.dynarch.com/projects/calendar/\n" + +"DostÄ™pny na licencji GNU LGPL. Zobacz szczegóły na http://gnu.org/licenses/lgpl.html." + +"\n\n" + +"WybĂłr daty:\n" + +"- UĹĽyj przyciskĂłw \xab, \xbb by wybrać rok\n" + +"- UĹĽyj przyciskĂłw " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " by wybrać miesiÄ…c\n" + +"- Przytrzymaj klawisz myszy nad jednym z powyĹĽszych przyciskĂłw dla szybszego wyboru."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"WybĂłr czasu:\n" + +"- Kliknij na jednym z pĂłl czasu by zwiÄ™kszyć jego wartość\n" + +"- lub kliknij trzymajÄ…c Shift by zmiejszyć jego wartość\n" + +"- lub kliknij i przeciÄ…gnij dla szybszego wyboru."; + +//Calendar._TT["TOGGLE"] = "ZmieĹ„ pierwszy dzieĹ„ tygodnia"; +Calendar._TT["PREV_YEAR"] = "Poprzedni rok (przytrzymaj dla menu)"; +Calendar._TT["PREV_MONTH"] = "Poprzedni miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["GO_TODAY"] = "IdĹş do dzisiaj"; +Calendar._TT["NEXT_MONTH"] = "NastÄ™pny miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["NEXT_YEAR"] = "NastÄ™pny rok (przytrzymaj dla menu)"; +Calendar._TT["SEL_DATE"] = "Wybierz datÄ™"; +Calendar._TT["DRAG_TO_MOVE"] = "PrzeciÄ…gnij by przesunąć"; +Calendar._TT["PART_TODAY"] = " (dzisiaj)"; +Calendar._TT["MON_FIRST"] = "WyĹ›wietl poniedziaĹ‚ek jako pierwszy"; +Calendar._TT["SUN_FIRST"] = "WyĹ›wietl niedzielÄ™ jako pierwszÄ…"; +Calendar._TT["CLOSE"] = "Zamknij"; +Calendar._TT["TODAY"] = "Dzisiaj"; +Calendar._TT["TIME_PART"] = "(Shift-)Kliknij lub przeciÄ…gnij by zmienić wartość"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%e %B, %A"; + +Calendar._TT["WK"] = "ty"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-pl.js b/public/bundles/dynarch_calendar/lang/calendar-pl.js new file mode 100644 index 0000000..76e0551 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-pl.js @@ -0,0 +1,56 @@ +// ** I18N +// Calendar PL language +// Author: Artur Filipiak, +// January, 2004 +// Encoding: UTF-8 +Calendar._DN = new Array +("Niedziela", "PoniedziaĹ‚ek", "Wtorek", "Ĺšroda", "Czwartek", "PiÄ…tek", "Sobota", "Niedziela"); + +Calendar._SDN = new Array +("N", "Pn", "Wt", "Ĺšr", "Cz", "Pt", "So", "N"); + +Calendar._MN = new Array +("StyczeĹ„", "Luty", "Marzec", "KwiecieĹ„", "Maj", "Czerwiec", "Lipiec", "SierpieĹ„", "WrzesieĹ„", "PaĹşdziernik", "Listopad", "GrudzieĹ„"); + +Calendar._SMN = new Array +("Sty", "Lut", "Mar", "Kwi", "Maj", "Cze", "Lip", "Sie", "Wrz", "PaĹş", "Lis", "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O kalendarzu"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"WybĂłr daty:\n" + +"- aby wybrać rok uĹĽyj przyciskĂłw \xab, \xbb\n" + +"- aby wybrać miesiÄ…c uĹĽyj przyciskĂłw " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "\n" + +"- aby przyspieszyć wybĂłr przytrzymaj wciĹ›niÄ™ty przycisk myszy nad ww. przyciskami."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"WybĂłr czasu:\n" + +"- aby zwiÄ™kszyć wartość kliknij na dowolnym elemencie selekcji czasu\n" + +"- aby zmniejszyć wartość uĹĽyj dodatkowo klawisza Shift\n" + +"- moĹĽesz rĂłwnieĹĽ poruszać myszkÄ™ w lewo i prawo wraz z wciĹ›niÄ™tym lewym klawiszem."; + +Calendar._TT["PREV_YEAR"] = "Poprz. rok (przytrzymaj dla menu)"; +Calendar._TT["PREV_MONTH"] = "Poprz. miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["GO_TODAY"] = "PokaĹĽ dziĹ›"; +Calendar._TT["NEXT_MONTH"] = "Nast. miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["NEXT_YEAR"] = "Nast. rok (przytrzymaj dla menu)"; +Calendar._TT["SEL_DATE"] = "Wybierz datÄ™"; +Calendar._TT["DRAG_TO_MOVE"] = "PrzesuĹ„ okienko"; +Calendar._TT["PART_TODAY"] = " (dziĹ›)"; +Calendar._TT["MON_FIRST"] = "PokaĹĽ PoniedziaĹ‚ek jako pierwszy"; +Calendar._TT["SUN_FIRST"] = "PokaĹĽ NiedzielÄ™ jako pierwszÄ…"; +Calendar._TT["CLOSE"] = "Zamknij"; +Calendar._TT["TODAY"] = "DziĹ›"; +Calendar._TT["TIME_PART"] = "(Shift-)klik | drag, aby zmienić wartość"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y.%m.%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/lang/calendar-pt.js b/public/bundles/dynarch_calendar/lang/calendar-pt.js new file mode 100644 index 0000000..deee8a1 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-pt.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar pt_BR language +// Author: Adalberto Machado, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Segunda", + "Terca", + "Quarta", + "Quinta", + "Sexta", + "Sabado", + "Domingo"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dom", + "Seg", + "Ter", + "Qua", + "Qui", + "Sex", + "Sab", + "Dom"); + +// full month names +Calendar._MN = new Array +("Janeiro", + "Fevereiro", + "Marco", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Fev", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Out", + "Nov", + "Dez"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Sobre o calendario"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Ultima versao visite: http://www.dynarch.com/projects/calendar/\n" + +"Distribuido sobre GNU LGPL. Veja http://gnu.org/licenses/lgpl.html para detalhes." + +"\n\n" + +"Selecao de data:\n" + +"- Use os botoes \xab, \xbb para selecionar o ano\n" + +"- Use os botoes " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mes\n" + +"- Segure o botao do mouse em qualquer um desses botoes para selecao rapida."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selecao de hora:\n" + +"- Clique em qualquer parte da hora para incrementar\n" + +"- ou Shift-click para decrementar\n" + +"- ou clique e segure para selecao rapida."; + +Calendar._TT["PREV_YEAR"] = "Ant. ano (segure para menu)"; +Calendar._TT["PREV_MONTH"] = "Ant. mes (segure para menu)"; +Calendar._TT["GO_TODAY"] = "Hoje"; +Calendar._TT["NEXT_MONTH"] = "Prox. mes (segure para menu)"; +Calendar._TT["NEXT_YEAR"] = "Prox. ano (segure para menu)"; +Calendar._TT["SEL_DATE"] = "Selecione a data"; +Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover"; +Calendar._TT["PART_TODAY"] = " (hoje)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostre %s primeiro"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Fechar"; +Calendar._TT["TODAY"] = "Hoje"; +Calendar._TT["TIME_PART"] = "(Shift-)Click ou arraste para mudar valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; + +Calendar._TT["WK"] = "sm"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-ro.js b/public/bundles/dynarch_calendar/lang/calendar-ro.js new file mode 100644 index 0000000..116e358 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-ro.js @@ -0,0 +1,66 @@ +// ** I18N +Calendar._DN = new Array +("DuminicÄ", + "Luni", + "MarĹŁi", + "Miercuri", + "Joi", + "Vineri", + "SâmbÄtÄ", + "DuminicÄ"); +Calendar._SDN_len = 2; +Calendar._MN = new Array +("Ianuarie", + "Februarie", + "Martie", + "Aprilie", + "Mai", + "Iunie", + "Iulie", + "August", + "Septembrie", + "Octombrie", + "Noiembrie", + "Decembrie"); + +// tooltips +Calendar._TT = {}; + +Calendar._TT["INFO"] = "Despre calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Pentru ultima versiune vizitaĹŁi: http://www.dynarch.com/projects/calendar/\n" + +"Distribuit sub GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"SelecĹŁia datei:\n" + +"- FolosiĹŁi butoanele \xab, \xbb pentru a selecta anul\n" + +"- FolosiĹŁi butoanele " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pentru a selecta luna\n" + +"- TineĹŁi butonul mouse-ului apÄsat pentru selecĹŁie mai rapidÄ."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"SelecĹŁia orei:\n" + +"- Click pe ora sau minut pentru a mÄri valoarea cu 1\n" + +"- Sau Shift-Click pentru a micĹźora valoarea cu 1\n" + +"- Sau Click Ĺźi drag pentru a selecta mai repede."; + +Calendar._TT["PREV_YEAR"] = "Anul precedent (lung pt menu)"; +Calendar._TT["PREV_MONTH"] = "Luna precedentÄ (lung pt menu)"; +Calendar._TT["GO_TODAY"] = "Data de azi"; +Calendar._TT["NEXT_MONTH"] = "Luna urmÄtoare (lung pt menu)"; +Calendar._TT["NEXT_YEAR"] = "Anul urmÄtor (lung pt menu)"; +Calendar._TT["SEL_DATE"] = "SelecteazÄ data"; +Calendar._TT["DRAG_TO_MOVE"] = "Trage pentru a miĹźca"; +Calendar._TT["PART_TODAY"] = " (astÄzi)"; +Calendar._TT["DAY_FIRST"] = "AfiĹźeazÄ %s prima zi"; +Calendar._TT["WEEKEND"] = "0,6"; +Calendar._TT["CLOSE"] = "ĂŽnchide"; +Calendar._TT["TODAY"] = "AstÄzi"; +Calendar._TT["TIME_PART"] = "(Shift-)Click sau drag pentru a selecta"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %d %B"; + +Calendar._TT["WK"] = "spt"; +Calendar._TT["TIME"] = "Ora:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-ru.js b/public/bundles/dynarch_calendar/lang/calendar-ru.js new file mode 100644 index 0000000..9f75a6a --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-ru.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar RU language +// Translation: Sly Golovanov, http://golovanov.net, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("воŃкреŃенье", + "понедельник", + "вторник", + "Ńреда", + "четверг", + "пятница", + "ŃŃббота", + "воŃкреŃенье"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("вŃĐş", + "пон", + "втр", + "Ńрд", + "чет", + "пят", + "ŃŃб", + "вŃĐş"); + +// full month names +Calendar._MN = new Array +("январь", + "февраль", + "март", + "апрель", + "ĐĽĐ°Đą", + "июнь", + "июль", + "авгŃŃŃ‚", + "Ńентябрь", + "октябрь", + "ноябрь", + "декабрь"); + +// short month names +Calendar._SMN = new Array +("янв", + "фев", + "ĐĽĐ°Ń€", + "апр", + "ĐĽĐ°Đą", + "июн", + "июл", + "авг", + "Ńен", + "окт", + "ноя", + "дек"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Đž календаре..."; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Как выбрать Đ´Đ°Ń‚Ń:\n" + +"- При помощи кнопок \xab, \xbb можно выбрать год\n" + +"- При помощи кнопок " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " можно выбрать меŃяц\n" + +"- Подержите эти кнопки нажатыми, чтобы появилоŃŃŚ меню быŃтрого выбора."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Как выбрать время:\n" + +"- При клике на чаŃĐ°Ń… или минŃŃ‚Đ°Ń… они ŃвеличиваютŃŃŹ\n" + +"- при клике Ń Đ˝Đ°Đ¶Đ°Ń‚ĐľĐą клавиŃей Shift они ŃменьŃĐ°ŃŽŃ‚ŃŃŹ\n" + +"- еŃли нажать и двигать ĐĽŃ‹Ńкой влево/вправо, они бŃĐ´ŃŃ‚ менятьŃŃŹ быŃтрее."; + +Calendar._TT["PREV_YEAR"] = "На год назад (Ńдерживать для меню)"; +Calendar._TT["PREV_MONTH"] = "На меŃяц назад (Ńдерживать для меню)"; +Calendar._TT["GO_TODAY"] = "Сегодня"; +Calendar._TT["NEXT_MONTH"] = "На меŃяц вперед (Ńдерживать для меню)"; +Calendar._TT["NEXT_YEAR"] = "На год вперед (Ńдерживать для меню)"; +Calendar._TT["SEL_DATE"] = "Выберите Đ´Đ°Ń‚Ń"; +Calendar._TT["DRAG_TO_MOVE"] = "ПеретаŃкивайте ĐĽŃ‹Ńкой"; +Calendar._TT["PART_TODAY"] = " (Ńегодня)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Первый день недели бŃдет %s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Закрыть"; +Calendar._TT["TODAY"] = "Сегодня"; +Calendar._TT["TIME_PART"] = "(Shift-)клик или нажать и двигать"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a"; + +Calendar._TT["WK"] = "нед"; +Calendar._TT["TIME"] = "Время:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-ru_win_.js b/public/bundles/dynarch_calendar/lang/calendar-ru_win_.js new file mode 100644 index 0000000..de455af --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-ru_win_.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar RU language +// Translation: Sly Golovanov, http://golovanov.net, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("âîńęđĺńĺíüĺ", + "ďîíĺäĺëüíčę", + "âňîđíčę", + "ńđĺäŕ", + "÷ĺňâĺđă", + "ď˙ňíčöŕ", + "ńóááîňŕ", + "âîńęđĺńĺíüĺ"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("âńę", + "ďîí", + "âňđ", + "ńđä", + "÷ĺň", + "ď˙ň", + "ńóá", + "âńę"); + +// full month names +Calendar._MN = new Array +("˙íâŕđü", + "ôĺâđŕëü", + "ěŕđň", + "ŕďđĺëü", + "ěŕé", + "čţíü", + "čţëü", + "ŕâăóńň", + "ńĺíň˙áđü", + "îęň˙áđü", + "íî˙áđü", + "äĺęŕáđü"); + +// short month names +Calendar._SMN = new Array +("˙íâ", + "ôĺâ", + "ěŕđ", + "ŕďđ", + "ěŕé", + "čţí", + "čţë", + "ŕâă", + "ńĺí", + "îęň", + "íî˙", + "äĺę"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Î ęŕëĺíäŕđĺ..."; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Ęŕę âűáđŕňü äŕňó:\n" + +"- Ďđč ďîěîůč ęíîďîę \xab, \xbb ěîćíî âűáđŕňü ăîä\n" + +"- Ďđč ďîěîůč ęíîďîę " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ěîćíî âűáđŕňü ěĺń˙ö\n" + +"- Ďîäĺđćčňĺ ýňč ęíîďęč íŕćŕňűěč, ÷ňîáű ďî˙âčëîńü ěĺíţ áűńňđîăî âűáîđŕ."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Ęŕę âűáđŕňü âđĺě˙:\n" + +"- Ďđč ęëčęĺ íŕ ÷ŕńŕő čëč ěčíóňŕő îíč óâĺëč÷čâŕţňń˙\n" + +"- ďđč ęëčęĺ ń íŕćŕňîé ęëŕâčřĺé Shift îíč óěĺíüřŕţňń˙\n" + +"- ĺńëč íŕćŕňü č äâčăŕňü ěűřęîé âëĺâî/âďđŕâî, îíč áóäóň ěĺí˙ňüń˙ áűńňđĺĺ."; + +Calendar._TT["PREV_YEAR"] = "Íŕ ăîä íŕçŕä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["PREV_MONTH"] = "Íŕ ěĺń˙ö íŕçŕä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["GO_TODAY"] = "Ńĺăîäí˙"; +Calendar._TT["NEXT_MONTH"] = "Íŕ ěĺń˙ö âďĺđĺä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["NEXT_YEAR"] = "Íŕ ăîä âďĺđĺä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["SEL_DATE"] = "Âűáĺđčňĺ äŕňó"; +Calendar._TT["DRAG_TO_MOVE"] = "Ďĺđĺňŕńęčâŕéňĺ ěűřęîé"; +Calendar._TT["PART_TODAY"] = " (ńĺăîäí˙)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Ďĺđâűé äĺíü íĺäĺëč áóäĺň %s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Çŕęđűňü"; +Calendar._TT["TODAY"] = "Ńĺăîäí˙"; +Calendar._TT["TIME_PART"] = "(Shift-)ęëčę čëč íŕćŕňü č äâčăŕňü"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a"; + +Calendar._TT["WK"] = "íĺä"; +Calendar._TT["TIME"] = "Âđĺě˙:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-si.js b/public/bundles/dynarch_calendar/lang/calendar-si.js new file mode 100644 index 0000000..cb3dfb9 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-si.js @@ -0,0 +1,94 @@ +/* Slovenian language file for the DHTML Calendar version 0.9.2 +* Author David Milost , January 2004. +* Feel free to use this script under the terms of the GNU Lesser General +* Public License, as long as you do not remove or alter this notice. +*/ + // full day names +Calendar._DN = new Array +("Nedelja", + "Ponedeljek", + "Torek", + "Sreda", + "ÄŚetrtek", + "Petek", + "Sobota", + "Nedelja"); + // short day names + Calendar._SDN = new Array +("Ned", + "Pon", + "Tor", + "Sre", + "ÄŚet", + "Pet", + "Sob", + "Ned"); +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Maj", + "Jun", + "Jul", + "Avg", + "Sep", + "Okt", + "Nov", + "Dec"); + // full month names +Calendar._MN = new Array +("Januar", + "Februar", + "Marec", + "April", + "Maj", + "Junij", + "Julij", + "Avgust", + "September", + "Oktober", + "November", + "December"); + +// tooltips +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O koledarju"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Za zadnjo verzijo pojdine na naslov: http://www.dynarch.com/projects/calendar/\n" + +"Distribuirano pod GNU LGPL. Poglejte http://gnu.org/licenses/lgpl.html za podrobnosti." + +"\n\n" + +"Izbor datuma:\n" + +"- Uporabite \xab, \xbb gumbe za izbor leta\n" + +"- Uporabite " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gumbe za izbor meseca\n" + +"- ZadrĹľite klik na kateremkoli od zgornjih gumbov za hiter izbor."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Izbor ćasa:\n" + +"- Kliknite na katerikoli del ćasa za poveć. le-tega\n" + +"- ali Shift-click za zmanj. le-tega\n" + +"- ali kliknite in povlecite za hiter izbor."; + +Calendar._TT["TOGGLE"] = "Spremeni dan s katerim se prićne teden"; +Calendar._TT["PREV_YEAR"] = "Predhodnje leto (dolg klik za meni)"; +Calendar._TT["PREV_MONTH"] = "Predhodnji mesec (dolg klik za meni)"; +Calendar._TT["GO_TODAY"] = "Pojdi na tekoći dan"; +Calendar._TT["NEXT_MONTH"] = "Naslednji mesec (dolg klik za meni)"; +Calendar._TT["NEXT_YEAR"] = "Naslednje leto (dolg klik za meni)"; +Calendar._TT["SEL_DATE"] = "Izberite datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Pritisni in povleci za spremembo pozicije"; +Calendar._TT["PART_TODAY"] = " (danes)"; +Calendar._TT["MON_FIRST"] = "PrikaĹľi ponedeljek kot prvi dan"; +Calendar._TT["SUN_FIRST"] = "PrikaĹľi nedeljo kot prvi dan"; +Calendar._TT["CLOSE"] = "Zapri"; +Calendar._TT["TODAY"] = "Danes"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Ted"; \ No newline at end of file diff --git a/public/bundles/dynarch_calendar/lang/calendar-sk.js b/public/bundles/dynarch_calendar/lang/calendar-sk.js new file mode 100644 index 0000000..4fe6a3c --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-sk.js @@ -0,0 +1,99 @@ +// ** I18N + +// Calendar SK language +// Author: Peter Valach (pvalach@gmx.net) +// Encoding: utf-8 +// Last update: 2003/10/29 +// Distributed under the same terms as the calendar itself. + +// full day names +Calendar._DN = new Array +("NedeĂ„Äľa", + "Pondelok", + "Utorok", + "Streda", + "Ĺ tvrtok", + "Piatok", + "Sobota", + "NedeĂ„Äľa"); + +// short day names +Calendar._SDN = new Array +("Ned", + "Pon", + "Uto", + "Str", + "Ĺ tv", + "Pia", + "Sob", + "Ned"); + +// full month names +Calendar._MN = new Array +("Január", + "Február", + "Marec", + "AprĂ­l", + "Máj", + "JÄ‚Ĺźn", + "JÄ‚Ĺźl", + "August", + "September", + "OktÄ‚Ĺ‚ber", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Máj", + "JÄ‚Ĺźn", + "JÄ‚Ĺźl", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O kalendári"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + +"PoslednÄ‚Ĺź verziu nájdete na: http://www.dynarch.com/projects/calendar/\n" + +"DistribuovanĂ© pod GNU LGPL. ViĂ„Ĺą http://gnu.org/licenses/lgpl.html pre detaily." + +"\n\n" + +"VÄ‚Ëťber dátumu:\n" + +"- PouĹľite tlaÄŤidlá \xab, \xbb pre vÄ‚Ëťber roku\n" + +"- PouĹľite tlaÄŤidlá " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pre vÄ‚Ëťber mesiaca\n" + +"- Ak ktorĂ©koĂ„Äľvek z tÄ‚Ëťchto tlaÄŤidiel podržíte dlhšie, zobrazĂ­ sa rÄ‚Ëťchly vÄ‚Ëťber."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"VÄ‚Ëťber ÄŤasu:\n" + +"- Kliknutie na niektorÄ‚Ĺź poloĹľku ÄŤasu ju zvýši\n" + +"- Shift-klik ju znĂ­Ĺľi\n" + +"- Ak podržíte tlaÄŤĂ­tko stlaÄŤenĂ©, posÄ‚ĹźvanĂ­m menĂ­te hodnotu."; + +Calendar._TT["PREV_YEAR"] = "PredošlÄ‚Ëť rok (podrĹľte pre menu)"; +Calendar._TT["PREV_MONTH"] = "PredošlÄ‚Ëť mesiac (podrĹľte pre menu)"; +Calendar._TT["GO_TODAY"] = "PrejsĹĄ na dnešok"; +Calendar._TT["NEXT_MONTH"] = "Nasl. mesiac (podrĹľte pre menu)"; +Calendar._TT["NEXT_YEAR"] = "Nasl. rok (podrĹľte pre menu)"; +Calendar._TT["SEL_DATE"] = "ZvoĂ„Äľte dátum"; +Calendar._TT["DRAG_TO_MOVE"] = "PodrĹľanĂ­m tlaÄŤĂ­tka zmenĂ­te polohu"; +Calendar._TT["PART_TODAY"] = " (dnes)"; +Calendar._TT["MON_FIRST"] = "ZobraziĹĄ pondelok ako prvÄ‚Ëť"; +Calendar._TT["SUN_FIRST"] = "ZobraziĹĄ nedeĂ„Äľu ako prvÄ‚Ĺź"; +Calendar._TT["CLOSE"] = "ZavrieĹĄ"; +Calendar._TT["TODAY"] = "Dnes"; +Calendar._TT["TIME_PART"] = "(Shift-)klik/ĹĄahanie zmenĂ­ hodnotu"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "$d. %m. %Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e. %b"; + +Calendar._TT["WK"] = "týž"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-sp.js b/public/bundles/dynarch_calendar/lang/calendar-sp.js new file mode 100644 index 0000000..239d1b3 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-sp.js @@ -0,0 +1,110 @@ +// ** I18N + +// Calendar SP language +// Author: Rafael Velasco +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Lunes", + "Martes", + "Miercoles", + "Jueves", + "Viernes", + "Sabado", + "Domingo"); + +Calendar._SDN = new Array +("Dom", + "Lun", + "Mar", + "Mie", + "Jue", + "Vie", + "Sab", + "Dom"); + +// full month names +Calendar._MN = new Array +("Enero", + "Febrero", + "Marzo", + "Abril", + "Mayo", + "Junio", + "Julio", + "Agosto", + "Septiembre", + "Octubre", + "Noviembre", + "Diciembre"); + +// short month names +Calendar._SMN = new Array +("Ene", + "Feb", + "Mar", + "Abr", + "May", + "Jun", + "Jul", + "Ago", + "Sep", + "Oct", + "Nov", + "Dic"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Información del Calendario"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Nuevas versiones en: http://www.dynarch.com/projects/calendar/\n" + +"Distribuida bajo licencia GNU LGPL. Para detalles vea http://gnu.org/licenses/lgpl.html ." + +"\n\n" + +"Selección de Fechas:\n" + +"- Use \xab, \xbb para seleccionar el ańo\n" + +"- Use " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar el mes\n" + +"- Mantenga presionado el botón del ratón en cualquiera de las opciones superiores para un acceso rapido ."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selección del Reloj:\n" + +"- Seleccione la hora para cambiar el reloj\n" + +"- o presione Shift-click para disminuirlo\n" + +"- o presione click y arrastre del ratón para una selección rapida."; + +Calendar._TT["PREV_YEAR"] = "Ańo anterior (Presione para menu)"; +Calendar._TT["PREV_MONTH"] = "Mes Anterior (Presione para menu)"; +Calendar._TT["GO_TODAY"] = "Ir a Hoy"; +Calendar._TT["NEXT_MONTH"] = "Mes Siguiente (Presione para menu)"; +Calendar._TT["NEXT_YEAR"] = "Ańo Siguiente (Presione para menu)"; +Calendar._TT["SEL_DATE"] = "Seleccione fecha"; +Calendar._TT["DRAG_TO_MOVE"] = "Arrastre y mueva"; +Calendar._TT["PART_TODAY"] = " (Hoy)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostrar %s primero"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Cerrar"; +Calendar._TT["TODAY"] = "Hoy"; +Calendar._TT["TIME_PART"] = "(Shift-)Click o arrastra para cambar el valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%dd-%mm-%yy"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y"; + +Calendar._TT["WK"] = "Sm"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-sv.js b/public/bundles/dynarch_calendar/lang/calendar-sv.js new file mode 100644 index 0000000..db1f4b8 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-sv.js @@ -0,0 +1,93 @@ +// ** I18N + +// Calendar SV language (Swedish, svenska) +// Author: Mihai Bazon, +// Translation team: +// Translator: Leonard Norrgĺrd +// Last translator: Leonard Norrgĺrd +// Encoding: iso-latin-1 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("söndag", + "mĺndag", + "tisdag", + "onsdag", + "torsdag", + "fredag", + "lördag", + "söndag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. +Calendar._SDN_len = 2; +Calendar._SMN_len = 3; + +// full month names +Calendar._MN = new Array +("januari", + "februari", + "mars", + "april", + "maj", + "juni", + "juli", + "augusti", + "september", + "oktober", + "november", + "december"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Om kalendern"; + +Calendar._TT["ABOUT"] = +"DHTML Datum/tid-väljare\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"För senaste version gĺ till: http://www.dynarch.com/projects/calendar/\n" + +"Distribueras under GNU LGPL. Se http://gnu.org/licenses/lgpl.html för detaljer." + +"\n\n" + +"Val av datum:\n" + +"- Använd knapparna \xab, \xbb för att välja ĺr\n" + +"- Använd knapparna " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " för att välja mĺnad\n" + +"- Hĺll musknappen nedtryckt pĺ nĺgon av ovanstĺende knappar för snabbare val."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Val av tid:\n" + +"- Klicka pĺ en del av tiden för att öka den delen\n" + +"- eller skift-klicka för att minska den\n" + +"- eller klicka och drag för snabbare val."; + +Calendar._TT["PREV_YEAR"] = "Föregĺende ĺr (hĺll för menu)"; +Calendar._TT["PREV_MONTH"] = "Föregĺende mĺnad (hĺll för menu)"; +Calendar._TT["GO_TODAY"] = "Gĺ till dagens datum"; +Calendar._TT["NEXT_MONTH"] = "Följande mĺnad (hĺll för menu)"; +Calendar._TT["NEXT_YEAR"] = "Följande ĺr (hĺll för menu)"; +Calendar._TT["SEL_DATE"] = "Välj datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag för att flytta"; +Calendar._TT["PART_TODAY"] = " (idag)"; +Calendar._TT["MON_FIRST"] = "Visa mĺndag först"; +Calendar._TT["SUN_FIRST"] = "Visa söndag först"; +Calendar._TT["CLOSE"] = "Stäng"; +Calendar._TT["TODAY"] = "Idag"; +Calendar._TT["TIME_PART"] = "(Skift-)klicka eller drag för att ändra tid"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A %d %b %Y"; + +Calendar._TT["WK"] = "vecka"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-tr.js b/public/bundles/dynarch_calendar/lang/calendar-tr.js new file mode 100644 index 0000000..2164687 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-tr.js @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// Turkish Translation by Nuri AKMAN +// Location: Ankara/TURKEY +// e-mail : nuriakman@hotmail.com +// Date : April, 9 2003 +// +// Note: if Turkish Characters does not shown on you screen +// please include falowing line your html code: +// +// +// +////////////////////////////////////////////////////////////////////////////////////////////// + +// ** I18N +Calendar._DN = new Array +("Pazar", + "Pazartesi", + "Salý", + "Çarţamba", + "Perţembe", + "Cuma", + "Cumartesi", + "Pazar"); +Calendar._MN = new Array +("Ocak", + "Ţubat", + "Mart", + "Nisan", + "Mayýs", + "Haziran", + "Temmuz", + "Ađustos", + "Eylül", + "Ekim", + "Kasým", + "Aralýk"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Haftanýn ilk gününü kaydýr"; +Calendar._TT["PREV_YEAR"] = "Önceki Yýl (Menü için basýlý tutunuz)"; +Calendar._TT["PREV_MONTH"] = "Önceki Ay (Menü için basýlý tutunuz)"; +Calendar._TT["GO_TODAY"] = "Bugün'e git"; +Calendar._TT["NEXT_MONTH"] = "Sonraki Ay (Menü için basýlý tutunuz)"; +Calendar._TT["NEXT_YEAR"] = "Sonraki Yýl (Menü için basýlý tutunuz)"; +Calendar._TT["SEL_DATE"] = "Tarih seçiniz"; +Calendar._TT["DRAG_TO_MOVE"] = "Taţýmak için sürükleyiniz"; +Calendar._TT["PART_TODAY"] = " (bugün)"; +Calendar._TT["MON_FIRST"] = "Takvim Pazartesi gününden baţlasýn"; +Calendar._TT["SUN_FIRST"] = "Takvim Pazar gününden baţlasýn"; +Calendar._TT["CLOSE"] = "Kapat"; +Calendar._TT["TODAY"] = "Bugün"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y"; +Calendar._TT["TT_DATE_FORMAT"] = "d MM y, DD"; + +Calendar._TT["WK"] = "Hafta"; diff --git a/public/bundles/dynarch_calendar/lang/calendar-zh.js b/public/bundles/dynarch_calendar/lang/calendar-zh.js new file mode 100644 index 0000000..4a0feb6 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/calendar-zh.js @@ -0,0 +1,119 @@ +// ** I18N + +// Calendar ZH language +// Author: muziq, +// Encoding: GB2312 or GBK +// Distributed under the same terms as the calendar itself. + +// full day names +Calendar._DN = new Array +("ĐÇĆÚČŐ", + "ĐÇĆÚŇ»", + "ĐÇĆÚ¶ţ", + "ĐÇĆÚČý", + "ĐÇĆÚËÄ", + "ĐÇĆÚÎĺ", + "ĐÇĆÚÁů", + "ĐÇĆÚČŐ"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("ČŐ", + "Ň»", + "¶ţ", + "Čý", + "ËÄ", + "Îĺ", + "Áů", + "ČŐ"); + +// full month names +Calendar._MN = new Array +("Ň»ÔÂ", + "¶ţÔÂ", + "ČýÔÂ", + "ËÄÔÂ", + "ÎĺÔÂ", + "ÁůÔÂ", + "ĆßÔÂ", + "°ËÔÂ", + "ľĹÔÂ", + "Ę®ÔÂ", + "ʮһÔÂ", + "Ę®¶ţÔÂ"); + +// short month names +Calendar._SMN = new Array +("Ň»ÔÂ", + "¶ţÔÂ", + "ČýÔÂ", + "ËÄÔÂ", + "ÎĺÔÂ", + "ÁůÔÂ", + "ĆßÔÂ", + "°ËÔÂ", + "ľĹÔÂ", + "Ę®ÔÂ", + "ʮһÔÂ", + "Ę®¶ţÔÂ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "°ďÖú"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"ѡÔńČŐĆÚ:\n" + +"- µă»÷ \xab, \xbb °´ĹĄŃˇÔńÄę·Ý\n" + +"- µă»÷ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " °´ĹĄŃˇÔńÔ·Ý\n" + +"- ł¤°´ŇÔÉĎ°´ĹĄżÉ´Ó˛ËµĄÖĐżěËŮѡÔńÄę·Ý»ňÔ·Ý"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"ѡÔńʱĽä:\n" + +"- µă»÷Сʱ»ň·ÖÖÓżÉĘą¸ÄĘýÖµĽÓŇ»\n" + +"- °´×ˇShiftĽüµă»÷Сʱ»ň·ÖÖÓżÉĘą¸ÄĘýÖµĽőŇ»\n" + +"- µă»÷Í϶ŻĘó±ężÉ˝řĐĐżěËŮѡÔń"; + +Calendar._TT["PREV_YEAR"] = "ÉĎŇ»Äę (°´×ˇłö˛ËµĄ)"; +Calendar._TT["PREV_MONTH"] = "ÉĎһԠ(°´×ˇłö˛ËµĄ)"; +Calendar._TT["GO_TODAY"] = "תµ˝˝ńČŐ"; +Calendar._TT["NEXT_MONTH"] = "ĎÂһԠ(°´×ˇłö˛ËµĄ)"; +Calendar._TT["NEXT_YEAR"] = "ĎÂŇ»Äę (°´×ˇłö˛ËµĄ)"; +Calendar._TT["SEL_DATE"] = "ѡÔńČŐĆÚ"; +Calendar._TT["DRAG_TO_MOVE"] = "Í϶Ż"; +Calendar._TT["PART_TODAY"] = " (˝ńČŐ)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "×î×ó±ßĎÔĘľ%s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "ąŘ±Ő"; +Calendar._TT["TODAY"] = "˝ńČŐ"; +Calendar._TT["TIME_PART"] = "(Shift-)µă»÷Ęó±ę»ňÍ϶Ż¸Ä±äÖµ"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %b %eČŐ"; + +Calendar._TT["WK"] = "ÖÜ"; +Calendar._TT["TIME"] = "ʱĽä:"; diff --git a/public/bundles/dynarch_calendar/lang/cn_utf8.js b/public/bundles/dynarch_calendar/lang/cn_utf8.js new file mode 100644 index 0000000..a0ef7c6 --- /dev/null +++ b/public/bundles/dynarch_calendar/lang/cn_utf8.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Translator : Niko +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("\u5468\u65e5",//\u5468\u65e5 + "\u5468\u4e00",//\u5468\u4e00 + "\u5468\u4e8c",//\u5468\u4e8c + "\u5468\u4e09",//\u5468\u4e09 + "\u5468\u56db",//\u5468\u56db + "\u5468\u4e94",//\u5468\u4e94 + "\u5468\u516d",//\u5468\u516d + "\u5468\u65e5");//\u5468\u65e5 + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("\u5468\u65e5", + "\u5468\u4e00", + "\u5468\u4e8c", + "\u5468\u4e09", + "\u5468\u56db", + "\u5468\u4e94", + "\u5468\u516d", + "\u5468\u65e5"); + +// full month names +Calendar._MN = new Array +("\u4e00\u6708", + "\u4e8c\u6708", + "\u4e09\u6708", + "\u56db\u6708", + "\u4e94\u6708", + "\u516d\u6708", + "\u4e03\u6708", + "\u516b\u6708", + "\u4e5d\u6708", + "\u5341\u6708", + "\u5341\u4e00\u6708", + "\u5341\u4e8c\u6708"); + +// short month names +Calendar._SMN = new Array +("\u4e00\u6708", + "\u4e8c\u6708", + "\u4e09\u6708", + "\u56db\u6708", + "\u4e94\u6708", + "\u516d\u6708", + "\u4e03\u6708", + "\u516b\u6708", + "\u4e5d\u6708", + "\u5341\u6708", + "\u5341\u4e00\u6708", + "\u5341\u4e8c\u6708"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "\u5173\u4e8e"; + +Calendar._TT["ABOUT"] = +" DHTML \u65e5\u8d77/\u65f6\u95f4\u9009\u62e9\u63a7\u4ef6\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: \u6700\u65b0\u7248\u672c\u8bf7\u767b\u9646http://www.dynarch.com/projects/calendar/\u5bdf\u770b\n" + +"\u9075\u5faaGNU LGPL. \u7ec6\u8282\u53c2\u9605 http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"\u65e5\u671f\u9009\u62e9:\n" + +"- \u70b9\u51fb\xab(\xbb)\u6309\u94ae\u9009\u62e9\u4e0a(\u4e0b)\u4e00\u5e74\u5ea6.\n" + +"- \u70b9\u51fb" + String.fromCharCode(0x2039) + "(" + String.fromCharCode(0x203a) + ")\u6309\u94ae\u9009\u62e9\u4e0a(\u4e0b)\u4e2a\u6708\u4efd.\n" + +"- \u957f\u65f6\u95f4\u6309\u7740\u6309\u94ae\u5c06\u51fa\u73b0\u66f4\u591a\u9009\u62e9\u9879."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"\u65f6\u95f4\u9009\u62e9:\n" + +"-\u5728\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)\u4e0a\u5355\u51fb\u9f20\u6807\u5de6\u952e\u6765\u589e\u52a0\u5f53\u524d\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)\n" + +"-\u5728\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)\u4e0a\u6309\u4f4fShift\u952e\u540e\u5355\u51fb\u9f20\u6807\u5de6\u952e\u6765\u51cf\u5c11\u5f53\u524d\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)."; + +Calendar._TT["PREV_YEAR"] = "\u4e0a\u4e00\u5e74"; +Calendar._TT["PREV_MONTH"] = "\u4e0a\u4e2a\u6708"; +Calendar._TT["GO_TODAY"] = "\u5230\u4eca\u5929"; +Calendar._TT["NEXT_MONTH"] = "\u4e0b\u4e2a\u6708"; +Calendar._TT["NEXT_YEAR"] = "\u4e0b\u4e00\u5e74"; +Calendar._TT["SEL_DATE"] = "\u9009\u62e9\u65e5\u671f"; +Calendar._TT["DRAG_TO_MOVE"] = "\u62d6\u52a8"; +Calendar._TT["PART_TODAY"] = " (\u4eca\u5929)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "%s\u4e3a\u8fd9\u5468\u7684\u7b2c\u4e00\u5929"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "\u5173\u95ed"; +Calendar._TT["TODAY"] = "\u4eca\u5929"; +Calendar._TT["TIME_PART"] = "(\u6309\u7740Shift\u952e)\u5355\u51fb\u6216\u62d6\u52a8\u6539\u53d8\u503c"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e\u65e5"; + +Calendar._TT["WK"] = "\u5468"; +Calendar._TT["TIME"] = "\u65f6\u95f4:"; diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-blue.css b/public/bundles/dynarch_calendar/stylesheets/calendar-blue.css new file mode 100644 index 0000000..9a4f7ce --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-blue.css @@ -0,0 +1,232 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #556; + font-size: 11px; + color: #000; + cursor: default; + background: #eef; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ +} + +.calendar .nav { + background: #778 url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #fff; + color: #000; + padding: 2px; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ + background: #778; + color: #fff; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #bdf; +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #556; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #a66; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #aaf; + color: #000; + border: 1px solid #04f; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #77c; + padding: 2px 0px 0px 2px; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + color: #456; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #bdf; +} + +.calendar tbody .rowhilite td { + background: #def; +} + +.calendar tbody .rowhilite td.wn { + background: #eef; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #def; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #cde; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fff; + color: #000; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #a66; +} + +.calendar tbody td.today { /* Cell showing selected date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #556; + color: #fff; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #fff; + color: #445; + border-top: 1px solid #556; + padding: 1px; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #aaf; + border: 1px solid #04f; + color: #000; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #77c; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #def; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #acf; +} + +.calendar .combo .active { + border-top: 1px solid #46a; + border-bottom: 1px solid #46a; + background: #eef; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #667; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-blue2.css b/public/bundles/dynarch_calendar/stylesheets/calendar-blue2.css new file mode 100644 index 0000000..2cf7387 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-blue2.css @@ -0,0 +1,236 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #206A9B; + font-size: 11px; + color: #000; + cursor: default; + background: #F1F8FC; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ +} + +.calendar .nav { + background: #007ED1 url(/bundles/dynarch_calendar/images/menuarrow2.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #000; + color: #fff; + padding: 2px; +} + +.calendar thead tr { /* Row containing navigation buttons */ + background: #007ED1; + color: #fff; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #C7E1F3; +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #206A9B; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #a66; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #34ABFA; + color: #000; + border: 1px solid #016DC5; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #006AA9; + border: 1px solid #008AFF; + padding: 2px 0px 0px 2px; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + color: #456; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #C7E1F3; +} + +.calendar tbody .rowhilite td { + background: #def; +} + +.calendar tbody .rowhilite td.wn { + background: #F1F8FC; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #def; + padding: 1px 3px 1px 1px; + border: 1px solid #8FC4E8; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #cde; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fff; + color: #000; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #a66; +} + +.calendar tbody td.today { /* Cell showing selected date */ + font-weight: bold; + color: #D50000; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #206A9B; + color: #fff; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #000; + color: #fff; + border-top: 1px solid #206A9B; + padding: 1px; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #B8DAF0; + border: 1px solid #178AEB; + color: #000; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #006AA9; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #def; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #34ABFA; + border-top: 1px solid #46a; + border-bottom: 1px solid #46a; + font-weight: bold; +} + +.calendar .combo .active { + border-top: 1px solid #46a; + border-bottom: 1px solid #46a; + background: #F1F8FC; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #E3F0F9; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #F1F8FC; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #267DB7; + color: #fff; +} + +.calendar td.time span.active { + border-color: red; + background-color: #000; + color: #A5FF00; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-brown.css b/public/bundles/dynarch_calendar/stylesheets/calendar-brown.css new file mode 100644 index 0000000..222ab8e --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-brown.css @@ -0,0 +1,225 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #655; + font-size: 11px; + color: #000; + cursor: default; + background: #ffd; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ +} + +.calendar .nav { + background: #edc url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #654; + color: #fed; + padding: 2px; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ + background: #edc; + color: #000; +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #655; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #faa; + color: #000; + border: 1px solid #f40; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #c77; + padding: 2px 0px 0px 2px; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #fed; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #fed; +} + +.calendar tbody .rowhilite td { + background: #ddf; +} + +.calendar tbody .rowhilite td.wn { + background: #efe; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #ffe; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #ddc; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fea; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { font-weight: bold; } + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #988; + color: #000; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + border-top: 1px solid #655; + background: #dcb; + color: #840; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #faa; + border: 1px solid #f40; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #c77; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #ffe; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #fc8; +} + +.calendar .combo .active { + border-top: 1px solid #a64; + border-bottom: 1px solid #a64; + background: #fee; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #a88; + padding: 1px 0px; + text-align: center; + background-color: #fed; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #988; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #866; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-green.css b/public/bundles/dynarch_calendar/stylesheets/calendar-green.css new file mode 100755 index 0000000..da4c935 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-green.css @@ -0,0 +1,229 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #565; + font-size: 11px; + color: #000; + cursor: default; + background: #efe; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ + background: #676; + color: #fff; + font-size: 90%; +} + +.calendar .nav { + background: #676 url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + padding: 2px; + background: #250; + color: #efa; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #565; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #a66; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #afa; + color: #000; + border: 1px solid #084; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #7c7; + padding: 2px 0px 0px 2px; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #dfb; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + color: #564; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #8a8; + background: #dfb; +} + +.calendar tbody .rowhilite td { + background: #dfd; +} + +.calendar tbody .rowhilite td.wn { + background: #efe; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #efd; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #dec; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #f8fff8; + color: #000; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #a66; +} + +.calendar tbody td.today { font-weight: bold; color: #0a0; } + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #565; + color: #fff; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + padding: 2px; + background: #250; + color: #efa; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #afa; + border: 1px solid #084; + color: #000; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #7c7; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #565; + background: #efd; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #af8; +} + +.calendar .combo .active { + border-top: 1px solid #6a4; + border-bottom: 1px solid #6a4; + background: #efe; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #8a8; + padding: 1px 0px; + text-align: center; + background-color: #dfb; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #898; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #686; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-system.css b/public/bundles/dynarch_calendar/stylesheets/calendar-system.css new file mode 100644 index 0000000..5d1ec79 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-system.css @@ -0,0 +1,251 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border: 1px solid; + border-color: #fff #000 #000 #fff; + font-size: 11px; + cursor: default; + background: Window; + color: WindowText; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border: 1px solid; + border-color: #fff #000 #000 #fff; + font-size: 11px; + cursor: default; + background: Window; + color: WindowText; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border: 1px solid; + border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight; + background: ButtonFace; +} + +.calendar .nav { + background: ButtonFace url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: ActiveCaption; + color: CaptionText; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid ButtonShadow; + padding: 2px; + text-align: center; + background: ButtonFace; + color: ButtonText; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border: 2px solid; + padding: 0px; + border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + border-width: 1px; + padding: 2px 0px 0px 2px; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid ButtonShadow; + background: ButtonFace; + color: ButtonText; +} + +.calendar tbody .rowhilite td { + background: Highlight; + color: HighlightText; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border: 1px solid; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; + padding: 2px 2px 0px 2px; + background: ButtonFace; + color: ButtonText; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody td.disabled { color: GrayText; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: ButtonFace; + padding: 1px; + border: 1px solid; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; + color: ButtonText; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #e4e0d8; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border: 1px solid; + border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight; + background: Menu; + color: MenuText; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + padding: 0px; + border: 1px solid #000; +} + +.calendar .combo .hilite { + background: Highlight; + color: HighlightText; +} + +.calendar td.time { + border-top: 1px solid ButtonShadow; + padding: 1px 0px; + text-align: center; + background-color: ButtonFace; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: Menu; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: Highlight; + color: HighlightText; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-tas.css b/public/bundles/dynarch_calendar/stylesheets/calendar-tas.css new file mode 100644 index 0000000..33c8951 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-tas.css @@ -0,0 +1,239 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #655; + font-size: 11px; + color: #000; + cursor: default; + background: #ffd; + font-family: tahoma,verdana,sans-serif; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#DDDCCC,EndColorStr=#FFFFFF); +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ + color:#363636; +} + +.calendar .nav { + background: #edc url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #654; + color: #363636; + padding: 2px; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#dddccc); +} + +.calendar thead .headrow { /* Row containing navigation buttons */ + /*background: #3B86A0;*/ + color: #363636; + font-weight: bold; +filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#3b86a0); +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #655; + padding: 2px; + text-align: center; + color: #363636; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#DDDCCC,EndColorStr=#FFFFFF); +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #ffcc86; + color: #000; + border: 1px solid #b59345; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #c77; + padding: 2px 0px 0px 2px; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #fed; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #fed; +} + +.calendar tbody .rowhilite td { + background: #ddf; + +} + +.calendar tbody .rowhilite td.wn { + background: #efe; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #ffe; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #ddc; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fea; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { font-weight: bold; } + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #988; + color: #000; + +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + border-top: 1px solid #655; + background: #dcb; + color: #363636; + font-weight: bold; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#FFFFFF,EndColorStr=#DDDCCC); +} +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #faa; + border: 1px solid #f40; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #c77; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #ffe; + color: #000; + font-size: smaller; + z-index: 100; +} + +.combo .label, +.combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.combo .label-IEfix { + width: 4em; +} + +.combo .hilite { + background: #fc8; +} + +.combo .active { + border-top: 1px solid #a64; + border-bottom: 1px solid #a64; + background: #fee; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #a88; + padding: 1px 0px; + text-align: center; + background-color: #fed; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #988; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #866; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-1.css b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-1.css new file mode 100644 index 0000000..eacf359 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-1.css @@ -0,0 +1,271 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #d4d0c8; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #d4d0c8; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #848078; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #f4f0e8; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #e4e0d8; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #c4c0b8; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #f4f0e8; +} + +.calendar tbody .rowhilite td { + background: #e4e0d8; +} + +.calendar tbody .rowhilite td.wn { + background: #d4d0c8; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + padding: 2px 2px 0px 2px; + background: #e4e0d8; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #f4f0e8; + padding: 1px; + border: 1px solid #000; + background: #848078; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #e4e0d8; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #e4e0d8; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #c4c0b8; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #048; + color: #fea; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #766; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-2.css b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-2.css new file mode 100644 index 0000000..e204b43 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-2.css @@ -0,0 +1,271 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #d4c8d0; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #d4c8d0; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #847880; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #f4e8f0; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #e4d8e0; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #c4b8c0; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #f4e8f0; +} + +.calendar tbody .rowhilite td { + background: #e4d8e0; +} + +.calendar tbody .rowhilite td.wn { + background: #d4c8d0; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + padding: 2px 2px 0px 2px; + background: #e4d8e0; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #f4e8f0; + padding: 1px; + border: 1px solid #000; + background: #847880; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #e4d8e0; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #e4d8e0; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #d4c8d0; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #408; + color: #fea; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #766; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-1.css b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-1.css new file mode 100644 index 0000000..3e2079d --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-1.css @@ -0,0 +1,265 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d0d4; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d0d4; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #788084; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #e8f0f4; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #d8e0e4; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #b8c0c4; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #e8f4f0; +} + +.calendar tbody .rowhilite td { + background: #d8e4e0; +} + +.calendar tbody .rowhilite td.wn { + background: #c8d4d0; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border: 1px solid; + border-color: #fff #000 #000 #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: #000 #fff #fff #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: #000 #fff #fff #000; + background: #d8e0e4; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #e8f0f4; + padding: 1px; + border: 1px solid #000; + background: #788084; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #d8e0e4; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #d8e0e4; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #c8d0d4; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #048; + color: #aef; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #e8f0f4; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #667; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-2.css b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-2.css new file mode 100644 index 0000000..d4313d4 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-2.css @@ -0,0 +1,271 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d4d0; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d4d0; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #788480; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #e8f4f0; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #d8e4e0; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #b8c4c0; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #e8f4f0; +} + +.calendar tbody .rowhilite td { + background: #d8e4e0; +} + +.calendar tbody .rowhilite td.wn { + background: #c8d4d0; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + padding: 2px 2px 0px 2px; + background: #d8e4e0; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #e8f4f0; + padding: 1px; + border: 1px solid #000; + background: #788480; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #d8e4e0; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #d8e4e0; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #c8d4d0; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #048; + color: #aef; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #e8f0f4; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #667; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/public/bundles/dynarch_calendar/stylesheets/calendar.css b/public/bundles/dynarch_calendar/stylesheets/calendar.css new file mode 100644 index 0000000..c8fbbc3 --- /dev/null +++ b/public/bundles/dynarch_calendar/stylesheets/calendar.css @@ -0,0 +1,106 @@ +/********** + syncPEOPLE calendar specific styles +*/ +.calendar { + width: 80%; + border: 0px; + border-style: none; + margin-top: 20px; + margin-left: auto; + margin-right: auto; +} +/*.calendar td { + border-style: none; +}*/ +.calendar th { + border-style: none; +} +.calendar_top { + width: 100%; + border-style: none; +} +.calendar_title { + font-size: 14pt; + font-weight: bold; + text-align: center; +} +.calendar_control { + text-align: center; +} +.calendar_table { + width: 100%; + border-style: outset; + border-width: 3px; + padding-top:0px; + padding-right:2px; + padding-bottom:0px + padding-left:2px; +} +.calendar_day_row0 { +} +.calendar_day_row1 { + background-color: #C0C0C0; +} +.calendar_time { + width: 20%; + text-align:right +} +.calendar_appointment { + width: 80%; +} +.calendar_today { + font-weight: bold; +} +.week_cell { + width: 25%; + height: 18ex; + vertical-align: top; + border: thin solid grey; +} +.week_row { +} +.month_day_in { + width: 13%; + height: 10ex; + vertical-align: top; + border: thin solid grey; + overflow: hidden; +} +.month_day_out { + width: 13%; + height: 10ex; + vertical-align: top; + border: thin solid grey; + background-color: #C0C0C0; + overflow: hidden; +} +.month_week { + width: 9%; + height: 10ex; + text-align: center; + border: none; +} +.month_row { +} +#appointment { + text-align: center; +} +#appoinment_edit { + margin-left: auto; + margin-right: auto; + +} +#appoinment_edit th { + text-align: right; + vertical-align: top; +} +#appoinment_edit td { + text-align: left; + border-style: none; + vertical-align: top; +} +.notice { + border: red solid; + background-color:mlight-red; + text-align: center; +} \ No newline at end of file diff --git a/public/bundles/jstree/javascripts/behaviour.js b/public/bundles/jstree/javascripts/behaviour.js new file mode 100644 index 0000000..bc5504f --- /dev/null +++ b/public/bundles/jstree/javascripts/behaviour.js @@ -0,0 +1,254 @@ +/* + Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work + of Simon Willison (see comments by Simon below). + + Description: + + Uses css selectors to apply javascript behaviours to enable + unobtrusive javascript in html documents. + + Usage: + + var myrules = { + 'b.someclass' : function(element){ + element.onclick = function(){ + alert(this.innerHTML); + } + }, + '#someid u' : function(element){ + element.onmouseover = function(){ + this.innerHTML = "BLAH!"; + } + } + }; + + Behaviour.register(myrules); + + // Call Behaviour.apply() to re-apply the rules (if you + // update the dom, etc). + + License: + + My stuff is BSD licensed. Not sure about Simon's. + + More information: + + http://ripcord.co.nz/behaviour/ + +*/ + +var Behaviour = { + list : new Array, + + register : function(sheet){ + Behaviour.list.push(sheet); + }, + + start : function(){ + Behaviour.addLoadEvent(function(){ + Behaviour.apply(); + }); + }, + + apply : function(){ + for (h=0;sheet=Behaviour.list[h];h++){ + for (selector in sheet){ + list = document.getElementsBySelector(selector); + + if (!list){ + continue; + } + + for (i=0;element=list[i];i++){ + sheet[selector](element); + } + } + } + }, + + addLoadEvent : function(func){ + var oldonload = window.onload; + + if (typeof window.onload != 'function') { + window.onload = func; + } else { + window.onload = function() { + oldonload(); + func(); + } + } + } +} + +Behaviour.start(); + +/* + The following code is Copyright (C) Simon Willison 2004. + + document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelect('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails +*/ + +function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); +} + +document.getElementsBySelector = function(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document.getElementsByTagName) { + return new Array(); + } + // Split selector in to tokens + var tokens = selector.split(' '); + var currentContext = new Array(document); + for (var i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');; + if (token.indexOf('#') > -1) { + // Token is an ID selector + var bits = token.split('#'); + var tagName = bits[0]; + var id = bits[1]; + var element = document.getElementById(id); + if (tagName && element.nodeName.toLowerCase() != tagName) { + // tag with that ID not found, return false + return new Array(); + } + // Set currentContext to contain just this element + currentContext = new Array(element); + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + var bits = token.split('.'); + var tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) { + currentContext[currentContextIndex++] = found[k]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { + var tagName = RegExp.$1; + var attrName = RegExp.$2; + var attrOperator = RegExp.$3; + var attrValue = RegExp.$4; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); }; + break; + case '^': // Match starts with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); }; + break; + case '*': // Match ends with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); }; + break; + default : + // Just test for existence of attribute + checkFunction = function(e) { return e.getAttribute(attrName); }; + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (checkFunction(found[k])) { + currentContext[currentContextIndex++] = found[k]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + + if (!currentContext[0]){ + return; + } + + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements = currentContext[h].getElementsByTagName(tagName); + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = found; + } + return currentContext; +} + +/* That revolting regular expression explained +/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ + \---/ \---/\-------------/ \-------/ + | | | | + | | | The value + | | ~,|,^,$,* or = + | Attribute + Tag +*/ diff --git a/public/bundles/jstree/javascripts/jstree.js b/public/bundles/jstree/javascripts/jstree.js new file mode 100644 index 0000000..333bd38 --- /dev/null +++ b/public/bundles/jstree/javascripts/jstree.js @@ -0,0 +1,124 @@ +JSTree = Class.create(); +Object.extend(JSTree.prototype, { + initialize: function(tree) { + this.element = $(tree); + this.options = Object.extend({ + duration: 0.25, + collapseAll: false, + collapsedClass: 'collapsed', + expandedClass: 'expanded', + imagePath: '.', + imageCollapse: 'minus.gif', + imageExpand: 'plus.gif' + }, arguments[1] || {}); + + this._setup(); + }, + + isCollapsed: function(el) { + el = $(el); + return el.className == this.options.collapsedClass; + }, + + collapse: function(el, duration) { + el = $(el); + if (duration == undefined) duration = this.options.duration; + var recurse = (arguments[2] == undefined) ? true : arguments[2]; + + el.className = this.options.collapsedClass; + + var a = el.firstChild; + if ((a.nodeType == 1) && (a.className == 'jstreeControl')) { + a.style.backgroundImage = this._imageUrl(this.options.imageExpand); + } + + if (recurse) { + var children = el.getElementsByTagName('li'); + for (var i = 0; i < children.length; i++) { + var c = children[i]; + this.collapse(c, duration, false); + } + + var sublists = el.getElementsByTagName('ul') + for (var j = 0; j < sublists.length; j++) { + var list = sublists[j]; + Effect.BlindUp(list, { 'duration': duration }); + } + + } + }, + + expand: function(el, duration) { + el = $(el); + if (duration == undefined) duration = this.options.duration; + + el.className = this.options.expandedClass; + + var a = el.firstChild; + if ((a.nodeType == 1) && (a.className == 'jstreeControl')) { + a.style.backgroundImage = this._imageUrl(this.options.imageCollapse); + } + + var children = el.childNodes; + for (i = 0; i < children.length; i++) { + var child = children[i]; + if ((child.nodeType == 1) && (child.tagName == 'UL')) { + Effect.BlindDown(child, { 'duration': duration }); + } + } + }, + + _imageUrl: function(image) { + var path = this.options.imagePath + '/' + image; + return 'url("' + path +'")'; + }, + + _toggle: function() { + if (this.treeControl.isCollapsed(this)) { + this.treeControl.expand(this); + } else { + this.treeControl.collapse(this); + } + }, + + _setup: function() { + var tree = this.element; + tree.setAttribute('style', 'list-style: none; ' + tree.style); + + lists = tree.getElementsByTagName('ul'); + for (var i = 0; i < lists.length; i++) { + var sublist = lists[i]; + sublist.setAttribute('style', 'list-style: none; ' + sublist.style); + } + + var items = tree.getElementsByTagName('li'); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var collapsable = item.getElementsByTagName('ul').length > 0; + if (collapsable) { + var collapsed = this.isCollapsed(item); + var img = this._imageUrl(collapsed ? this.options.imageExpand : this.options.imageCollapse); + var style = 'padding-left: 15px; background: ' + img + ' left no-repeat;'; + var a = document.createElement('a'); +a.innerHTML = ' '; + a.setAttribute('style', style); + a.setAttribute('class', 'jstreeControl'); + a.onclick = this._toggle.bindAsEventListener(item); + item.insertBefore(a, item.firstChild); + item.treeControl = this; + + if (collapsed || this.options.collapseAll) { + this.collapse(item, 0); + } + } + } + } +}); + +Behaviour.register({ + 'ul.jstree': function(list) { + // new JSTree(list, { 'collapseAll': true, 'duration': 0 }); + // new JSTree(list, { 'collapseAll': true, 'duration': 3 }); + new JSTree(list, { 'collapseAll': true }); + } +}); diff --git a/public/bundles/lightbox/images/loading.gif b/public/bundles/lightbox/images/loading.gif new file mode 100644 index 0000000..fbe57be Binary files /dev/null and b/public/bundles/lightbox/images/loading.gif differ diff --git a/public/bundles/lightbox/images/overlay.png b/public/bundles/lightbox/images/overlay.png new file mode 100644 index 0000000..7cee298 Binary files /dev/null and b/public/bundles/lightbox/images/overlay.png differ diff --git a/public/bundles/lightbox/javascripts/lightbox.js b/public/bundles/lightbox/javascripts/lightbox.js new file mode 100644 index 0000000..b5589eb --- /dev/null +++ b/public/bundles/lightbox/javascripts/lightbox.js @@ -0,0 +1,318 @@ +/* + Lightbox JS: Fullsize Image Overlays + by Lokesh Dhakar - http://www.huddletogether.com + + For more information on this script, visit: + http://huddletogether.com/projects/lightbox/ + + Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/ + (basically, do anything you want, just leave my name and link) + + Table of Contents + ----------------- + Configuration + + Functions + - getPageScroll() + - getPageSize() + - pause() + - showLightbox() + - hideLightbox() + - initLightbox() + - addLoadEvent() + + Function Calls + - addLoadEvent(initLightbox) + +*/ + + + +// +// Configuration +// + +// If you would like to use a loading image, point to it in the next line, otherwise leave as-is. +var loadingImage = '/bundles/lightbox/images/loading.gif'; + + + + + +// +// getPageScroll() +// Returns array with x,y page scroll values. +// Core code from - quirksmode.org +// +function getPageScroll(){ + + var yScroll; + + if (self.pageYOffset) { + yScroll = self.pageYOffset; + } else if (document.documentElement && document.documentElement.scrollTop){ // Explorer 6 Strict + yScroll = document.documentElement.scrollTop; + } else if (document.body) {// all other Explorers + yScroll = document.body.scrollTop; + } + + arrayPageScroll = new Array('',yScroll) + return arrayPageScroll; +} + + + +// +// getPageSize() +// Returns array with page width, height and window width, height +// Core code from - quirksmode.org +// +function getPageSize(){ + + var xScroll, yScroll; + + if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac + xScroll = document.body.scrollWidth; + yScroll = document.body.scrollHeight; + } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari + xScroll = document.body.offsetWidth; + yScroll = document.body.offsetHeight; + } + + var windowWidth, windowHeight; + if (self.innerHeight) { // all except Explorer + windowWidth = self.innerWidth; + windowHeight = self.innerHeight; + } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode + windowWidth = document.documentElement.clientWidth; + windowHeight = document.documentElement.clientHeight; + } else if (document.body) { // other Explorers + windowWidth = document.body.clientWidth; + windowHeight = document.body.clientHeight; + } + + // for small pages with total height less then height of the viewport + if(yScroll < windowHeight){ + pageHeight = windowHeight; + } else { + pageHeight = yScroll; + } + + // for small pages with total width less then width of the viewport + if(xScroll < windowWidth){ + pageWidth = windowWidth; + } else { + pageWidth = xScroll; + } + + + arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight) + return arrayPageSize; +} + +// +// pause(numberMillis) +// Pauses code execution for specified time. Uses busy code, not good. +// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602 +// +function pause(numberMillis) { + var now = new Date(); + var exitTime = now.getTime() + numberMillis; + while (true) { + now = new Date(); + if (now.getTime() > exitTime) + return; + } +} + + + + + +// +// showLightbox() +// Preloads images. Pleaces new image in lightbox then centers and displays. +// +function showLightbox(objLink) +{ + // prep objects + var objOverlay = document.getElementById('overlay'); + var objLightbox = document.getElementById('lightbox'); + var objImage = document.getElementById('lightboxImage'); + var objLoadingImage = document.getElementById('loadingImage'); + + var arrayPageSize = getPageSize(); + var arrayPageScroll = getPageScroll(); + + // center loadingImage if it exists + if (objLoadingImage) { + objLoadingImage.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - objLoadingImage.height) / 2) + 'px'); + objLoadingImage.style.left = (((arrayPageSize[0] - 40 - objLoadingImage.width) / 2) + 'px'); + } + + // set height of Overlay to take up whole page and show + objOverlay.style.height = (arrayPageSize[1] + 'px'); + objOverlay.style.display = 'block'; + + // preload image + imgPreload = new Image(); + + imgPreload.onload=function(){ + objImage.src = objLink.href; + + // center lightbox + objLightbox.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - imgPreload.height) / 2) + 'px'); + objLightbox.style.left = (((arrayPageSize[0] - 40 - imgPreload.width) / 2) + 'px'); + + // A small pause between the image loading and displaying is required with IE, + // this prevents the previous image displaying for a short burst causing flicker. + if (navigator.appVersion.indexOf("MSIE")!=-1){ + pause(250); + } + + objLightbox.style.display = 'block'; + + return false; + } + + imgPreload.src = objLink.href; + +} + + + + + +// +// hideLightbox() +// +function hideLightbox() +{ + // get objects + objOverlay = document.getElementById('overlay'); + objLightbox = document.getElementById('lightbox'); + + // hide lightbox and overlay + objOverlay.style.display = 'none'; + objLightbox.style.display = 'none'; +} + + + + +// +// initLightbox() +// Function runs on window load, going through link tags looking for rel="lightbox". +// These links receive onclick events that enable the lightbox display for their targets. +// The function also inserts html markup at the top of the page which will be used as a +// container for the overlay pattern and the inline image. +// +function initLightbox() +{ + + if (!document.getElementsByTagName){ return; } + var anchors = document.getElementsByTagName("a"); + + // loop through all anchor tags + for (var i=0; i + // + + var objBody = document.getElementsByTagName("body").item(0); + + // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file) + var objOverlay = document.createElement("div"); + objOverlay.setAttribute('id','overlay'); + objOverlay.style.display = 'none'; + objOverlay.style.position = 'absolute'; + objOverlay.style.top = '0'; + objOverlay.style.left = '0'; + objOverlay.style.zIndex = '90'; + objOverlay.style.width = '100%'; + objBody.insertBefore(objOverlay, objBody.firstChild); + + var arrayPageSize = getPageSize(); + var arrayPageScroll = getPageScroll(); + + // preload and create loader image + var imgPreloader = new Image(); + + // if loader image found, create link to hide lightbox and create loadingimage + imgPreloader.onload=function(){ + + var objLoadingImageLink = document.createElement("a"); + objLoadingImageLink.setAttribute('href','#'); + objLoadingImageLink.onclick = function () {hideLightbox(); return false;} + objOverlay.appendChild(objLoadingImageLink); + + var objLoadingImage = document.createElement("img"); + objLoadingImage.src = loadingImage; + objLoadingImage.setAttribute('id','loadingImage'); + objLoadingImage.style.position = 'absolute'; + objLoadingImage.style.zIndex = '150'; + objLoadingImageLink.appendChild(objLoadingImage); + + imgPreloader.onload=function(){}; // clear onLoad, as IE will flip out w/animated gifs + + return false; + } + + imgPreloader.src = loadingImage; + + // create lightbox div, same note about styles as above + var objLightbox = document.createElement("div"); + objLightbox.setAttribute('id','lightbox'); + objLightbox.style.display = 'none'; + objLightbox.style.position = 'absolute'; + objLightbox.style.zIndex = '100'; + objBody.insertBefore(objLightbox, objOverlay.nextSibling); + + // create link + var objLink = document.createElement("a"); + objLink.setAttribute('href','#'); + objLink.onclick = function () {hideLightbox(); return false;} + objLightbox.appendChild(objLink); + + // create image + var objImage = document.createElement("img"); + objImage.setAttribute('id','lightboxImage'); + objLink.appendChild(objImage); +} + + + + +// +// addLoadEvent() +// Adds event to window.onload without overwriting currently assigned onload functions. +// Function found at Simon Willison's weblog - http://simon.incutio.com/ +// +function addLoadEvent(func) +{ + var oldonload = window.onload; + if (typeof window.onload != 'function'){ + window.onload = func; + } else { + window.onload = function(){ + oldonload(); + func(); + } + } + +} + + + + +addLoadEvent(initLightbox); // run initLightbox onLoad \ No newline at end of file diff --git a/public/bundles/lightbox/stylesheets/lightbox.css b/public/bundles/lightbox/stylesheets/lightbox.css new file mode 100644 index 0000000..99de44b --- /dev/null +++ b/public/bundles/lightbox/stylesheets/lightbox.css @@ -0,0 +1,15 @@ +#lightbox{ + background-color:#eee; + padding: 10px; + border-bottom: 2px solid #666; + border-right: 2px solid #666; + } + +#overlay{ background-image: url(/bundles/lightbox/images/overlay.png); } + +* html #overlay{ + background-color: #333; + back\ground-color: transparent; + background-image: url(/bundles/lightbox/images/blank.gif); + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/bundles/lightbox/images/overlay.png", sizingMethod="scale"); + } \ No newline at end of file diff --git a/public/bundles/qforms/javascripts/qforms.js b/public/bundles/qforms/javascripts/qforms.js new file mode 100755 index 0000000..cab3723 --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms.js @@ -0,0 +1,1143 @@ +/****************************************************************************** + qForm JavaScript API + + Author: Dan G. Switzer, II + Date: December 10, 2000 + Build: 139 + + Description: + This library provides a API to forms on your page. This simplifies retrieval + of field values by providing methods to retrieve the values from fields, + without having to do complicate coding. + + To contribute money to further the development of the qForms API, see: + http://www.pengoworks.com/qForms/donations/ + + GNU License + --------------------------------------------------------------------------- + This library provides common methods for interacting with HTML forms + Copyright (C) 2001 Dan G. Switzer, II + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for mser details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +******************************************************************************/ +// find out which version of JavaScript the user has +var _jsver = 11; +for( var z=2; z < 6; z++ ) document.write("_jsver = 1" + z + ";"); + +/****************************************************************************** + qForm API Initialization +******************************************************************************/ +// define _a object +function _a(){ + // qForm's Version info + this.version = "139"; + + // initialize the number of qForm instances + this.instances = 0; + // initialize an object to use for pointers + this.objects = new Object(); + // the path where the external library components are found + this.librarypath = ""; + // specifies whether the browser should autodetect the version of JavaScript being used + this.autodetect = true; + // this specifies the default modules to load when the wildcard ("*") is specified + this.modules = new Array("field", "functions|12", "validation"); + // this is the name of the modules that have been loaded, libraries will not be loaded more then once + this.packages = new Object(); + // this is a list of validators that has loaded + this.validators = new Array(); + // this contains a list of the original contents of a container, when the setValue() method is used on a container, then the containers object is checked to see if the key exists + this.containers = new Object(); + // this structure defines the version of JavaScript being used + this.jsver = new Object(); + for( var z=1; z < 9; z++ ) this.jsver["1" + z] = "1." + z; + + // this is background color style to use when a form field validation error has occurred + this.errorColor = "red"; + // the style attribute to adjust when throwing an error + this.styleAttribute = "backgroundColor"; + // this specifies whether or not to use error color coding (by default browser that support it use it) + this.useErrorColorCoding = (document.all || document.getElementById) ? true : false; + // this specifies whether all qForm objects should be validated upon a form submission, or just the form being submitted. By default only the form being submitted is validated. + this.validateAll = false; + // this specifies whether or not a form can be submitted if validation errors occurred. If set to false, the user gets an alert box, if set to true, the user receives a confirm box. + this.allowSubmitOnError = false; + // the place holder for the number of custom validators that have been initialized + this.customValidators = 0; + // specify whether the reset method should be run when the form object is initialized + this.resetOnInit = false; + // determine whether to show status bar messages + this.showStatusMsgs = true; + + // set the regular expression attributes + this.reAttribs = "gi"; + return true; +} +qFormAPI = new _a(); + +// define _a setLibraryPath(); prototype +function _a_setLibraryPath(path){ + if( path.substring(path.length-1) != '/' ) path += '/'; + this.librarypath = path; + return true; +} +_a.prototype.setLibraryPath = _a_setLibraryPath; + +// define _a include(); prototype +function _a_include(src, path, ver){ + var source = src; + if( !source ) return true; + if( !path ) var path = this.librarypath + "qforms/"; + if( !ver ) var ver = ""; + + if( source.substring(source.length-3) != ".js" ) source += ".js"; + var thisPackage = source.substring(0,source.length-3); + + var strJS = ""; + + // if the package is already loaded, then kill method + if( this.packages[thisPackage] ) return true; + + if( thisPackage == "*" ){ + for( var i=0; i < this.modules.length; i++ ){ + var source = this.modules[i]; + var ver = "99"; + if( source.indexOf("|") > -1 ){ + ver = source.substring(source.indexOf("|") + 1); + source = source.substring(0, source.indexOf("|")); + } + if( _jsver > ver && this.autodetect ){ + document.write(strJS + this.jsver[ver] + "\" src=\"" + path + source + "_js" + ver + ".js" + strEJS); + } else { + document.write(strJS + "\" src=\"" + path + source + ".js" + strEJS); + } + this.packages[source] = true; + } + } else { + if( !this.autodetect || _jsver < 12 || ver.length == 0 ){ + document.write(strJS + "\" src=\"" + path + source + strEJS); + } else if( this.autodetect && (parseInt(_jsver, 10) >= parseInt(ver, 10)) ){ + source = source.substring(0,source.length-3) + "_js" + ver + source.substring(source.length-3); + document.write(strJS + this.jsver[ver] + "\" src=\"" + path + source + strEJS); + } else { + document.write(strJS + "\" src=\"" + path + source + strEJS); + } + } + + this.packages[thisPackage] = true; + return true; +} +_a.prototype.include = _a_include; + +function _a_unload(){ + var isFramed = false; + // loop through all the forms and reset the status of the form to idle + for( obj in qFormAPI.objects ){ + qFormAPI.objects[obj]._status = "idle"; + if( !!qFormAPI.objects[obj]._frame ) isFramed = true; + } + // some psuedo garbage collection to destroy some of the pointers if in a framed environment + if( isFramed ){ + // kill the objects if using frames + this.objects = new Object(); + // kill the containers if using frames + this.containers = new Object(); + } + return true; +} +_a.prototype.unload = _a_unload; + +// define _a validate(); prototype +function _a_validate(qForm){ + // if just validate a single form, then validate now and exit + if( !this.validateAll ) return qFormAPI.objects[qForm].validate(); + + var aryErrors = new Array(); + + // loop through all the forms + for( obj in qFormAPI.objects ){ + // check the form for errors + qFormAPI.objects[obj].checkForErrors(); + // add the errors from this form t adde queue + for( var i=0; i < qFormAPI.objects[obj]._queue.errors.length; i++ ){ + aryErrors[aryErrors.length] = qFormAPI.objects[obj]._queue.errors[i]; + } + } + + // if there are no errors then return true + if( aryErrors.length == 0 ) return true; + + var strError = "The following error(s) occurred:\n"; + for( var i=0; i < aryErrors.length; i++ ) strError += " - " + aryErrors[i] + "\n"; + + var result = false; + // check to see if the user is allowed to submit the form even if an error occurred + if( this._allowSubmitOnError && this._showAlerts ) result = confirm(strError + "\nAre you sure you want to continue?"); + // if the form can be submitted with errors and errors should not be alerted set a hidden field equal to the errors + else if( this._allowSubmitOnError && !this._showAlerts ) result = true; + // otherwise, just display the error + else alert(strError); + + return result; +} +_a.prototype.validate = _a_validate; + +function _a_reset(hardReset){ + // loop through all the forms and reset the properties + for( obj in qFormAPI.objects ) qFormAPI.objects[obj].reset(hardReset); + return true; +} +_a.prototype.reset = _a_reset; + +// define _a getFields(); prototype +function _a_getFields(){ + stcAllData = new Object(); + + // loop through all the forms + for( obj in qFormAPI.objects ){ + // check the form for errors + var tmpStruct = qFormAPI.objects[obj].getFields(); + // add the value from this form to the structure + for( field in tmpStruct ){ + if( !stcAllData[field] ){ + stcAllData[field] = tmpStruct[field]; + } else { + stcAllData[field] += "," + tmpStruct[field]; + } + } + } + + // return all the form data + return stcAllData; +} +_a.prototype.getFields = _a_getFields; + +// define _a setFields(); prototype +function _a_setFields(struct, rd, ra){ + // loop through each form and populate the fields + for( obj in qFormAPI.objects ) qFormAPI.objects[obj].setFields(struct, rd, ra); +} +_a.prototype.setFields = _a_setFields; + +// define _a dump(); prototype +function _a_dump(){ + var str = ""; + formData = this.getFields(); + for( field in formData ) str += field + " = " + formData[field] + "\n"; + alert(str); +} +_a.prototype.dump = _a_dump; + + +/****************************************************************************** + qForm Object +******************************************************************************/ +// define qForm object +function qForm(name, parent, frame){ + if( name == null ) return true; + if( !name ) return alert("No form specified."); + // increase the instance counter + qFormAPI.instances++; + // make sure the unload event is called + if( qFormAPI.instances == 1 ) window.onunload = new Function(_functionToString(window.onunload, ";qFormAPI.unload();")); + this._name = name; + this._parent = (!!parent) ? parent : null; + this._frame = (!!frame) ? frame : null; + this._status = null; + this._queue = new Object(); + this._queue.errorFields = ","; + this._queue.errors = new Array(); + this._queue.validation = new Array(); + this._showAlerts = true; + this._allowSubmitOnError = qFormAPI.allowSubmitOnError; + this._locked = false; + this._skipValidation = false; + qFormAPI.objects[this._name] = this; + // this is a string pointer to the qFormAPI object copy of this object + this._pointer = "qFormAPI.objects['" + this._name + "']"; + this.init(); + return true; +} +// initialize dummy qForm object so that NS will initialize the prototype object +new qForm(null, null, null); + +// define qForm init(); prototype +function _q_init(){ + if( !this._name ) return false; + + // if this is NS4 and the form is in a layer + if( this._parent && document.layers ) this._form = this._parent + ".document." + this._name; + // otherwise point to the form + else this._form = "document." + this._name; + + // if the form is in a frame, then add path to the frame + if( this._frame ) this._form = this._frame + "." + this._form; + + // create a pointer to the form object + this.obj = eval(this._form); + + // if the object doesn't exist, thrown an error + if( !this.obj ) return alert("The form \"" + this._name + "\" does not exist. This error \nwill occur if the Form object was initialized before the form \nhas been created or if it simply doesn't exist. Please make \nsure to initialize the Form object after page loads to avoid \npotential problems."); + + // set the onSubmit method equal to whatever the current onSubmit is + // this function is then run whenever the submitCheck determines it's ok to submit the form + this.onSubmit = new Function(_functionToString(this.obj.onsubmit, "")); + // replace the form's onSubmit event and just run the submitCheck() method + var strSubmitCheck = this._pointer + ".submitCheck();"; + if( this._frame ) strSubmitCheck = "top." + strSubmitCheck; + this.obj.onsubmit = new Function("return " + strSubmitCheck); + + // loop through form elements + this._fields = new Array(); + this._pointers = new Object(); + for( var j=0; j < this.obj.elements.length; j++ ) this.addField(this.obj.elements[j].name); + this._status = "initialized"; + + // reset the form + if( qFormAPI.resetOnInit ) this.reset(); + + return true; +} +qForm.prototype.init = _q_init; + +// define qForm addField prototype +function _q_addField(field){ + if( typeof field == "undefined" || field.length == 0 ) return false; + o = this.obj[field]; + if( typeof o == "undefined" ) return false; + // if the field is an array + if( typeof o.type == "undefined" ) o = o[0]; + if( (!!o.type) && (typeof this[field] == "undefined") && (field.length > 0) ){ + this[field] = new Field(o, field, this._name); + this._fields[this._fields.length] = field; + this._pointers[field.toLowerCase()] = this[field]; + } + return true; +} +qForm.prototype.addField = _q_addField; + +// define qForm removeField prototype +function _q_removeField(field){ + // this function requires a JS1.2 browser + + // currently, events attached to a form field are not + // deleted. this means you'll need to manually remove + // the field from the DOM, or errors will occur + if( typeof this[field] == "undefined" ) return false; + + var f = this._fields; + // find the field in the fields array and remove it + for( var i=0; i < f.length; i++ ){ + if( f[i] == field ){ + var fp = i; + break; + } + } + + if( _jsver >= 12 ){ + delete this[field]; + f.splice(fp,1); + delete this._pointers[field.toLowerCase()]; + + var q = this._queue.validation; + // loop through validation queue, and remove references of + for( var j=0; j < q.length; j++ ){ + if( q[j][0] == field ){ + q.splice(j,1); + j--; + } + } + } + return true; +} +qForm.prototype.removeField = _q_removeField; + + +// define qForm submitCheck prototype +function _q_submitCheck(){ + // make sure the form is submitted more then once + if( this._status == "submitting" || this._status == "validating" ) return false; + this._status = "submitting"; + + // validate the form + var result = qFormAPI.validate(this._name); + // if no errors occurred, run the onSubmit() method + if( result ){ + // run the custom onSubmit method + var x = this.onSubmit(); + // if a boolean value was passed back, then update the result value + if( typeof x == "boolean" ) result = x; + } + + // if the form shouldn't be submitted, then reset the form's status + if( !result ){ + // if any validation errors occur or the form is not to be submitted because the + // onSubmit() event return false, then set the reset the form's status + this._status = "idle"; + // run any processing that should be done before submitting the form + } else { + // make sure to select all "container" objects so the values are included when submitted + _setContainerValues(this); + } + return result; +} +qForm.prototype.submitCheck = _q_submitCheck; + +// define qForm onSubmit(); prototype +qForm.prototype.onSubmit = new Function(""); + + +// define qForm addMethod(); prototype +function _q_addMethod(name, fn, type){ + if( arguments.length < 2 ) return alert("To create a new method, you must specify \nboth a name and function to run: \n obj.addMethod(\"checkTime\", _isTime);"); + var type = _param(arguments[2], "from").toLowerCase(); + + // set the object to attach the prototype method to + if( type == "field" ) type = "Field"; + else type = "qForm"; + + // if adding a predefined function, then add it now + if( typeof fn == "function" ){ + strFN = fn.toString(); + strFN = strFN.substring(strFN.indexOf(" "), strFN.indexOf("(")); + eval(type + ".prototype." + name + " = " + strFN); + + // if creating a new function, then add it now + } else { + var fnTemp = new Function(fn); + eval(type + ".prototype." + name + " = fnTemp;"); + } + return true; +} +qForm.prototype.addMethod = _q_addMethod; + +// define qForm addEvent(); prototype +function _q_addEvent(event, cmd, append){ + if( arguments.length < 2 ) return alert("Invalid arguments. Please use the format \naddEvent(event, command, [append])."); + var append = _param(arguments[2], true, "boolean"); + _addEvent(this._pointer + ".obj", arguments[0], arguments[1], append); + return true; +} +qForm.prototype.addEvent = _q_addEvent; + +// define qForm required(); prototype +function _q_required(fields, value){ + var value = _param(arguments[1], true, "boolean"); + aryField = _removeSpaces(fields).split(","); + + for( var i=0; i < aryField.length; i++ ){ + if( !this[aryField[i]] ) return alert("The form field \"" + aryField[i] + "\" does not exist."); + this[aryField[i]].required = value; + } + return true; +} +qForm.prototype.required = _q_required; + +// define qForm optional(); prototype +function _q_optional(fields){ + // turn the fields off + this.required(fields, false); + return true; +} +qForm.prototype.optional = _q_optional; + + +// define qForm forceValidation(); prototype +function _q_forceValidation(fields, value){ + var value = _param(arguments[1], true, "boolean"); + aryField = _removeSpaces(fields).split(","); + + for( var i=0; i < aryField.length; i++ ){ + if( !this[aryField[i]] ) return alert("The form field \"" + aryField[i] + "\" does not exist."); + this[aryField[i]].validate = value; + } + return true; +} +qForm.prototype.forceValidation = _q_forceValidation; + + +// define qForm submit(); prototype +function _q_submit(){ + var x = false; + // do not submit the form more then once + if( this._status == "submitting" ) return false; + if( this.obj.onsubmit() ) x = this.obj.submit(); + return (typeof x == "undefined") ? true : x; +} +qForm.prototype.submit = _q_submit; + +// define qForm disabled(); prototype +function _q_disabled(status){ + var objExists = (typeof this.obj.disabled == "boolean") ? true : false; + if( arguments.length == 0 ) var status = (this.obj.disabled) ? false : true; + // if the "disabled" var doesn't exist, then use the build in "locked" feature + if( !objExists ) this._locked = status; + // switch the status of the disabled property + else this.obj.disabled = status; + return true; +} +qForm.prototype.disabled = _q_disabled; + +// define qForm reset(); prototype +function _q_reset(hardReset){ + if( this._status == null ) return false; + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + // reset the value for this field + this[this._fields[j]].setValue(((!!hardReset) ? null : this[this._fields[j]].defaultValue), true, false); + // enforce any depencies of the current field + if( this[this._fields[j]]._queue.dependencies.length > 0 ) this[this._fields[j]].enforceDependency(); + } + return true; +} +qForm.prototype.reset = _q_reset; + +// define qForm getFields(); prototype +function _q_getFields(){ + if( this._status == null ) return false; + struct = new Object(); + // loop through form elements + for( var j=0; j < this._fields.length; j++ ) struct[this._fields[j]] = this[this._fields[j]].getValue(); + return struct; +} +qForm.prototype.getFields = _q_getFields; + +// define qForm setFields(); prototype +function _q_setFields(struct, rd, ra){ + if( this._status == null ) return false; + // if you need to reset the default values of the fields + var resetDefault = _param(arguments[1], false, "boolean"); + var resetAll = _param(arguments[2], true, "boolean"); + // reset the form + if( resetAll ) this.reset(); + // loop through form elements + for( key in struct ){ + var obj = this._pointers[key.toLowerCase()]; + if( obj ){ + obj.setValue(struct[key], true, false); + if(resetDefault) obj.defaultValue = struct[key]; + } + } + return true; +} +qForm.prototype.setFields = _q_setFields; + +// define qForm hasChanged(); prototype +function _q_hasChanged(){ + if( this._status == null ) return false; + var b = false; + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + if( this[this._fields[j]].getValue() != this[this._fields[j]].defaultValue ){ + b = true; + break; + } + } + return b; +} +qForm.prototype.hasChanged = _q_hasChanged; + +// define qForm changedFields(); prototype +function _q_changedFields(){ + if( this._status == null ) return false; + struct = new Object(); + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + if( this[this._fields[j]].getValue() != this[this._fields[j]].defaultValue ){ + struct[this._fields[j]] = this[this._fields[j]].getValue(); + } + } + return struct; +} +qForm.prototype.changedFields = _q_changedFields; + +// define qForm dump(); prototype +function _q_dump(){ + var str = ""; + var f = this.getFields(); + for( fld in f ) str += fld + " = " + f[fld] + "\n"; + alert(str); +} +qForm.prototype.dump = _q_dump; + +/****************************************************************************** + Field Object +******************************************************************************/ +// define Field object +function Field(form, field, formName, init){ + if( arguments.length > 3 ) return true; + this._queue = new Object(); + this._queue.dependencies = new Array(); + this._queue.validation = new Array(); + this.qForm = qFormAPI.objects[formName]; + this.name = field; + this.path = this.qForm._form + "['" + field + "']"; + this.pointer = this.qForm._pointer + "['" + field + "']"; + this.obj = eval(this.path); + this.locked = false; + this.description = field.toLowerCase(); + this.required = false; + this.validate = false; + this.container = false; + this.type = (!this.obj.type && !!this.obj[0]) ? this.obj[0].type : this.obj.type; + this.validatorAttached = false; + + var value = this.getValue(); + this.defaultValue = value; + this.lastValue = value; + + // initialize the field object + this.init(); + + return true; +} +new Field(null, null, null, true); + +// define Field init(); prototype +function _f_init(){ + if( qFormAPI.useErrorColorCoding && this.obj.style ) this.styleValue = (!!this.obj.style[qFormAPI.styleAttribute]) ? this.obj.style[qFormAPI.styleAttribute].toLowerCase() : ""; + + if( document.layers && (this.type == "radio" || this.type == "checkbox") && !!this.obj[0] ){ + this.addEvent("onclick", "return " + this.pointer + ".allowFocus();"); + } else { + this.addEvent("onfocus", "return " + this.pointer + ".allowFocus();"); + } +} +Field.prototype.init = _f_init; + +// define Field allowFocus(); prototype +function _f_allowFocus(){ + // if the background color equals the error color, then reset the style to the original background + if( qFormAPI.useErrorColorCoding && this.obj.style ){ + if( this.qForm._queue.errorFields.indexOf(","+this.name+",") > -1 ) this.obj.style[qFormAPI.styleAttribute] = this.styleValue; + } + // store the current value in the lastValue property + this.lastValue = this.getValue(); + // check to see if the field is locked + var result = this.checkIfLocked(); + + // if the field is locked, and we have a select box, we need to reset the value of the field + // and call the onblur method to remove focus + if( (this.type.indexOf("select") > -1) && !result ){ + this.resetLast(); + this.blur(); + } + + // if the field isn't locked, run the onFocus event + if( !result ) this.onFocus(); + // return the result of the checkIfLocked() method + return result; +} +Field.prototype.allowFocus = _f_allowFocus; + +// define qForm onFocus(); prototype +Field.prototype.onFocus = new Function(""); + +// define Field addEvent(); prototype +function _f_addEvent(event, cmd, append){ + if( arguments.length < 2 ) return alert("Invalid arguments. Please use the format \naddEvent(event, command, [append])."); + var append = _param(arguments[2], true, "boolean"); + + // if the field is a multi-array element, then apply the event to all items in the array + if( (this.type == "radio" || this.type == "checkbox") && !!this.obj[0] ){ + for( var i=0; i < this.obj.length; i++ ) _addEvent(this.path + "[" + i + "]", arguments[0], arguments[1], append); + } else { + _addEvent(this.path, arguments[0], arguments[1], append); + } + return true; +} +Field.prototype.addEvent = _f_addEvent; + +// define Field disabled(); prototype +function _f_disabled(s){ + var status = arguments[0]; + var oField = (this.type == "radio") ? this.obj[0] : this.obj; + var objExists = (typeof oField.disabled == "boolean") ? true : false; + if( arguments.length == 0 ) var status = (oField.disabled) ? false : true; + // if the "disabled" var doesn't exist, then use the build in "locked" feature + if( !objExists ) this.locked = status; + // switch the status of the disabled property + else { + if( !!this.obj[0] && this.type.indexOf("select") == -1 ) for( var i=0; i < this.obj.length; i++ ) this.obj[i].disabled = status; + else this.obj.disabled = status; + } + return true; +} +Field.prototype.disabled = _f_disabled; + +// define Field checkIfLocked(); prototype +function _f_checkIfLocked(showMsg){ + var bShowMsg = _param(arguments[0], this.qForm._showAlerts); + // if the value isn't equal to the key, then don't relocate the user + if( this.isLocked() ){ + this.blur(); + if( bShowMsg ) alert("This field is disabled."); + return false; + } + return true; +} +Field.prototype.checkIfLocked = _f_checkIfLocked; + +// define Field isLocked(); prototype +function _f_isLocked(){ + var isLocked = this.locked; + if( this.qForm._locked ) isLocked = true; // if the entire form is locked + return isLocked; +} +Field.prototype.isLocked = _f_isLocked; + +// define Field isDisabled(); prototype +function _f_isDisabled(){ + // if the disabled object exists, then get its status + if( typeof this.obj.disabled == "boolean" ){ + var isDisabled = this.obj.disabled; + if( this.qForm.obj.disabled ) isDisabled = true; // if the entire form is locked + return isDisabled; + // otherwise, return false (saying it's not disabled) + } else { + return false; + } +} +Field.prototype.isDisabled = _f_isDisabled; + +// define Field focus(); prototype +function _f_focus(){ + if( !!this.obj.focus ) this.obj.focus(); +} +Field.prototype.focus = _f_focus; + +// define Field blur(); prototype +function _f_blur(){ + if( !!this.obj.blur ) this.obj.blur(); +} +Field.prototype.blur = _f_blur; + +// define Field select(); prototype +function _f_select(){ + if( !!this.obj.select ) this.obj.select(); +} +Field.prototype.select = _f_select; + +// define Field reset(); prototype +function _f_reset(){ + this.setValue(this.defaultValue, true, false); +} +Field.prototype.reset = _f_reset; + +// define Field getValue(); prototype +function _f_getValue(){ + var type = (this.type.substring(0,6) == "select") ? "select" : this.type; + var value = new Array(); + + if( type == "select" ){ + if( this.type == "select-one" && !this.container ){ + value[value.length] = (this.obj.selectedIndex == -1) ? "" : this.obj[this.obj.selectedIndex].value; + } else { + // loop through all element in the array for this field + for( var i=0; i < this.obj.length; i++ ){ + // if the element is selected, get the selected values (unless it's a dummy container) + if( (this.obj[i].selected || this.container) && (!this.dummyContainer) ){ + // append the selected value, if the value property doesn't exist, use the text + value[value.length] = this.obj[i].value; + } + } + } + } else if( (type == "checkbox") || (type == "radio") ){ + // if more then one checkbox + if( !!this.obj[0] && !this.obj.value ){ + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ) if( this.obj[i].checked ) value[value.length] = this.obj[i].value; + // otherwise, store the value of the field (if checkmarked) into the list + } else if( this.obj.checked ){ + value[value.length] = this.obj.value; + } + } else { + value[value.length] = this.obj.value; + } + return value.join(","); +} +Field.prototype.getValue = _f_getValue; + +// define Field setValue(); prototype +function _f_setValue(value, bReset, doEvents){ + this.lastValue = this.getValue(); + var reset = _param(arguments[1], true, "boolean"); + var doEvents = _param(arguments[2], true, "boolean"); + var type = (this.type.substring(0,6) == "select") ? "select" : this.type; + var v; + + if( type == "select" ){ + var bSelectOne = (this.type == "select-one") ? true : false; + var orig = value; + value = "," + value + ","; + bLookForFirst = true; // if select-one type, then only select the first value found + // if the select box is not a container + if( !this.container ){ + // loop through all element in the array for this field + for( var i=0; i < this.obj.length; i++ ){ + v = this.obj[i].value; + bSelectItem = (value.indexOf("," + v + ",") > -1) ? true : false; + if( bSelectItem && (bLookForFirst || !bSelectOne) ) this.obj[i].selected = true; + else if( reset || bSelectOne) this.obj[i].selected = false; + if( bSelectItem && bLookForFirst ) bLookForFirst = false; + } + // if a select-one box and nothing selected, then try to select the default value + if( bSelectOne && bLookForFirst ){ + if( this.defaultValue == orig ) if( this.obj.length > 0 ) this.obj[0].selected = true; + else this.setValue(this.defaultValue); + } + // if the select box is a container, then search through the container's original contents + } else { + newValues = new Object(); + for( var i=0; i < this.boundContainers.length; i++ ){ + var sCName = this.qForm._name + "_" + this.boundContainers[i]; + // check to see if the container exists, if it does check for the value + if( qFormAPI.containers[sCName] ){ + // loop through all the container objects + for( key in qFormAPI.containers[sCName] ){ + // if the key is in the container, then make sure to add the value + if( value.indexOf("," + key + ",") > -1 ){ + newValues[key] = qFormAPI.containers[sCName][key]; + } + } + } + } + // populate the container values + this.populate(newValues, reset) + } + + } else if( (type == "checkbox") || (type == "radio") ){ + // if more then one checkbox + if( !!this.obj[0] && !this.obj.value ){ + // surround the value by commas for detection + value = "," + value + ","; + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ){ + if( value.indexOf("," + this.obj[i].value + ",") > -1 ) this.obj[i].checked = true; + else if( reset ) this.obj[i].checked = false; + } + // otherwise, store the value of the field (if checkmarked) into the list + } else if( this.obj.value == value ){ + this.obj.checked = true; + } else if( reset ){ + this.obj.checked = false; + } + + } else { + this.obj.value = (!value) ? "" : value; + } + + // run the trigger events + if( doEvents ){ + this.triggerEvent("onblur"); + // run the onchange event if the value has changed + if( this.lastValue != value ) this.triggerEvent("onchange"); + } + // run the onSetValue method + this.onSetValue(); + + return true; +} +Field.prototype.setValue = _f_setValue; + +// define Field onSetValue(); prototype +Field.prototype.onSetValue = new Function(""); + +// define Field triggerEvent(); prototype +function _f_triggerEvent(event){ + oEvent = eval("this.obj." + event); + if( (this.obj.type == "checkbox") || (this.obj.type == "radio") && !!this.obj[0] ){ + for( var k=0; k < this.obj.length; k++ ){ + oEvent = eval("this.obj[k]." + event); + if( typeof oEvent == "function" ) oEvent(); + } + } else if( typeof oEvent == "function" ){ + oEvent(); + } +} +Field.prototype.triggerEvent = _f_triggerEvent; + +/****************************************************************************** + Validation Object +******************************************************************************/ +// define qForm addValidator(); prototype +function _q_addValidator(name, fn){ + if( arguments.length < 2 ) return alert("To create a new validation object, you must specify \nboth a name and function to run: \n obj.addValidator(\"isTime\", __isTime);"); + if( typeof fn == "string" ){ + var _func = new Function(fn); + _addValidator(name, _func); + } else { + _addValidator(name, fn); + } + return true; +} +qForm.prototype.addValidator = _q_addValidator; + +// define Field validateExp(); prototype +function _f_validateExp(expression, error, cmd){ + var expression = _param(arguments[0], "false"); + var error = _param(arguments[1], "An error occurred on the field '\" + this.description + \"'."); + var cmd = _param(arguments[2]); + + var strFn = "if( " + expression + " ){ this.error = \"" + error + "\";}"; + if( cmd.length > 0 ) strFn += cmd; + strValidateExp = "_validateExp" + qFormAPI.customValidators; + _addValidator(strValidateExp, new Function(strFn)); + eval(this.pointer + ".validate" + strValidateExp + "();"); + qFormAPI.customValidators++; +} +Field.prototype.validateExp = _f_validateExp; + +function _addValidator(name, fn, alwaysRun){ + var alwaysRun = _param(arguments[2], false, "boolean"); + + if( arguments.length < 2 ) return alert("To create a new validation object, you must specify \nboth a name and function to run: \n _addValidator(\"isTime\", __isTime);"); + // strip "is" out of name if present + if( name.substring(0,2).toLowerCase() == "is" ) name = name.substring(2); + + // if the validator has already been loaded, do not load it + for( var a=0; a < qFormAPI.validators.length; a++ ) if( qFormAPI.validators[a] == name ) return alert("The " + name + " validator has already been loaded."); + + // add the validator to the array of validators + qFormAPI.validators[qFormAPI.validators.length] = name; + + // if not registering a simple expression evaluator, then update the status bar + if( qFormAPI.showStatusMsgs && name.substring(0,12) != "_validateExp" ){ + // update the status bar with the initialization request + window.status = "Initializing the validate" + name + "() and is" + name + "() validation scripts..."; + // clear the status bar + setTimeout("window.status = ''", 100); + } + + var strFN = fn.toString(); + var strName = strFN.substring(strFN.indexOf(" "), strFN.indexOf("(")); + var strArguments = strFN.substring( strFN.indexOf("(")+1, strFN.indexOf(")") ); + // remove spaces from the arguments + while( strArguments.indexOf(" ") > -1 ) strArguments = strArguments.substring( 0, strArguments.indexOf(" ") ) + strArguments.substring( strArguments.indexOf(" ")+1 ); + + // add rountine to check to see if the validation method should be processed + // if displaying errors, but the field is locked then return false immediately + var strBody = "var display = (this.qForm._status == 'validating') ? false : true;\n"; + strBody += "if( (display && this.isLocked()) || this.qForm._status.substring(0,5) == 'error') return false;\n this.value = this.getValue();"; + if( !alwaysRun ) strBody += "if( !display && this.value.length == 0 && !this.required ) return false;\n"; + strBody += "this.error = '';\n"; + + // get the body of the custom function + strBody += strFN.substring( strFN.indexOf("{")+1, strFN.lastIndexOf("}") ); + + // if alerting the user to the error + strBody += "if( this.error.length > 0 && !!errorMsg) this.error = errorMsg;\n"; + strBody += "if( display && this.error.length > 0 ){\n"; + strBody += "if( this.qForm._status.indexOf('_ShowError') > -1 ){\n"; + strBody += "this.qForm._status = 'error';\n"; + // if the user has specified an error message, then display the custom message + strBody += "alert(this.error);\n"; + strBody += "setTimeout(this.pointer + \".focus();\", 1);\n"; + strBody += "setTimeout(this.pointer + \".qForm._status = 'idle';\", 100);\n"; + strBody += "} return false;\n"; + strBody += "} else if ( display ){ return true; } return this.error;\n"; + + // start build a string to create the new function + var strNewFN = "new Function("; + var aryArguments = strArguments.split(","); + for( var i=0; i < aryArguments.length; i++ ){ + if(aryArguments[i] != "") strNewFN += "\"" + aryArguments[i] + "\","; + } + var strRuleFN = strNewFN; + + strNewFN += "\"errorMsg\",strBody);"; + + // create the Field prototype for validation + eval("Field.prototype.is" + name + " = " + strNewFN); + + // create validation rule, the validation rule must loop through the arguments provided + // and create a string to stick in the validation queue. This string will be eval() later + // on to check for errors + var strRule = "var cmd = this.pointer + '.is" + name + "';\n"; + strRule += "cmd += '( ';\n"; + strRule += "for( i=0; i < arguments.length; i++ ){ \n"; + strRule += "if( typeof arguments[i] == 'string' ) cmd += '\"' + arguments[i] + '\",';\n"; + strRule += "else cmd += arguments[i] + ',';\n"; + strRule += "}\n"; + strRule += "cmd = cmd.substring(0, cmd.length-1);\n"; + strRule += "cmd += ')';\n"; + strRule += "this.qForm._queue.validation[this.qForm._queue.validation.length] = new Array(this.name, cmd);\n"; + strRule += "this._queue.validation[this._queue.validation.length] = cmd;\n"; + strRule += "if( !this.validatorAttached ){ this.addEvent('onblur', this.pointer + '.checkForErrors()');"; + strRule += "this.validatorAttached = true;}\n"; + strRule += "return true;\n"; + strRuleFN += "\"errorMsg\",strRule);"; + eval("Field.prototype.validate" + name + " = " + strRuleFN); + + return true; +} + +// define Field checkForErrors(); prototype +function _f_checkForErrors(){ + if( !this.validate || this.qForms._skipValidation ) return true; + // change the status of the form + this.qForm._status += "_ShowError"; + // loop through the validation queue and validation each item, if the item has already been validated, don't validate again + for( var i=0; i < this._queue.validation.length; i++ ) if( !eval(this._queue.validation[i]) ) break; + // reset the status to idle + setTimeout(this.pointer + ".qForm._status = 'idle';", 100); + return true; +} +Field.prototype.checkForErrors = _f_checkForErrors; + +// define qForm validate(); prototype +function _q_validate(){ + // if validation library hasn't been loaded, then return true + if( !qFormAPI.packages.validation || this._skipValidation ) return true; + + // check the form for errors + this.checkForErrors(); + + // if there are no errors then return true + if( this._queue.errors.length == 0 ) return true; + + // run the custom onError event, if it returns false, cancel request + var result = this.onError(); + if( result == false ) return true; + + var strError = "The following error(s) occurred:\n"; + for( var i=0; i < this._queue.errors.length; i++ ) strError += " - " + this._queue.errors[i] + "\n"; + + var result = false; + // check to see if the user is allowed to submit the form even if an error occurred + if( this._allowSubmitOnError && this._showAlerts ) result = confirm(strError + "\nAre you sure you want to continue?"); + // if the form can be submitted with errors and errors should not be alerted set a hidden field equal to the errors + else if( this._allowSubmitOnError && !this._showAlerts ) result = true; + // otherwise, just display the error + else alert(strError); + + return result; +} +qForm.prototype.validate = _q_validate; + +// define qForm checkForErrors(); prototype +function _q_checkForErrors(){ + var status = this._status; // copy the current form's status + this._status = "validating"; // set form's status to validating + this._queue.errors = new Array(); // clear the current error queue + aryQueue = new Array(); // create a local queue for the required fields + this._queue.errorFields = ","; + + + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + // if the current field is required, then check to make sure it's value isn't blank + if( this[this._fields[j]].required ) aryQueue[aryQueue.length] = new Array(this._fields[j], this._pointer + "['" + this._fields[j] + "'].isNotEmpty(\"The " + this[this._fields[j]].description + " field is required.\");"); + // reset the CSS settings on the field + if( qFormAPI.useErrorColorCoding && this[this._fields[j]].obj.style ) this[this._fields[j]].obj.style[qFormAPI.styleAttribute] = this[this._fields[j]].styleValue; + } + + // loop through the required fields queue, if the field throws an error, don't validate later + for( var i=0; i < aryQueue.length; i++ ) this[aryQueue[i][0]].throwError(eval(aryQueue[i][1])); + + // loop through the validation queue and validation each item, if the item has already been validated, don't validate again + for( var i=0; i < this._queue.validation.length; i++ ) this[this._queue.validation[i][0]].throwError(eval(this._queue.validation[i][1])); + + // run the custom validation routine + this.onValidate(); + + // set form's status back to it's last status + this._status = status; + + return true; +} +qForm.prototype.checkForErrors = _q_checkForErrors; + +// define qForm onValidate(); prototype +qForm.prototype.onValidate = new Function(""); + +// define qForm onError(); prototype +qForm.prototype.onError = new Function(""); + +// define Field throwError() prototype +function _f_throwError(error){ + var q = this.qForm; + // if the error msg is a valid string and this field hasn't errored already, then queue msg + if( (typeof error == "string") && (error.length > 0) && (q._queue.errorFields.indexOf("," + this.name + ",") == -1) ){ + q._queue.errors[q._queue.errors.length] = error; + q._queue.errorFields += this.name + ","; + // change the background color of failed validation fields to red + if( qFormAPI.useErrorColorCoding && this.obj.style ) this.obj.style[qFormAPI.styleAttribute] = qFormAPI.errorColor; + return true; + } + return false; +} +Field.prototype.throwError = _f_throwError; + +/****************************************************************************** + Required Functions +******************************************************************************/ +// define the addEvent() function +function _addEvent(obj, event, cmd, append){ + if( arguments.length < 3 ) return alert("Invalid arguments. Please use the format \n_addEvent(object, event, command, [append])."); + var append = _param(arguments[3], true, "boolean"); + var event = arguments[0] + "." + arguments[1].toLowerCase(); + var objEvent = eval(event); + var strEvent = (objEvent) ? objEvent.toString() : ""; + // strip out the body of the function + strEvent = strEvent.substring(strEvent.indexOf("{")+1, strEvent.lastIndexOf("}")); + strEvent = (append) ? (strEvent + cmd) : (cmd + strEvent); + strEvent += "\n"; + eval(event + " = new Function(strEvent)"); + return true; +} + +// define the _functionToString() function +function _functionToString(fn, cmd, append){ + if( arguments.length < 1 ) return alert("Invalid arguments. Please use the format \n_functionToString(function, [command], [append])."); + var append = _param(arguments[2], true, "boolean"); + var strFunction = (!fn) ? "" : fn.toString(); + // strip out the body of the function + strFunction = strFunction.substring(strFunction.indexOf("{")+1, strFunction.lastIndexOf("}")); + if( cmd ) strFunction = (append) ? (strFunction + cmd + "\n") : (cmd + strFunction + "\n"); + return strFunction; +} + +// define the _param(value, default, type) function +function _param(v, d, t){ + // if no default value is present, use an empty string + if( typeof d == "undefined" ) d = ""; + // if no type value is present, use "string" + if( typeof t == "undefined" ) t = "string"; + // if datatype should be a number and it's a string, convert it to a number + if( t == "number" && typeof v == "string" ) var v = parseFloat(arguments[0]); + // get the value to return, if the v param is not equal to the type, use default value + var value = (typeof v != "undefined" && typeof v == t.toLowerCase()) ? v : d; + return value; +} + +// define the _removeSpaces(value) function +function _removeSpaces(v){ + // remove all spaces + while( v.indexOf(" ") > -1 ) v = v.substring( 0, v.indexOf(" ") ) + v.substring( v.indexOf(" ")+1 ); + return v; +} + +// defined the _setContainerValues(obj) function +function _setContainerValues(obj){ + // loop through form elements + for( var i=0; i < obj._fields.length; i++ ){ + if( obj[obj._fields[i]].container && obj[obj._fields[i]].type.substring(0,6) == "select" ){ + for( var x=0; x < obj[obj._fields[i]].obj.length; x++ ){ + obj[obj._fields[i]].obj[x].selected = (!obj[obj._fields[i]].dummyContainer); + } + } + } +} diff --git a/public/bundles/qforms/javascripts/qforms/bits.js b/public/bundles/qforms/javascripts/qforms/bits.js new file mode 100755 index 0000000..7c8bdbf --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/bits.js @@ -0,0 +1,54 @@ +/****************************************************************************** + qForm JSAPI: Bits Extensions Library + + Author: Dan G. Switzer, II + Build: 101 +******************************************************************************/ +// define Field getBits(); prototype +function _Field_getBits(useValue){ + var isCheckbox = (this.type == "checkbox") ? true : false; + var isSelect = (this.type == "select-multiple") ? true : false; + + if( !isCheckbox && !isSelect && (this.obj.length > 0) ) return alert("This method is only available to checkboxes or select boxes with multiple options."); + var useValue = _param(arguments[0], false, "boolean"); + + var iBit = 0; + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ){ + // if the option is checked, then add the 2 ^ i to the existing value + if( isCheckbox && this.obj[i].checked ){ + // append the selected value + iBit += (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i); + } else if( isSelect && this.obj.options[i].selected ){ + iBit += (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i); + } + } + return iBit; +} +Field.prototype.getBits = _Field_getBits; + +// define Field setBits(); prototype +function _Field_setBits(value, useValue){ + var isCheckbox = (this.type == "checkbox") ? true : false; + var isSelect = (this.type == "select-multiple") ? true : false; + + if( !isCheckbox && !isSelect && (this.obj.length > 0) ) return alert("This method is only available to checkboxes or select boxes with multiple options."); + var value = _param(arguments[0], "0"); + var useValue = _param(arguments[1], false, "boolean"); + + var value = parseInt(value, 10); + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ){ + // if the bitand returns the same as the value being checked, then the current + // checkbox should be checked + var j = (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i); + var result = ( (value & j) == j) ? true : false; + if( isCheckbox ) this.obj[i].checked = result; + else if( isSelect ) this.obj.options[i].selected = result; + } + // if the value provided is greater then the last bit value, return false to indicate an error + // otherwise return true to say everything is ok + return (value < Math.pow(2, i)) ? true : false; +} +Field.prototype.setBits = _Field_setBits; + diff --git a/public/bundles/qforms/javascripts/qforms/cfform.js b/public/bundles/qforms/javascripts/qforms/cfform.js new file mode 100755 index 0000000..4c79ee7 --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/cfform.js @@ -0,0 +1,191 @@ +/****************************************************************************** + qForm JSAPI: CFFORM Validation Library + + Author: Dan G. Switzer, II + Build: 100 +******************************************************************************/ +qFormAPI.packages.cfform = true; + +_addValidator( + "isBoolean", + function (){ + if( + (this.value.toUpperCase() != "TRUE") + || (this.value.toUpperCase() != "FALSE") + || (this.value.toUpperCase() != "YES") + || (this.value.toUpperCase() != "NO") + || (this.value != "0") + || (this.value != "1") + ){ + this.error = "The " + this.description + " field does not contain a boolean value."; + } + } +); + +/** + * A string GUID value is required. A GUID is a string + * of length 36 formatted as XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where X is a + * hexadecimal digit (0-9 or A-F). + */ +_addValidator( + "isGUID", + function (){ + var v = _trim(this.value); + if( !(/[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{12,12}/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid GUID."; + } + } +); + +/** + * A string UUID value is required. A UUID is a string + * of length 35 formatted as XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX, where X is a + * hexadecimal digit (0-9 or A-F). + */ +_addValidator( + "isUUID", + function (){ + var v = _trim(this.value); + if( !(/[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{16,16}/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid GUID."; + } + } +); + + +/** + * validate that the value is formatted correctly for a http/https/ftp url + * This pattern will match http/https/ftp urls. + * + * Matches: http://www.mm.com/index.cfm + * HTTP://WWW.MM.COM + * http://www.mm.com/index.cfm?userid=1&name=mike+nimer + * http://www.mm.com/index.cfm/userid/1/name/mike+nimer - trick used by cf developers so search engines can parse their sites (search engines ignore query strings) + * ftp://www.mm.com/ + * ftp://uname:pass@www.mm.com/ + * mailto:email@address.com + * news:rec.gardening + * news:rec.gardening + * http://a/ + * file://ftp.yoyodyne.com/pub/files/foobar.txt + * Non-Matches: www.yahoo.com + * http:www.mm.com + * + */ +_addValidator( + "isURL", + function (){ + var v = _trim(this.value).toLowerCase(); + if( !(/^((http|https|ftp|file)\:\/\/([a-zA-Z0-0]*:[a-zA-Z0-0]*(@))?[a-zA-Z0-9-\.]+(\.[a-zA-Z]{2,3})?(:[a-zA-Z0-9]*)?\/?([a-zA-Z0-9-\._\?\,\'\/\+&%\$#\=~])*)|((mailto)\:[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z0-9]{2,7})|((news)\:[a-zA-Z0-9\.]*)$/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid URL."; + } + } +); + + +/** + * A string UUID value is required. A UUID is a string + * of length 35 formatted as XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX, where X is a + * hexadecimal digit (0-9 or A-F). + */ +_addValidator( + "isRegEx", + function (r){ + var v = _trim(this.value); + if( !(r.test(v)) ){ + this.error = "The " + this.description + " field contains invalid data."; + } + } +); + +/** + * validate that the value is formatted as a telephone correctly + * This pattern matches any US Telephone Number. + * This regular expression excludes the first number, after the area code,from being 0 or 1; + * it also allows an extension to be added where it does not have to be prefixed by 'x'. + * + * Matches: + * 617.219.2000 + * 219-2000 + * (617)283-3599 x234 + * 1(222)333-4444 + * 1 (222) 333-4444 + * 222-333-4444 + * 1-222-333-4444 + * Non-Matches: + * 44-1344-458606 + * +44-1344-458606 + * +34-91-397-6611 + * 7-095-940-2000 + * +7-095-940-2000 + * +49-(0)-889-748-5516 +*/ +_addValidator( + "isUSPhone", + function (){ + var v = _trim(this.value); + if( !(/^(((1))?[ ,\-,\.]?([\\(]?([1-9][0-9]{2})[\\)]?))?[ ,\-,\.]?([^0-1]){1}([0-9]){2}[ ,\-,\.]?([0-9]){4}(( )((x){0,1}([0-9]){1,5}){0,1})?$/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid phone number."; + } + } +); + + +_addValidator( + "isTime", + function (){ + var aTime = this.value.split(":"); + var isTime = true; + + if( (this.value.length == 0) || (aTime.length != 2) ) isTime = false; + + if( isTime ){ + var sHour = aTime[0]; + var iHour = parseInt(sHour, 10); + var sMinute = aTime[1]; + var iMinute = parseInt(sMinute, 10); + + if( (sHour != String(iHour)) || (sMinute != String(iMinute)) ) isTime = false; + else if( (iHour < 0) || (iHour > 23) ) isTime = false; + else if( (iMinute < 0) || (iMinute > 59) ) isTime = false; + } + + if( !isTime ){ + this.error = "The " + this.description + " field does not contain a valid time."; + } + } +); + +_addValidator( + "isInt", + function (){ + var v = this.value; + var isNumeric = (v == String(parseFloat(v, 10))); + if( !isNumeric || (parseInt(v, 10) != parseFloat(v, 10)) ){ + this.error = "The " + this.description + " field must contain an integer value."; + } + } +); + +_addValidator( + "isFloat", + function (){ + var v = this.value; + var isNumeric = (v == String(parseFloat(v, 10))); + if( !isNumeric || (parseInt(v, 10) == parseFloat(v, 10)) ){ + this.error = "The " + this.description + " field must contain an floating number."; + } + } +); + +_addValidator( + "isNumber", + function (){ + var v = this.value; + if( v != String(parseFloat(v, 10)) ){ + this.error = "The " + this.description + " field must contain a valid number."; + } + } +); + + diff --git a/public/bundles/qforms/javascripts/qforms/cookies.js b/public/bundles/qforms/javascripts/qforms/cookies.js new file mode 100755 index 0000000..f61f98e --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/cookies.js @@ -0,0 +1,89 @@ +/****************************************************************************** + qForm JSAPI: Cookie Library + + Author: Dan G. Switzer, II + Build: 105 +******************************************************************************/ +// initialize workspace variables +var _c_dToday = new Date(); +var _c_iExpiresIn = 90; +var _c_strName = self.location.pathname; + +/****************************************************************************** + Required Functions +******************************************************************************/ +// retrieve a cookie from the browser +function _getCookie(name){ + var iStart = document.cookie.indexOf(name + "="); + var iLength = iStart + name.length + 1; + if( (iStart == -1) || (!iStart && (name == document.cookie.substring(0)) ) ) return null; + var iEnd = document.cookie.indexOf(";", iLength); + if( iEnd == -1 ) iEnd = document.cookie.length; + return unescape(document.cookie.substring(iLength, iEnd)); +} + +// set a cookie to the browser +function _setCookie(name, value, expires, path, domain, secure){ + document.cookie = name + "=" + escape(value) + + ( (expires) ? ";expires=" + expires.toGMTString() : "") + + ( (path) ? ";path=" + path : "") + + ( (domain) ? ";domain=" + domain : "") + + ( (secure) ? ";secure" : ""); +} + +function _deleteCookie(name, path, domain){ + if (Get_Cookie(name)) document.cookie = name + "=" + + ( (path) ? ";path=" + path : "") + + ( (domain) ? ";domain=" + domain : "") + + ";expires=Thu, 01-Jan-1970 00:00:01 GMT"; +} + +function _createCookiePackage(struct){ + var cookie = ""; + for( key in struct ){ + if( cookie.length > 0 ) cookie += "&"; + cookie += key + ":" + escape(struct[key]); + } + return cookie; +} + +function _readCookiePackage(pkg){ + struct = new Object(); + // break the package into key/value pairs + var a = pkg.split("&"); + // loop through the array and seperate the key/value pairs + for( var i=0; i < a.length; i++ ) a[i] = a[i].split(":"); + // convert the values into a structure + for( var i=0; i < a.length; i++ ) struct[a[i][0]] = unescape(a[i][1]); + // return the structure + return struct; +} + +/****************************************************************************** + qForm Methods +******************************************************************************/ +// define qForm loadFields(); prototype +function _qForm_loadFields(){ + var strPackage = _getCookie("qForm_" + this._name + "_" + _c_strName); + // there is no form saved + if( strPackage == null ) return false; + + this.setFields(_readCookiePackage(strPackage), null, true); +} +qForm.prototype.loadFields = _qForm_loadFields; + +// define qForm saveFields(); prototype +function _qForm_saveFields(){ + var expires = new Date(_c_dToday.getTime() + (_c_iExpiresIn * 86400000)); + var strPackage = _createCookiePackage(this.getFields()); + _setCookie("qForm_" + this._name + "_" + _c_strName, strPackage, expires); +} +qForm.prototype.saveFields = _qForm_saveFields; + +// define qForm saveOnSubmit(); prototype +function _qForm_saveOnSubmit(){ + // grab the current onSubmit() method and append the saveFields() method to it + var fn = _functionToString(this.onSubmit, "this.saveFields();"); + this.onSubmit = new Function(fn); +} +qForm.prototype.saveOnSubmit = _qForm_saveOnSubmit; diff --git a/public/bundles/qforms/javascripts/qforms/field.js b/public/bundles/qforms/javascripts/qforms/field.js new file mode 100755 index 0000000..dbcd296 --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/field.js @@ -0,0 +1,281 @@ +/****************************************************************************** + qForm JSAPI: Field Extensions Library + + Author: Dan G. Switzer, II + Build: 109 +******************************************************************************/ +// define Field makeContainer(); prototype +function _Field_makeContainer(bindTo){ + lstContainers = (arguments.length == 0) ? this.name : this.name + "," + arguments[0]; + this.container = true; + this.defaultValue = this.getValue(); + this.lastValue = this.defaultValue; + this.dummyContainer = false; + this.boundContainers = _listToArray(lstContainers.toLowerCase()); + var thisKey = this.qForm._name + "_" + this.name.toLowerCase(); + + // copy objects from the select box into the container object + qFormAPI.containers[thisKey] = new Object(); + for( var i=0; i < this.obj.options.length; i++ ){ + qFormAPI.containers[thisKey][this.obj.options[i].value] = this.obj.options[i].text; + } +} +Field.prototype.makeContainer = _Field_makeContainer; + +// define Field resetLast(); prototype +function _Field_resetLast(){ + this.setValue(this.lastValue, null, false); + return true; +} +Field.prototype.resetLast = _Field_resetLast; + +// define Field toUpperCase(); prototype +function _Field_toUpperCase(){ + this.setValue(this.getValue().toUpperCase(), null, false); + return true; +} +Field.prototype.toUpperCase = _Field_toUpperCase; + +// define Field toLowerCase(); prototype +function _Field_toLowerCase(){ + this.setValue(this.getValue().toLowerCase(), null, false); + return true; +} +Field.prototype.toLowerCase = _Field_toLowerCase; + +// define Field ltrim(); prototype +function _Field_ltrim(){ + this.setValue(_ltrim(this.getValue()), null, false); + return true; +} +Field.prototype.ltrim = _Field_ltrim; + +// define Field rtrim(); prototype +function _Field_rtrim(){ + this.setValue(_rtrim(this.getValue()), null, false); + return true; +} +Field.prototype.rtrim = _Field_rtrim; + +// define Field trim(); prototype +function _Field_trim(){ + this.setValue(_trim(this.getValue()), null, false); + return true; +} +Field.prototype.trim = _Field_trim; + +// define Field compare(); prototype +function _Field_compare(field){ + if( this.getValue() == this.qForm[field].getValue() ){ + return true; + } else { + return false; + } + return true; +} +Field.prototype.compare = _Field_compare; + +// define Field mirrorTo(); prototype +function _Field_mirrorTo(objName){ + // test to see if the object is qForm object + isQForm = ( objName.indexOf(".") > -1 ) ? !eval("!objName.substring(0,objName.indexOf('.'))") : false; + + // if it's a qForm object, then set the value of the field to the current field when updated + if( isQForm ){ + var strCommand = objName + ".setValue(" + this.pointer + ".getValue()" + ", null, false);"; + // otherwise, set the local variable + } else { + var strCommand = objName + " = " + this.pointer + ".getValue();"; + } + + // add an onblur event so that when the field is updated, the requested field + // is updated with the value + this.addEvent(_getEventType(this.type), strCommand, false); +} +Field.prototype.mirrorTo = _Field_mirrorTo; + +// define Field createDependencyTo(); prototype +function _Field_createDependencyTo(field, condition){ + var condition = (arguments.length > 1) ? "\"" + arguments[1] + "\"" : null; + var otherField = this.qForm._pointer + "['" + field + "']"; + if( !eval(otherField) ) return alert("The " + field + " field does not exist. The dependency \nto " + this.name + " can not be created."); + // add an onblur event so that when the field is updated, the requested field + // is updated with the value + if( this.qForm[field]._queue.dependencies.length == 0 ) this.qForm[field].addEvent(_getEventType(this.qForm[field].type), otherField + ".enforceDependency();", false); + this.qForm[field]._queue.dependencies[this.qForm[field]._queue.dependencies.length] = otherField + ".isDependent('" + this.name + "', " + condition + ");"; + return true; +} +Field.prototype.createDependencyTo = _Field_createDependencyTo; + +// *this is an internal method that should only be used by the API* +// define Field isDependent(); prototype +function _Field_isDependent(field, condition){ + var condition = _param(arguments[1], null); + this.value = this.getValue(); + + // if the current field is empty or not equal to the specified value, then the + // dependent field is not required, otherwise the dependency is enforced + if( condition == null ){ + var result = (this.isNotEmpty() || this.required); + } else { + // if there's a space in the condition, assume you're to evaluate the string + if( condition.indexOf("this.") > -1 || condition == "true" || condition == "false" ){ + var result = eval(condition); + // otherwise, you're doing a simple value compare + } else { + var result = (this.value == condition); + } + } + // return both the field and the result + var o = null; + o = new Object(); + o.field = field; + o.result = result; + return o; +} +Field.prototype.isDependent = _Field_isDependent; + +// *this is an internal method that should only be used by the API* +// define Field enforceDependency(); prototype +function _Field_enforceDependency(e){ + var lstExcludeFields = _param(arguments[0], ","); + var lstFieldsChecked = ","; + var lstFieldsRequired = ","; + // loop through all the dependency and run each one + for( var i=0; i < this._queue.dependencies.length; i++ ){ + var s = eval(this._queue.dependencies[i]); + // keep a unique list of field checked + if( lstFieldsChecked.indexOf("," + s.field + ",") == -1 ) lstFieldsChecked += s.field + ","; + // keep a unique list of fields that now should be required + if( s.result && lstFieldsRequired.indexOf("," + s.field + ",") == -1 ) lstFieldsRequired += s.field + ","; + } + // create an array of the field checked + aryFieldsChecked = lstFieldsChecked.split(","); + // loop through the array skipping the first and last elements + for( var j=1; j < aryFieldsChecked.length-1; j++ ){ + // determine if the field is required + var result = (lstFieldsRequired.indexOf("," + aryFieldsChecked[j] + ",") > -1); + // update it's status + this.qForm[aryFieldsChecked[j]].required = result; + // now go check the dependencies for the field whose required status was changed + // if the dependency rules for the field have already been run, then don't run + // them again + if( lstExcludeFields.indexOf("," + aryFieldsChecked[j] + ",") == -1 ) setTimeout(this.qForm._pointer + "." + aryFieldsChecked[j] + ".enforceDependency('" + lstExcludeFields + this.name + ",')", 1); + } +} +Field.prototype.enforceDependency = _Field_enforceDependency; + + +// define Field location(); prototype +function _Field_location(target, key){ + var target = _param(arguments[0], "self"); + var key = _param(arguments[1]); + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return this.setValue(key, null, false); + + var value = this.getValue(); + this.setValue(key, null, false); + // if the value isn't equal to the key, then don't relocate the user + if( value != key ) eval(target + ".location = value"); + + return true; +} +Field.prototype.location = _Field_location; + +// define Field format(); prototype +function _Field_format(mask, type){ + var type = _param(arguments[1], "numeric").toLowerCase(); + this.validate = true; + this.validateFormat(mask, type); +} +Field.prototype.format = _Field_format; + + +// define Field populate(); prototype +function _Field_populate(struct, reset, sort, prefix){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return false; + + var reset = _param(arguments[1], true, "boolean"); + var sort = _param(arguments[2], false, "boolean"); + var prefix = _param(arguments[3], null, "object"); + + if( this.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes."); + + // clear the select box + if( reset ) this.obj.length = 0; + + // if prefixing options + if( !!prefix ) for( key in prefix ) this.obj.options[this.obj.length] = new Option(prefix[key], key); + + // populate the select box + for( key in struct ) this.obj.options[this.obj.length] = new Option(struct[key], key); + + // if the user wishes to sort the options in the select box + if( sort ) _sortOptions(this.obj); + return true; +} +Field.prototype.populate = _Field_populate; + +// define Field transferTo(); prototype +function _Field_transferTo(field, sort, type, selectItems, reset){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return false; + var sort = _param(arguments[1], true, "boolean"); + var type = _param(arguments[2], "selected"); + var selectItems = _param(arguments[3], true, "boolean"); + var reset = _param(arguments[4], false, "boolean"); + + _transferOptions(this.obj, this.qForm[field].obj, sort, type, selectItems, reset); + return true; +} +Field.prototype.transferTo = _Field_transferTo; + +// define Field transferFrom(); prototype +function _Field_transferFrom(field, sort, type, selectItems, reset){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return false; + var sort = _param(arguments[1], true, "boolean"); + var type = _param(arguments[2], "selected"); + var selectItems = _param(arguments[3], true, "boolean"); + var reset = _param(arguments[4], false, "boolean"); + + _transferOptions(this.qForm[field].obj, this.obj, sort, type, selectItems, reset); + return true; +} +Field.prototype.transferFrom = _Field_transferFrom; + +// define Field moveUp(); prototype +function _Field_moveUp(){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() || this.type.substring(0,6) != "select" ) return false; + + var oOptions = this.obj.options; + // rearrange + for( var i=1; i < oOptions.length; i++ ){ + // swap options + if( oOptions[i].selected ){ + _swapOptions(oOptions[i], oOptions[i-1]); + } + } + return true; +} +Field.prototype.moveUp = _Field_moveUp; + +// define Field moveDown(); prototype +function _Field_moveDown(){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() || this.type.substring(0,6) != "select" ) return false; + + var oOptions = this.obj.options; + // rearrange + for( var i=oOptions.length-2; i > -1; i-- ){ + // swap options + if( oOptions[i].selected ){ + _swapOptions(oOptions[i+1], oOptions[i]); + } + } + return true; +} +Field.prototype.moveDown = _Field_moveDown; + diff --git a/public/bundles/qforms/javascripts/qforms/functions.js b/public/bundles/qforms/javascripts/qforms/functions.js new file mode 100755 index 0000000..af931ea --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/functions.js @@ -0,0 +1,200 @@ +/****************************************************************************** + qForm JSAPI: Functions Library + + Author: Dan G. Switzer, II + Build: 108 +******************************************************************************/ +function _trim(s){ return _rtrim(_ltrim(s)); } + +function _ltrim(s){ + var w = " \n\t\f"; + // remove all beginning white space + while( w.indexOf(s.charAt(0)) != -1 && s.length != 0 ) s = s.substring(1); + return s; +} + +function _rtrim(s){ + var w = " \n\t\f"; + // remove all ending white space + while( w.indexOf(s.charAt(s.length-1)) != -1 && s.length != 0 ) s = s.substring(0, s.length-1); + return s; +} + +function _listToArray(string,delim){ + var delim = _param(arguments[1], ","); + tmp = string.split(delim); + for( var i=0; i < tmp.length; i++ ) tmp[i] = _trim(tmp[i]); + return tmp; +} + +function _listSum(string,delim){ + var delim = _param(arguments[1], ","); + tmp = _listToArray(string,delim); + iValue = 0; + for( var i=0; i < tmp.length; i++ ) iValue += parseInt(tmp[i], 10); + return iValue; +} + +function _stripInvalidChars(string, type){ + var string = _param(arguments[0]); + var type = _param(arguments[1], "numeric").toLowerCase(); + var validchars = type; + + stcTypes = new Object(); + stcTypes.numeric = "1234567890"; + stcTypes.alpha = "abcdefghijklmnopqrstuvwxyz"; + stcTypes.alphnumeric = "abcdefghijklmnopqrstuvwxyz1234567890"; + + if( stcTypes[type] ) validchars = stcTypes[type]; + + var tmp = ""; + var lc = string.toLowerCase(); + // loop through the string an make sure each character is an alpha character + for( var i=0;i < string.length;i++ ){ + if (validchars.indexOf(lc.charAt(i)) != -1) tmp += string.charAt(i); + } + return tmp; +} + +function _isLength(string, len, type){ + var string = _param(arguments[0]); + var len = parseInt(_param(arguments[1], 10, "number"), 10); + var type = _param(arguments[2], "numeric"); + + var tmp = _stripInvalidChars(string, type); + return (tmp.length == len) ? true : false; +} + +function _getState(abbr){ + var abbr = _param(arguments[0]).toLowerCase(); + _s = new Object(); _s.al = "Alabama"; _s.ak = "Alaska"; _s.as = "American Samoa"; _s.az = "Arizona"; _s.ar = "Arkansas"; _s.ca = "California"; _s.co = "Colorado"; _s.ct = "Connecticut"; _s.de = "Delaware"; _s.dc = "District of Columbia"; _s.fm = "Federal States of Micronesia"; _s.fl = "Florida"; _s.ga = "Georgia"; _s.gu = "Guam"; _s.hi = "Hawaii"; _s.id = "Idaho"; _s.il = "Illinois"; _s["in"] = "Indiana"; _s.ia = "Iowa"; _s.ks = "Kansas"; _s.ky = "Kentucky"; _s.la = "Louisana"; _s.me = "Maine"; _s.mh = "Marshall Islands"; _s.md = "Maryland"; _s.ma = "Massachusetts"; _s.mi = "Michigan"; _s.mn = "Minnesota"; _s.ms = "Mississippi"; _s.mo = "Missouri"; _s.mt = "Montana"; _s.ne = "Nebraska"; _s.nv = "Nevada"; _s.nh = "New Hampshire"; _s.nj = "New Jersey"; _s.nm = "New Mexico"; _s.ny = "New York"; _s.nc = "North Carolina"; _s.nd = "North Dakota"; _s.mp = "Northern Mariana Islands"; _s.oh = "Ohio"; _s.ok = "Oklahoma"; _s.or = "Oregon"; _s.pw = "Palau"; _s.pa = "Pennsylvania"; _s.pr = "Puerto Rico"; _s.ri = "Rhode Island"; _s.sc = "South Carolina"; _s.sd = "South Dakota"; _s.tn = "Tennessee"; _s.tx = "Texas"; _s.ut = "Utah"; _s.vt = "Vermont"; _s.vi = "Virgin Islands"; _s.va = "Virginia"; _s.wa = "Washington"; _s.wv = "West Virginia"; _s.wi = "Wisconsin"; _s.wy = "Wyoming"; _s.aa = "Armed Forces Americas"; _s.ae = "Armed Forces Africa/Europe/Middle East"; _s.ap = "Armed Forces Pacific"; + if( !_s[abbr] ){ + return null; + } else { + return _s[abbr]; + } +} + +// define the default properties for the sort +qFormAPI.sortOptions = new Object(); +qFormAPI.sortOptions.order = "asc"; +qFormAPI.sortOptions.byText = true; +function _sortOptions(obj, order, byText){ + var order = _param(arguments[1], qFormAPI.sortOptions.order); + if( order != "asc" && order != "desc" ) order = "asc"; + var byText = _param(arguments[2], qFormAPI.sortOptions.byText, "boolean"); + var orderAsc = (order == "asc") ? true : false; + + // loop through all the options and sort them asc + for( var i=0; i < obj.options.length; i++ ){ + for( var j=0; j < obj.options.length-1; j++ ){ + // if an option is greater than the next option, swap them + if( orderAsc && (byText && obj.options[j].text > obj.options[j+1].text) || (!byText && obj.options[j].value > obj.options[j+1].value) ){ + tmpTxt = obj.options[j].text; + tmpVal = obj.options[j].value; + tmpSel = obj.options[j].selected; + obj.options[j].text = obj.options[j+1].text ; + obj.options[j].value = obj.options[j+1].value; + obj.options[j].selected = obj.options[j+1].selected; + obj.options[j+1].text = tmpTxt ; + obj.options[j+1].value = tmpVal; + obj.options[j+1].selected = tmpSel; + } else if( !orderAsc && (byText && obj.options[j].text < obj.options[j+1].text) || (!byText && obj.options[j].value < obj.options[j+1].value) ){ + tmpTxt = obj.options[j].text; + tmpVal = obj.options[j].value; + tmpSel = obj.options[j].selected; + obj.options[j].text = obj.options[j+1].text ; + obj.options[j].value = obj.options[j+1].value; + obj.options[j].selected = obj.options[j+1].selected; + obj.options[j+1].text = tmpTxt ; + obj.options[j+1].value = tmpVal; + obj.options[j+1].selected = tmpSel; + } + } + } + return true; +} + +function _transferOptions(field1, field2, sort, type, selectItems, reset){ + var sort = _param(arguments[2], true, "boolean"); + var type = _param(arguments[3].toLowerCase(), "selected"); + if( type != "all" && type != "selected" ) type = "selected"; + var selectItems = _param(arguments[4], true, "boolean"); + var reset = _param(arguments[5], false, "boolean"); + var doAll = (type == "all") ? true : false; + + if( field1.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field1.name + "\" is not a select box."); + if( field2.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field2.name + "\" is not a select box."); + + // clear the select box + if( reset ) field2.length = 0; + + for( var i=0; i < field1.length; i++ ){ + // if the current option is selected, move it + if( doAll || field1.options[i].selected ){ + field2.options[field2.length] = new Option(field1.options[i].text, field1.options[i].value, false, selectItems); + field1.options[i] = null; + i--; // since you just deleted a option, redo this array position next loop + } + } + + // if sorting the fields + if( sort ) _sortOptions(field2); + return true; +} + + +function _getURLParams(){ + struct = new Object(); + var strURL = document.location.href; + var iPOS = strURL.indexOf("?"); + // if there are some query string params, split them into an array + if( iPOS != -1 ){ + var strQS = strURL.substring(iPOS + 1); + var aryQS = strQS.split("&"); + + // otherwise, return the empty structure + } else { + return struct; + } + + // loop through the array + for( var i=0; i < aryQS.length; i++ ){ + iPOS = aryQS[i].indexOf("="); + // if no equal sign is found, then the value is null + if( iPOS == -1 ){ + struct[aryQS[i]] = null; + // otherwise grab the variable name and it's value and stick it in structure + } else { + var key = aryQS[i].substring(0, iPOS); + var value = unescape(aryQS[i].substring(iPOS+1)); + // if the value doesn't exist, then create a new key + if( !struct[key] ) struct[key] = value; + // otherwise, append the value + else struct[key] += "," + value; + } + } + + return struct; +} + +function _createFields(struct, type){ + var type = _param(arguments[1], "hidden"); + if( this.status == null ) return false; + // loop through form elements + for( key in struct ){ + document.write(""); + } + return true; +} + +function _getEventType(type){ + // the default event type + var strEvent = "onblur"; + // if this is a checkbox & radio button, then mirror value on click + if( type == "checkbox" || type == "radio" ) strEvent = "onclick"; + // if this is a select box, then mirror value when the value changes + else if( type.substring(0,6) == "select" ) strEvent = "onchange"; + return strEvent; +} + diff --git a/public/bundles/qforms/javascripts/qforms/functions_js12.js b/public/bundles/qforms/javascripts/qforms/functions_js12.js new file mode 100755 index 0000000..f96487e --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/functions_js12.js @@ -0,0 +1,189 @@ +/****************************************************************************** + qForm JSAPI: Functions Library + + Author: Dan G. Switzer, II + Build: 110 +******************************************************************************/ +function _trim(s){ + return _rtrim(_ltrim(s)); +} + +function _ltrim(s){ + // remove all beginning white space + return (s.length == 0) ? s : s.replace(new RegExp("^\\s+", qFormAPI.reAttribs), ""); +} + +function _rtrim(s){ + // remove all ending white space + return (s.length == 0) ? s : s.replace(new RegExp("\\s+$", qFormAPI.reAttribs), ""); +} + +function _listToArray(string,delim){ + var delim = _param(arguments[1], ","); + tmp = string.split(delim); + for( var i=0; i < tmp.length; i++ ) tmp[i] = _trim(tmp[i]); + return tmp; +} + +function _listSum(string,delim){ + var delim = _param(arguments[1], ","); + tmp = _listToArray(string,delim); + iValue = 0; + for( var i=0; i < tmp.length; i++ ) iValue += parseInt(_trim(tmp[i]), 10); + return iValue; +} + +function _stripInvalidChars(_s, _t){ + var s = _param(arguments[0]); + var t = _param(arguments[1], "numeric").toLowerCase(); + var r; + + if( t == "numeric" ) r = new RegExp("([^0-9.]*)(^-?\\d*\\.?\\d*)(\\D*)", qFormAPI.reAttribs); + else if( t == "alpha" ) r = new RegExp("[^A-Za-z]+", qFormAPI.reAttribs); + else if( t == "alphanumeric" ) r = new RegExp("\\W+", qFormAPI.reAttribs); + else r = new RegExp("[^" + t + "]+", qFormAPI.reAttribs); + + if( t == "numeric" ) s = s.replace(r, "$2"); + else s = s.replace(r, ""); + + return s; +} + +function _isLength(string, len, type){ + var string = _param(arguments[0]); + var len = parseInt(_param(arguments[1], 10, "number"), 10); + var type = _param(arguments[2], "numeric"); + + var tmp = _stripInvalidChars(string, type); + return (tmp.length == len) ? true : false; +} + +function _getState(abbr){ + var abbr = _param(arguments[0]).toLowerCase(); + _s = new Object(); _s.al = "Alabama"; _s.ak = "Alaska"; _s.as = "American Samoa"; _s.az = "Arizona"; _s.ar = "Arkansas"; _s.ca = "California"; _s.co = "Colorado"; _s.ct = "Connecticut"; _s.de = "Delaware"; _s.dc = "District of Columbia"; _s.fm = "Federal States of Micronesia"; _s.fl = "Florida"; _s.ga = "Georgia"; _s.gu = "Guam"; _s.hi = "Hawaii"; _s.id = "Idaho"; _s.il = "Illinois"; _s["in"] = "Indiana"; _s.ia = "Iowa"; _s.ks = "Kansas"; _s.ky = "Kentucky"; _s.la = "Louisana"; _s.me = "Maine"; _s.mh = "Marshall Islands"; _s.md = "Maryland"; _s.ma = "Massachusetts"; _s.mi = "Michigan"; _s.mn = "Minnesota"; _s.ms = "Mississippi"; _s.mo = "Missouri"; _s.mt = "Montana"; _s.ne = "Nebraska"; _s.nv = "Nevada"; _s.nh = "New Hampshire"; _s.nj = "New Jersey"; _s.nm = "New Mexico"; _s.ny = "New York"; _s.nc = "North Carolina"; _s.nd = "North Dakota"; _s.mp = "Northern Mariana Islands"; _s.oh = "Ohio"; _s.ok = "Oklahoma"; _s.or = "Oregon"; _s.pw = "Palau"; _s.pa = "Pennsylvania"; _s.pr = "Puerto Rico"; _s.ri = "Rhode Island"; _s.sc = "South Carolina"; _s.sd = "South Dakota"; _s.tn = "Tennessee"; _s.tx = "Texas"; _s.ut = "Utah"; _s.vt = "Vermont"; _s.vi = "Virgin Islands"; _s.va = "Virginia"; _s.wa = "Washington"; _s.wv = "West Virginia"; _s.wi = "Wisconsin"; _s.wy = "Wyoming"; _s.aa = "Armed Forces Americas"; _s.ae = "Armed Forces Africa/Europe/Middle East"; _s.ap = "Armed Forces Pacific"; + if( !_s[abbr] ){ + return null; + } else { + return _s[abbr]; + } +} + +// define the default properties for the sort +qFormAPI.sortOptions = new Object(); +qFormAPI.sortOptions.order = "asc"; +qFormAPI.sortOptions.byText = true; + +function _sortOptions(obj, order, byText){ + var order = _param(arguments[1], qFormAPI.sortOptions.order); + if( order != "asc" && order != "desc" ) order = "asc"; + var byText = _param(arguments[2], qFormAPI.sortOptions.byText, "boolean"); + var orderAsc = (order == "asc") ? true : false; + + // loop through all the options and sort them asc + for( var i=0; i < obj.options.length; i++ ){ + for( var j=0; j < obj.options.length-1; j++ ){ + // if an option is greater than the next option, swap them + if( orderAsc && (byText && obj.options[j].text > obj.options[j+1].text) || (!byText && obj.options[j].value > obj.options[j+1].value) ){ + _swapOptions(obj.options[j], obj.options[j+1]); + } else if( !orderAsc && (byText && obj.options[j].text < obj.options[j+1].text) || (!byText && obj.options[j].value < obj.options[j+1].value) ){ + _swapOptions(obj.options[j], obj.options[j+1]); + } + } + } + return true; +} + +function _swapOptions(o1, o2){ + var sText = o1.text; + var sValue = o1.value; + var sSelected = o1.selected; + o1.text = o2.text ; + o1.value = o2.value; + o1.selected = o2.selected; + o2.text = sText; + o2.value = sValue; + o2.selected = sSelected; +} + +function _transferOptions(field1, field2, sort, type, selectItems, reset){ + var sort = _param(arguments[2], true, "boolean"); + var type = _param(arguments[3], "selected").toLowerCase(); + if( type != "all" && type != "selected" ) type = "selected"; + var selectItems = _param(arguments[4], true, "boolean"); + var reset = _param(arguments[5], false, "boolean"); + var doAll = (type == "all") ? true : false; + + if( field1.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field1.name + "\" is not a select box."); + if( field2.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field2.name + "\" is not a select box."); + + // clear the select box + if( reset ) field2.length = 0; + + for( var i=0; i < field1.length; i++ ){ + // if the current option is selected, move it + if( doAll || field1.options[i].selected ){ + field2.options[field2.length] = new Option(field1.options[i].text, field1.options[i].value, false, selectItems); + field1.options[i] = null; + i--; // since you just deleted a option, redo this array position next loop + } + } + + // if sorting the fields + if( sort ) _sortOptions(field2); + return true; +} + + +function _getURLParams(){ + struct = new Object(); + var strURL = document.location.href; + var iPOS = strURL.indexOf("?"); + // if there are some query string params, split them into an array + if( iPOS != -1 ){ + var strQS = strURL.substring(iPOS + 1); + var aryQS = strQS.split("&"); + // otherwise, return the empty structure + } else { + return struct; + } + + // loop through the array + for( var i=0; i < aryQS.length; i++ ){ + iPOS = aryQS[i].indexOf("="); + // if no equal sign is found, then the value is null + if( iPOS == -1 ){ + struct[aryQS[i]] = null; + // otherwise grab the variable name and it's value and stick it in structure + } else { + var key = aryQS[i].substring(0, iPOS); + var value = unescape(aryQS[i].substring(iPOS+1)); + // if the value doesn't exist, then create a new key + if( !struct[key] ) struct[key] = value; + // otherwise, append the value + else struct[key] += "," + value; + } + } + return struct; +} + +function _createFields(struct, type){ + var type = _param(arguments[1], "hidden"); + if( this.status == null ) return false; + // loop through form elements + for( key in struct ){ + document.write(""); + } + return true; +} + +function _getEventType(type){ + // the default event type + var strEvent = "onblur"; + // if this is a checkbox & radio button, then mirror value on click + if( type == "checkbox" || type == "radio" ) strEvent = "onclick"; + // if this is a select box, then mirror value when the value changes + else if( type.substring(0,6) == "select" ) strEvent = "onchange"; + return strEvent; +} + + diff --git a/public/bundles/qforms/javascripts/qforms/validation.js b/public/bundles/qforms/javascripts/qforms/validation.js new file mode 100755 index 0000000..963cf70 --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/validation.js @@ -0,0 +1,404 @@ +/****************************************************************************** + qForm JSAPI: Validation Library + + Author: Dan G. Switzer, II + Build: 113 +******************************************************************************/ +qFormAPI.packages.validation = true; + +// define Field isNotNull(); prototype +function _Field_isNotNull(){ + // check for blank field + if( this.value.length == 0 ){ + this.error = "You must specify a(n) " + this.description + "."; + } +} +_addValidator("isNotNull", _Field_isNotNull, true); + +// define Field isNotEmpty(); prototype +function _Field_isNotEmpty(){ + // check for blank field + if( _ltrim(this.value).length == 0 ){ + this.error = "You must specify a(n) " + this.description + "."; + } +} +_addValidator("isNotEmpty", _Field_isNotEmpty); + +// define Field isEmail(); prototype +function _Field_isEmail(){ + // check for @ . or blank field + if( this.value.indexOf(" ") != -1 ){ + this.error = "Invalid " + this.description + " address. An e-mail address should not contain a space."; + } else if( this.value.indexOf("@") == -1 ){ + this.error = "Invalid " + this.description + " address. An e-mail address must contain the @ symbol."; + } else if( this.value.indexOf("@") == 0 ){ + this.error = "Invalid " + this.description + " address. The @ symbol can not be the first character of an e-mail address."; + } else if( this.value.substring(this.value.indexOf("@")+2).indexOf(".") == -1 ){ + this.error = "Invalid " + this.description + " address. An e-mail address must contain at least one period after the @ symbol."; + } else if( this.value.lastIndexOf("@") == this.value.length-1 ){ + this.error = "Invalid " + this.description + " address. The @ symbol can not be the last character of an e-mail address."; + } else if( this.value.lastIndexOf(".") == this.value.length-1 ){ + this.error = "Invalid " + this.description + " address. A period can not be the last character of an e-mail address."; + } +} +_addValidator("isEmail", _Field_isEmail); + +// define Field isPassword(); prototype +function _Field_isPassword(field, minlen, maxlen){ + var minlen = _param(arguments[1], 1, "number"); // default minimum length of password + var maxlen = _param(arguments[2], 255, "number"); // default maximum length of password + + if( field != null && !this.compare(field) ){ + this.error = "The " + this.description + " and " + this.qForm[field].description + " values do not match."; + } + + // if there aren't an errors yet + if( this.error.length == 0 ){ + if( (this.value.length < minlen) || (this.value.length > maxlen) ){ + this.error = "The " + this.description + " field must be between " + minlen.toString() + " and " + maxlen.toString() + " characters long."; + } + } +} +_addValidator("isPassword", _Field_isPassword); + +// define Field isSame(); prototype +function _Field_isSame(field){ + if( !this.compare(field) ){ + this.error = "The " + this.description + " and " + this.qForm[field].description + " values do not match."; + } +} +_addValidator("isSame", _Field_isSame); + +// define Field isDifferent(); prototype +function _Field_isDifferent(field){ + if( this.compare(field) ){ + this.error = "The " + this.description + " and " + this.qForm[field].description + " must be different."; + } +} +_addValidator("isDifferent", _Field_isDifferent); + +// define Field isRange(); prototype +function _Field_isRange(low, high){ + var low = _param(arguments[0], 0, "number"); + var high = _param(arguments[1], 9999999, "number"); + var iValue = parseInt(this.value, 10); + if( isNaN(iValue) ) iValue = 0; + + // check to make sure the number is within the valid range + if( ( low > iValue ) || ( high < iValue ) ){ + this.error = "The " + this.description + " field does not contain a\nvalue between " + low + " and " + high + "."; + } +} +_addValidator("isRange", _Field_isRange); + +// define Field isInteger(); prototype +function _Field_isInteger(){ + var i = parseInt(this.value, 10); + // make sure the user specified a numeric value + if( isNaN(i) || (String(i) != this.value) ){ + this.error = "The value for " + this.description + " is not a numeric value. This field requires a numeric value."; + } +} +_addValidator("isInteger", _Field_isInteger); + +// define Field isNumeric(); prototype +function _Field_isNumeric(){ + var i = parseFloat(this.value, 10); + // make sure the user specified a numeric value + if( isNaN(i) || (String(i) != this.value) ){ + this.error = "The value for " + this.description + " is not a numeric value. This field requires a numeric value."; + } +} +_addValidator("isNumeric", _Field_isNumeric); + +// define Field isAlpha(); prototype +function _Field_isAlpha(){ + if( !_isLength(this.value, this.value.length, "alpha") ){ + this.error = "The value for " + this.description + " must contain only alpha characters."; + } +} +_addValidator("isAlpha", _Field_isAlpha); + +// define Field isAlphaNumeric(); prototype +function _Field_isAlphaNumeric(){ + if( !_isLength(this.value, this.value.length, "alphanumeric") ){ + this.error = "The value for " + this.description + " must contain only alpha-numeric characters."; + } +} +_addValidator("isAlphaNumeric", _Field_isAlphaNumeric); + +// define Field isDate(); prototype +function _Field_isDate(mask){ + var strMask = _param(arguments[0], "mm/dd/yyyy"); + var iMaskMonth = strMask.lastIndexOf("m") - strMask.indexOf("m") + 1; + var iMaskDay = strMask.lastIndexOf("d") - strMask.indexOf("d") + 1; + var iMaskYear = strMask.lastIndexOf("y") - strMask.indexOf("y") + 1; + + var strDate = this.value; + + // find the delimiter + var delim = "", lstMask = "mdy"; + for( var i=0; i < strMask.length; i++ ){ + if (lstMask.indexOf(strMask.substring(i, i+1)) == -1){ + delim = strMask.substring(i, i+1); + break; + } + } + aMask = strMask.split(delim); + if( aMask.length == 3 ){ + dt = this.value.split(delim); + if( dt.length != 3 ) this.error = "An invalid date was provided for " + this.description + " field."; + for( i=0; i < aMask.length; i++ ){ + if( aMask[i].indexOf("m") > -1 ) var sMonth = dt[i]; + else if( aMask[i].indexOf("d") > -1 ) var sDay = dt[i]; + else if( aMask[i].indexOf("y") > -1 ) var sYear = dt[i]; + } + } else if( mask.length == 1 ){ + var sMonth = this.value.substring(strMask.indexOf("m")-1, strMask.lastIndexOf("m")); + var sDay = this.value.substring(strMask.indexOf("d")-1, strMask.lastIndexOf("d")); + var sYear = this.value.substring(strMask.indexOf("y")-1, strMask.lastIndexOf("y")); + } else { + this.error = "An invalid date mask was provided for " + this.description + " field."; + } + + var iMonth = parseInt(sMonth, 10); + var iDay = parseInt(sDay, 10); + var iYear = parseInt(sYear, 10); + + if( isNaN(iMonth) || sMonth.length > iMaskMonth ) iMonth = 0; + if( isNaN(iDay) || sDay.length > iMaskDay ) iDay = 0; + if( isNaN(sYear) || sYear.length != iMaskYear ) sYear = null; + + lst30dayMonths = ",4,6,9,11,"; + + if( sYear == null ){ + this.error = "An invalid year was provided for the " + this.description + " field. The year \n should be a " + iMaskYear + " digit number."; + } else if( (iMonth < 1) || (iMonth > 12 ) ){ + this.error = "An invalid month was provided for " + this.description + " field."; + } else { + if( iYear < 100 ) var iYear = iYear + ((iYear > 20) ? 1900 : 2000); + var iYear = (sYear.length == 4) ? parseInt(sYear, 10) : parseInt("20" + sYear, 10); + if( lst30dayMonths.indexOf("," + iMonth + ",") > -1 ){ + if( (iDay < 1) || (iDay > 30 ) ) this.error = "An invalid day was provided for the " + this.description + " field."; + } else if( iMonth == 2 ){ + if( (iDay < 1) || (iDay > 28 && !( (iDay == 29) && (iYear%4 == 0 ) ) ) ) this.error = "An invalid day was provided for the " + this.description + " field."; + } else { + if( (iDay < 1) || (iDay > 31 ) ) this.error = "An invalid day was provided for the " + this.description + " field."; + } + } + +} +_addValidator("isDate", _Field_isDate); + + +// define Field isCreditCard(); prototype +function _Field_isCreditCard(){ + var strCC = _stripInvalidChars(this.value, "numeric").toString(); + var isNumeric = (strCC.length > 0) ? true : false; + + if( isNumeric ){ + // now check mod10 + var dd = (strCC.length % 2 == 1) ? false : true; + var cd = 0; + var td; + + for( var i=0; i < strCC.length; i++ ){ + td = parseInt(strCC.charAt(i), 10); + if( dd ){ + td *= 2; + cd += (td % 10); + if ((td / 10) >= 1.0) cd++; + dd = false; + } else { + cd += td; + dd = true; + } + } + if( (cd % 10) != 0 ) this.error = "The credit card number entered in the " + this.description + " field is invalid."; + } else { + this.error = "The credit card number entered in the " + this.description + " field is invalid."; + } +} +_addValidator("isCreditCard", _Field_isCreditCard); + +// define Field isPhoneNumber(); prototype +function _Field_isPhoneNumber(len){ + var len = parseInt(_param(arguments[0], 10, "number"), 10); + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + + // check to make sure the phone is the correct length + if( !_isLength(this.value, len) ){ + this.error = "The " + description + " field must include " + len + " digits."; + } +} +_addValidator("isPhoneNumber", _Field_isPhoneNumber); + +// define Field isLength(); prototype +function _Field_isLength(len, type){ + var len = parseInt(_param(arguments[0], 10, "number"), 10); + var type = _param(arguments[1], "numeric"); + + // check to make sure the phone is the correct length + if( !_isLength(this.value, len, type) ){ + this.error = "The " + this.description + " field must include " + len + " " + type + " characters."; + } +} +_addValidator("isLength", _Field_isLength); + +// define Field isSSN(); prototype +function _Field_isSSN(){ + var description = (this.description == this.name.toLowerCase()) ? "social security" : this.description; + + // check to make sure the phone is the correct length + if( !_isLength(this.value, 9) ){ + this.error = "The " + description + " field must include 9 digits."; + } +} +_addValidator("isSSN", _Field_isSSN); + + +// define Field isState(); prototype +function _Field_isState(){ + // check to make sure the phone is the correct length + if( _getState(this.value) == null ){ + this.error = "The " + this.description + " field must contain a valid 2-digit state abbreviation."; + } +} +_addValidator("isState", _Field_isState); + +// define Field isZipCode(); prototype +function _Field_isZipCode(){ + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + + iZipLen = _stripInvalidChars(this.value).length; + + // check to make sure the zip code is the correct length + if( iZipLen != 5 && iZipLen != 9 ){ + this.error = "The " + description + " field must contain either 5 or 9 digits."; + } +} +_addValidator("isZipCode", _Field_isZipCode); + +// define Field isFormat(); prototype +function _Field_isFormat(mask, type){ + var mask = _param(arguments[0]); + var type = _param(arguments[1], "numeric").toLowerCase(); + var strErrorMsg = ""; + + var strMaskLC = mask.toLowerCase(); + // define quick masks + if( strMaskLC == "ssn" ){ + mask = "xxx-xx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "social security number" : this.description; + strErrorMsg = "The " + description + " field must contain 9 digits and \nshould be in the format: " + mask; + + } else if( (strMaskLC == "phone") || (strMaskLC == "phone1") ){ + mask = "(xxx) xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "phone2" ){ + mask = "xxx-xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "phone3" ){ + mask = "xxx/xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "phone7" ){ + mask = "xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 7 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "zip" ){ + if( _stripInvalidChars(this.value).length < 6 ){ + mask = "xxxxx"; + } else { + mask = "xxxxx-xxxx"; + } + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + strErrorMsg = "The " + description + " field should contain either 5 or 9 digits \nand be in the format: xxxxx or xxxxx-xxxx"; + + } else if( strMaskLC == "zip5" ){ + mask = "xxxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + strErrorMsg = "The " + description + " field should contain 5 digits \nand be in the format: " + mask; + + } else if( strMaskLC == "zip9" ){ + mask = "xxxxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + strErrorMsg = "The " + description + " field should contain 9 digits \nand be in the format: " + mask; + } else { + var description = this.description; + } + + var string = _stripInvalidChars(this.value, type); + var masklen = _stripInvalidChars(mask, "x").length; + + // check to make sure the string contains the correct number of characters + if( string.length != masklen && this.value.length > 0){ + if( strErrorMsg.length == 0 ) strErrorMsg = "This field requires at least " + masklen + " valid characters. Please \nmake sure to enter the value in the format: \n " + mask + "\n(where 'x' is a valid character.)"; + this.error = strErrorMsg; + + // else re-format the string as defined by the mask + } else if( string.length == masklen ){ + // find the position of all non "X" characters + var stcMask = new Object(); + var lc = mask.toLowerCase(); + // loop through the string an make sure each character is an valid character + for( var i=0; i < mask.length; i++ ){ + if( lc.charAt(i) != "x" ) stcMask[i] = mask.charAt(i); + } + + // put all the non-"X" characters back into the parsed string + var iLastChar = 0; + var newString = ""; + var i = 0; + for( var pos in stcMask ){ + pos = parseInt(pos, 10); + if( pos > iLastChar ){ + newString += string.substring(iLastChar, pos-i) + stcMask[pos]; + iLastChar = pos-i; + } else { + newString += stcMask[pos]; + } + i++; + } + if( i == 0 ){ + newString = string; + } else { + newString += string.substring(iLastChar); + } + + // set the value of the field to the new string--make sure not to perform the onBlur event + this.setValue(newString, true, false); + } +} +_addValidator("isFormat", _Field_isFormat); + +// define Field isLengthGT(); prototype +function _Field_isLengthGT(len){ + if( this.obj.value.length <= len){ + this.error = "The " + this.description + " field must be greater than " + len + " characters."; + } +} +_addValidator("isLengthGT", _Field_isLengthGT); + +// define Field isLengthLT(); prototype +function _Field_isLengthLT(len){ + if( this.obj.value.length >= len){ + this.error = "The " + this.description + " field must be less than " + len + " characters."; + } +} +_addValidator("isLengthLT", _Field_isLengthLT); + diff --git a/public/bundles/qforms/javascripts/qforms/validation_addon.js b/public/bundles/qforms/javascripts/qforms/validation_addon.js new file mode 100755 index 0000000..3ac7f29 --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/validation_addon.js @@ -0,0 +1,34 @@ +/****************************************************************************** + qForm JSAPI: Add-on Validation Library + + Author: Dan G. Switzer, II + Build: 100 +******************************************************************************/ +qFormAPI.packages.validation = true; + +// [start] validation routine +function _f_isAtLeastOne(_f){ + var sFields = this.name + ((typeof _f == "string") ? "," + _removeSpaces(_f) : ""); + var aFields = sFields.split(","), v = new Array(), d = new Array(), x = ","; + + for( var i=0; i < aFields.length; i++ ){ + if( !this.qForm[aFields[i]] ) return alert("The field name \"" + aFields[i] + "\" does not exist."); + // store the value in an array + v[v.length] = this.qForm[aFields[i]].getValue(); + // if the field name is already in the list, don't add it + if( x.indexOf("," + aFields[i] + ",") == -1 ){ + d[d.length] = this.qForm[aFields[i]].description; + x += aFields[i] + ","; + } + } + + // if all of the form fields has empty lengths, then throw + // an error message to the page + if( v.join("").length == 0 ){ + this.error = "At least one of the following fields is required:\n " + d.join(", "); + for( i=0; i < aFields.length; i++ ){ + if( qFormAPI.useErrorColorCoding && this.qForm[aFields[i]].obj.style ) this.qForm[aFields[i]].obj.style[qFormAPI.styleAttribute] = qFormAPI.errorColor; + } + } +} +_addValidator("isAtLeastOne", _f_isAtLeastOne, true); diff --git a/public/bundles/qforms/javascripts/qforms/wddx.js b/public/bundles/qforms/javascripts/qforms/wddx.js new file mode 100755 index 0000000..a3f0a12 --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms/wddx.js @@ -0,0 +1,102 @@ +/****************************************************************************** + qForm JSAPI: WDDX Mod Library + + Author: Dan G. Switzer, II + Build: 101 +******************************************************************************/ + +/****************************************************************************** + Required Functions +******************************************************************************/ +function __serializeStruct(struct){ + // open packet + var aWDDX = new Array("
    "); + for( var key in struct ) aWDDX[aWDDX.length] = "" + __wddxValue(struct[key]) + ""; + // close packet + aWDDX[aWDDX.length] = ""; + return aWDDX.join(""); +} + + +function __wddxValue(str){ + var aValue = new Array(); + for( var i=0; i < str.length; ++i) aValue[aValue.length] = _encoding.table[str.charAt(i)]; + return aValue.join(""); +} + +function _wddx_Encoding(){ + // Encoding table for strings (CDATA) + var et = new Array(); + + // numbers to characters table + var n2c = new Array(); + + for( var i=0; i < 256; ++i ){ + // build a character from octal code + var d1 = Math.floor(i/64); + var d2 = Math.floor((i%64)/8); + var d3 = i%8; + var c = eval("\"\\" + d1.toString(10) + d2.toString(10) + d3.toString(10) + "\""); + + // modify character-code conversion tables + n2c[i] = c; + + // modify encoding table + if( i < 32 && i != 9 && i != 10 && i != 13 ){ + // control characters that are not tabs, newlines, and carriage returns + + // create a two-character hex code representation + var hex = i.toString(16); + if( hex.length == 1 ) hex = "0" + hex; + + et[n2c[i]] = ""; + + } else if( i < 128 ){ + // low characters that are not special control characters + et[n2c[i]] = n2c[i]; + } else { + // high characters + et[n2c[i]] = "&#x" + i.toString(16) + ";"; + } + } + + // special escapes for CDATA encoding + et["<"] = "<"; + et[">"] = ">"; + et["&"] = "&"; + + this.table = et; +} +_encoding = new _wddx_Encoding(); + + +/****************************************************************************** + qForm Methods +******************************************************************************/ +function _a_serialize(exclude){ + // if you need to reset the default values of the fields + var lstExclude = (arguments.length > 0) ? "," + _removeSpaces(arguments[0]) + "," : ""; + struct = new Object(); + stcAllFields = qFormAPI.getFields(); + // loop through form elements + for( key in stcAllFields ){ + if( lstExclude.indexOf("," + key + ",") == -1 ) struct[key] = stcAllFields[key]; + } + // create & return the serialized object + return __serializeStruct(struct); +} +_a.prototype.serialize = _a_serialize; + +// define qForm serialize(); prototype +function _q_serialize(exclude){ + // if you need to reset the default values of the fields + var lstExclude = (arguments.length > 0) ? "," + _removeSpaces(arguments[0]) + "," : ""; + struct = new Object(); + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + if( lstExclude.indexOf("," + this._fields[j] + ",") == -1 ) struct[this._fields[j]] = this[this._fields[j]].getValue(); + } + // create & return the serialized object + return __serializeStruct(struct); +} +qForm.prototype.serialize = _q_serialize; diff --git a/public/bundles/qforms/javascripts/qforms_init.js b/public/bundles/qforms/javascripts/qforms_init.js new file mode 100755 index 0000000..68ccea8 --- /dev/null +++ b/public/bundles/qforms/javascripts/qforms_init.js @@ -0,0 +1,9 @@ +// set the path to the qForms directory +qFormAPI.setLibraryPath("/bundles/qforms/javascripts/"); +// this loads all the default libraries +qFormAPI.include("*"); + + +/******************************************************************** + load custom validation scripts here +********************************************************************/ diff --git a/public/bundles/stateful_form/javascripts/stateful_form.js b/public/bundles/stateful_form/javascripts/stateful_form.js new file mode 100644 index 0000000..00cb433 --- /dev/null +++ b/public/bundles/stateful_form/javascripts/stateful_form.js @@ -0,0 +1,18 @@ +function remember_visible_state_of(element) { + var visible_state = (Element.visible(element) ? 1 : 0); + // Allow optional second argument to override the state + if (arguments.length > 1) visible_state = (arguments[1] == true || arguments[1] == 1) ? 1 : 0; + + // See if the element has a hidden field where we will store its state + element = $(element); + var hidden_field_id = 'visible_state_' + element.id + var hidden_field = $(hidden_field_id); + + if (hidden_field) + hidden_field.value = visible_state; + else + new Insertion.Top(element, + ""); +} diff --git a/public/bundles/textarea/images/areaLess.png b/public/bundles/textarea/images/areaLess.png new file mode 100755 index 0000000..bae913c Binary files /dev/null and b/public/bundles/textarea/images/areaLess.png differ diff --git a/public/bundles/textarea/images/areaMore.png b/public/bundles/textarea/images/areaMore.png new file mode 100755 index 0000000..910e18f Binary files /dev/null and b/public/bundles/textarea/images/areaMore.png differ diff --git a/public/bundles/textarea/images/fontDown.png b/public/bundles/textarea/images/fontDown.png new file mode 100755 index 0000000..48dfe73 Binary files /dev/null and b/public/bundles/textarea/images/fontDown.png differ diff --git a/public/bundles/textarea/images/fontUp.png b/public/bundles/textarea/images/fontUp.png new file mode 100755 index 0000000..13523bf Binary files /dev/null and b/public/bundles/textarea/images/fontUp.png differ diff --git a/public/bundles/textarea/javascripts/resizable.js b/public/bundles/textarea/javascripts/resizable.js new file mode 100755 index 0000000..ef49338 --- /dev/null +++ b/public/bundles/textarea/javascripts/resizable.js @@ -0,0 +1,130 @@ +document.getElementsByClassName = function (needle) +{ + var my_array = document.getElementsByTagName("*"); + var retvalue = new Array(); + var i; + var j; + + for (i = 0, j = 0; i < my_array.length; i++) + { + var c = " " + my_array[i].className + " "; + if (c.indexOf(" " + needle + " ") != -1) + retvalue[j++] = my_array[i]; + } + return retvalue; +} + +function addEvent(obj, evType, fn) +{ + if (obj.addEventListener) + { + obj.addEventListener(evType, fn, true); + return true; + } + else if (obj.attachEvent) + { + var r = obj.attachEvent("on"+evType, fn); + return r; + } + else + { + return false; + } +} + +addEvent(window, 'load', function() +{ + var resizables = document.getElementsByClassName('resizable'); + var i, ta, wrapper, bigger, smaller, biggerFont, smallerFont, biggerSpan, smallerSpan, biggerFontSpan, smallerFontSpan; + for (i=0; i 30) + this.resizer.style.height = (parseInt(this.resizer.style.height) - 30) + "px"; + return false; + } + + biggerFont.onclick = function() + { + if (this.resizer.style.fontSize == '') + this.resizer.style.fontSize = '1em'; + + this.resizer.style.fontSize = (parseFloat(this.resizer.style.fontSize) + 0.1) + "em"; + + return false; + } + + smallerFont.onclick = function() + { + if (this.resizer.style.fontSize == '') + this.resizer.style.fontSize = '1em'; + + if ((parseFloat(this.resizer.style.fontSize) - 0.1) > 0.5) + this.resizer.style.fontSize = (parseFloat(this.resizer.style.fontSize) - 0.1) + "em"; + + return false; + } + + wrapper.appendChild(bigger); + wrapper.appendChild(smaller); + wrapper.appendChild(biggerFont); + wrapper.appendChild(smallerFont); + } +}); diff --git a/public/bundles/textarea/javascripts/wordcount.js b/public/bundles/textarea/javascripts/wordcount.js new file mode 100755 index 0000000..ef6c32f --- /dev/null +++ b/public/bundles/textarea/javascripts/wordcount.js @@ -0,0 +1,29 @@ + +addEvent(window, 'load', function() +{ + var counts = document.getElementsByClassName('wordcount'); + var i, countHolder; + + for (i=0; i0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js new file mode 100644 index 0000000..91088b5 --- /dev/null +++ b/public/javascripts/dragdrop.js @@ -0,0 +1,968 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// See scriptaculous.js for full license. + +/*--------------------------------------------------------------------------*/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(arguments[1] && typeof arguments[1].endeffect == 'undefined') + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) { + var h = Element.childrenWithClassName(this.element, options.handle, true); + if(h.length>0) this.handle = h[0]; + } + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft; + p[1] += this.options.scroll.scrollTop; + + p[0] += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0); + p[1] += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); + + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + window.status = r.inspect(); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + //greedy: !options.dropOnEmpty + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.childrenWithClassName(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.addClassName(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.left = offsets[0] + 'px'; + Sortable._marker.style.top = offsets[1] + 'px'; + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; + else + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + + Element.show(Sortable._marker); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: new Array, + position: parent.children.length, + container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + /* Finds the first element of the given tag type within a parent element. + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ + _findChildrenElement: function (element, containerTag) { + if (element && element.hasChildNodes) + for (var i = 0; i < element.childNodes.length; ++i) + if (element.childNodes[i].tagName == containerTag) + return element.childNodes[i]; + + return null; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: new Array, + container: element, + position: 0 + } + + return Sortable._tree (element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +/* Returns true if child is contained within element */ +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + + if (child.parentNode == element) return true; + + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + if (type == 'vertical' || type == 'height') + return element.offsetHeight; + else + return element.offsetWidth; +} \ No newline at end of file diff --git a/public/javascripts/effects.js b/public/javascripts/effects.js new file mode 100644 index 0000000..8b07d0e --- /dev/null +++ b/public/javascripts/effects.js @@ -0,0 +1,975 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : 1.0 }); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className, findFirst) { + var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); + var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { + return (c.className && c.className.match(classNameRegExp)); + }); + if(!results) results = []; + return results; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = Prototype.K; + +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide(); + effect.element.setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from); + effect.element.show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned(); + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.undoPositioned(); + effect.element.setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned(); + effect.element.setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + element.cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + // IE will crash if child is undoPositioned first + if(/MSIE/.test(navigator.userAgent) && !window.opera){ + effect.element.undoPositioned(); + effect.element.firstChild.undoPositioned(); + }else{ + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + } + effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + element.cleanWhitespace(); + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + effect.element.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + effect.element.hide(effect.element); + effect.element.undoClipping(effect.element); } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide(); + effect.element.makeClipping(); + effect.element.makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}); + effect.effects[0].element.show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned(); + effect.effects[0].element.makeClipping(); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/public/javascripts/lowpro.js b/public/javascripts/lowpro.js new file mode 100644 index 0000000..c0124e6 --- /dev/null +++ b/public/javascripts/lowpro.js @@ -0,0 +1,307 @@ +LowPro = {}; +LowPro.Version = '0.1'; + +// Adapted from DOM Ready extension by Dan Webb +// http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype +// which was based on work by Matthias Miller, Dean Edwards and John Resig +// +// Usage: +// +// Event.onReady(callbackFunction); +Object.extend(Event, { + _domReady : function() { + if (arguments.callee.done) return; + arguments.callee.done = true; + + if (Event._timer) clearInterval(Event._timer); + + Event._readyCallbacks.each(function(f) { f() }); + Event._readyCallbacks = null; + + }, + onReady : function(f) { + if (!this._readyCallbacks) { + var domReady = this._domReady; + + if (domReady.done) return f(); + + if (document.addEventListener) + document.addEventListener("DOMContentLoaded", domReady, false); + + /*@cc_on @*/ + /*@if (@_win32) + document.write(" +<% end %> + + + + +<% model_filters = model.filters +unless model_filters.empty? %> +

    +

    Filter

    +<% model_filters.each do |filter| -%> +

    By <%=h model.column_label( filter.name ).downcase %>

    +
      +<% filter.options.each do |opt| -%> + <%= content_tag 'li', + link_to( h(opt[:label]), similar_list_page_with_filter( filter.name, opt[:name] ) ), + :class => ( params[:filter][filter.name] == opt[:name].to_s ? 'selected' : '' ) %> +<% end -%> +
    +<% end -%> +
    +<% end %> + +<% +admin_table( :model => model ) do |builder| + builder.outer do + builder.prologue do + model.list_columns.build builder + end + @objects.each do |obj| + builder.with_object(obj) do + model.list_columns.build builder + end + end + builder.epilogue do + end + end +end +%> + + +

    +<%= +pagination_links_each(@pages, :link_to_current_page => true, :window_size => 3) do |num| + "\n " + case num + when @pages.current_page.number + %(#{num}) + when @pages.last_page.number + link_to num.to_s, similar_list_page(:page => num), { :class => 'end' } + else + link_to num.to_s, similar_list_page(:page => num) + end +end +%> + +<%= @pages.item_count %> <%=h human_model( nil, @pages.item_count ).downcase %> +

    + + diff --git a/vendor/plugins/auto-admin/themes/django/views/login.rhtml b/vendor/plugins/auto-admin/themes/django/views/login.rhtml new file mode 100644 index 0000000..267e128 --- /dev/null +++ b/vendor/plugins/auto-admin/themes/django/views/login.rhtml @@ -0,0 +1,21 @@ +<% +@body_class = '' +@content_class = 'colM' +@title = 'Log in' +%> +<%= form_tag %> +

    + <%= text_field_tag 'username', '' %> +

    +

    + <%= password_field_tag 'password', '' %> +

    + +
    +<%= submit_tag 'Log in' %> +
    + + +<%= end_form_tag %> diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar.rb b/vendor/plugins/bundled_resource/bundles/dynarch_calendar.rb new file mode 100644 index 0000000..936459a --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar.rb @@ -0,0 +1,171 @@ +# == Usage == +# Using the defaults: +# require_bundle :dynarch_calendar +# +# Or with some optional parameters: +# require_bundle :dynarch_calendar, :color => 'green', :language => 'en', :icon => '/images/my_calendar.gif' +# +# See the 'lang/' folder inside the dynarch bundle for a list of valid language options. +# See the 'stylesheets/' folder inside the dynarch bundle for a list of valid color options. +# +# 1. Use the 'dynarch_date_select' method in place of Rails' date_select: +# <%= dynarch_date_select 'user', 'birthday' %> +# +# Using this method will first call the Rails built-in date_select method +# so that browsers that don't support javascript can still work. +# +# If javascript is enabled, however, the ugly data select drop-downs +# will subsequently be replaced by the snazzy dynarch calendar. +# +# 2. Note that convert_date_to_multi_params! is no longer needed, and in fact has +# been removed as of bundled_resource 0.8. +# +# == Full Dynarch Calendar Documentation == +# ? + +module BundledResource::DynarchCalendar + mattr_accessor :icon + + # As of bundled_resource 0.8, it is no longer necessary to use the 'convert_date_to_multi_params!' + # method in your controller. Everything should 'just work'. + def bundle(options = {}) + defaults = {:language => 'en', :color => 'blue', :stripped => true, :icon => 1} + options = defaults.merge(options) + require_stylesheet "/bundles/dynarch_calendar/stylesheets/calendar-#{options[:color]}" + + require_javascript "/bundles/dynarch_calendar/javascripts/calendar#{options[:stripped] ? '_stripped' : ''}" + require_javascript "/bundles/dynarch_calendar/lang/calendar-#{options[:language]}" + require_javascript "/bundles/dynarch_calendar/javascripts/calendar-setup#{options[:stripped] ? '_stripped' : ''}" + require_javascript "/bundles/dynarch_calendar/javascripts/convert_calendar_field" + + if options[:icon].is_a? Fixnum + BundledResource::DynarchCalendar.icon = "/bundles/dynarch_calendar/images/calendar_icon#{options[:icon]}.gif" + else + BundledResource::DynarchCalendar.icon = options[:icon] + end + end + + module Helper + def dynarch_object_name(object_name) + object_name =~ /^(.+)\[\]$/ ? $1 : object_name + end + + def dynarch_object(object_name) + instance_variable_get("@#{dynarch_object_name(object_name)}") + end + + def dynarch_value(object_name, method_name, options) + object = dynarch_object(object_name) + options[:index] ||= object.id_before_type_cast if object_name =~ /^(.+)\[\]$/ + object.send(method_name) + end + + # Shows a calendar image that the user can click on to show a pop-up Dynarch calendar. + def dynarch_date_select(object_name, method_name, options = {}) + image_url = BundledResource::DynarchCalendar.icon + date_format = options[:date_format] || "%A, %B %d, %Y" + datetime = ((dynarch_value(object_name, method_name, options) || DateTime.now) rescue DateTime.now) + initial_date = datetime.strftime("%m/%d/%Y %H:%M") + initial_display = datetime.strftime(date_format) + index = options[:index] ? "'#{options[:index]}'" : "null" + obj_name = dynarch_object_name(object_name) + container_id = "#{obj_name}" + (options[:index] ? "_#{options[:index]}" : "") + "_#{method_name}_container" + buffer = "" + buffer << content_tag('span', date_select(object_name.dup, method_name, options) + "\n", :id => container_id) + buffer << "\n" + end + + # If you want a drop-down to select the hour and minute, use :select_time => true. + # Otherwise, two text fields will be used. + def dynarch_time_select(object_name, method_name, options = {}) + defaults = { :discard_type => true, :select_time => false } + options = defaults.merge(options) + datetime = ((dynarch_value(object_name, method_name, options) || DateTime.now) rescue DateTime.now) + # Strip off [], if any + object_name = dynarch_object_name(object_name) + # Add [#{index}] if index is given + if options[:index] + options_with_prefix = Proc.new { |position, is_integer| options.merge(:prefix => "#{object_name}[#{options[:index]}][#{method_name}(#{position}#{is_integer ? 'i' : ''})]") } + else + options_with_prefix = Proc.new { |position, is_integer| options.merge(:prefix => "#{object_name}[#{method_name}(#{position}#{is_integer ? 'i' : ''})]") } + end + buffer = "" + twelve_hour = datetime.hour >= 12 ? datetime.hour - 12 : datetime.hour + twelve_hour = 12 if twelve_hour == 0 + if options[:select_time] + buffer << select_twelve_hour(datetime, options_with_prefix.call(4, true)) << ':' + buffer << select_minute(datetime, options_with_prefix.call(5, true)) + else + if options[:index] + buffer << text_field_tag("#{object_name}[#{options[:index]}][#{method_name}(4i)]", twelve_hour, :size => 2) << ':' + buffer << text_field_tag("#{object_name}[#{options[:index]}][#{method_name}(5i)]", leading_zero_on_single_digits(datetime.min), :size => 2) + else + buffer << text_field_tag("#{object_name}[#{method_name}(4i)]", twelve_hour, :size => 2) << ':' + buffer << text_field_tag("#{object_name}[#{method_name}(5i)]", leading_zero_on_single_digits(datetime.min), :size => 2) + end + end + # RAILS_DEFAULT_LOGGER.warn "DYNARCH TIME SELECT: #{buffer}" + buffer << select_am_pm(datetime, options_with_prefix.call(6, false)) + end + + def dynarch_datetime_select(object, method, options = {}) + dynarch_date_select(object, method, options) + + (options[:separator] || ' at ') + + dynarch_time_select(object, method, options) + end + end +end + + +module ActionView::Helpers::DateHelper + def select_twelve_hour(datetime, options = {}) + hour_options = [] + + 0.upto(11) do |hour| + if datetime + datetime_hour = (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) + selected = ' selected' if datetime_hour == hour or (datetime_hour - 12) == hour + else + selected = '' + end + hour_options << + %(\n) + end + + RAILS_DEFAULT_LOGGER.warn "select twelve hour prefix: #{options[:prefix]}" + select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) + end + + def select_am_pm(datetime, options = {}) + am_pm_options = [] + + ['AM', 'PM'].each do |am_pm| + if datetime + datetime_am_pm = (datetime.kind_of?(String) ? datetime : datetime.strftime("%p")) + selected = ' selected' if datetime_am_pm == am_pm + else + selected = '' + end + am_pm_options << + %(\n) + end + + select_html(options[:field_name] || 'am_pm', am_pm_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled]) + end +end + +class ::DateTime + alias dynarch_original_initialize initialize + def initialize(*args) + # Allow the 6th parameter, which is normally seconds, to contain 'AM' or 'PM' + if args.size >= 6 + if args[5] == 'PM' and args[3] < 12 + args[3] += 12 + args[5] = nil + elsif args[5] == 'AM' + args[5] = nil + end + end + dynarch_original_initialize(*args) + end +end \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/field-button.jpg b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/field-button.jpg new file mode 100644 index 0000000..ecbe9d8 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/field-button.jpg differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference-Z-S.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference-Z-S.css new file mode 100644 index 0000000..02a6f88 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference-Z-S.css @@ -0,0 +1,193 @@ + + body { + color: black; + /* background-color: #e5e5e5;*/ + background-color: #ffffff; + /*background-color: beige;*/ + margin-top: 2em; + margin-left: 8%; + margin-right: 8%; + } + + h1,h2,h3,h4,h5,h6 { + margin-top: .5em; + } + + .title { + font-size: 200%; + font-weight: normal; + } + + .partheading { + font-size: 100%; + } + + .chapterheading { + font-size: 100%; + } + + .beginsection { + font-size: 110%; + } + + .tiny { + font-size: 40%; + } + + .scriptsize { + font-size: 60%; + } + + .footnotesize { + font-size: 75%; + } + + .small { + font-size: 90%; + } + + .normalsize { + font-size: 100%; + } + + .large { + font-size: 120%; + } + + .largecap { + font-size: 150%; + } + + .largeup { + font-size: 200%; + } + + .huge { + font-size: 300%; + } + + .hugecap { + font-size: 350%; + } + + pre { + margin-left: 2em; + } + + blockquote { + margin-left: 2em; + } + + ol { + list-style-type: decimal; + } + + ol ol { + list-style-type: lower-alpha; + } + + ol ol ol { + list-style-type: lower-roman; + } + + ol ol ol ol { + list-style-type: upper-alpha; + } + + /* + .verbatim { + color: #4d0000; + } + */ + + tt i { + font-family: serif; + } + + .verbatim em { + font-family: serif; + } + + .scheme em { + font-family: serif; + color: black; + } + + .scheme { + color: brown; + } + + .scheme .keyword { + color: #990000; + font-weight: bold; + } + + .scheme .builtin { + color: #990000; + } + + .scheme .variable { + color: navy; + } + + .scheme .global { + color: purple; + } + + .scheme .selfeval { + color: green; + } + + .scheme .comment { + color: teal; + } + + .schemeresponse { + color: green; + } + + .navigation { + color: red; + text-align: right; + font-size: medium; + font-style: italic; + } + + .disable { + /* color: #e5e5e5; */ + color: gray; + } + + .smallcaps { + font-size: 75%; + } + + .smallprint { + color: gray; + font-size: 75%; + text-align: right; + } + + /* + .smallprint hr { + text-align: left; + width: 40%; + } + */ + + .footnoterule { + text-align: left; + width: 40%; + } + + .colophon { + color: gray; + font-size: 80%; + text-align: right; + } + + .colophon a { + color: gray; + } + + \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference.css new file mode 100644 index 0000000..42e9283 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference.css @@ -0,0 +1,34 @@ +html { margin: 0px; padding: 0px; background-color: #08f; color: #444; font-family: georgia,serif; } +body { margin: 2em 8%; background-color: #fff; padding: 1em; border: 2px ridge #048; } + +a:link, a:visited { text-decoration: none; color: #00f; } +a:hover { color: #f00; text-decoration: underline; } +a:active { color: #f84; } + +h1, h2, h3, h4, h5, h6 { font-family: tahoma,verdana,sans-serif; } + +h2, h3 { font-weight: normal; } + +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover { text-decoration: none; } + +h1 { font-size: 170%; border: 2px ridge #048; letter-spacing: 2px; color: #000; margin-left: -2em; margin-right: -2em; +background-color: #fff; padding: 2px 1em; background-color: #def; } +h2 { font-size: 140%; color: #222; } +h3 { font-size: 120%; color: #444; } + +h1.title { font-size: 300%; font-family: georgia,serif; font-weight: normal; color: #846; letter-spacing: -1px; +border: none; +padding: none; +background-color: #fff; +border-bottom: 3px double #624; padding-bottom: 2px; margin-left: 8%; margin-right: 8%; } + +.colophon { padding-top: 2em; color: #999; font-size: 90%; font-family: georgia,"times new roman",serif; } +.colophon a:link, .colophon a:visited { color: #755; } +.colophon a:hover { color: #422; text-decoration: underline; } + +.footnote { font-size: 90%; font-style: italic; font-family: georgia,"times new roman",serif; margin: 0px 3em; } +.footnote sup { font-size: 120%; padding: 0px 0.3em; position: relative; top: -0.2em; } + +.small { font-size: 90%; } + +.verbatim { background-color: #eee; padding: 0.2em 1em; border: 1px solid #aaa; } diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference.html b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference.html new file mode 100644 index 0000000..4bf6380 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/html/reference.html @@ -0,0 +1,1738 @@ + + + + + +DHTML Calendar Widget + + + + + + +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    +

    + + + + +

    +

    + + +

    +

    +

    +

    +

    +



    DHTML Calendar Widget

    +

    +
    +Mihai Bazon, <mihai_bazon@yahoo.com>
    +© Dynarch.com 2002-2005, www.dynarch.com

    March 7, 2005

    +

    +

    +calendar version: 1.0 ``It is happening again'' +

    +
    +

    +

    +$Id: reference.tex,v 1.23 2005/03/05 11:37:14 mishoo Exp $ +

    +
    +
    + +
    + +
    + +

    Contents

    +

    +

    +    1  Overview
    +        1.1  How does this thing work?
    +        1.2  Project files
    +        1.3  License
    +

    +

    +    2  Quick startup
    +        2.1  Installing a popup calendar
    +        2.2  Installing a flat calendar
    +        2.3  Calendar.setup in detail
    +

    +

    +    3  Recipes
    +        3.1  Popup calendars
    +            3.1.1  Simple text field with calendar attached to a button
    +            3.1.2  Simple field with calendar attached to an image
    +            3.1.3  Hidden field, plain text triggers
    +            3.1.4  2 Linked fields, no trigger buttons
    +        3.2  Flat calendars
    +        3.3  Highlight special dates
    +        3.4  Select multiple dates
    +

    +

    +    4  The Calendar object overview
    +        4.1  Creating a calendar
    +        4.2  Order does matter ;-)
    +        4.3  Caching the object
    +        4.4  Callback functions
    +

    +

    +    5  The Calendar object API reference
    +        5.1  Calendar constructor
    +        5.2  Useful member variables (properties)
    +        5.3  Public methods
    +            5.3.1  Calendar.create
    +            5.3.2  Calendar.callHandler
    +            5.3.3  Calendar.callCloseHandler
    +            5.3.4  Calendar.hide
    +            5.3.5  Calendar.setDateFormat
    +            5.3.6  Calendar.setTtDateFormat
    +            5.3.7  Calendar.setDisabledHandler
    +            5.3.8  Calendar.setDateStatusHandler
    +            5.3.9  Calendar.show
    +            5.3.10  Calendar.showAt
    +            5.3.11  Calendar.showAtElement
    +            5.3.12  Calendar.setDate
    +            5.3.13  Calendar.setFirstDayOfWeek
    +            5.3.14  Calendar.parseDate
    +            5.3.15  Calendar.setRange
    +

    +

    +    6  Side effects
    +

    +

    +    7  Credits
    +

    +

    +

    +

    +

    + +

    1  Overview

    +

    The DHTML Calendar widget1 +is an (HTML) user interface element that gives end-users a friendly way to +select date and time. It works in a web browser. The first versions only provided +support for popup calendars, while starting with version 0.9 it also supports +``flat'' display. A ``flat'' calendar is a calendar that stays visible in the +page all the time. In this mode it could be very useful for ``blog'' pages and +other pages that require the calendar to be always present.

    +

    +The calendar is compatible with most popular browsers nowadays. While it's +created using web standards and it should generally work with any compliant +browser, the following browsers were found to work: Mozilla/Firefox (the +development platform), Netscape 6.0 or better, all other Gecko-based browsers, +Internet Explorer 5.0 or better for Windows2, Opera 73, Konqueror 3.1.2 and Apple Safari for +MacOSX.

    +

    +You can find the latest info and version at the calendar homepage:

    +

    +

    + +

    +

    + +

    1.1  How does this thing work?

    +

    DHTML is not ``another kind of HTML''. It's merely a naming convention. DHTML +refers to the combination of HTML, CSS, JavaScript and DOM. DOM (Document +Object Model) is a set of interfaces that glues the other three together. In +other words, DOM allows dynamic modification of an HTML page through a program. +JavaScript is our programming language, since that's what browsers like. CSS +is a way to make it look good ;-). So all this soup is generically known as +DHTML.

    +

    +Using DOM calls, the program dynamically creates a <table> element +that contains a calendar for the given date and then inserts it in the document +body. Then it shows this table at a specified position. Usually the position +is related to some element in which the date needs to be displayed/entered, +such as an input field.

    +

    +By assigning a certain CSS class to the table we can control the look of the +calendar through an external CSS file; therefore, in order to change the +colors, backgrounds, rollover effects and other stuff, you can only change a +CSS file -- modification of the program itself is not necessary.

    +

    +

    + +

    1.2  Project files

    +

    Here's a description of the project files, excluding documentation and example +files.

    +

    +

    +

      +

      +
    • the main program file (calendar.js). This defines all the logic +behind the calendar widget.

      +

      +

      +
    • the CSS files (calendar-*.css). Loading one of them is +necessary in order to see the calendar as intended.

      +

      +

      +
    • the language definition files (lang/calendar-*.js). They are +plain JavaScript files that contain all texts that are displayed by the +calendar. Loading one of them is necessary.

      +

      +

      +
    • helper functions for quick setup of the calendar +(calendar-setup.js). You can do fine without it, but starting with +version 0.9.3 this is the recommended way to setup a calendar.

      +

      +

      +

    +

    +

    + +

    1.3  License

    +

    +
    + +© Dynarch.com 2002-2005, +www.dynarch.com +Author: Mihai Bazon +
    +

    +The calendar is released under the +GNU Lesser General Public License.

    +

    +

    + +

    2  Quick startup

    +

    +

    +Installing the calendar used to be quite a task until version 0.9.3. Starting +with 0.9.3 I have included the file calendar-setup.js whose goal is to +assist you to setup a popup or flat calendar in minutes. You are +encouraged to modify this file and not calendar.js if you need +extra customization, but you're on your own.

    +

    +First you have to include the needed scripts and style-sheet. Make sure you do +this in your document's <head> section, also make sure you put the +correct paths to the scripts.

    +

    +

    +
    <style type="text/css">@import url(calendar-win2k-1.css);</style>
    +<script type="text/javascript" src="calendar.js"></script>
    +<script type="text/javascript" src="lang/calendar-en.js"></script>
    +<script type="text/javascript" src="calendar-setup.js"></script>
    +

    +

    +

    + +

    2.1  Installing a popup calendar

    +

    +

    +Now suppose you have the following HTML:

    +

    +

    +
    <form ...>
    +  <input type="text" id="data" name="data" />
    +  <button id="trigger">...</button>
    +</form>
    +

    +

    +You want the button to popup a calendar widget when clicked? Just +insert the following code immediately after the HTML form:

    +

    +

    +
    <script type="text/javascript">
    +  Calendar.setup(
    +    {
    +      inputField  : "data",         // ID of the input field
    +      ifFormat    : "%m %d, %Y",    // the date format
    +      button      : "trigger"       // ID of the button
    +    }
    +  );
    +</script>
    +

    +

    +The Calendar.setup function, defined in calendar-setup.js +takes care of ``patching'' the button to display a calendar when clicked. The +calendar is by default in single-click mode and linked with the given input +field, so that when the end-user selects a date it will update the input field +with the date in the given format and close the calendar. If you are a +long-term user of the calendar you probably remember that for doing this you +needed to write a couple functions and add an ``onclick'' handler for the +button by hand.

    +

    +By looking at the example above we can see that the function +Calendar.setup receives only one parameter: a JavaScript object. +Further, that object can have lots of properties that tell to the setup +function how would we like to have the calendar. For instance, if we would +like a calendar that closes at double-click instead of single-click we would +also include the following: singleClick:false.

    +

    +For a list of all supported parameters please see the section +2.3.

    +

    +

    + +

    2.2  Installing a flat calendar

    +

    +

    +Here's how to configure a flat calendar, using the same Calendar.setup +function. First, you should have an empty element with an ID. This element +will act as a container for the calendar. It can be any block-level element, +such as DIV, TABLE, etc. We will use a DIV in this example.

    +

    +

    +
    <div id="calendar-container"></div>
    +

    +

    +Then there is the JavaScript code that sets up the calendar into the +``calendar-container'' DIV. The code can occur anywhere in HTML +after the DIV element.

    +

    +

    +
    <script type="text/javascript">
    +  function dateChanged(calendar) {
    +    // Beware that this function is called even if the end-user only
    +    // changed the month/year.  In order to determine if a date was
    +    // clicked you can use the dateClicked property of the calendar:
    +    if (calendar.dateClicked) {
    +      // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
    +      var y = calendar.date.getFullYear();
    +      var m = calendar.date.getMonth();     // integer, 0..11
    +      var d = calendar.date.getDate();      // integer, 1..31
    +      // redirect...
    +      window.location = "/" + y + "/" + m + "/" + d + "/index.php";
    +    }
    +  };
    +
    +  Calendar.setup(
    +    {
    +      flat         : "calendar-container", // ID of the parent element
    +      flatCallback : dateChanged           // our callback function
    +    }
    +  );
    +</script>
    +

    +

    +

    + +

    2.3  Calendar.setup in detail

    +

    +

    +Following there is the complete list of properties interpreted by +Calendar.setup. All of them have default values, so you can pass only those +which you would like to customize. Anyway, you must pass at least one +of inputField, displayArea or button, for a popup +calendar, or flat for a flat calendar. Otherwise you will get a +warning message saying that there's nothing to setup.

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    property type description default +
    inputField +string The ID of your input field. +null +
    displayArea +string This is the ID of a <span>, <div>, or any other element that you would like to use to display the current date. This is generally useful only if the input field is hidden, as an area to display the date. +null +
    button +string The ID of the calendar ``trigger''. This is an element (ordinarily a button or an image) that will dispatch a certain event (usually ``click'') to the function that creates and displays the calendar. +null +
    eventName +string The name of the event that will trigger the calendar. The name should be without the ``on'' prefix, such as ``click'' instead of ``onclick''. Virtually all users will want to let this have the default value (``click''). Anyway, it could be useful if, say, you want the calendar to appear when the input field is focused and have no trigger button (in this case use ``focus'' as the event name). +``click'' +
    ifFormat +string The format string that will be used to enter the date in the input field. This format will be honored even if the input field is hidden. +``%Y/%m/%d'' +
    daFormat +string Format of the date displayed in the displayArea (if specified). +``%Y/%m/%d'' +
    singleClick +boolean Wether the calendar is in ``single-click mode'' or ``double-click mode''. If true (the default) the calendar will be created in single-click mode. +true +
    disableFunc +function A function that receives a JS Date object. It should return +true if that date has to be disabled, false otherwise. +DEPRECATED (see below). +null +
    dateStatusFunc +function A function that receives a JS Date object and returns a boolean +or a string. This function allows one to set a certain CSS class to some +date, therefore making it look different. If it returns true then +the date will be disabled. If it returns false nothing special +happens with the given date. If it returns a string then that will be taken +as a CSS class and appended to the date element. If this string is +``disabled'' then the date is also disabled (therefore is like returning +true). For more information please also refer to section +5.3.8. +null +
    firstDay +integer Specifies which day is to be displayed as the first day of +week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday, ..., 6 +means Saturday. The end user can easily change this too, by clicking on the +day name in the calendar header. +0 +
    weekNumbers +boolean If ``true'' then the calendar will display week numbers. +true +
    align +string Alignment of the calendar, relative to the reference element. The +reference element is dynamically chosen like this: if a displayArea is +specified then it will be the reference element. Otherwise, the input field +is the reference element. For the meaning of the alignment characters +please section 5.3.11. +``Bl'' +
    range +array An array having exactly 2 elements, integers. (!) The first [0] element is the minimum year that is available, and the second [1] element is the maximum year that the calendar will allow. +[1900, 2999] +
    flat +string If you want a flat calendar, pass the ID of the parent object in +this property. If not, pass null here (or nothing at all as +null is the default value). +null +
    flatCallback +function You should provide this function if the calendar is flat. It +will be called when the date in the calendar is changed with a reference to +the calendar object. See section 2.2 for an example +of how to setup a flat calendar. +null +
    onSelect +function If you provide a function handler here then you have to manage +the ``click-on-date'' event by yourself. Look in the calendar-setup.js and +take as an example the onSelect handler that you can see there. +null +
    onClose +function This handler will be called when the calendar needs to close. +You don't need to provide one, but if you do it's your responsibility to +hide/destroy the calendar. You're on your own. Check the calendar-setup.js +file for an example. +null +
    onUpdate +function If you supply a function handler here, it will be called right +after the target field is updated with a new date. You can use this to +chain 2 calendars, for instance to setup a default date in the second just +after a date was selected in the first. +null +
    date +date This allows you to setup an initial date where the calendar will be +positioned to. If absent then the calendar will open to the today date. +null +
    showsTime +boolean If this is set to true then the calendar will also +allow time selection. +false +
    timeFormat +string Set this to ``12'' or ``24'' to configure the way that the +calendar will display time. +``24'' +
    electric +boolean Set this to ``false'' if you want the calendar to update the +field only when closed (by default it updates the field at each date change, +even if the calendar is not closed) true +
    position +array Specifies the [x, y] position, relative to page's top-left corner, +where the calendar will be displayed. If not passed then the position will +be computed based on the ``align'' parameter. Defaults to ``null'' (not +used). null +
    cache +boolean Set this to ``true'' if you want to cache the calendar object. +This means that a single calendar object will be used for all fields that +require a popup calendar false +
    showOthers +boolean If set to ``true'' then days belonging to months overlapping +with the currently displayed month will also be displayed in the calendar +(but in a ``faded-out'' color) false + +
    + +

    +

    + +

    3  Recipes

    +

    This section presents some common ways to setup a calendar using the +Calendar.setup function detailed in the previous section.

    +

    +We don't discuss here about loading the JS or CSS code -- so make sure you +add the proper <script> and <style> or <link> elements in your +HTML code. Also, when we present input fields, please note that they should +be embedded in some form in order for data to be actually sent to server; we +don't discuss these things here because they are not related to our +calendar.

    +

    +

    + +

    3.1  Popup calendars

    +

    These samples can be found in the file “simple-1.html” from the +calendar package.

    +

    +

    + +

    3.1.1  Simple text field with calendar attached to a button

    +

    +

    +This piece of code will create a calendar for a simple input field with a +button that will open the calendar when clicked.

    +

    +

    +
    <input type="text" name="date" id="f_date_b"
    +       /><button type="reset" id="f_trigger_b"
    +       >...</button>
    +<script type="text/javascript">
    +    Calendar.setup({
    +        inputField     :    "f_date_b",           //*
    +        ifFormat       :    "%m/%d/%Y %I:%M %p",
    +        showsTime      :    true,
    +        button         :    "f_trigger_b",        //*
    +        step           :    1
    +    });
    +</script>
    +

    +

    +Note that this code does more actually; the only required fields are +those marked with “//*” -- that is, the ID of the input field and the ID of +the button need to be passed to Calendar.setup in order for the +calendar to be properly assigned to this input field. As one can easily +guess from the argument names, the other arguments configure a certain date +format, instruct the calendar to also include a time selector and display +every year in the drop-down boxes (the “step” parameter) -- instead of showing +every other year as the default calendar does.

    +

    +

    + +

    3.1.2  Simple field with calendar attached to an image

    +

    Same as the above, but the element that triggers the calendar is this time +an image, not a button.

    +

    +

    +
    <input type="text" name="date" id="f_date_c" readonly="1" />
    +<img src="img.gif" id="f_trigger_c"
    +     style="cursor: pointer; border: 1px solid red;"
    +     title="Date selector"
    +     onmouseover="this.style.background='red';"
    +     onmouseout="this.style.background=''" />
    +<script type="text/javascript">
    +    Calendar.setup({
    +        inputField     :    "f_date_c",
    +        ifFormat       :    "%B %e, %Y",
    +        button         :    "f_trigger_c",
    +        align          :    "Tl",
    +        singleClick    :    false
    +    });
    +</script>
    +

    +

    +Note that the same 2 parameters are required as in the previous case; the +difference is that the 'button' parameter now gets the ID of the image +instead of the ID of the button. But the event is the same: at 'onclick' on +the element that is passed as 'button', the calendar will be shown.

    +

    +The above code additionally sets an alignment mode -- the parameters are +described in 5.3.11.

    +

    +

    + +

    3.1.3  Hidden field, plain text triggers

    +

    Sometimes, to assure that the date is well formatted, you might want not to +allow the end user to write a date manually. This can easily be achieved +with an input field by setting its readonly attribute, which is +defined by the HTML4 standard; however, here's an even nicer approach: our +calendar widget allows you to use a hidden field as the way to pass data to +server, and a “display area” to show the end user the selected date. The +“display area” can be any HTML element, such as a DIV or a SPAN or +whatever -- we will use a SPAN in our sample.

    +

    +

    +
    <input type="hidden" name="date" id="f_date_d" />
    +
    +<p>Your birthday:
    +   <span style="background-color: #ff8; cursor: default;"
    +         onmouseover="this.style.backgroundColor='#ff0';"
    +         onmouseout="this.style.backgroundColor='#ff8';"
    +         id="show_d"
    +   >Click to open date selector</span>.</p>
    +
    +<script type="text/javascript">
    +    Calendar.setup({
    +        inputField     :    "f_date_d",
    +        ifFormat       :    "%Y/%d/%m",
    +        displayArea    :    "show_d",
    +        daFormat       :    "%A, %B %d, %Y",
    +    });
    +</script>
    +

    +

    +The above code will configure a calendar attached to the hidden field and to +the SPAN having the id=“show_d”. When the SPAN element is clicked, the +calendar opens and allows the end user to chose a date. When the date is +chosen, the input field will be updated with the value in the format +“%Y/%d/%m”, and the SPAN element will display the date in a +friendlier format (defined by “daFormat”).

    +

    +Beware that using this approach will make your page unfunctional in browsers +that do not support JavaScript or our calendar.

    +

    +

    + +

    3.1.4  2 Linked fields, no trigger buttons

    +

    Supposing you want to create 2 fields that hold an interval of exactly one +week. The first is the starting date, and the second is the ending date. +You want the fields to be automatically updated when some date is clicked in +one or the other, in order to keep exactly one week difference between them.

    +

    +

    +
    <input type="text" name="date" id="f_date_a" />
    +<input type="text" name="date" id="f_calcdate" />
    +
    +<script type="text/javascript">
    +    function catcalc(cal) {
    +        var date = cal.date;
    +        var time = date.getTime()
    +        // use the _other_ field
    +        var field = document.getElementById("f_calcdate");
    +        if (field == cal.params.inputField) {
    +            field = document.getElementById("f_date_a");
    +            time -= Date.WEEK; // substract one week
    +        } else {
    +            time += Date.WEEK; // add one week
    +        }
    +        var date2 = new Date(time);
    +        field.value = date2.print("%Y-%m-%d %H:%M");
    +    }
    +    Calendar.setup({
    +        inputField     :    "f_date_a",
    +        ifFormat       :    "%Y-%m-%d %H:%M",
    +        showsTime      :    true,
    +        timeFormat     :    "24",
    +        onUpdate       :    catcalc
    +    });
    +    Calendar.setup({
    +        inputField     :    "f_calcdate",
    +        ifFormat       :    "%Y-%m-%d %H:%M",
    +        showsTime      :    true,
    +        timeFormat     :    "24",
    +        onUpdate       :    catcalc
    +    });
    +</script>
    +

    +

    +The above code will configure 2 input fields with calendars attached, as +usual. The first thing to note is that there's no trigger button -- in such +case, the calendar will popup when one clicks into the input field. Using +the onUpdate parameter, we pass a reference to a function of ours +that will get called after a date was selected. In that function we +determine what field was updated and we compute the date in the other input +field such that it keeps a one week difference between the two. Enjoy! :-)

    +

    +

    + +

    3.2  Flat calendars

    +

    This sample can be found in “simple-2.html”. It will configure a +flat calendar that is always displayed in the page, in the DIV having the +id=“calendar-container”. When a date is clicked our function hander gets +called (dateChanged) and it will compute an URL to jump to based on +the selected date, then use window.location to visit the new link.

    +

    +

    +
    <div style="float: right; margin-left: 1em; margin-bottom: 1em;"
    +id="calendar-container"></div>
    +
    +<script type="text/javascript">
    +  function dateChanged(calendar) {
    +    // Beware that this function is called even if the end-user only
    +    // changed the month/year.  In order to determine if a date was
    +    // clicked you can use the dateClicked property of the calendar:
    +    if (calendar.dateClicked) {
    +      // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
    +      var y = calendar.date.getFullYear();
    +      var m = calendar.date.getMonth();     // integer, 0..11
    +      var d = calendar.date.getDate();      // integer, 1..31
    +      // redirect...
    +      window.location = "/" + y + "/" + m + "/" + d + "/index.php";
    +    }
    +  };
    +
    +  Calendar.setup(
    +    {
    +      flat         : "calendar-container", // ID of the parent element
    +      flatCallback : dateChanged           // our callback function
    +    }
    +  );
    +</script>
    +

    +

    +

    + +

    3.3  Highlight special dates

    +

    So you want to display certain dates in a different color, or with bold +font, or whatever, right? Well, no problem -- our calendar can do this as +well. It doesn't matter if it's a flat or popup calendar -- we'll use a flat +one for this sample. The idea, however, is that you need to have the dates +in an array or a JavaScript object -- whatever is suitable for your way of +thinking -- and use it from a function that returns a value, telling the +calendar what kind of date is the passed one.

    +

    +Too much talking, here's the code ;-)

    +

    +

    +
    <!-- this goes into the <head> tag -->
    +<style type="text/css">
    +  .special { background-color: #000; color: #fff; }
    +</style>
    +
    +<!-- and the rest inside the <body> -->
    +<div style="float: right; margin-left: 1em; margin-bottom: 1em;"
    +id="calendar-container"></div>
    +
    +<script type="text/javascript">
    +  var SPECIAL_DAYS = {
    +    0 : [ 13, 24 ],		// special days in January
    +    2 : [ 1, 6, 8, 12, 18 ],	// special days in March
    +    8 : [ 21, 11 ]		// special days in September
    +  };
    +
    +  function dateIsSpecial(year, month, day) {
    +    var m = SPECIAL_DAYS[month];
    +    if (!m) return false;
    +    for (var i in m) if (m[i] == day) return true;
    +    return false;
    +  };
    +
    +  function dateChanged(calendar) {
    +    // Beware that this function is called even if the end-user only
    +    // changed the month/year.  In order to determine if a date was
    +    // clicked you can use the dateClicked property of the calendar:
    +    if (calendar.dateClicked) {
    +      // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
    +      var y = calendar.date.getFullYear();
    +      var m = calendar.date.getMonth();     // integer, 0..11
    +      var d = calendar.date.getDate();      // integer, 1..31
    +      // redirect...
    +      window.location = "/" + y + "/" + m + "/" + d + "/index.php";
    +    }
    +  };
    +
    +  function ourDateStatusFunc(date, y, m, d) {
    +    if (dateIsSpecial(y, m, d))
    +      return "special";
    +    else
    +      return false; // other dates are enabled
    +      // return true if you want to disable other dates
    +  };
    +
    +  Calendar.setup(
    +    {
    +      flat         : "calendar-container", // ID of the parent element
    +      flatCallback : dateChanged,          // our callback function
    +      dateStatusFunc : ourDateStatusFunc
    +    }
    +  );
    +</script>
    +

    +

    +So the above code creates a normal flat calendar, like in the previous +sample. We hook into it with the function “ourDateStatusFunc”, +which receives a date object as the first argument, and also the year, +month, date as the next 3 arguments (normally, you can extract year, month, +date from the first parameter too, but we pass them separately for +convenience, as it's very likely that they are going to be used in this +function).

    +

    +So, this function receives a date. It can return false if you want +no special action to be taken on that date, true if that date +should be disabled (unselectable), or a string if you want to assign a +special CSS class to that date. We return “special” for the dates that we +want to highlight -- and note that we defined a “special” look for them in +the CSS section.

    +

    +I used a simple approach here to define what dates are special. There's a +JavaScript object (the SPECIAL_DAYS global variable) which holds an array +of dates for each month. Month numbers start at zero (January). Months +that don't contain special dates can be absent from this object. Note that +the way to implement this is completely separated from the calendar +code -- therefore, feel free to use your imagination if you have better +ideas. :-)

    +

    +

    + +

    3.4  Select multiple dates

    +

    Starting version 1.0, the calendar is able to handle multiple dates +selection. You just need to pass the “multiple” parameter to +Calendar.setup and add some special code that interprets the +selection once the calendar is closed.

    +

    +

    +
    <a id="trigger" href="#">[open calendar...]</a>
    +<div id="output"></div>
    +<script type="text/javascript">//<![CDATA[
    +    // the default multiple dates selected,
    +    // first time the calendar is displayed
    +    var MA = [];
    +
    +    function closed(cal) {
    +
    +      // here we'll write the output; this is only for example.  You
    +      // will normally fill an input field or something with the dates.
    +      var el = document.getElementById("output");
    +
    +      // reset initial content.
    +      el.innerHTML = "";
    +
    +      // Reset the "MA", in case one triggers the calendar again.
    +      // CAREFUL!  You don't want to do "MA = [];".  We need to modify
    +      // the value of the current array, instead of creating a new one.
    +      // Calendar.setup is called only once! :-)  So be careful.
    +      MA.length = 0;
    +
    +      // walk the calendar's multiple dates selection hash
    +      for (var i in cal.multiple) {
    +        var d = cal.multiple[i];
    +        // sometimes the date is not actually selected,
    +        // so let's check
    +        if (d) {
    +          // OK, selected.  Fill an input field or something.
    +          el.innerHTML += d.print("%A, %Y %B %d") + "<br />";
    +          // and push it in the "MA", in case one triggers the calendar again.
    +          MA[MA.length] = d;
    +        }
    +      }
    +      cal.hide();
    +      return true;
    +    };
    +
    +    Calendar.setup({
    +      align      : "BR",
    +      showOthers : true,
    +      multiple   : MA, // pass the initial or computed array of multiple dates
    +      onClose    : closed,
    +      button     : "trigger"
    +    });
    +//]]></script>
    +

    +

    +The above code creates a popup calendar and passes to it an array of dates, +which is initially empty, in the “multiple” argument. When the calendar is +closed it will call our “closed” function handler; in this handler +we determine what dates were actually selected, inspecting the +“cal.multiple” property, we display them in a DIV element right +next to the <a> element that opens the calendar, and we reinitialize the +global array of selected dates (which will be used if the end user opens the +calendar again). I guess the code speaks for itself, right? :-)

    +

    +

    + +

    4  The Calendar object overview

    +

    +

    +Basically you should be able to setup the calendar with the function presented +in the previous section. However, if for some reason Calendar.setup +doesn't provide all the functionality that you need and you want to tweak into +the process of creating and configuring the calendar ``by hand'', then this +section is the way to go.

    +

    +The file calendar.js implements the functionality of the calendar. +All (well, almost all) functions and variables are embedded in the JavaScript +object ``Calendar''.

    +

    +You can instantiate a Calendar object by calling the constructor, like +this: var cal = new Calendar(...). We will discuss the parameters +later. After creating the object, the variable cal will contain a +reference to it. You can use this reference to access further options of the +calendar, for instance:

    +

    +

    +
    cal.weekNumbers = false; // do not display week numbers
    +cal.showsTime = true;    // include a time selector
    +cal.setDateFormat("%Y.%m.%d %H:%M"); // set this format: 2003.12.31 23:59
    +cal.setDisabledHandler(function(date, year, month, day) {
    +  // verify date and return true if it has to be disabled
    +  // ``date'' is a JS Date object, but if you only need the
    +  // year, month and/or day you can get them separately as
    +  // next 3 parameters, as you can see in the declaration
    +  if (year == 2004) {
    +    // disable all dates from 2004
    +    return true;
    +  }
    +  return false;
    +});
    +

    +

    +etc. Prior to version +0.9.3 this was the only way to configure it. The Calendar.setup +function, documented in section 2, basically does the same +things (actually more) in order to setup the calendar, based on the parameters +that you provided.

    +

    +

    + +

    4.1  Creating a calendar

    +

    The calendar is created by following some steps (even the function +Calendar.setup, described in section 2, does the +same). While you can skip optional (marked ``opt'') steps if you're happy with +the defaults, please respect the order below.

    +

    +

    +

      +

      +
    1. Instantiate a Calendar object. Details about this in +section 5.1.

      +

      +

      +
    2. opt   Set the weekNumbers property to false if you don't want +the calendar to display week numbers.

      +

      +

      +
    3. opt   Set the showsTime property to true if you +want the calendar to also provide a time selector.

      +

      +

      +
    4. opt   Set the time24 property to false if you want +the time selector to be in 12-hour format. Default is 24-hour format. This +property only has effect if you also set showsTime to +true.

      +

      +

      +
    5. opt   Set the range of years available for selection (see section +5.3.15). The default range is [1970..2050].

      +

      +

      +
    6. opt   Set the getDateStatus property. You should pass +here a function that receives a JavaScript Date object and returns +true if the given date should be disabled, false otherwise (details in +section 5.3.7).

      +

      +

      +
    7. opt   Set a date format. Your handler function, passed to the +calendar constructor, will be called when a date is selected with a reference +to the calendar and a date string in this format.

      +

      +

      +
    8. Create the HTML elements related to the calendar. This step +practically puts the calendar in your HTML page. You simply call +Calendar.create(). You can give an optional parameter if you wanna +create a flat calendar (details in section 5.3.1).

      +

      +

      +
    9. opt   Initialize the calendar to a certain date, for instance from +the input field.

      +

      +

      +
    10. Show the calendar (details in section 5.3.9).

      +

      +

      +

    +

    +

    + +

    4.2  Order does matter ;-)

    +

    As you could see in the previous section, there are more steps to be followed +in order to setup the calendar. This happens because there are two different +things that need to be accomplished: first there is the JavaScript object, that +is created with new Calendar(...). Secondly there are the HTML +elements that actually lets you see and manipulate the calendar.

    +

    +

    +[ Those that did UI4 programming, no matter in what +language and on what platform, may be familiar with this concept. First there +is the object in memory that lets you manipulate the UI element, and secondly +there is the UI element (known as ``control'', ``window'', ``widget'', etc.), +also in memory but you don't usually access it directly. ] +

    +By instantiating the calendar we create the JavaScript object. It lets us +configure some properties and it also knows how to create the UI element (the +HTML elements actually) that will eventually be what the end-user sees on +screen. Creation of the HTML element is accomplished by the function +Calendar.create. It knows how to create popup or flat calendars. +This function is described in section 5.3.1.

    +

    +Some properties need to be set prior to creating the HTML elements, because +otherwise they wouldn't have any effect. Such a property is +weekNumbers -- it has the default value ``true'', and if you don't +want the calendar to display the week numbers you have to set it to false. If, +however, you do that after calling Calendar.create the calendar +would still display the week numbers, because the HTML elements are already +created (including the <td>-s in the <table> element that +should contain the week numbers). For this reason the order of the steps above +is important.

    +

    +Another example is when you want to show the calendar. The ``create'' function +does create the HTML elements, but they are initially hidden (have the style +``display: none'') unless the calendar is a flat calendar that should be always +visible in the page. Obviously, the Calendar.show function should be +called after calling Calendar.create.

    +

    +

    + +

    4.3  Caching the object

    +

    Suppose the end-user has popped up a calendar and selects a date. The calendar +then closes. What really happens now?

    +

    +There are two approaches. The first (used in very old versions of the +calendar) was to drop completely the Calendar object and when the end-user pops +up the calendar again to create another one. This approach is bad for more +reasons:

    +

    +

    +

      +

      +
    • creating the JavaScript object and HTML elements is time-consuming

      +

      +

      +
    • we may loose some end-user preferences (i.e. he might prefer to have +Monday for the first day of week and probably already clicked it the first time +when the calendar was opened, but now he has to do it again)

      +

      +

      +

    +

    +The second approach, implemented by the Calendar.setup function, is to +cache the JavaScript object. It does this by checking the global variable +window.calendar and if it is not null it assumes it is the created +Calendar object. When the end-user closes the calendar, our code will only +call ``hide'' on it, therefore keeping the JavaScript object and the +HTML elements in place.

    +

    +CAVEAT:     Since time selection support was introduced, this +``object caching'' mechanism has the following drawback: if you once created +the calendar with the time selection support, then other items that may not +require this functionality will still get a calendar with the time selection +support enabled. And reciprocal. ;-) Hopefully this will be corrected in a +later version, but for now it doesn't seem such a big problem.

    +

    +

    + +

    4.4  Callback functions

    +

    You might rightfully wonder how is the calendar related to the input field? +Who tells it that it has to update that input field when a date is +selected, or that it has to jump to that URL when a date is clicked in +flat mode?

    +

    +All this magic is done through callback functions. The calendar doesn't know +anything about the existence of an input field, nor does it know where to +redirect the browser when a date is clicked in flat mode. It just calls your +callback when a particular event is happening, and you're responsible to handle +it from there. For a general purpose library I think this is the best model of +making a truly reusable thing.

    +

    +The calendar supports the following user callbacks:

    +

    +

    +

      +

      +
    • onSelect   -- this gets called when the end-user changes the date in the +calendar. Documented in section 5.1.

      +

      +

      +
    • onClose   -- this gets called when the calendar should close. It's +user's responsibility to close the calendar. Details in section +5.1.

      +

      +

      +
    • getDateStatus   -- this function gets called for any day in a month, +just before displaying the month. It is called with a JavaScript Date +object and should return true if that date should be disabled, false +if it's an ordinary date and no action should be taken, or it can return a +string in which case the returned value will be appended to the element's CSS +class (this way it provides a powerful way to make some dates ``special'', +i.e. highlight them differently). Details in section +5.3.8.

      +

      +

      +

    +

    +

    + +

    5  The Calendar object API reference

    +

    +

    +

    + +

    5.1  Calendar constructor

    +

    +

    +Synopsis:

    +

    +

    +
    var calendar = Calendar(firstDayOfWeek, date, onSelect, onClose);
    +

    +

    +Parameters are as follows:

    +

    +

    +

      +

      +
    • firstDayOfWeek   -- specifies which day is to be displayed as the first +day of week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday, +..., 6 means Saturday.

      +

      +

      +
    • date   -- a JavaScript Date object or null. If null +is passed then the calendar will default to today date. Otherwise it will +initialize on the given date.

      +

      +

      +
    • onSelect   -- your callback for the ``onChange'' event. See above.

      +

      +

      +
    • onClose   -- your callback for the ``onClose'' event. See above.

      +

      +

      +

    +

    +

    + +

    The onSelect event

    +

    +

    +Here is a typical implementation of this function:

    +

    +

    +
    function onSelect(calendar, date) {
    +  var input_field = document.getElementById("date");
    +  input_field.value = date;
    +};
    +

    +

    +date is in the format selected with calendar.setDateFormat +(see section 5.3.5). This code simply updates the +input field. If you want the calendar to be in single-click mode then you +should also close the calendar after you updated the input field, so we come to +the following version:

    +

    +

    +
    function onSelect(calendar, date) {
    +  var input_field = document.getElementById("date");
    +  input_field.value = date;
    +  if (calendar.dateClicked) {
    +    calendar.callCloseHandler(); // this calls "onClose" (see above)
    +  }
    +};
    +

    +

    +Note that we checked the member variable dateClicked and +only hide the calendar if it's true. If this variable is false it +means that no date was actually selected, but the user only changed the +month/year using the navigation buttons or the menus. We don't want to hide +the calendar in that case.

    +

    +

    + +

    The onClose event

    +

    +

    +This event is triggered when the calendar should close. It should hide or +destroy the calendar object -- the calendar itself just triggers the event, but +it won't close itself.

    +

    +A typical implementation of this function is the following:

    +

    +

    +
    function onClose(calendar) {
    +  calendar.hide();
    +  // or calendar.destroy();
    +};
    +

    +

    +

    + +

    5.2  Useful member variables (properties)

    +

    +

    +After creating the Calendar object you can access the following properties:

    +

    +

    +

      +

      +
    • date -- is a JavaScript Date object. It will always +reflect the date shown in the calendar (yes, even if the calendar is hidden).

      +

      +

      +
    • isPopup -- if this is true then the current Calendar object is +a popup calendar. Otherwise (false) we have a flat calendar. This variable is +set from Calendar.create and has no meaning before this function was +called.

      +

      +

      +
    • dateClicked -- particularly useful in the onSelect +handler, this variable tells us if a date was really clicked. That's because +the onSelect handler is called even if the end-user only changed the +month/year but did not select a date. We don't want to close the calendar in +that case.

      +

      +

      +
    • weekNumbers -- if true (default) then the calendar +displays week numbers. If you don't want week numbers you have to set this +variable to false before calling Calendar.create.

      +

      +

      +
    • showsTime - if you set this to true (it is +false by default) then the calendar will also include a time selector.

      +

      +

      +
    • time24 - if you set this to false then the time +selector will be in 12-hour format. It is in 24-hour format by default.

      +

      +

      +
    • firstDayOfWeek -- specifies the first day of week (0 to 6, pass +0 for Sunday, 1 for Monday, ..., 6 for Saturday). This variable is set from +constructor, but you still have a chance to modify it before calling +Calendar.create.

      +

      +

      +

    +

    +There are lots of other member variables, but one should access them only +through member functions so I won't document them here.

    +

    +

    + +

    5.3  Public methods

    +

    + +

    5.3.1  Calendar.create

    +

    +

    +This function creates the afferent HTML elements that are needed to display the +calendar. You should call it after setting the calendar properties. Synopsis: +

    +
    calendar.create(); // creates a popup calendar
    +  // -- or --
    +calendar.create(document.getElementById(parent_id)); // makes a flat calendar
    +

    +

    +It can create a popup calendar or a flat calendar. If the ``parent'' argument +is present (it should be a reference -- not ID -- to an HTML element) then +a flat calendar is created and it is inserted in the given element.

    +

    +At any moment, given a reference to a calendar object, we can inspect if it's a +popup or a flat calendar by checking the boolean member variable +isPopup:

    +

    +

    +
    if (calendar.isPopup) {
    +   // this is a popup calendar
    +} else {
    +   // this is a flat calendar
    +}
    +

    +

    +

    + +

    5.3.2  Calendar.callHandler

    +

    +

    +This function calls the first user callback (the +onSelect handler) with the required parameters.

    +

    +

    + +

    5.3.3  Calendar.callCloseHandler

    +

    +

    +This function calls the second user callback (the +onClose handler). It's useful when you want to have a +``single-click'' calendar -- just call this in your onSelect handler, +if a date was clicked.

    +

    +

    + +

    5.3.4  Calendar.hide

    +

    +

    +Call this function to hide the calendar. The calendar object and HTML elements +will not be destroyed, thus you can later call one of the show +functions on the same element.

    +

    +

    + +

    5.3.5  Calendar.setDateFormat

    +

    +

    +This function configures the format in which the calendar reports the date to +your ``onSelect'' handler. Call it like this:

    +

    +

    +
    calendar.setDateFormat("%y/%m/%d");
    +

    +

    +As you can see, it receives only one parameter, the required format. The magic +characters are the following:

    +

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    %a abbreviated weekday name
    %A full weekday name
    %b abbreviated month name
    %B full month name
    %C century number
    %d the day of the month ( 00 .. 31 )
    %e the day of the month ( 0 .. 31 )
    %H hour ( 00 .. 23 )
    %I hour ( 01 .. 12 )
    %j day of the year ( 000 .. 366 )
    %k hour ( 0 .. 23 )
    %l hour ( 1 .. 12 )
    %m month ( 01 .. 12 )
    %M minute ( 00 .. 59 )
    %n a newline character
    %p ``PM'' or ``AM''
    %P ``pm'' or ``am''
    %S second ( 00 .. 59 )
    %s number of seconds since Epoch (since Jan 01 1970 00:00:00 UTC)
    %t a tab character
    %U, %W, %V the week number
    %u the day of the week ( 1 .. 7, 1 = MON )
    %w the day of the week ( 0 .. 6, 0 = SUN )
    %y year without the century ( 00 .. 99 )
    %Y year including the century ( ex. 1979 )
    %% a literal % character +

    +There are more algorithms for computing the week number. All +three specifiers currently implement the same one, as defined by ISO 8601: +``the week 01 is the week that has the Thursday in the current year, which is +equivalent to the week that contains the fourth day of January. Weeks start on +Monday.''

    +

    +

    + +

    5.3.6  Calendar.setTtDateFormat

    +

    +

    +Has the same prototype as Calendar.setDateFormat, but refers to the +format of the date displayed in the ``status bar'' when the mouse is over some +date.

    +

    +

    + +

    5.3.7  Calendar.setDisabledHandler

    +

    +

    +This function allows you to specify a callback function that checks if a +certain date must be disabled by the calendar. You are responsible to write +the callback function. Synopsis:

    +

    +

    +
    function disallowDate(date) {
    +  // date is a JS Date object
    +  if (  date.getFullYear() == 2003 &&
    +        date.getMonth()    == 6 /* July, it's zero-based */ &&
    +        date.getDate()     == 5  ) {
    +    return true; // disable July 5 2003
    +  }
    +  return false; // enable other dates
    +};
    +
    +calendar.setDisabledHandler(disallowDate);
    +

    +

    +If you change this function in ``real-time'', meaning, without creating a new +calendar, then you have to call calendar.refresh() to make it +redisplay the month and take into account the new disabledHandler. +Calendar.setup does this, so you have no such trouble with it.

    +

    +Note that disallowDate should be very fast, as it is called for each +date in the month. Thus, it gets called, say, 30 times before displaying the +calendar, and 30 times when the month is changed. Tests I've done so far show +that it's still good, but in the future I might switch it to a different design +(for instance, to call it once per month and to return an array of dates that +must be disabled).

    +

    +This function should be considered deprecated in the favor of +Calendar.setDateStatusHandler, described below.

    +

    +

    + +

    5.3.8  Calendar.setDateStatusHandler

    +

    +

    +This function obsoletes Calendar.setDisabledHandler. You call it with +a function parameter, but this function can return a boolean +or a string. If the return value is a boolean (true or +false) then it behaves just like setDisabledHandler, +therefore disabling the date if the return value is true.

    +

    +If the returned value is a string then the given date will gain an additional +CSS class, namely the returned value. You can use this to highlight some dates +in some way. Note that you are responsible for defining the CSS class that you +return. If you return the string ``disabled'' then that date will be disabled, +just as if you returned true.

    +

    +Here is a simple scenario that shows what you can do with this function. The +following should be present in some of your styles, or in the document head in +a STYLE tag (but put it after the place where the calendar styles were +loaded):

    +

    +

    +
    .special { background-color: #000; color: #fff; }
    +

    +

    +And you would use the following code before calling Calendar.create():

    +

    +

    +
    // this table holds your special days, so that we can automatize
    +// things a bit:
    +var SPECIAL_DAYS = {
    +    0 : [ 13, 24 ],             // special days in January
    +    2 : [ 1, 6, 8, 12, 18 ],    // special days in March
    +    8 : [ 21, 11 ],             // special days in September
    +   11 : [ 25, 28 ]              // special days in December
    +};
    +
    +// this function returns true if the passed date is special
    +function dateIsSpecial(year, month, day) {
    +    var m = SPECIAL_DAYS[month];
    +    if (!m) return false;
    +    for (var i in m) if (m[i] == day) return true;
    +    return false;
    +}
    +
    +// this is the actual date status handler.  Note that it receives the
    +// date object as well as separate values of year, month and date, for
    +// your confort.
    +function dateStatusHandler(date, y, m, d) {
    +    if (dateIsSpecial(y, m, d)) return ``special'';
    +    else return false;
    +    // return true above if you want to disable other dates
    +}
    +
    +// configure it to the calendar
    +calendar.setDateStatusHandler(dateStatusHandler);
    +

    +

    +The above code adds the ``special'' class name to some dates that are defined +in the SPECIAL_DAYS table. Other dates will simply be displayed as default, +enabled.

    +

    +

    + +

    5.3.9  Calendar.show

    +

    +

    +Call this function do show the calendar. It basically sets the CSS ``display'' +property to ``block''. It doesn't modify the calendar position.

    +

    +This function only makes sense when the calendar is in popup mode.

    +

    +

    + +

    5.3.10  Calendar.showAt

    +

    +

    +Call this to show the calendar at a certain (x, y) position. Prototype:

    +

    +

    +
    calendar.showAt(x, y);
    +

    +

    +The parameters are absolute coordinates relative to the top left +corner of the page, thus they are page coordinates not screen +coordinates.

    +

    +After setting the given coordinates it calls Calendar.show. This function only +makes sense when the calendar is in popup mode.

    +

    +

    + +

    5.3.11  Calendar.showAtElement

    +

    +

    +This function is useful if you want to display the calendar near some element. +You call it like this:

    +

    +

    +
    calendar.showAtElement(element, align);
    +

    +

    +where element is a reference to your element (for instance it can be the input +field that displays the date) and align is an optional parameter, of type string, +containing one or two characters. For instance, if you pass "Br" as +align, the calendar will appear below the element and with its right +margin continuing the element's right margin.

    +

    +As stated above, align may contain one or two characters. The first character +dictates the vertical alignment, relative to the element, and the second +character dictates the horizontal alignment. If the second character is +missing it will be assumed "l" (the left margin of the calendar will +be at the same horizontal position as the left margin of the element).

    +

    +The characters given for the align parameters are case sensitive. This +function only makes sense when the calendar is in popup mode. After computing +the position it uses Calendar.showAt to display the calendar there.

    +

    +

    + +

    Vertical alignment

    +

    The first character in ``align'' can take one of the following values:

    +

    +

    +

      +

      +
    • T -- completely above the reference element (bottom margin of +the calendar aligned to the top margin of the element).

      +

      +

      +
    • t -- above the element but may overlap it (bottom margin of the calendar aligned to +the bottom margin of the element).

      +

      +

      +
    • c -- the calendar displays vertically centered to the reference +element. It might overlap it (that depends on the horizontal alignment).

      +

      +

      +
    • b -- below the element but may overlap it (top margin of the calendar aligned to +the top margin of the element).

      +

      +

      +
    • B -- completely below the element (top margin of the calendar +aligned to the bottom margin of the element).

      +

      +

      +

    +

    +

    + +

    Horizontal alignment

    +

    The second character in ``align'' can take one of the following values:

    +

    +

    +

      +

      +
    • L -- completely to the left of the reference element (right +margin of the calendar aligned to the left margin of the element).

      +

      +

      +
    • l -- to the left of the element but may overlap it (left margin +of the calendar aligned to the left margin of the element).

      +

      +

      +
    • c -- horizontally centered to the element. Might overlap it, +depending on the vertical alignment.

      +

      +

      +
    • r -- to the right of the element but may overlap it (right +margin of the calendar aligned to the right margin of the element).

      +

      +

      +
    • R -- completely to the right of the element (left margin of the +calendar aligned to the right margin of the element).

      +

      +

      +

    +

    +

    + +

    Default values

    +

    If the ``align'' parameter is missing the calendar will choose +``Br''.

    +

    +

    + +

    5.3.12  Calendar.setDate

    +

    +

    +Receives a JavaScript Date object. Sets the given date in the +calendar. If the calendar is visible the new date is displayed immediately.

    +

    +

    +
    calendar.setDate(new Date()); // go today
    +

    +

    +

    + +

    5.3.13  Calendar.setFirstDayOfWeek

    +

    +

    +Changes the first day of week. The parameter has to be a numeric value ranging +from 0 to 6. Pass 0 for Sunday, 1 for Monday, ..., 6 for Saturday.

    +

    +

    +
    calendar.setFirstDayOfWeek(5); // start weeks on Friday
    +

    +

    +

    + +

    5.3.14  Calendar.parseDate

    +

    +

    +Use this function to parse a date given as string and to move the calendar to +that date.

    +

    +The algorithm tries to parse the date according to the format that was +previously set with Calendar.setDateFormat; if that fails, it still +tries to get some valid date out of it (it doesn't read your thoughts, though).

    +

    +

    +
    calendar.parseDate("2003/07/06");
    +

    +

    +

    + +

    5.3.15  Calendar.setRange

    +

    +

    +Sets the range of years that are allowed in the calendar. Synopsis:

    +

    +

    +
    calendar.setRange(1970, 2050);
    +

    +

    +

    + +

    6  Side effects

    +

    The calendar code was intentionally embedded in an object to make it have as +less as possible side effects. However, there are some -- not harmful, after +all. Here is a list of side effects; you can count they already happened after +calendar.js was loaded.

    +

    +

    +

      +

      +
    1. The global variable window.calendar will be set to null. This +variable is used by the calendar code, especially when doing drag & drop for +moving the calendar. In the future I might get rid of it, but for now it +didn't harm anyone.

      +

      +

      +
    2. The JavaScript Date object is modified. We add some properties +and functions that are very useful to our calendar. It made more sense to add +them directly to the Date object than to the calendar itself. +Complete list:

      +

      +

      +

        +

        +
      1. Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); +

        +
      2. Date.SECOND = 1000 /* milliseconds */; +

        +
      3. Date.MINUTE = 60 * Date.SECOND; +

        +
      4. Date.HOUR = 60 * Date.MINUTE; +

        +
      5. Date.DAY = 24 * Date.HOUR; +

        +
      6. Date.WEEK = 7 * Date.DAY;

        +

        +

        +
      7. Date.prototype.getMonthDays(month) -- returns the number of days +of the given month, or of the current date object if no month was given.

        +

        +

        +
      8. Date.prototype.getWeekNumber() -- returns the week number of the +date in the current object.

        +

        +

        +
      9. Date.prototype.equalsTo(other_date) -- compare the current date +object with other_date and returns true if the dates are +equal. It ignores time.

        +

        +

        +
      10. Date.prototype.print(format) -- returns a string with the +current date object represented in the given format. It implements the format +specified in section 5.3.5.

        +

        +

        +

      +

      +

      +

    +

    +

    + +

    7  Credits

    +

    The following people either sponsored, donated money to the project or bought +commercial licenses (listed in reverse chronological order). Your name could +be here too! If you wish to sponsor the project (for instance request a +feature and pay me for implementing it) or donate some money please +please contact me at mihai_bazon@yahoo.com.

    +

    +

    +

    +

    +

    +
    + +Thank you!
    + -- mihai_bazon@yahoo.com +
    +

    +

    +

    +

    1 +by the term ``widget'' I understand a single element of user interface. +But that's in Linux world. For those that did lots of Windows +programming the term ``control'' might be more familiar +

    +

    2 people report that the calendar does +not work with IE5/Mac. However, this browser was discontinued and we +believe that supporting it doesn't worth the efforts, given the fact that +it has the worst, buggiest implementation for DOM I've ever seen.

    +

    3 under Opera 7 the calendar still lacks some functionality, such as +keyboard navigation; also Opera doesn't seem to allow disabling text +selection when one drags the mouse on the page; despite all that, the +calendar is still highly functional under Opera 7 and looks as good as +in other supported browsers.

    +

    4 user interface

    +
    +
    +Last modified: Saturday, March 5th, 2005
    +HTML conversion by TeX2page 2004-09-11
    +
    + + diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/reference.pdf b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/reference.pdf new file mode 100644 index 0000000..a09497f Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/reference.pdf differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon1.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon1.gif new file mode 100755 index 0000000..cb9aee7 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon1.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon2.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon2.gif new file mode 100644 index 0000000..8680b8f Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon2.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon3.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon3.gif new file mode 100644 index 0000000..a549652 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon3.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon4.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon4.gif new file mode 100755 index 0000000..d7538cb Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon4.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow.gif new file mode 100755 index 0000000..ed2dee0 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow2.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow2.gif new file mode 100644 index 0000000..40c0aad Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow2.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/pixel.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/pixel.gif new file mode 100755 index 0000000..b740647 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/pixel.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-en.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-en.js new file mode 100755 index 0000000..0dbde79 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-en.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + +// full month names +Calendar._MN = new Array +("January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "About the calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; +Calendar._TT["GO_TODAY"] = "Go Today"; +Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Select date"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; +Calendar._TT["PART_TODAY"] = " (today)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Display %s first"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; +Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Time:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup.js new file mode 100755 index 0000000..f2b4854 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup.js @@ -0,0 +1,200 @@ +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * --------------------------------------------------------------------------- + * + * The DHTML Calendar + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + * + * This file defines helper functions for setting up the calendar. They are + * intended to help non-programmers get a working calendar on their site + * quickly. This script should not be seen as part of the calendar. It just + * shows you what one can do with the calendar, while in the same time + * providing a quick and simple method for setting it up. If you need + * exhaustive customization of the calendar creation process feel free to + * modify this code to suit your needs (this is recommended and much better + * than modifying calendar.js itself). + */ + +// $Id: calendar-setup.js,v 1.25 2005/03/07 09:51:33 mishoo Exp $ + +/** + * This function "patches" an input field (or other element) to use a calendar + * widget for date selection. + * + * The "params" is a single object that can have the following properties: + * + * prop. name | description + * ------------------------------------------------------------------------------------------------- + * inputField | the ID of an input field to store the date + * displayArea | the ID of a DIV or other element to show the date + * button | ID of a button or other element that will trigger the calendar + * eventName | event that will trigger the calendar, without the "on" prefix (default: "click") + * ifFormat | date format that will be stored in the input field + * daFormat | the date format that will be used to display the date in displayArea + * singleClick | (true/false) wether the calendar is in single click mode or not (default: true) + * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc. + * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation + * range | array with 2 elements. Default: [1900, 2999] -- the range of years available + * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers + * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID + * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar) + * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar + * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay) + * onClose | function that gets called when the calendar is closed. [default] + * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar. + * date | the date that the calendar will be initially displayed to + * showsTime | default: false; if true the calendar will include a time selector + * timeFormat | the time format; can be "12" or "24", default is "12" + * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close + * step | configures the step of the years in drop-down boxes; default: 2 + * position | configures the calendar absolute position; default: null + * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible + * showOthers | if "true" (but default: "false") it will show days from other months too + * + * None of them is required, they all have default values. However, if you + * pass none of "inputField", "displayArea" or "button" you'll get a warning + * saying "nothing to setup". + */ +Calendar.setup = function (params) { + function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } }; + + param_default("inputField", null); + param_default("displayArea", null); + param_default("button", null); + param_default("eventName", "click"); + param_default("ifFormat", "%Y/%m/%d"); + param_default("daFormat", "%Y/%m/%d"); + param_default("singleClick", true); + param_default("disableFunc", null); + param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined + param_default("dateText", null); + param_default("firstDay", null); + param_default("align", "Br"); + param_default("range", [1900, 2999]); + param_default("weekNumbers", true); + param_default("flat", null); + param_default("flatCallback", null); + param_default("onSelect", null); + param_default("onClose", null); + param_default("onUpdate", null); + param_default("date", null); + param_default("showsTime", false); + param_default("timeFormat", "24"); + param_default("electric", true); + param_default("step", 2); + param_default("position", null); + param_default("cache", false); + param_default("showOthers", false); + param_default("multiple", null); + + var tmp = ["inputField", "displayArea", "button"]; + for (var i in tmp) { + if (typeof params[tmp[i]] == "string") { + params[tmp[i]] = document.getElementById(params[tmp[i]]); + } + } + if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) { + alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code"); + return false; + } + + function onSelect(cal) { + var p = cal.params; + var update = (cal.dateClicked || p.electric); + if (update && p.inputField) { + p.inputField.value = cal.date.print(p.ifFormat); + if (typeof p.inputField.onchange == "function") + p.inputField.onchange(); + } + if (update && p.displayArea) + p.displayArea.innerHTML = cal.date.print(p.daFormat); + if (update && typeof p.onUpdate == "function") + p.onUpdate(cal); + if (update && p.flat) { + if (typeof p.flatCallback == "function") + p.flatCallback(cal); + } + if (update && p.singleClick && cal.dateClicked) + cal.callCloseHandler(); + }; + + if (params.flat != null) { + if (typeof params.flat == "string") + params.flat = document.getElementById(params.flat); + if (!params.flat) { + alert("Calendar.setup:\n Flat specified but can't find parent."); + return false; + } + var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect); + cal.showsOtherMonths = params.showOthers; + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.params = params; + cal.weekNumbers = params.weekNumbers; + cal.setRange(params.range[0], params.range[1]); + cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + if (params.ifFormat) { + cal.setDateFormat(params.ifFormat); + } + if (params.inputField && typeof params.inputField.value == "string") { + cal.parseDate(params.inputField.value); + } + cal.create(params.flat); + cal.show(); + return false; + } + + var triggerEl = params.button || params.displayArea || params.inputField; + triggerEl["on" + params.eventName] = function() { + var dateEl = params.inputField || params.displayArea; + var dateFmt = params.inputField ? params.ifFormat : params.daFormat; + var mustCreate = false; + var cal = window.calendar; + if (dateEl) + params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt); + if (!(cal && params.cache)) { + window.calendar = cal = new Calendar(params.firstDay, + params.date, + params.onSelect || onSelect, + params.onClose || function(cal) { cal.hide(); }); + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.weekNumbers = params.weekNumbers; + mustCreate = true; + } else { + if (params.date) + cal.setDate(params.date); + cal.hide(); + } + if (params.multiple) { + cal.multiple = {}; + for (var i = params.multiple.length; --i >= 0;) { + var d = params.multiple[i]; + var ds = d.print("%Y%m%d"); + cal.multiple[ds] = d; + } + } + cal.showsOtherMonths = params.showOthers; + cal.yearStep = params.step; + cal.setRange(params.range[0], params.range[1]); + cal.params = params; + cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + cal.setDateFormat(dateFmt); + if (mustCreate) + cal.create(); + cal.refresh(); + if (!params.position) + cal.showAtElement(params.button || params.displayArea || params.inputField, params.align); + else + cal.showAt(params.position[0], params.position[1]); + return false; + }; + + return cal; +}; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js new file mode 100755 index 0000000..91c927f --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js @@ -0,0 +1,21 @@ +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * --------------------------------------------------------------------------- + * + * The DHTML Calendar + * + * Details and latest version at: + * http://dynarch.com/mishoo/calendar.epl + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + * + * This file defines helper functions for setting up the calendar. They are + * intended to help non-programmers get a working calendar on their site + * quickly. This script should not be seen as part of the calendar. It just + * shows you what one can do with the calendar, while in the same time + * providing a quick and simple method for setting it up. If you need + * exhaustive customization of the calendar creation process feel free to + * modify this code to suit your needs (this is recommended and much better + * than modifying calendar.js itself). + */ + Calendar.setup=function(params){function param_default(pname,def){if(typeof params[pname]=="undefined"){params[pname]=def;}};param_default("inputField",null);param_default("displayArea",null);param_default("button",null);param_default("eventName","click");param_default("ifFormat","%Y/%m/%d");param_default("daFormat","%Y/%m/%d");param_default("singleClick",true);param_default("disableFunc",null);param_default("dateStatusFunc",params["disableFunc"]);param_default("dateText",null);param_default("firstDay",null);param_default("align","Br");param_default("range",[1900,2999]);param_default("weekNumbers",true);param_default("flat",null);param_default("flatCallback",null);param_default("onSelect",null);param_default("onClose",null);param_default("onUpdate",null);param_default("date",null);param_default("showsTime",false);param_default("timeFormat","24");param_default("electric",true);param_default("step",2);param_default("position",null);param_default("cache",false);param_default("showOthers",false);param_default("multiple",null);var tmp=["inputField","displayArea","button"];for(var i in tmp){if(typeof params[tmp[i]]=="string"){params[tmp[i]]=document.getElementById(params[tmp[i]]);}}if(!(params.flat||params.multiple||params.inputField||params.displayArea||params.button)){alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");return false;}function onSelect(cal){var p=cal.params;var update=(cal.dateClicked||p.electric);if(update&&p.inputField){p.inputField.value=cal.date.print(p.ifFormat);if(typeof p.inputField.onchange=="function")p.inputField.onchange();}if(update&&p.displayArea)p.displayArea.innerHTML=cal.date.print(p.daFormat);if(update&&typeof p.onUpdate=="function")p.onUpdate(cal);if(update&&p.flat){if(typeof p.flatCallback=="function")p.flatCallback(cal);}if(update&&p.singleClick&&cal.dateClicked)cal.callCloseHandler();};if(params.flat!=null){if(typeof params.flat=="string")params.flat=document.getElementById(params.flat);if(!params.flat){alert("Calendar.setup:\n Flat specified but can't find parent.");return false;}var cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect);cal.showsOtherMonths=params.showOthers;cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.params=params;cal.weekNumbers=params.weekNumbers;cal.setRange(params.range[0],params.range[1]);cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;if(params.ifFormat){cal.setDateFormat(params.ifFormat);}if(params.inputField&&typeof params.inputField.value=="string"){cal.parseDate(params.inputField.value);}cal.create(params.flat);cal.show();return false;}var triggerEl=params.button||params.displayArea||params.inputField;triggerEl["on"+params.eventName]=function(){var dateEl=params.inputField||params.displayArea;var dateFmt=params.inputField?params.ifFormat:params.daFormat;var mustCreate=false;var cal=window.calendar;if(dateEl)params.date=Date.parseDate(dateEl.value||dateEl.innerHTML,dateFmt);if(!(cal&¶ms.cache)){window.calendar=cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect,params.onClose||function(cal){cal.hide();});cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.weekNumbers=params.weekNumbers;mustCreate=true;}else{if(params.date)cal.setDate(params.date);cal.hide();}if(params.multiple){cal.multiple={};for(var i=params.multiple.length;--i>=0;){var d=params.multiple[i];var ds=d.print("%Y%m%d");cal.multiple[ds]=d;}}cal.showsOtherMonths=params.showOthers;cal.yearStep=params.step;cal.setRange(params.range[0],params.range[1]);cal.params=params;cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;cal.setDateFormat(dateFmt);if(mustCreate)cal.create();cal.refresh();if(!params.position)cal.showAtElement(params.button||params.displayArea||params.inputField,params.align);else cal.showAt(params.position[0],params.position[1]);return false;};return cal;}; \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar.js new file mode 100755 index 0000000..9088e0e --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar.js @@ -0,0 +1,1806 @@ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- + * + * The DHTML Calendar, version 1.0 "It is happening again" + * + * Details and latest version at: + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + */ + +// $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $ + +/** The Calendar object constructor. */ +Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { + // member variables + this.activeDiv = null; + this.currentDateEl = null; + this.getDateStatus = null; + this.getDateToolTip = null; + this.getDateText = null; + this.timeout = null; + this.onSelected = onSelected || null; + this.onClose = onClose || null; + this.dragging = false; + this.hidden = false; + this.minYear = 1970; + this.maxYear = 2050; + this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; + this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; + this.isPopup = true; + this.weekNumbers = true; + this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. + this.showsOtherMonths = false; + this.dateStr = dateStr; + this.ar_days = null; + this.showsTime = false; + this.time24 = true; + this.yearStep = 2; + this.hiliteToday = true; + this.multiple = null; + // HTML elements + this.table = null; + this.element = null; + this.tbody = null; + this.firstdayname = null; + // Combo boxes + this.monthsCombo = null; + this.yearsCombo = null; + this.hilitedMonth = null; + this.activeMonth = null; + this.hilitedYear = null; + this.activeYear = null; + // Information + this.dateClicked = false; + + // one-time initializations + if (typeof Calendar._SDN == "undefined") { + // table of short day names + if (typeof Calendar._SDN_len == "undefined") + Calendar._SDN_len = 3; + var ar = new Array(); + for (var i = 8; i > 0;) { + ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); + } + Calendar._SDN = ar; + // table of short month names + if (typeof Calendar._SMN_len == "undefined") + Calendar._SMN_len = 3; + ar = new Array(); + for (var i = 12; i > 0;) { + ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); + } + Calendar._SMN = ar; + } +}; + +// ** constants + +/// "static", needed for event handlers. +Calendar._C = null; + +/// detect a special case of "web browser" +Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && + !/opera/i.test(navigator.userAgent) ); + +Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) ); + +/// detect Opera browser +Calendar.is_opera = /opera/i.test(navigator.userAgent); + +/// detect KHTML-based browsers +Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); + +// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate +// library, at some point. + +Calendar.getAbsolutePos = function(el) { + var SL = 0, ST = 0; + var is_div = /^div$/i.test(el.tagName); + if (is_div && el.scrollLeft) + SL = el.scrollLeft; + if (is_div && el.scrollTop) + ST = el.scrollTop; + var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; + if (el.offsetParent) { + var tmp = this.getAbsolutePos(el.offsetParent); + r.x += tmp.x; + r.y += tmp.y; + } + return r; +}; + +Calendar.isRelated = function (el, evt) { + var related = evt.relatedTarget; + if (!related) { + var type = evt.type; + if (type == "mouseover") { + related = evt.fromElement; + } else if (type == "mouseout") { + related = evt.toElement; + } + } + while (related) { + if (related == el) { + return true; + } + related = related.parentNode; + } + return false; +}; + +Calendar.removeClass = function(el, className) { + if (!(el && el.className)) { + return; + } + var cls = el.className.split(" "); + var ar = new Array(); + for (var i = cls.length; i > 0;) { + if (cls[--i] != className) { + ar[ar.length] = cls[i]; + } + } + el.className = ar.join(" "); +}; + +Calendar.addClass = function(el, className) { + Calendar.removeClass(el, className); + el.className += " " + className; +}; + +// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. +Calendar.getElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; + while (f.nodeType != 1 || /^div$/i.test(f.tagName)) + f = f.parentNode; + return f; +}; + +Calendar.getTargetElement = function(ev) { + var f = Calendar.is_ie ? window.event.srcElement : ev.target; + while (f.nodeType != 1) + f = f.parentNode; + return f; +}; + +Calendar.stopEvent = function(ev) { + ev || (ev = window.event); + if (Calendar.is_ie) { + ev.cancelBubble = true; + ev.returnValue = false; + } else { + ev.preventDefault(); + ev.stopPropagation(); + } + return false; +}; + +Calendar.addEvent = function(el, evname, func) { + if (el.attachEvent) { // IE + el.attachEvent("on" + evname, func); + } else if (el.addEventListener) { // Gecko / W3C + el.addEventListener(evname, func, true); + } else { + el["on" + evname] = func; + } +}; + +Calendar.removeEvent = function(el, evname, func) { + if (el.detachEvent) { // IE + el.detachEvent("on" + evname, func); + } else if (el.removeEventListener) { // Gecko / W3C + el.removeEventListener(evname, func, true); + } else { + el["on" + evname] = null; + } +}; + +Calendar.createElement = function(type, parent) { + var el = null; + if (document.createElementNS) { + // use the XHTML namespace; IE won't normally get here unless + // _they_ "fix" the DOM2 implementation. + el = document.createElementNS("http://www.w3.org/1999/xhtml", type); + } else { + el = document.createElement(type); + } + if (typeof parent != "undefined") { + parent.appendChild(el); + } + return el; +}; + +// END: UTILITY FUNCTIONS + +// BEGIN: CALENDAR STATIC FUNCTIONS + +/** Internal -- adds a set of events to make some element behave like a button. */ +Calendar._add_evs = function(el) { + with (Calendar) { + addEvent(el, "mouseover", dayMouseOver); + addEvent(el, "mousedown", dayMouseDown); + addEvent(el, "mouseout", dayMouseOut); + if (is_ie) { + addEvent(el, "dblclick", dayMouseDblClick); + el.setAttribute("unselectable", true); + } + } +}; + +Calendar.findMonth = function(el) { + if (typeof el.month != "undefined") { + return el; + } else if (typeof el.parentNode.month != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.findYear = function(el) { + if (typeof el.year != "undefined") { + return el; + } else if (typeof el.parentNode.year != "undefined") { + return el.parentNode; + } + return null; +}; + +Calendar.showMonthsCombo = function () { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var mc = cal.monthsCombo; + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + if (cal.activeMonth) { + Calendar.removeClass(cal.activeMonth, "active"); + } + var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; + Calendar.addClass(mon, "active"); + cal.activeMonth = mon; + var s = mc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var mcw = mc.offsetWidth; + if (typeof mcw == "undefined") + // Konqueror brain-dead techniques + mcw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; +}; + +Calendar.showYearsCombo = function (fwd) { + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var yc = cal.yearsCombo; + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + if (cal.activeYear) { + Calendar.removeClass(cal.activeYear, "active"); + } + cal.activeYear = null; + var Y = cal.date.getFullYear() + (fwd ? 1 : -1); + var yr = yc.firstChild; + var show = false; + for (var i = 12; i > 0; --i) { + if (Y >= cal.minYear && Y <= cal.maxYear) { + yr.innerHTML = Y; + yr.year = Y; + yr.style.display = "block"; + show = true; + } else { + yr.style.display = "none"; + } + yr = yr.nextSibling; + Y += fwd ? cal.yearStep : -cal.yearStep; + } + if (show) { + var s = yc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var ycw = yc.offsetWidth; + if (typeof ycw == "undefined") + // Konqueror brain-dead techniques + ycw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; + } +}; + +// event handlers + +Calendar.tableMouseUp = function(ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + if (cal.timeout) { + clearTimeout(cal.timeout); + } + var el = cal.activeDiv; + if (!el) { + return false; + } + var target = Calendar.getTargetElement(ev); + ev || (ev = window.event); + Calendar.removeClass(el, "active"); + if (target == el || target.parentNode == el) { + Calendar.cellClick(el, ev); + } + var mon = Calendar.findMonth(target); + var date = null; + if (mon) { + date = new Date(cal.date); + if (mon.month != date.getMonth()) { + date.setMonth(mon.month); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } else { + var year = Calendar.findYear(target); + if (year) { + date = new Date(cal.date); + if (year.year != date.getFullYear()) { + date.setFullYear(year.year); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } + } + with (Calendar) { + removeEvent(document, "mouseup", tableMouseUp); + removeEvent(document, "mouseover", tableMouseOver); + removeEvent(document, "mousemove", tableMouseOver); + cal._hideCombos(); + _C = null; + return stopEvent(ev); + } +}; + +Calendar.tableMouseOver = function (ev) { + var cal = Calendar._C; + if (!cal) { + return; + } + var el = cal.activeDiv; + var target = Calendar.getTargetElement(ev); + if (target == el || target.parentNode == el) { + Calendar.addClass(el, "hilite active"); + Calendar.addClass(el.parentNode, "rowhilite"); + } else { + if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) + Calendar.removeClass(el, "active"); + Calendar.removeClass(el, "hilite"); + Calendar.removeClass(el.parentNode, "rowhilite"); + } + ev || (ev = window.event); + if (el.navtype == 50 && target != el) { + var pos = Calendar.getAbsolutePos(el); + var w = el.offsetWidth; + var x = ev.clientX; + var dx; + var decrease = true; + if (x > pos.x + w) { + dx = x - pos.x - w; + decrease = false; + } else + dx = pos.x - x; + + if (dx < 0) dx = 0; + var range = el._range; + var current = el._current; + var count = Math.floor(dx / 10) % range.length; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + while (count-- > 0) + if (decrease) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + + cal.onUpdateTime(); + } + var mon = Calendar.findMonth(target); + if (mon) { + if (mon.month != cal.date.getMonth()) { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + Calendar.addClass(mon, "hilite"); + cal.hilitedMonth = mon; + } else if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + } else { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + var year = Calendar.findYear(target); + if (year) { + if (year.year != cal.date.getFullYear()) { + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + Calendar.addClass(year, "hilite"); + cal.hilitedYear = year; + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.tableMouseDown = function (ev) { + if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { + return Calendar.stopEvent(ev); + } +}; + +Calendar.calDragIt = function (ev) { + var cal = Calendar._C; + if (!(cal && cal.dragging)) { + return false; + } + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posX = ev.pageX; + posY = ev.pageY; + } + cal.hideShowCovered(); + var st = cal.element.style; + st.left = (posX - cal.xOffs) + "px"; + st.top = (posY - cal.yOffs) + "px"; + return Calendar.stopEvent(ev); +}; + +Calendar.calDragEnd = function (ev) { + var cal = Calendar._C; + if (!cal) { + return false; + } + cal.dragging = false; + with (Calendar) { + removeEvent(document, "mousemove", calDragIt); + removeEvent(document, "mouseup", calDragEnd); + tableMouseUp(ev); + } + cal.hideShowCovered(); +}; + +Calendar.dayMouseDown = function(ev) { + var el = Calendar.getElement(ev); + if (el.disabled) { + return false; + } + var cal = el.calendar; + cal.activeDiv = el; + Calendar._C = cal; + if (el.navtype != 300) with (Calendar) { + if (el.navtype == 50) { + el._current = el.innerHTML; + addEvent(document, "mousemove", tableMouseOver); + } else + addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); + addClass(el, "hilite active"); + addEvent(document, "mouseup", tableMouseUp); + } else if (cal.isPopup) { + cal._dragStart(ev); + } + if (el.navtype == -1 || el.navtype == 1) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); + } else if (el.navtype == -2 || el.navtype == 2) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); + } else { + cal.timeout = null; + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseDblClick = function(ev) { + Calendar.cellClick(Calendar.getElement(ev), ev || window.event); + if (Calendar.is_ie) { + document.selection.empty(); + } +}; + +Calendar.dayMouseOver = function(ev) { + var el = Calendar.getElement(ev); + if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { + return false; + } + if (el.ttip) { + if (el.ttip.substr(0, 1) == "_") { + el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); + } + el.calendar.tooltips.innerHTML = el.ttip; + } + if (el.navtype != 300) { + Calendar.addClass(el, "hilite"); + if (el.caldate) { + Calendar.addClass(el.parentNode, "rowhilite"); + } + } + return Calendar.stopEvent(ev); +}; + +Calendar.dayMouseOut = function(ev) { + with (Calendar) { + var el = getElement(ev); + if (isRelated(el, ev) || _C || el.disabled) + return false; + removeClass(el, "hilite"); + if (el.caldate) + removeClass(el.parentNode, "rowhilite"); + if (el.calendar) + el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; + return stopEvent(ev); + } +}; + +/** + * A generic "click" handler :) handles all types of buttons defined in this + * calendar. + */ +Calendar.cellClick = function(el, ev) { + var cal = el.calendar; + var closing = false; + var newdate = false; + var date = null; + if (typeof el.navtype == "undefined") { + if (cal.currentDateEl) { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } + } + cal.date.setDateOnly(el.caldate); + date = cal.date; + var other_month = !(cal.dateClicked = !el.otherMonth); + if (!other_month && !cal.currentDateEl) + cal._toggleMultipleDate(new Date(date)); + else + newdate = !el.disabled; + // a date was clicked + if (other_month) + cal._init(cal.firstDayOfWeek, date); + } else { + if (el.navtype == 200) { + Calendar.removeClass(el, "hilite"); + cal.callCloseHandler(); + return; + } + date = new Date(cal.date); + if (el.navtype == 0) + date.setDateOnly(new Date()); // TODAY + // unless "today" was clicked, we assume no date was clicked so + // the selected handler will know not to close the calenar when + // in single-click mode. + // cal.dateClicked = (el.navtype == 0); + cal.dateClicked = false; + var year = date.getFullYear(); + var mon = date.getMonth(); + function setMonth(m) { + var day = date.getDate(); + var max = date.getMonthDays(m); + if (day > max) { + date.setDate(max); + } + date.setMonth(m); + }; + switch (el.navtype) { + case 400: + Calendar.removeClass(el, "hilite"); + var text = Calendar._TT["ABOUT"]; + if (typeof text != "undefined") { + text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; + } else { + // FIXME: this should be removed as soon as lang files get updated! + text = "Help and about box text is not translated into this language.\n" + + "If you know this language and you feel generous please update\n" + + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + + "and send it back to to get it into the distribution ;-)\n\n" + + "Thank you!\n" + + "http://dynarch.com/mishoo/calendar.epl\n"; + } + alert(text); + return; + case -2: + if (year > cal.minYear) { + date.setFullYear(year - 1); + } + break; + case -1: + if (mon > 0) { + setMonth(mon - 1); + } else if (year-- > cal.minYear) { + date.setFullYear(year); + setMonth(11); + } + break; + case 1: + if (mon < 11) { + setMonth(mon + 1); + } else if (year < cal.maxYear) { + date.setFullYear(year + 1); + setMonth(0); + } + break; + case 2: + if (year < cal.maxYear) { + date.setFullYear(year + 1); + } + break; + case 100: + cal.setFirstDayOfWeek(el.fdow); + return; + case 50: + var range = el._range; + var current = el.innerHTML; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + if (ev && ev.shiftKey) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + cal.onUpdateTime(); + return; + case 0: + // TODAY will bring us here + if ((typeof cal.getDateStatus == "function") && + cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { + return false; + } + break; + } + if (!date.equalsTo(cal.date)) { + cal.setDate(date); + newdate = true; + } else if (el.navtype == 0) + newdate = closing = true; + } + if (newdate) { + ev && cal.callHandler(); + } + if (closing) { + Calendar.removeClass(el, "hilite"); + ev && cal.callCloseHandler(); + } +}; + +// END: CALENDAR STATIC FUNCTIONS + +// BEGIN: CALENDAR OBJECT FUNCTIONS + +/** + * This function creates the calendar inside the given parent. If _par is + * null than it creates a popup calendar inside the BODY element. If _par is + * an element, be it BODY, then it creates a non-popup calendar (still + * hidden). Some properties need to be set before calling this function. + */ +Calendar.prototype.create = function (_par) { + var parent = null; + if (! _par) { + // default parent is the document body, in which case we create + // a popup calendar. + parent = document.getElementsByTagName("body")[0]; + this.isPopup = true; + } else { + parent = _par; + this.isPopup = false; + } + this.date = this.dateStr ? new Date(this.dateStr) : new Date(); + + var table = Calendar.createElement("table"); + this.table = table; + table.cellSpacing = 0; + table.cellPadding = 0; + table.calendar = this; + Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); + + var div = Calendar.createElement("div"); + this.element = div; + div.className = "calendar"; + if (this.isPopup) { + div.style.position = "absolute"; + div.style.display = "none"; + } + div.appendChild(table); + + var thead = Calendar.createElement("thead", table); + var cell = null; + var row = null; + + var cal = this; + var hh = function (text, cs, navtype) { + cell = Calendar.createElement("td", row); + cell.colSpan = cs; + cell.className = "button"; + if (navtype != 0 && Math.abs(navtype) <= 2) + cell.className += " nav"; + Calendar._add_evs(cell); + cell.calendar = cal; + cell.navtype = navtype; + cell.innerHTML = "
    " + text + "
    "; + return cell; + }; + + row = Calendar.createElement("tr", thead); + var title_length = 6; + (this.isPopup) && --title_length; + (this.weekNumbers) && ++title_length; + + hh("?", 1, 400).ttip = Calendar._TT["INFO"]; + this.title = hh("", title_length, 300); + this.title.className = "title"; + if (this.isPopup) { + this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; + this.title.style.cursor = "move"; + hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; + } + + row = Calendar.createElement("tr", thead); + row.className = "headrow"; + + this._nav_py = hh("«", 1, -2); + this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; + + this._nav_pm = hh("‹", 1, -1); + this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; + + this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); + this._nav_now.ttip = Calendar._TT["GO_TODAY"]; + + this._nav_nm = hh("›", 1, 1); + this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; + + this._nav_ny = hh("»", 1, 2); + this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; + + // day names + row = Calendar.createElement("tr", thead); + row.className = "daynames"; + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + cell.className = "name wn"; + cell.innerHTML = Calendar._TT["WK"]; + } + for (var i = 7; i > 0; --i) { + cell = Calendar.createElement("td", row); + if (!i) { + cell.navtype = 100; + cell.calendar = this; + Calendar._add_evs(cell); + } + } + this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; + this._displayWeekdays(); + + var tbody = Calendar.createElement("tbody", table); + this.tbody = tbody; + + for (i = 6; i > 0; --i) { + row = Calendar.createElement("tr", tbody); + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + } + for (var j = 7; j > 0; --j) { + cell = Calendar.createElement("td", row); + cell.calendar = this; + Calendar._add_evs(cell); + } + } + + if (this.showsTime) { + row = Calendar.createElement("tr", tbody); + row.className = "time"; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + cell.innerHTML = Calendar._TT["TIME"] || " "; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = this.weekNumbers ? 4 : 3; + + (function(){ + function makeTimePart(className, init, range_start, range_end) { + var part = Calendar.createElement("span", cell); + part.className = className; + part.innerHTML = init; + part.calendar = cal; + part.ttip = Calendar._TT["TIME_PART"]; + part.navtype = 50; + part._range = []; + if (typeof range_start != "number") + part._range = range_start; + else { + for (var i = range_start; i <= range_end; ++i) { + var txt; + if (i < 10 && range_end >= 10) txt = '0' + i; + else txt = '' + i; + part._range[part._range.length] = txt; + } + } + Calendar._add_evs(part); + return part; + }; + var hrs = cal.date.getHours(); + var mins = cal.date.getMinutes(); + var t12 = !cal.time24; + var pm = (hrs > 12); + if (t12 && pm) hrs -= 12; + var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); + var span = Calendar.createElement("span", cell); + span.innerHTML = ":"; + span.className = "colon"; + var M = makeTimePart("minute", mins, 0, 59); + var AP = null; + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + if (t12) + AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); + else + cell.innerHTML = " "; + + cal.onSetTime = function() { + var pm, hrs = this.date.getHours(), + mins = this.date.getMinutes(); + if (t12) { + pm = (hrs >= 12); + if (pm) hrs -= 12; + if (hrs == 0) hrs = 12; + AP.innerHTML = pm ? "pm" : "am"; + } + H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; + M.innerHTML = (mins < 10) ? ("0" + mins) : mins; + }; + + cal.onUpdateTime = function() { + var date = this.date; + var h = parseInt(H.innerHTML, 10); + if (t12) { + if (/pm/i.test(AP.innerHTML) && h < 12) + h += 12; + else if (/am/i.test(AP.innerHTML) && h == 12) + h = 0; + } + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + date.setHours(h); + date.setMinutes(parseInt(M.innerHTML, 10)); + date.setFullYear(y); + date.setMonth(m); + date.setDate(d); + this.dateClicked = false; + this.callHandler(); + }; + })(); + } else { + this.onSetTime = this.onUpdateTime = function() {}; + } + + var tfoot = Calendar.createElement("tfoot", table); + + row = Calendar.createElement("tr", tfoot); + row.className = "footrow"; + + cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); + cell.className = "ttip"; + if (this.isPopup) { + cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; + cell.style.cursor = "move"; + } + this.tooltips = cell; + + div = Calendar.createElement("div", this.element); + this.monthsCombo = div; + div.className = "combo"; + for (i = 0; i < Calendar._MN.length; ++i) { + var mn = Calendar.createElement("div"); + mn.className = Calendar.is_ie ? "label-IEfix" : "label"; + mn.month = i; + mn.innerHTML = Calendar._SMN[i]; + div.appendChild(mn); + } + + div = Calendar.createElement("div", this.element); + this.yearsCombo = div; + div.className = "combo"; + for (i = 12; i > 0; --i) { + var yr = Calendar.createElement("div"); + yr.className = Calendar.is_ie ? "label-IEfix" : "label"; + div.appendChild(yr); + } + + this._init(this.firstDayOfWeek, this.date); + parent.appendChild(this.element); +}; + +/** keyboard navigation, only for popup calendars */ +Calendar._keyEvent = function(ev) { + var cal = window._dynarch_popupCalendar; + if (!cal || cal.multiple) + return false; + (Calendar.is_ie) && (ev = window.event); + var act = (Calendar.is_ie || ev.type == "keypress"), + K = ev.keyCode; + if (ev.ctrlKey) { + switch (K) { + case 37: // KEY left + act && Calendar.cellClick(cal._nav_pm); + break; + case 38: // KEY up + act && Calendar.cellClick(cal._nav_py); + break; + case 39: // KEY right + act && Calendar.cellClick(cal._nav_nm); + break; + case 40: // KEY down + act && Calendar.cellClick(cal._nav_ny); + break; + default: + return false; + } + } else switch (K) { + case 32: // KEY space (now) + Calendar.cellClick(cal._nav_now); + break; + case 27: // KEY esc + act && cal.callCloseHandler(); + break; + case 37: // KEY left + case 38: // KEY up + case 39: // KEY right + case 40: // KEY down + if (act) { + var prev, x, y, ne, el, step; + prev = K == 37 || K == 38; + step = (K == 37 || K == 39) ? 1 : 7; + function setVars() { + el = cal.currentDateEl; + var p = el.pos; + x = p & 15; + y = p >> 4; + ne = cal.ar_days[y][x]; + };setVars(); + function prevMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() - step); + cal.setDate(date); + }; + function nextMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() + step); + cal.setDate(date); + }; + while (1) { + switch (K) { + case 37: // KEY left + if (--x >= 0) + ne = cal.ar_days[y][x]; + else { + x = 6; + K = 38; + continue; + } + break; + case 38: // KEY up + if (--y >= 0) + ne = cal.ar_days[y][x]; + else { + prevMonth(); + setVars(); + } + break; + case 39: // KEY right + if (++x < 7) + ne = cal.ar_days[y][x]; + else { + x = 0; + K = 40; + continue; + } + break; + case 40: // KEY down + if (++y < cal.ar_days.length) + ne = cal.ar_days[y][x]; + else { + nextMonth(); + setVars(); + } + break; + } + break; + } + if (ne) { + if (!ne.disabled) + Calendar.cellClick(ne); + else if (prev) + prevMonth(); + else + nextMonth(); + } + } + break; + case 13: // KEY enter + if (act) + Calendar.cellClick(cal.currentDateEl, ev); + break; + default: + return false; + } + return Calendar.stopEvent(ev); +}; + +/** + * (RE)Initializes the calendar to the given date and firstDayOfWeek + */ +Calendar.prototype._init = function (firstDayOfWeek, date) { + var today = new Date(), + TY = today.getFullYear(), + TM = today.getMonth(), + TD = today.getDate(); + this.table.style.visibility = "hidden"; + var year = date.getFullYear(); + if (year < this.minYear) { + year = this.minYear; + date.setFullYear(year); + } else if (year > this.maxYear) { + year = this.maxYear; + date.setFullYear(year); + } + this.firstDayOfWeek = firstDayOfWeek; + this.date = new Date(date); + var month = date.getMonth(); + var mday = date.getDate(); + var no_days = date.getMonthDays(); + + // calendar voodoo for computing the first day that would actually be + // displayed in the calendar, even if it's from the previous month. + // WARNING: this is magic. ;-) + date.setDate(1); + var day1 = (date.getDay() - this.firstDayOfWeek) % 7; + if (day1 < 0) + day1 += 7; + date.setDate(-day1); + date.setDate(date.getDate() + 1); + + var row = this.tbody.firstChild; + var MN = Calendar._SMN[month]; + var ar_days = this.ar_days = new Array(); + var weekend = Calendar._TT["WEEKEND"]; + var dates = this.multiple ? (this.datesCells = {}) : null; + for (var i = 0; i < 6; ++i, row = row.nextSibling) { + var cell = row.firstChild; + if (this.weekNumbers) { + cell.className = "day wn"; + cell.innerHTML = date.getWeekNumber(); + cell = cell.nextSibling; + } + row.className = "daysrow"; + var hasdays = false, iday, dpos = ar_days[i] = []; + for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { + iday = date.getDate(); + var wday = date.getDay(); + cell.className = "day"; + cell.pos = i << 4 | j; + dpos[j] = cell; + var current_month = (date.getMonth() == month); + if (!current_month) { + if (this.showsOtherMonths) { + cell.className += " othermonth"; + cell.otherMonth = true; + } else { + cell.className = "emptycell"; + cell.innerHTML = " "; + cell.disabled = true; + continue; + } + } else { + cell.otherMonth = false; + hasdays = true; + } + cell.disabled = false; + cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; + if (dates) + dates[date.print("%Y%m%d")] = cell; + if (this.getDateStatus) { + var status = this.getDateStatus(date, year, month, iday); + if (this.getDateToolTip) { + var toolTip = this.getDateToolTip(date, year, month, iday); + if (toolTip) + cell.title = toolTip; + } + if (status === true) { + cell.className += " disabled"; + cell.disabled = true; + } else { + if (/disabled/i.test(status)) + cell.disabled = true; + cell.className += " " + status; + } + } + if (!cell.disabled) { + cell.caldate = new Date(date); + cell.ttip = "_"; + if (!this.multiple && current_month + && iday == mday && this.hiliteToday) { + cell.className += " selected"; + this.currentDateEl = cell; + } + if (date.getFullYear() == TY && + date.getMonth() == TM && + iday == TD) { + cell.className += " today"; + cell.ttip += Calendar._TT["PART_TODAY"]; + } + if (weekend.indexOf(wday.toString()) != -1) + cell.className += cell.otherMonth ? " oweekend" : " weekend"; + } + } + if (!(hasdays || this.showsOtherMonths)) + row.className = "emptyrow"; + } + this.title.innerHTML = Calendar._MN[month] + ", " + year; + this.onSetTime(); + this.table.style.visibility = "visible"; + this._initMultipleDates(); + // PROFILE + // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; +}; + +Calendar.prototype._initMultipleDates = function() { + if (this.multiple) { + for (var i in this.multiple) { + var cell = this.datesCells[i]; + var d = this.multiple[i]; + if (!d) + continue; + if (cell) + cell.className += " selected"; + } + } +}; + +Calendar.prototype._toggleMultipleDate = function(date) { + if (this.multiple) { + var ds = date.print("%Y%m%d"); + var cell = this.datesCells[ds]; + if (cell) { + var d = this.multiple[ds]; + if (!d) { + Calendar.addClass(cell, "selected"); + this.multiple[ds] = date; + } else { + Calendar.removeClass(cell, "selected"); + delete this.multiple[ds]; + } + } + } +}; + +Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { + this.getDateToolTip = unaryFunction; +}; + +/** + * Calls _init function above for going to a certain date (but only if the + * date is different than the currently selected one). + */ +Calendar.prototype.setDate = function (date) { + if (!date.equalsTo(this.date)) { + this._init(this.firstDayOfWeek, date); + } +}; + +/** + * Refreshes the calendar. Useful if the "disabledHandler" function is + * dynamic, meaning that the list of disabled date can change at runtime. + * Just * call this function if you think that the list of disabled dates + * should * change. + */ +Calendar.prototype.refresh = function () { + this._init(this.firstDayOfWeek, this.date); +}; + +/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */ +Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { + this._init(firstDayOfWeek, this.date); + this._displayWeekdays(); +}; + +/** + * Allows customization of what dates are enabled. The "unaryFunction" + * parameter must be a function object that receives the date (as a JS Date + * object) and returns a boolean value. If the returned value is true then + * the passed date will be marked as disabled. + */ +Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { + this.getDateStatus = unaryFunction; +}; + +/** Customization of allowed year range for the calendar. */ +Calendar.prototype.setRange = function (a, z) { + this.minYear = a; + this.maxYear = z; +}; + +/** Calls the first user handler (selectedHandler). */ +Calendar.prototype.callHandler = function () { + if (this.onSelected) { + this.onSelected(this, this.date.print(this.dateFormat)); + } +}; + +/** Calls the second user handler (closeHandler). */ +Calendar.prototype.callCloseHandler = function () { + if (this.onClose) { + this.onClose(this); + } + this.hideShowCovered(); +}; + +/** Removes the calendar object from the DOM tree and destroys it. */ +Calendar.prototype.destroy = function () { + var el = this.element.parentNode; + el.removeChild(this.element); + Calendar._C = null; + window._dynarch_popupCalendar = null; +}; + +/** + * Moves the calendar element to a different section in the DOM tree (changes + * its parent). + */ +Calendar.prototype.reparent = function (new_parent) { + var el = this.element; + el.parentNode.removeChild(el); + new_parent.appendChild(el); +}; + +// This gets called when the user presses a mouse button anywhere in the +// document, if the calendar is shown. If the click was outside the open +// calendar this function closes it. +Calendar._checkCalendar = function(ev) { + var calendar = window._dynarch_popupCalendar; + if (!calendar) { + return false; + } + var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); + for (; el != null && el != calendar.element; el = el.parentNode); + if (el == null) { + // calls closeHandler which should hide the calendar. + window._dynarch_popupCalendar.callCloseHandler(); + return Calendar.stopEvent(ev); + } +}; + +/** Shows the calendar. */ +Calendar.prototype.show = function () { + var rows = this.table.getElementsByTagName("tr"); + for (var i = rows.length; i > 0;) { + var row = rows[--i]; + Calendar.removeClass(row, "rowhilite"); + var cells = row.getElementsByTagName("td"); + for (var j = cells.length; j > 0;) { + var cell = cells[--j]; + Calendar.removeClass(cell, "hilite"); + Calendar.removeClass(cell, "active"); + } + } + this.element.style.display = "block"; + this.hidden = false; + if (this.isPopup) { + window._dynarch_popupCalendar = this; + Calendar.addEvent(document, "keydown", Calendar._keyEvent); + Calendar.addEvent(document, "keypress", Calendar._keyEvent); + Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); + } + this.hideShowCovered(); +}; + +/** + * Hides the calendar. Also removes any "hilite" from the class of any TD + * element. + */ +Calendar.prototype.hide = function () { + if (this.isPopup) { + Calendar.removeEvent(document, "keydown", Calendar._keyEvent); + Calendar.removeEvent(document, "keypress", Calendar._keyEvent); + Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); + } + this.element.style.display = "none"; + this.hidden = true; + this.hideShowCovered(); +}; + +/** + * Shows the calendar at a given absolute position (beware that, depending on + * the calendar element style -- position property -- this might be relative + * to the parent's containing rectangle). + */ +Calendar.prototype.showAt = function (x, y) { + var s = this.element.style; + s.left = x + "px"; + s.top = y + "px"; + this.show(); +}; + +/** Shows the calendar near a given element. */ +Calendar.prototype.showAtElement = function (el, opts) { + var self = this; + var p = Calendar.getAbsolutePos(el); + if (!opts || typeof opts != "string") { + this.showAt(p.x, p.y + el.offsetHeight); + return true; + } + function fixPosition(box) { + if (box.x < 0) + box.x = 0; + if (box.y < 0) + box.y = 0; + var cp = document.createElement("div"); + var s = cp.style; + s.position = "absolute"; + s.right = s.bottom = s.width = s.height = "0px"; + document.body.appendChild(cp); + var br = Calendar.getAbsolutePos(cp); + document.body.removeChild(cp); + if (Calendar.is_ie) { + br.y += document.body.scrollTop; + br.x += document.body.scrollLeft; + } else { + br.y += window.scrollY; + br.x += window.scrollX; + } + var tmp = box.x + box.width - br.x; + if (tmp > 0) box.x -= tmp; + tmp = box.y + box.height - br.y; + if (tmp > 0) box.y -= tmp; + }; + this.element.style.display = "block"; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + var w = self.element.offsetWidth; + var h = self.element.offsetHeight; + self.element.style.display = "none"; + var valign = opts.substr(0, 1); + var halign = "l"; + if (opts.length > 1) { + halign = opts.substr(1, 1); + } + // vertical alignment + switch (valign) { + case "T": p.y -= h; break; + case "B": p.y += el.offsetHeight; break; + case "C": p.y += (el.offsetHeight - h) / 2; break; + case "t": p.y += el.offsetHeight - h; break; + case "b": break; // already there + } + // horizontal alignment + switch (halign) { + case "L": p.x -= w; break; + case "R": p.x += el.offsetWidth; break; + case "C": p.x += (el.offsetWidth - w) / 2; break; + case "l": p.x += el.offsetWidth - w; break; + case "r": break; // already there + } + p.width = w; + p.height = h + 40; + self.monthsCombo.style.display = "none"; + fixPosition(p); + self.showAt(p.x, p.y); + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); +}; + +/** Customizes the date format. */ +Calendar.prototype.setDateFormat = function (str) { + this.dateFormat = str; +}; + +/** Customizes the tooltip date format. */ +Calendar.prototype.setTtDateFormat = function (str) { + this.ttDateFormat = str; +}; + +/** + * Tries to identify the date represented in a string. If successful it also + * calls this.setDate which moves the calendar to the given date. + */ +Calendar.prototype.parseDate = function(str, fmt) { + if (!fmt) + fmt = this.dateFormat; + this.setDate(Date.parseDate(str, fmt)); +}; + +Calendar.prototype.hideShowCovered = function () { + if (!Calendar.is_ie && !Calendar.is_opera) + return; + function getVisib(obj){ + var value = obj.style.visibility; + if (!value) { + if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C + if (!Calendar.is_khtml) + value = document.defaultView. + getComputedStyle(obj, "").getPropertyValue("visibility"); + else + value = ''; + } else if (obj.currentStyle) { // IE + value = obj.currentStyle.visibility; + } else + value = ''; + } + return value; + }; + + var tags = new Array("applet", "iframe", "select"); + var el = this.element; + + var p = Calendar.getAbsolutePos(el); + var EX1 = p.x; + var EX2 = el.offsetWidth + EX1; + var EY1 = p.y; + var EY2 = el.offsetHeight + EY1; + + for (var k = tags.length; k > 0; ) { + var ar = document.getElementsByTagName(tags[--k]); + var cc = null; + + for (var i = ar.length; i > 0;) { + cc = ar[--i]; + + p = Calendar.getAbsolutePos(cc); + var CX1 = p.x; + var CX2 = cc.offsetWidth + CX1; + var CY1 = p.y; + var CY2 = cc.offsetHeight + CY1; + + if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = cc.__msh_save_visibility; + } else { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = "hidden"; + } + } + } +}; + +/** Internal function; it displays the bar with the names of the weekday. */ +Calendar.prototype._displayWeekdays = function () { + var fdow = this.firstDayOfWeek; + var cell = this.firstdayname; + var weekend = Calendar._TT["WEEKEND"]; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + var realday = (i + fdow) % 7; + if (i) { + cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); + cell.navtype = 100; + cell.calendar = this; + cell.fdow = realday; + Calendar._add_evs(cell); + } + if (weekend.indexOf(realday.toString()) != -1) { + Calendar.addClass(cell, "weekend"); + } + cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; + cell = cell.nextSibling; + } +}; + +/** Internal function. Hides all combo boxes that might be displayed. */ +Calendar.prototype._hideCombos = function () { + this.monthsCombo.style.display = "none"; + this.yearsCombo.style.display = "none"; +}; + +/** Internal function. Starts dragging the element. */ +Calendar.prototype._dragStart = function (ev) { + if (this.dragging) { + return; + } + this.dragging = true; + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posY = ev.clientY + window.scrollY; + posX = ev.clientX + window.scrollX; + } + var st = this.element.style; + this.xOffs = posX - parseInt(st.left); + this.yOffs = posY - parseInt(st.top); + with (Calendar) { + addEvent(document, "mousemove", calDragIt); + addEvent(document, "mouseup", calDragEnd); + } +}; + +// BEGIN: DATE OBJECT PATCHES + +/** Adds the number of days array to the Date object. */ +Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); + +/** Constants used for time computations */ +Date.SECOND = 1000 /* milliseconds */; +Date.MINUTE = 60 * Date.SECOND; +Date.HOUR = 60 * Date.MINUTE; +Date.DAY = 24 * Date.HOUR; +Date.WEEK = 7 * Date.DAY; + +Date.parseDate = function(str, fmt) { + var today = new Date(); + var y = 0; + var m = -1; + var d = 0; + var a = str.split(/\W+/); + var b = fmt.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%b": + case "%B": + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } + } + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + else if (/am/i.test(a[i]) && hr >= 12) + hr -= 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (isNaN(y)) y = today.getFullYear(); + if (isNaN(m)) m = today.getMonth(); + if (isNaN(d)) d = today.getDate(); + if (isNaN(hr)) hr = today.getHours(); + if (isNaN(min)) min = today.getMinutes(); + if (y != 0 && m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + y = 0; m = -1; d = 0; + for (i = 0; i < a.length; ++i) { + if (a[i].search(/[a-zA-Z]+/) != -1) { + var t = -1; + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } + } + if (t != -1) { + if (m != -1) { + d = m+1; + } + m = t; + } + } else if (parseInt(a[i], 10) <= 12 && m == -1) { + m = a[i]-1; + } else if (parseInt(a[i], 10) > 31 && y == 0) { + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + } else if (d == 0) { + d = a[i]; + } + } + if (y == 0) + y = today.getFullYear(); + if (m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + return today; +}; + +/** Returns the number of days in the current month */ +Date.prototype.getMonthDays = function(month) { + var year = this.getFullYear(); + if (typeof month == "undefined") { + month = this.getMonth(); + } + if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { + return 29; + } else { + return Date._MD[month]; + } +}; + +/** Returns the number of day in the year. */ +Date.prototype.getDayOfYear = function() { + var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0); + var time = now - then; + return Math.floor(time / Date.DAY); +}; + +/** Returns the number of the week in year, as defined in ISO 8601. */ +Date.prototype.getWeekNumber = function() { + var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var DoW = d.getDay(); + d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu + var ms = d.valueOf(); // GMT + d.setMonth(0); + d.setDate(4); // Thu in Week 1 + return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; +}; + +/** Checks date and time equality */ +Date.prototype.equalsTo = function(date) { + return ((this.getFullYear() == date.getFullYear()) && + (this.getMonth() == date.getMonth()) && + (this.getDate() == date.getDate()) && + (this.getHours() == date.getHours()) && + (this.getMinutes() == date.getMinutes())); +}; + +/** Set only the year, month, date parts (keep existing time) */ +Date.prototype.setDateOnly = function(date) { + var tmp = new Date(date); + this.setDate(1); + this.setFullYear(tmp.getFullYear()); + this.setMonth(tmp.getMonth()); + this.setDate(tmp.getDate()); +}; + +/** Prints the date in a string according to the given format. */ +Date.prototype.print = function (str) { + var m = this.getMonth(); + var d = this.getDate(); + var y = this.getFullYear(); + var wn = this.getWeekNumber(); + var w = this.getDay(); + var s = {}; + var hr = this.getHours(); + var pm = (hr >= 12); + var ir = (pm) ? (hr - 12) : hr; + var dy = this.getDayOfYear(); + if (ir == 0) + ir = 12; + var min = this.getMinutes(); + var sec = this.getSeconds(); + s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] + s["%A"] = Calendar._DN[w]; // full weekday name + s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] + s["%B"] = Calendar._MN[m]; // full month name + // FIXME: %c : preferred date and time representation for the current locale + s["%C"] = 1 + Math.floor(y / 100); // the century number + s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + // FIXME: %D : american date style: %m/%d/%y + // FIXME: %E, %F, %G, %g, %h (man strftime) + s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) + s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) + s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) + s["%k"] = hr; // hour, range 0 to 23 (24h format) + s["%l"] = ir; // hour, range 1 to 12 (12h format) + s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 + s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 + s["%n"] = "\n"; // a newline character + s["%p"] = pm ? "PM" : "AM"; + s["%P"] = pm ? "pm" : "am"; + // FIXME: %r : the time in am/pm notation %I:%M:%S %p + // FIXME: %R : the time in 24-hour notation %H:%M + s["%s"] = Math.floor(this.getTime() / 1000); + s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 + s["%t"] = "\t"; // a tab character + // FIXME: %T : the time in 24-hour notation (%H:%M:%S) + s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; + s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) + s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) + // FIXME: %x : preferred date representation for the current locale without the time + // FIXME: %X : preferred time representation for the current locale without the date + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + s["%%"] = "%"; // a literal '%' character + + var re = /%./g; + if (!Calendar.is_ie5 && !Calendar.is_khtml) + return str.replace(re, function (par) { return s[par] || par; }); + + var a = str.match(re); + for (var i = 0; i < a.length; i++) { + var tmp = s[a[i]]; + if (tmp) { + re = new RegExp(a[i], 'g'); + str = str.replace(re, tmp); + } + } + + return str; +}; + +Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear; +Date.prototype.setFullYear = function(y) { + var d = new Date(this); + d.__msh_oldSetFullYear(y); + if (d.getMonth() != this.getMonth()) + this.setDate(28); + this.__msh_oldSetFullYear(y); +}; + +// END: DATE OBJECT PATCHES + + +// global object that remembers the calendar +window._dynarch_popupCalendar = null; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar_stripped.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar_stripped.js new file mode 100755 index 0000000..4fe03f1 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar_stripped.js @@ -0,0 +1,14 @@ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- + * + * The DHTML Calendar, version 1.0 "It is happening again" + * + * Details and latest version at: + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. + * + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html + */ + Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.getDateToolTip=null;this.getDateText=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled)return false;removeClass(el,"hilite");if(el.caldate)removeClass(el.parentNode,"rowhilite");if(el.calendar)el.calendar.tooltips.innerHTML=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new Date());cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;cell.innerHTML="
    "+text+"
    ";return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("×",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("«",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("‹",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("›",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("»",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.innerHTML=Calendar._TT["WK"];}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=Calendar._TT["TIME"]||" ";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.innerHTML=init;part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.innerHTML=":";span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML=" ";cal.onSetTime=function(){var pm,hrs=this.date.getHours(),mins=this.date.getMinutes();if(t12){pm=(hrs>=12);if(pm)hrs-=12;if(hrs==0)hrs=12;AP.innerHTML=pm?"pm":"am";}H.innerHTML=(hrs<10)?("0"+hrs):hrs;M.innerHTML=(mins<10)?("0"+mins):mins;};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.innerHTML,10);if(t12){if(/pm/i.test(AP.innerHTML)&&h<12)h+=12;else if(/am/i.test(AP.innerHTML)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.innerHTML,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){var cal=window._dynarch_popupCalendar;if(!cal||cal.multiple)return false;(Calendar.is_ie)&&(ev=window.event);var act=(Calendar.is_ie||ev.type=="keypress"),K=ev.keyCode;if(ev.ctrlKey){switch(K){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(K){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var prev,x,y,ne,el,step;prev=K==37||K==38;step=(K==37||K==39)?1:7;function setVars(){el=cal.currentDateEl;var p=el.pos;x=p&15;y=p>>4;ne=cal.ar_days[y][x];};setVars();function prevMonth(){var date=new Date(cal.date);date.setDate(date.getDate()-step);cal.setDate(date);};function nextMonth(){var date=new Date(cal.date);date.setDate(date.getDate()+step);cal.setDate(date);};while(1){switch(K){case 37:if(--x>=0)ne=cal.ar_days[y][x];else{x=6;K=38;continue;}break;case 38:if(--y>=0)ne=cal.ar_days[y][x];else{prevMonth();setVars();}break;case 39:if(++x<7)ne=cal.ar_days[y][x];else{x=0;K=40;continue;}break;case 40:if(++ythis.maxYear){year=this.maxYear;date.setFullYear(year);}this.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=this.ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];var dates=this.multiple?(this.datesCells={}):null;for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.innerHTML=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false,iday,dpos=ar_days[i]=[];for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(iday+1)){iday=date.getDate();var wday=date.getDay();cell.className="day";cell.pos=i<<4|j;dpos[j]=cell;var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML=" ";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.innerHTML=this.getDateText?this.getDateText(date,iday):iday;if(dates)dates[date.print("%Y%m%d")]=cell;if(this.getDateStatus){var status=this.getDateStatus(date,year,month,iday);if(this.getDateToolTip){var toolTip=this.getDateToolTip(date,year,month,iday);if(toolTip)cell.title=toolTip;}if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){cell.caldate=new Date(date);cell.ttip="_";if(!this.multiple&¤t_month&&iday==mday&&this.hiliteToday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==TY&&date.getMonth()==TM&&iday==TD){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1)cell.className+=cell.otherMonth?" oweekend":" weekend";}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.title.innerHTML=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";this._initMultipleDates();};Calendar.prototype._initMultipleDates=function(){if(this.multiple){for(var i in this.multiple){var cell=this.datesCells[i];var d=this.multiple[i];if(!d)continue;if(cell)cell.className+=" selected";}}};Calendar.prototype._toggleMultipleDate=function(date){if(this.multiple){var ds=date.print("%Y%m%d");var cell=this.datesCells[ds];if(cell){var d=this.multiple[ds];if(!d){Calendar.addClass(cell,"selected");this.multiple[ds]=date;}else{Calendar.removeClass(cell,"selected");delete this.multiple[ds];}}}};Calendar.prototype.setDateToolTipHandler=function(unaryFunction){this.getDateToolTip=unaryFunction;};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window._dynarch_popupCalendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){var calendar=window._dynarch_popupCalendar;if(!calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window._dynarch_popupCalendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window._dynarch_popupCalendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "l":p.x+=el.offsetWidth-w;break;case "r":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){if(!fmt)fmt=this.dateFormat;this.setDate(Date.parseDate(str,fmt));};Calendar.prototype.hideShowCovered=function(){if(!Calendar.is_ie&&!Calendar.is_opera)return;function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=this.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(this.hidden||(CX1>EX2)||(CX2EY2)||(CY229)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;else if(/am/i.test(a[i])&&hr>=12)hr-=12;break;case "%M":min=parseInt(a[i],10);break;}}if(isNaN(y))y=today.getFullYear();if(isNaN(m))m=today.getMonth();if(isNaN(d))d=today.getDate();if(isNaN(hr))hr=today.getHours();if(isNaN(min))min=today.getMinutes();if(y!=0&&m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);y=0;m=-1;d=0;for(i=0;i31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0)y=today.getFullYear();if(m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);return today;};Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.setDateOnly=function(date){var tmp=new Date(date);this.setDate(1);this.setFullYear(tmp.getFullYear());this.setMonth(tmp.getMonth());this.setDate(tmp.getDate());};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=/%./g;if(!Calendar.is_ie5&&!Calendar.is_khtml)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i" + initial_display + "\n"; + dynarch_contents += "Calendar Icon\n"; + + date = Date.parseDate(initial_date, ifFormat); + + // Use a hidden, nameless field to store the date from the dynarch calendar + dynarch_contents += "\n"; + + // Use three hidden, named fields to pass the dates along in a Rails-compatible format + dynarch_contents += "\n"; + dynarch_contents += "\n"; + dynarch_contents += "\n"; + + // Replace the default Rails select boxes with our dynarch contents + //alert("Contents for " + tag_id + '_container' + ": \n" + dynarch_contents); + container = $(tag_id + '_container'); + container.innerHTML = dynarch_contents; + + Calendar.setup({ + inputField : tag_id + '_input_field', + ifFormat : ifFormat, + displayArea : tag_id + '_display', + daFormat : daFormat, + button : tag_id + '_button', + align : "Tl", + singleClick : true, + cache : true + //showsTime : true + }); +} + +function update_multiparams(tag_id, ifFormat) { + date_string = $(tag_id + '_input_field').value; + date = Date.parseDate(date_string, ifFormat); + $(tag_id + '_year').value = date.getFullYear(); + $(tag_id + '_month').value = date.getMonth() + 1; + $(tag_id + '_day').value = date.getDate(); +} + +function dynarch_tag_name(object_name, method_name, index) { + tag_name = object_name.replace('[]', ''); + if (index) tag_name += '[' + index + ']'; + tag_name += '[' + method_name + ']'; + return tag_name; +} + +function dynarch_tag_id(object_name, method_name, index) { + tag_id = object_name.replace('[]', ''); + if (index) tag_id += '_' + index; + tag_id += '_' + method_name; + return tag_id; +} \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-af.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-af.js new file mode 100644 index 0000000..aeda581 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-af.js @@ -0,0 +1,39 @@ +// ** I18N Afrikaans +Calendar._DN = new Array +("Sondag", + "Maandag", + "Dinsdag", + "Woensdag", + "Donderdag", + "Vrydag", + "Saterdag", + "Sondag"); +Calendar._MN = new Array +("Januarie", + "Februarie", + "Maart", + "April", + "Mei", + "Junie", + "Julie", + "Augustus", + "September", + "Oktober", + "November", + "Desember"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Verander eerste dag van die week"; +Calendar._TT["PREV_YEAR"] = "Vorige jaar (hou vir keuselys)"; +Calendar._TT["PREV_MONTH"] = "Vorige maand (hou vir keuselys)"; +Calendar._TT["GO_TODAY"] = "Gaan na vandag"; +Calendar._TT["NEXT_MONTH"] = "Volgende maand (hou vir keuselys)"; +Calendar._TT["NEXT_YEAR"] = "Volgende jaar (hou vir keuselys)"; +Calendar._TT["SEL_DATE"] = "Kies datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Sleep om te skuif"; +Calendar._TT["PART_TODAY"] = " (vandag)"; +Calendar._TT["MON_FIRST"] = "Vertoon Maandag eerste"; +Calendar._TT["SUN_FIRST"] = "Display Sunday first"; +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-al.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-al.js new file mode 100644 index 0000000..4f701cf --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-al.js @@ -0,0 +1,101 @@ +// Calendar ALBANIAN language +//author Rigels Gordani rige@hotmail.com + +// ditet +Calendar._DN = new Array +("E Diele", +"E Hene", +"E Marte", +"E Merkure", +"E Enjte", +"E Premte", +"E Shtune", +"E Diele"); + +//ditet shkurt +Calendar._SDN = new Array +("Die", +"Hen", +"Mar", +"Mer", +"Enj", +"Pre", +"Sht", +"Die"); + +// muajt +Calendar._MN = new Array +("Janar", +"Shkurt", +"Mars", +"Prill", +"Maj", +"Qeshor", +"Korrik", +"Gusht", +"Shtator", +"Tetor", +"Nentor", +"Dhjetor"); + +// muajte shkurt +Calendar._SMN = new Array +("Jan", +"Shk", +"Mar", +"Pri", +"Maj", +"Qes", +"Kor", +"Gus", +"Sht", +"Tet", +"Nen", +"Dhj"); + +// ndihmesa +Calendar._TT = {}; +Calendar._TT["INFO"] = "Per kalendarin"; + +Calendar._TT["ABOUT"] = +"Zgjedhes i ores/dates ne DHTML \n" + +"\n\n" +"Zgjedhja e Dates:\n" + +"- Perdor butonat \xab, \xbb per te zgjedhur vitin\n" + +"- Perdor butonat" + String.fromCharCode(0x2039) + ", " + +String.fromCharCode(0x203a) + +" per te zgjedhur muajin\n" + +"- Mbani shtypur butonin e mousit per nje zgjedje me te shpejte."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Zgjedhja e kohes:\n" + +"- Kliko tek ndonje nga pjeset e ores per ta rritur ate\n" + +"- ose kliko me Shift per ta zvogeluar ate\n" + +"- ose cliko dhe terhiq per zgjedhje me te shpejte."; + +Calendar._TT["PREV_YEAR"] = "Viti i shkuar (prit per menune)"; +Calendar._TT["PREV_MONTH"] = "Muaji i shkuar (prit per menune)"; +Calendar._TT["GO_TODAY"] = "Sot"; +Calendar._TT["NEXT_MONTH"] = "Muaji i ardhshem (prit per menune)"; +Calendar._TT["NEXT_YEAR"] = "Viti i ardhshem (prit per menune)"; +Calendar._TT["SEL_DATE"] = "Zgjidh daten"; +Calendar._TT["DRAG_TO_MOVE"] = "Terhiqe per te levizur"; +Calendar._TT["PART_TODAY"] = " (sot)"; + +// "%s" eshte dita e pare e javes +// %s do te zevendesohet me emrin e dite +Calendar._TT["DAY_FIRST"] = "Trego te %s te paren"; + + +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Mbyll"; +Calendar._TT["TODAY"] = "Sot"; +Calendar._TT["TIME_PART"] = "Kliko me (Shift-)ose terhiqe per te ndryshuar +vleren"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Java"; +Calendar._TT["TIME"] = "Koha:"; + diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-bg.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-bg.js new file mode 100644 index 0000000..5eb73ec --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-bg.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar BG language +// Author: Mihai Bazon, +// Translator: Valentin Sheiretsky, +// Encoding: Windows-1251 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Íĺäĺë˙", + "Ďîíĺäĺëíčę", + "Âňîđíčę", + "Ńđ˙äŕ", + "×ĺňâúđňúę", + "Ďĺňúę", + "Ńúáîňŕ", + "Íĺäĺë˙"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Íĺä", + "Ďîí", + "Âňî", + "Ńđ˙", + "×ĺň", + "Ďĺň", + "Ńúá", + "Íĺä"); + +// full month names +Calendar._MN = new Array +("ßíóŕđč", + "Ôĺâđóŕđč", + "Ěŕđň", + "Ŕďđčë", + "Ěŕé", + "Ţíč", + "Ţëč", + "Ŕâăóńň", + "Ńĺďňĺěâđč", + "Îęňîěâđč", + "Íîĺěâđč", + "Äĺęĺěâđč"); + +// short month names +Calendar._SMN = new Array +("ßíó", + "Ôĺâ", + "Ěŕđ", + "Ŕďđ", + "Ěŕé", + "Ţíč", + "Ţëč", + "Ŕâă", + "Ńĺď", + "Îęň", + "Íîĺ", + "Äĺę"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Číôîđěŕöč˙ çŕ ęŕëĺíäŕđŕ"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Ďđĺäíŕ ăîäčíŕ (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["PREV_MONTH"] = "Ďđĺäĺí ěĺńĺö (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["GO_TODAY"] = "Čçáĺđĺňĺ äíĺń"; +Calendar._TT["NEXT_MONTH"] = "Ńëĺäâŕů ěĺńĺö (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["NEXT_YEAR"] = "Ńëĺäâŕůŕ ăîäčíŕ (çŕäđúćňĺ çŕ ěĺíţ)"; +Calendar._TT["SEL_DATE"] = "Čçáĺđĺňĺ äŕňŕ"; +Calendar._TT["DRAG_TO_MOVE"] = "Ďđĺěĺńňâŕíĺ"; +Calendar._TT["PART_TODAY"] = " (äíĺń)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "%s ęŕňî ďúđâč äĺí"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Çŕňâîđĺňĺ"; +Calendar._TT["TODAY"] = "Äíĺń"; +Calendar._TT["TIME_PART"] = "(Shift-)Click čëč drag çŕ äŕ ďđîěĺíčňĺ ńňîéíîńňňŕ"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A - %e %B %Y"; + +Calendar._TT["WK"] = "Ńĺäě"; +Calendar._TT["TIME"] = "×ŕń:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-big5-utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-big5-utf8.js new file mode 100644 index 0000000..14e0d5d --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-big5-utf8.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar big5-utf8 language +// Author: Gary Fu, +// Encoding: utf8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("ćźćśźć—Ą", + "ćźćśźä¸€", + "ćźćśźäşŚ", + "ćźćśźä¸‰", + "ćźćśźĺ››", + "ćźćśźäş”", + "ćźćśźĺ…­", + "ćźćśźć—Ą"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("ć—Ą", + "一", + "二", + "三", + "ĺ››", + "äş”", + "ĺ…­", + "ć—Ą"); + +// full month names +Calendar._MN = new Array +("一ćś", + "二ćś", + "三ćś", + "ĺ››ćś", + "äş”ćś", + "ĺ…­ćś", + "ä¸ćś", + "ĺ…«ćś", + "äąťćś", + "ĺŤćś", + "ĺŤä¸€ćś", + "ĺŤäşŚćś"); + +// short month names +Calendar._SMN = new Array +("一ćś", + "二ćś", + "三ćś", + "ĺ››ćś", + "äş”ćś", + "ĺ…­ćś", + "ä¸ćś", + "ĺ…«ćś", + "äąťćś", + "ĺŤćś", + "ĺŤä¸€ćś", + "ĺŤäşŚćś"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "é—ść–Ľ"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"ć—Ąćśźé¸ć“‡ć–ąćł•:\n" + +"- 使用 \xab, \xbb 按é•ĺŹŻé¸ć“‡ĺą´ä»˝\n" + +"- 使用 " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " 按é•ĺŹŻé¸ć“‡ćśä»˝\n" + +"- 按住上面的按é•ĺŹŻä»ĄĺŠ ĺż«é¸ĺŹ–"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"時間é¸ć“‡ć–ąćł•:\n" + +"- 點擊任何的時間é¨ä»˝ĺŹŻĺ˘žĺŠ ĺ…¶ĺ€Ľ\n" + +"- ĺŚć™‚按Shift鍵再點擊可減少其值\n" + +"- 點擊並拖曳可加快改變的值"; + +Calendar._TT["PREV_YEAR"] = "上一年 (按住é¸ĺ–®)"; +Calendar._TT["PREV_MONTH"] = "下一年 (按住é¸ĺ–®)"; +Calendar._TT["GO_TODAY"] = "ĺ°ä»Šć—Ą"; +Calendar._TT["NEXT_MONTH"] = "ä¸Šä¸€ćś (按住é¸ĺ–®)"; +Calendar._TT["NEXT_YEAR"] = "ä¸‹ä¸€ćś (按住é¸ĺ–®)"; +Calendar._TT["SEL_DATE"] = "é¸ć“‡ć—Ąćśź"; +Calendar._TT["DRAG_TO_MOVE"] = "拖曳"; +Calendar._TT["PART_TODAY"] = " (今日)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "ĺ°‡ %s 顯示在前"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "é—śé–‰"; +Calendar._TT["TODAY"] = "今日"; +Calendar._TT["TIME_PART"] = "點擊or拖曳可改變時間(ĺŚć™‚按Shift為減)"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "週"; +Calendar._TT["TIME"] = "Time:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-big5.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-big5.js new file mode 100644 index 0000000..a589358 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-big5.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar big5 language +// Author: Gary Fu, +// Encoding: big5 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("¬P´Á¤é", + "¬P´Á¤@", + "¬P´Á¤G", + "¬P´Á¤T", + "¬P´ÁĄ|", + "¬P´Á¤­", + "¬P´Á¤»", + "¬P´Á¤é"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("¤é", + "¤@", + "¤G", + "¤T", + "Ą|", + "¤­", + "¤»", + "¤é"); + +// full month names +Calendar._MN = new Array +("¤@¤ë", + "¤G¤ë", + "¤T¤ë", + "Ą|¤ë", + "¤­¤ë", + "¤»¤ë", + "¤C¤ë", + "¤K¤ë", + "¤E¤ë", + "¤Q¤ë", + "¤Q¤@¤ë", + "¤Q¤G¤ë"); + +// short month names +Calendar._SMN = new Array +("¤@¤ë", + "¤G¤ë", + "¤T¤ë", + "Ą|¤ë", + "¤­¤ë", + "¤»¤ë", + "¤C¤ë", + "¤K¤ë", + "¤E¤ë", + "¤Q¤ë", + "¤Q¤@¤ë", + "¤Q¤G¤ë"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Ăö©ó"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"¤é´ÁżďľÜ¤čŞk:\n" + +"- ¨ĎĄÎ \xab, \xbb «ö¶sĄiżďľÜ¦~Ą÷\n" + +"- ¨ĎĄÎ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " «ö¶sĄiżďľÜ¤ëĄ÷\n" + +"- «ö¦í¤W­±Şş«ö¶sĄiĄHĄ[§Öżď¨ú"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"®É¶ˇżďľÜ¤čŞk:\n" + +"- ÂIŔ»Ąô¦óŞş®É¶ˇłˇĄ÷ĄiĽWĄ[¨ä­Č\n" + +"- ¦P®É«öShiftÁä¦AÂIŔ»Ąi´î¤Ö¨ä­Č\n" + +"- ÂIŔ»¨Ă©ě¦˛ĄiĄ[§Ö§ďĹÜŞş­Č"; + +Calendar._TT["PREV_YEAR"] = "¤W¤@¦~ («ö¦íżďłć)"; +Calendar._TT["PREV_MONTH"] = "¤U¤@¦~ («ö¦íżďłć)"; +Calendar._TT["GO_TODAY"] = "¨ě¤µ¤é"; +Calendar._TT["NEXT_MONTH"] = "¤W¤@¤ë («ö¦íżďłć)"; +Calendar._TT["NEXT_YEAR"] = "¤U¤@¤ë («ö¦íżďłć)"; +Calendar._TT["SEL_DATE"] = "żďľÜ¤é´Á"; +Calendar._TT["DRAG_TO_MOVE"] = "©ě¦˛"; +Calendar._TT["PART_TODAY"] = " (¤µ¤é)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "±N %s ĹăĄÜ¦b«e"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Ăöł¬"; +Calendar._TT["TODAY"] = "¤µ¤é"; +Calendar._TT["TIME_PART"] = "ÂIŔ»or©ě¦˛Ąi§ďĹܮɶˇ(¦P®É«öShift¬°´î)"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "¶g"; +Calendar._TT["TIME"] = "Time:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-br.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-br.js new file mode 100644 index 0000000..bfb0747 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-br.js @@ -0,0 +1,108 @@ +// ** I18N + +// Calendar pt-BR language +// Author: Fernando Dourado, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Segunda", + "Terça", + "Quarta", + "Quinta", + "Sexta", + "Sabádo", + "Domingo"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +// [No changes using default values] + +// full month names +Calendar._MN = new Array +("Janeiro", + "Fevereiro", + "Março", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro"); + +// short month names +// [No changes using default values] + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Sobre o calendário"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Translate to portuguese Brazil (pt-BR) by Fernando Dourado (fernando.dourado@ig.com.br)\n" + +"Tradução para o portuguĂŞs Brasil (pt-BR) por Fernando Dourado (fernando.dourado@ig.com.br)" + +"\n\n" + +"Selecionar data:\n" + +"- Use as teclas \xab, \xbb para selecionar o ano\n" + +"- Use as teclas " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mĂŞs\n" + +"- Clique e segure com o mouse em qualquer botĂŁo para selecionar rapidamente."; + +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selecionar hora:\n" + +"- Clique em qualquer uma das partes da hora para aumentar\n" + +"- ou Shift-clique para diminuir\n" + +"- ou clique e arraste para selecionar rapidamente."; + +Calendar._TT["PREV_YEAR"] = "Ano anterior (clique e segure para menu)"; +Calendar._TT["PREV_MONTH"] = "MĂŞs anterior (clique e segure para menu)"; +Calendar._TT["GO_TODAY"] = "Ir para a data atual"; +Calendar._TT["NEXT_MONTH"] = "PrĂłximo mĂŞs (clique e segure para menu)"; +Calendar._TT["NEXT_YEAR"] = "PrĂłximo ano (clique e segure para menu)"; +Calendar._TT["SEL_DATE"] = "Selecione uma data"; +Calendar._TT["DRAG_TO_MOVE"] = "Clique e segure para mover"; +Calendar._TT["PART_TODAY"] = " (hoje)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Exibir %s primeiro"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Fechar"; +Calendar._TT["TODAY"] = "Hoje"; +Calendar._TT["TIME_PART"] = "(Shift-)Clique ou arraste para mudar o valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%d de %B de %Y"; + +Calendar._TT["WK"] = "sem"; +Calendar._TT["TIME"] = "Hora:"; + diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ca.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ca.js new file mode 100644 index 0000000..a2121bc --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ca.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar CA language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Diumenge", + "Dilluns", + "Dimarts", + "Dimecres", + "Dijous", + "Divendres", + "Dissabte", + "Diumenge"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Diu", + "Dil", + "Dmt", + "Dmc", + "Dij", + "Div", + "Dis", + "Diu"); + +// full month names +Calendar._MN = new Array +("Gener", + "Febrer", + "Març", + "Abril", + "Maig", + "Juny", + "Juliol", + "Agost", + "Setembre", + "Octubre", + "Novembre", + "Desembre"); + +// short month names +Calendar._SMN = new Array +("Gen", + "Feb", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Oct", + "Nov", + "Des"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Sobre el calendari"; + +Calendar._TT["ABOUT"] = +"DHTML Selector de Data/Hora\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Sel.lecció de Dates:\n" + +"- Fes servir els botons \xab, \xbb per sel.leccionar l'any\n" + +"- Fes servir els botons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per se.lecciconar el mes\n" + +"- Manté el ratolí apretat en qualsevol dels anteriors per sel.lecció rŕpida."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- claca en qualsevol de les parts de la hora per augmentar-les\n" + +"- o Shift-click per decrementar-la\n" + +"- or click and arrastra per sel.lecció rŕpida."; + +Calendar._TT["PREV_YEAR"] = "Any anterior (Mantenir per menu)"; +Calendar._TT["PREV_MONTH"] = "Mes anterior (Mantenir per menu)"; +Calendar._TT["GO_TODAY"] = "Anar a avui"; +Calendar._TT["NEXT_MONTH"] = "Mes següent (Mantenir per menu)"; +Calendar._TT["NEXT_YEAR"] = "Any següent (Mantenir per menu)"; +Calendar._TT["SEL_DATE"] = "Sel.leccionar data"; +Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar per moure"; +Calendar._TT["PART_TODAY"] = " (avui)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostra %s primer"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Tanca"; +Calendar._TT["TODAY"] = "Avui"; +Calendar._TT["TIME_PART"] = "(Shift-)Click a arrastra per canviar el valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "st"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-cs-utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-cs-utf8.js new file mode 100644 index 0000000..f6bbbeb --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-cs-utf8.js @@ -0,0 +1,65 @@ +/* + calendar-cs-win.js + language: Czech + encoding: windows-1250 + author: Lubos Jerabek (xnet@seznam.cz) + Jan Uhlir (espinosa@centrum.cz) +*/ + +// ** I18N +Calendar._DN = new Array('NedÄ›le','PondÄ›lĂ­','ĂšterĂ˝','StĹ™eda','ÄŚtvrtek','Pátek','Sobota','NedÄ›le'); +Calendar._SDN = new Array('Ne','Po','Ăšt','St','ÄŚt','Pá','So','Ne'); +Calendar._MN = new Array('Leden','Ăšnor','BĹ™ezen','Duben','KvÄ›ten','ÄŚerven','ÄŚervenec','Srpen','Září','ĹĂ­jen','Listopad','Prosinec'); +Calendar._SMN = new Array('Led','Ăšno','BĹ™e','Dub','KvÄ›','ÄŚrv','ÄŚvc','Srp','Zář','ĹĂ­j','Lis','Pro'); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O komponentÄ› kalendář"; +Calendar._TT["TOGGLE"] = "ZmÄ›na prvnĂ­ho dne v tĂ˝dnu"; +Calendar._TT["PREV_YEAR"] = "PĹ™edchozĂ­ rok (pĹ™idrĹľ pro menu)"; +Calendar._TT["PREV_MONTH"] = "PĹ™edchozĂ­ mÄ›sĂ­c (pĹ™idrĹľ pro menu)"; +Calendar._TT["GO_TODAY"] = "DnešnĂ­ datum"; +Calendar._TT["NEXT_MONTH"] = "Další mÄ›sĂ­c (pĹ™idrĹľ pro menu)"; +Calendar._TT["NEXT_YEAR"] = "Další rok (pĹ™idrĹľ pro menu)"; +Calendar._TT["SEL_DATE"] = "Vyber datum"; +Calendar._TT["DRAG_TO_MOVE"] = "ChyĹĄ a táhni, pro pĹ™esun"; +Calendar._TT["PART_TODAY"] = " (dnes)"; +Calendar._TT["MON_FIRST"] = "UkaĹľ jako prvnĂ­ PondÄ›lĂ­"; +//Calendar._TT["SUN_FIRST"] = "UkaĹľ jako prvnĂ­ NedÄ›li"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"VĂ˝bÄ›r datumu:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- PouĹľijte tlaÄŤĂ­tka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " k vĂ˝bÄ›ru mÄ›sĂ­ce\n" + +"- PodrĹľte tlaÄŤĂ­tko myši na jakĂ©mkoliv z tÄ›ch tlaÄŤĂ­tek pro rychlejší vĂ˝bÄ›r."; + +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"VĂ˝bÄ›r ÄŤasu:\n" + +"- KliknÄ›te na jakoukoliv z částĂ­ vĂ˝bÄ›ru ÄŤasu pro zvýšenĂ­.\n" + +"- nebo Shift-click pro snĂ­ĹľenĂ­\n" + +"- nebo kliknÄ›te a táhnÄ›te pro rychlejší vĂ˝bÄ›r."; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Zobraz %s prvnĂ­"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Zavřít"; +Calendar._TT["TODAY"] = "Dnes"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikni nebo táhni pro zmÄ›nu hodnoty"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "ÄŚas:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-cs-win.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-cs-win.js new file mode 100644 index 0000000..140dff3 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-cs-win.js @@ -0,0 +1,65 @@ +/* + calendar-cs-win.js + language: Czech + encoding: windows-1250 + author: Lubos Jerabek (xnet@seznam.cz) + Jan Uhlir (espinosa@centrum.cz) +*/ + +// ** I18N +Calendar._DN = new Array('Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota','Neděle'); +Calendar._SDN = new Array('Ne','Po','Út','St','Čt','Pá','So','Ne'); +Calendar._MN = new Array('Leden','Únor','Březen','Duben','Květen','Červen','Červenec','Srpen','Září','Říjen','Listopad','Prosinec'); +Calendar._SMN = new Array('Led','Úno','Bře','Dub','Kvě','Črv','Čvc','Srp','Zář','Říj','Lis','Pro'); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O komponentě kalendář"; +Calendar._TT["TOGGLE"] = "Změna prvního dne v týdnu"; +Calendar._TT["PREV_YEAR"] = "Předchozí rok (přidrž pro menu)"; +Calendar._TT["PREV_MONTH"] = "Předchozí měsíc (přidrž pro menu)"; +Calendar._TT["GO_TODAY"] = "Dnešní datum"; +Calendar._TT["NEXT_MONTH"] = "Další měsíc (přidrž pro menu)"; +Calendar._TT["NEXT_YEAR"] = "Další rok (přidrž pro menu)"; +Calendar._TT["SEL_DATE"] = "Vyber datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Chyť a táhni, pro přesun"; +Calendar._TT["PART_TODAY"] = " (dnes)"; +Calendar._TT["MON_FIRST"] = "Ukaž jako první Pondělí"; +//Calendar._TT["SUN_FIRST"] = "Ukaž jako první Neděli"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Výběr datumu:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Použijte tlačítka " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " k výběru měsíce\n" + +"- Podržte tlačítko myši na jakémkoliv z těch tlačítek pro rychlejší výběr."; + +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Výběr času:\n" + +"- Klikněte na jakoukoliv z částí výběru času pro zvýšení.\n" + +"- nebo Shift-click pro snížení\n" + +"- nebo klikněte a táhněte pro rychlejší výběr."; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Zobraz %s první"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Zavřít"; +Calendar._TT["TODAY"] = "Dnes"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikni nebo táhni pro změnu hodnoty"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "d.m.yy"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Čas:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-da.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-da.js new file mode 100644 index 0000000..a99b598 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-da.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar DA language +// Author: Michael Thingmand Henriksen, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Søndag", +"Mandag", +"Tirsdag", +"Onsdag", +"Torsdag", +"Fredag", +"Lørdag", +"Søndag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Søn", +"Man", +"Tir", +"Ons", +"Tor", +"Fre", +"Lør", +"Søn"); + +// full month names +Calendar._MN = new Array +("Januar", +"Februar", +"Marts", +"April", +"Maj", +"Juni", +"Juli", +"August", +"September", +"Oktober", +"November", +"December"); + +// short month names +Calendar._SMN = new Array +("Jan", +"Feb", +"Mar", +"Apr", +"Maj", +"Jun", +"Jul", +"Aug", +"Sep", +"Okt", +"Nov", +"Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Om Kalenderen"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For den seneste version besøg: http://www.dynarch.com/projects/calendar/\n"; + +"Distribueret under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detajler." + +"\n\n" + +"Valg af dato:\n" + +"- Brug \xab, \xbb knapperne for at vælge ĂĄr\n" + +"- Brug " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knapperne for at vælge mĂĄned\n" + +"- Hold knappen pĂĄ musen nede pĂĄ knapperne ovenfor for hurtigere valg."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Valg af tid:\n" + +"- Klik pĂĄ en vilkĂĄrlig del for større værdi\n" + +"- eller Shift-klik for for mindre værdi\n" + +"- eller klik og træk for hurtigere valg."; + +Calendar._TT["PREV_YEAR"] = "Ét ĂĄr tilbage (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Én mĂĄned tilbage (hold for menu)"; +Calendar._TT["GO_TODAY"] = "GĂĄ til i dag"; +Calendar._TT["NEXT_MONTH"] = "Én mĂĄned frem (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Ét ĂĄr frem (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Vælg dag"; +Calendar._TT["DRAG_TO_MOVE"] = "Træk vinduet"; +Calendar._TT["PART_TODAY"] = " (i dag)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Vis %s først"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Luk"; +Calendar._TT["TODAY"] = "I dag"; +Calendar._TT["TIME_PART"] = "(Shift-)klik eller træk for at ændre værdi"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Uge"; +Calendar._TT["TIME"] = "Tid:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-de.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-de.js new file mode 100644 index 0000000..4bc1137 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-de.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar DE language +// Author: Jack (tR), +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sonntag", + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag", + "Sonntag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("So", + "Mo", + "Di", + "Mi", + "Do", + "Fr", + "Sa", + "So"); + +// full month names +Calendar._MN = new Array +("Januar", + "Februar", + "M\u00e4rz", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Dezember"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "M\u00e4r", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Dez"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "\u00DCber dieses Kalendarmodul"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Datum ausw\u00e4hlen:\n" + +"- Benutzen Sie die \xab, \xbb Buttons um das Jahr zu w\u00e4hlen\n" + +"- Benutzen Sie die " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " Buttons um den Monat zu w\u00e4hlen\n" + +"- F\u00fcr eine Schnellauswahl halten Sie die Maustaste \u00fcber diesen Buttons fest."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Zeit ausw\u00e4hlen:\n" + +"- Klicken Sie auf die Teile der Uhrzeit, um diese zu erh\u00F6hen\n" + +"- oder klicken Sie mit festgehaltener Shift-Taste um diese zu verringern\n" + +"- oder klicken und festhalten f\u00fcr Schnellauswahl."; + +Calendar._TT["TOGGLE"] = "Ersten Tag der Woche w\u00e4hlen"; +Calendar._TT["PREV_YEAR"] = "Voriges Jahr (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["PREV_MONTH"] = "Voriger Monat (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["GO_TODAY"] = "Heute ausw\u00e4hlen"; +Calendar._TT["NEXT_MONTH"] = "N\u00e4chst. Monat (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["NEXT_YEAR"] = "N\u00e4chst. Jahr (Festhalten f\u00fcr Schnellauswahl)"; +Calendar._TT["SEL_DATE"] = "Datum ausw\u00e4hlen"; +Calendar._TT["DRAG_TO_MOVE"] = "Zum Bewegen festhalten"; +Calendar._TT["PART_TODAY"] = " (Heute)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Woche beginnt mit %s "; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Schlie\u00dfen"; +Calendar._TT["TODAY"] = "Heute"; +Calendar._TT["TIME_PART"] = "(Shift-)Klick oder Festhalten und Ziehen um den Wert zu \u00e4ndern"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Zeit:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-du.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-du.js new file mode 100644 index 0000000..2200448 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-du.js @@ -0,0 +1,45 @@ +// ** I18N +Calendar._DN = new Array +("Zondag", + "Maandag", + "Dinsdag", + "Woensdag", + "Donderdag", + "Vrijdag", + "Zaterdag", + "Zondag"); +Calendar._MN = new Array +("Januari", + "Februari", + "Maart", + "April", + "Mei", + "Juni", + "Juli", + "Augustus", + "September", + "Oktober", + "November", + "December"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Toggle startdag van de week"; +Calendar._TT["PREV_YEAR"] = "Vorig jaar (indrukken voor menu)"; +Calendar._TT["PREV_MONTH"] = "Vorige month (indrukken voor menu)"; +Calendar._TT["GO_TODAY"] = "Naar Vandaag"; +Calendar._TT["NEXT_MONTH"] = "Volgende Maand (indrukken voor menu)"; +Calendar._TT["NEXT_YEAR"] = "Volgend jaar (indrukken voor menu)"; +Calendar._TT["SEL_DATE"] = "Selecteer datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Sleep om te verplaatsen"; +Calendar._TT["PART_TODAY"] = " (vandaag)"; +Calendar._TT["MON_FIRST"] = "Toon Maandag eerst"; +Calendar._TT["SUN_FIRST"] = "Toon Zondag eerst"; +Calendar._TT["CLOSE"] = "Sluiten"; +Calendar._TT["TODAY"] = "Vandaag"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd"; +Calendar._TT["TT_DATE_FORMAT"] = "D, M d"; + +Calendar._TT["WK"] = "wk"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-el.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-el.js new file mode 100644 index 0000000..43a9b2c --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-el.js @@ -0,0 +1,89 @@ +// ** I18N +Calendar._DN = new Array +("ΚυĎιακή", + "ΔευτέĎα", + "ΤĎίτη", + "ΤετάĎτη", + "Πέμπτη", + "ΠαĎαĎκευή", + "Σάββατο", + "ΚυĎιακή"); + +Calendar._SDN = new Array +("Κυ", + "Δε", + "TĎ", + "Τε", + "Πε", + "Πα", + "Σα", + "Κυ"); + +Calendar._MN = new Array +("ΙανουάĎιος", + "ΦεβĎουάĎιος", + "ΜάĎτιος", + "ΑπĎίλιος", + "Μάϊος", + "Ιούνιος", + "Ιούλιος", + "ΑύγουĎτος", + "ΣεπτέμβĎιος", + "ΟκτώβĎιος", + "ΝοέμβĎιος", + "ΔεκέμβĎιος"); + +Calendar._SMN = new Array +("Ιαν", + "Φεβ", + "ΜαĎ", + "ΑπĎ", + "Μαι", + "Ιουν", + "Ιουλ", + "Αυγ", + "Σεπ", + "Οκτ", + "Νοε", + "Δεκ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Για Ď„Îż ημεĎολόγιο"; + +Calendar._TT["ABOUT"] = +"Επιλογέας ημεĎομηνίας/ĎŽĎας Ďε DHTML\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Για τελευταία έκδοĎη: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Επιλογή ημεĎομηνίας:\n" + +"- ΧĎηĎιμοποιείĎτε τα κουμπιά \xab, \xbb για επιλογή έτους\n" + +"- ΧĎηĎιμοποιείĎτε τα κουμπιά " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " για επιλογή μήνα\n" + +"- ΚĎατήĎτε κουμπί ποντικού πατημένο Ďτα παĎαπάνω κουμπιά για πιο ÎłĎήγοĎη επιλογή."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Επιλογή ĎŽĎας:\n" + +"- Κάντε κλικ Ďε ένα από τα μέĎη της ĎŽĎας για αύξηĎη\n" + +"- ή Shift-κλικ για μείωĎη\n" + +"- ή κλικ και μετακίνηĎη για πιο ÎłĎήγοĎη επιλογή."; +Calendar._TT["TOGGLE"] = "ΜπάĎα Ď€Ďώτης ημέĎας της εβδομάδας"; +Calendar._TT["PREV_YEAR"] = "ΠĎοηγ. έτος (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["PREV_MONTH"] = "ΠĎοηγ. μήνας (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["GO_TODAY"] = "ΣήμεĎα"; +Calendar._TT["NEXT_MONTH"] = "Επόμενος μήνας (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["NEXT_YEAR"] = "Επόμενο έτος (ÎşĎατήĎτε για Ď„Îż μενού)"; +Calendar._TT["SEL_DATE"] = "Επιλέξτε ημεĎομηνία"; +Calendar._TT["DRAG_TO_MOVE"] = "ÎŁĎŤĎτε για να μετακινήĎετε"; +Calendar._TT["PART_TODAY"] = " (ĎήμεĎα)"; +Calendar._TT["MON_FIRST"] = "ΕμφάνιĎη ΔευτέĎας Ď€Ďώτα"; +Calendar._TT["SUN_FIRST"] = "ΕμφάνιĎη ΚυĎιακής Ď€Ďώτα"; +Calendar._TT["CLOSE"] = "ΚλείĎιμο"; +Calendar._TT["TODAY"] = "ΣήμεĎα"; +Calendar._TT["TIME_PART"] = "(Shift-)κλικ ή μετακίνηĎη για αλλαγή"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y"; +Calendar._TT["TT_DATE_FORMAT"] = "D, d M"; + +Calendar._TT["WK"] = "εβδ"; + diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-en.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-en.js new file mode 100644 index 0000000..0dbde79 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-en.js @@ -0,0 +1,127 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + +// full month names +Calendar._MN = new Array +("January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "About the calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + +Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; +Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; +Calendar._TT["GO_TODAY"] = "Go Today"; +Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)"; +Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)"; +Calendar._TT["SEL_DATE"] = "Select date"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag to move"; +Calendar._TT["PART_TODAY"] = " (today)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Display %s first"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Close"; +Calendar._TT["TODAY"] = "Today"; +Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Time:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-es.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-es.js new file mode 100644 index 0000000..19c1b30 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-es.js @@ -0,0 +1,129 @@ +// ** I18N + +// Calendar ES (spanish) language +// Author: Mihai Bazon, +// Updater: Servilio Afre Puentes +// Updated: 2004-06-03 +// Encoding: utf-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Lunes", + "Martes", + "Miércoles", + "Jueves", + "Viernes", + "Sábado", + "Domingo"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dom", + "Lun", + "Mar", + "Mié", + "Jue", + "Vie", + "Sáb", + "Dom"); + +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 1; + +// full month names +Calendar._MN = new Array +("Enero", + "Febrero", + "Marzo", + "Abril", + "Mayo", + "Junio", + "Julio", + "Agosto", + "Septiembre", + "Octubre", + "Noviembre", + "Diciembre"); + +// short month names +Calendar._SMN = new Array +("Ene", + "Feb", + "Mar", + "Abr", + "May", + "Jun", + "Jul", + "Ago", + "Sep", + "Oct", + "Nov", + "Dic"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Acerca del calendario"; + +Calendar._TT["ABOUT"] = +"Selector DHTML de Fecha/Hora\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Para conseguir la última versión visite: http://www.dynarch.com/projects/calendar/\n" + +"Distribuido bajo licencia GNU LGPL. Visite http://gnu.org/licenses/lgpl.html para más detalles." + +"\n\n" + +"Selección de fecha:\n" + +"- Use los botones \xab, \xbb para seleccionar el ańo\n" + +"- Use los botones " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar el mes\n" + +"- Mantenga pulsado el ratón en cualquiera de estos botones para una selección rápida."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selección de hora:\n" + +"- Pulse en cualquiera de las partes de la hora para incrementarla\n" + +"- o pulse las mayúsculas mientras hace clic para decrementarla\n" + +"- o haga clic y arrastre el ratón para una selección más rápida."; + +Calendar._TT["PREV_YEAR"] = "Ańo anterior (mantener para menú)"; +Calendar._TT["PREV_MONTH"] = "Mes anterior (mantener para menú)"; +Calendar._TT["GO_TODAY"] = "Ir a hoy"; +Calendar._TT["NEXT_MONTH"] = "Mes siguiente (mantener para menú)"; +Calendar._TT["NEXT_YEAR"] = "Ańo siguiente (mantener para menú)"; +Calendar._TT["SEL_DATE"] = "Seleccionar fecha"; +Calendar._TT["DRAG_TO_MOVE"] = "Arrastrar para mover"; +Calendar._TT["PART_TODAY"] = " (hoy)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Hacer %s primer día de la semana"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Cerrar"; +Calendar._TT["TODAY"] = "Hoy"; +Calendar._TT["TIME_PART"] = "(Mayúscula-)Clic o arrastre para cambiar valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y"; + +Calendar._TT["WK"] = "sem"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-fi.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-fi.js new file mode 100644 index 0000000..328eabb --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-fi.js @@ -0,0 +1,98 @@ +// ** I18N + +// Calendar FI language (Finnish, Suomi) +// Author: Jarno Käyhkö, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// full day names +Calendar._DN = new Array +("Sunnuntai", + "Maanantai", + "Tiistai", + "Keskiviikko", + "Torstai", + "Perjantai", + "Lauantai", + "Sunnuntai"); + +// short day names +Calendar._SDN = new Array +("Su", + "Ma", + "Ti", + "Ke", + "To", + "Pe", + "La", + "Su"); + +// full month names +Calendar._MN = new Array +("Tammikuu", + "Helmikuu", + "Maaliskuu", + "Huhtikuu", + "Toukokuu", + "Kesäkuu", + "Heinäkuu", + "Elokuu", + "Syyskuu", + "Lokakuu", + "Marraskuu", + "Joulukuu"); + +// short month names +Calendar._SMN = new Array +("Tam", + "Hel", + "Maa", + "Huh", + "Tou", + "Kes", + "Hei", + "Elo", + "Syy", + "Lok", + "Mar", + "Jou"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Tietoja kalenterista"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Uusin versio osoitteessa: http://www.dynarch.com/projects/calendar/\n" + +"Julkaistu GNU LGPL lisenssin alaisuudessa. Lisätietoja osoitteessa http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"Päivämäärä valinta:\n" + +"- Käytä \xab, \xbb painikkeita valitaksesi vuosi\n" + +"- Käytä " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " painikkeita valitaksesi kuukausi\n" + +"- Pitämällä hiiren painiketta minkä tahansa yllä olevan painikkeen kohdalla, saat näkyviin valikon nopeampaan siirtymiseen."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Ajan valinta:\n" + +"- Klikkaa kellonajan numeroita lisätäksesi aikaa\n" + +"- tai pitämällä Shift-näppäintä pohjassa saat aikaa taaksepäin\n" + +"- tai klikkaa ja pidä hiiren painike pohjassa sekä liikuta hiirtä muuttaaksesi aikaa nopeasti eteen- ja taaksepäin."; + +Calendar._TT["PREV_YEAR"] = "Edell. vuosi (paina hetki, näet valikon)"; +Calendar._TT["PREV_MONTH"] = "Edell. kuukausi (paina hetki, näet valikon)"; +Calendar._TT["GO_TODAY"] = "Siirry tähän päivään"; +Calendar._TT["NEXT_MONTH"] = "Seur. kuukausi (paina hetki, näet valikon)"; +Calendar._TT["NEXT_YEAR"] = "Seur. vuosi (paina hetki, näet valikon)"; +Calendar._TT["SEL_DATE"] = "Valitse päivämäärä"; +Calendar._TT["DRAG_TO_MOVE"] = "Siirrä kalenterin paikkaa"; +Calendar._TT["PART_TODAY"] = " (tänään)"; +Calendar._TT["MON_FIRST"] = "Näytä maanantai ensimmäisenä"; +Calendar._TT["SUN_FIRST"] = "Näytä sunnuntai ensimmäisenä"; +Calendar._TT["CLOSE"] = "Sulje"; +Calendar._TT["TODAY"] = "Tänään"; +Calendar._TT["TIME_PART"] = "(Shift-) Klikkaa tai liikuta muuttaaksesi aikaa"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%d.%m.%Y"; + +Calendar._TT["WK"] = "Vko"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-fr.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-fr.js new file mode 100644 index 0000000..2a9e0b2 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-fr.js @@ -0,0 +1,125 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// Translator: David Duret, from previous french version + +// full day names +Calendar._DN = new Array +("Dimanche", + "Lundi", + "Mardi", + "Mercredi", + "Jeudi", + "Vendredi", + "Samedi", + "Dimanche"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dim", + "Lun", + "Mar", + "Mar", + "Jeu", + "Ven", + "Sam", + "Dim"); + +// full month names +Calendar._MN = new Array +("Janvier", + "Février", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Aoűt", + "Septembre", + "Octobre", + "Novembre", + "Décembre"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Fev", + "Mar", + "Avr", + "Mai", + "Juin", + "Juil", + "Aout", + "Sep", + "Oct", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "A propos du calendrier"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Heure Selecteur\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Pour la derniere version visitez : http://www.dynarch.com/projects/calendar/\n" + +"Distribué par GNU LGPL. Voir http://gnu.org/licenses/lgpl.html pour les details." + +"\n\n" + +"Selection de la date :\n" + +"- Utiliser les bouttons \xab, \xbb pour selectionner l\'annee\n" + +"- Utiliser les bouttons " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pour selectionner les mois\n" + +"- Garder la souris sur n'importe quels boutons pour une selection plus rapide"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selection de l\'heure :\n" + +"- Cliquer sur heures ou minutes pour incrementer\n" + +"- ou Maj-clic pour decrementer\n" + +"- ou clic et glisser-deplacer pour une selection plus rapide"; + +Calendar._TT["PREV_YEAR"] = "Année préc. (maintenir pour menu)"; +Calendar._TT["PREV_MONTH"] = "Mois préc. (maintenir pour menu)"; +Calendar._TT["GO_TODAY"] = "Atteindre la date du jour"; +Calendar._TT["NEXT_MONTH"] = "Mois suiv. (maintenir pour menu)"; +Calendar._TT["NEXT_YEAR"] = "Année suiv. (maintenir pour menu)"; +Calendar._TT["SEL_DATE"] = "Sélectionner une date"; +Calendar._TT["DRAG_TO_MOVE"] = "Déplacer"; +Calendar._TT["PART_TODAY"] = " (Aujourd'hui)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Afficher %s en premier"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Fermer"; +Calendar._TT["TODAY"] = "Aujourd'hui"; +Calendar._TT["TIME_PART"] = "(Maj-)Clic ou glisser pour modifier la valeur"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Sem."; +Calendar._TT["TIME"] = "Heure :"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-he-utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-he-utf8.js new file mode 100644 index 0000000..7861217 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-he-utf8.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar EN language +// Author: Idan Sofer, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("ר×שון", + "שני", + "שלישי", + "רביעי", + "חמישי", + "שישי", + "שבת", + "ר×שון"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("×", + "ב", + "×’", + "ד", + "×”", + "ו", + "ש", + "×"); + +// full month names +Calendar._MN = new Array +("ינו×ר", + "פברו×ר", + "מרץ", + "×פריל", + "מ××™", + "יוני", + "יולי", + "×וגוס×", + "ספ×מבר", + "×וק×ובר", + "נובמבר", + "דצמבר"); + +// short month names +Calendar._SMN = new Array +("×™× ×", + "פבר", + "מרץ", + "×פר", + "מ××™", + "יונ", + "יול", + "×וג", + "ספ×", + "×וק", + "נוב", + "דצמ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "×ודות השנתון"; + +Calendar._TT["ABOUT"] = +"בחרן ת×ריך/שעה DHTML\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"×”×’×™×¨×ˇ× ×”×חרונה זמינה ב: http://www.dynarch.com/projects/calendar/\n" + +"מופץ תחת זיכיון ×” GNU LGPL. עיין ב http://gnu.org/licenses/lgpl.html לפר×ים נוספים." + +"\n\n" + +בחירת ת×ריך:\n" + +"- השתמש בכפתורים \xab, \xbb לבחירת שנה\n" + +"- השתמש בכפתורים " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " לבחירת חודש\n" + +"- החזק העכבר לחוץ מעל הכפתורים המוזכרים לעיל לבחירה מהירה יותר."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"בחירת זמן:\n" + +"- לחץ על כל ×חד מחלקי הזמן כדי להוסיף\n" + +"- ×ו shift בשילוב עם לחיצה כדי להחסיר\n" + +"- ×ו לחץ וגרור לפעולה מהירה יותר."; + +Calendar._TT["PREV_YEAR"] = "שנה קודמת - החזק לקבלת תפרי×"; +Calendar._TT["PREV_MONTH"] = "חודש קודם - החזק לקבלת תפרי×"; +Calendar._TT["GO_TODAY"] = "עבור להיום"; +Calendar._TT["NEXT_MONTH"] = "חודש ×”×‘× - החזק לתפרי×"; +Calendar._TT["NEXT_YEAR"] = "שנה הב××” - החזק לתפרי×"; +Calendar._TT["SEL_DATE"] = "בחר ת×ריך"; +Calendar._TT["DRAG_TO_MOVE"] = "גרור להזזה"; +Calendar._TT["PART_TODAY"] = " )היום("; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "הצג %s קודם"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "6"; + +Calendar._TT["CLOSE"] = "סגור"; +Calendar._TT["TODAY"] = "היום"; +Calendar._TT["TIME_PART"] = "(שיפ×-)לחץ וגרור כדי לשנות ערך"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "שעה::"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hr-utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hr-utf8.js new file mode 100644 index 0000000..d569cfd --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hr-utf8.js @@ -0,0 +1,49 @@ +/* Croatian language file for the DHTML Calendar version 0.9.2 +* Author Krunoslav Zubrinic , June 2003. +* Feel free to use this script under the terms of the GNU Lesser General +* Public License, as long as you do not remove or alter this notice. +*/ +Calendar._DN = new Array +("Nedjelja", + "Ponedjeljak", + "Utorak", + "Srijeda", + "ÄŚetvrtak", + "Petak", + "Subota", + "Nedjelja"); +Calendar._MN = new Array +("SijeÄŤanj", + "VeljaÄŤa", + "OĹľujak", + "Travanj", + "Svibanj", + "Lipanj", + "Srpanj", + "Kolovoz", + "Rujan", + "Listopad", + "Studeni", + "Prosinac"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Promjeni dan s kojim poÄŤinje tjedan"; +Calendar._TT["PREV_YEAR"] = "Prethodna godina (dugi pritisak za meni)"; +Calendar._TT["PREV_MONTH"] = "Prethodni mjesec (dugi pritisak za meni)"; +Calendar._TT["GO_TODAY"] = "Idi na tekući dan"; +Calendar._TT["NEXT_MONTH"] = "Slijedeći mjesec (dugi pritisak za meni)"; +Calendar._TT["NEXT_YEAR"] = "Slijedeća godina (dugi pritisak za meni)"; +Calendar._TT["SEL_DATE"] = "Izaberite datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Pritisni i povuci za promjenu pozicije"; +Calendar._TT["PART_TODAY"] = " (today)"; +Calendar._TT["MON_FIRST"] = "PrikaĹľi ponedjeljak kao prvi dan"; +Calendar._TT["SUN_FIRST"] = "PrikaĹľi nedjelju kao prvi dan"; +Calendar._TT["CLOSE"] = "Zatvori"; +Calendar._TT["TODAY"] = "Danas"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y"; +Calendar._TT["TT_DATE_FORMAT"] = "DD, dd.mm.y"; + +Calendar._TT["WK"] = "Tje"; \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hr.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hr.js new file mode 100644 index 0000000..6c27f60 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hr.js differ diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hu.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hu.js new file mode 100644 index 0000000..f5bf057 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-hu.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar HU language +// Author: ??? +// Modifier: KARASZI Istvan, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Vasárnap", + "Hétfő", + "Kedd", + "Szerda", + "Csütörtök", + "Péntek", + "Szombat", + "Vasárnap"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("v", + "h", + "k", + "sze", + "cs", + "p", + "szo", + "v"); + +// full month names +Calendar._MN = new Array +("január", + "február", + "március", + "április", + "május", + "június", + "július", + "augusztus", + "szeptember", + "október", + "november", + "december"); + +// short month names +Calendar._SMN = new Array +("jan", + "feb", + "már", + "ápr", + "máj", + "jún", + "júl", + "aug", + "sze", + "okt", + "nov", + "dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "A kalendáriumról"; + +Calendar._TT["ABOUT"] = +"DHTML dátum/idő kiválasztó\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"a legfrissebb verzió megtalálható: http://www.dynarch.com/projects/calendar/\n" + +"GNU LGPL alatt terjesztve. Lásd a http://gnu.org/licenses/lgpl.html oldalt a részletekhez." + +"\n\n" + +"Dátum választás:\n" + +"- használja a \xab, \xbb gombokat az év kiválasztásához\n" + +"- használja a " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gombokat a hónap kiválasztásához\n" + +"- tartsa lenyomva az egérgombot a gyors választáshoz."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Idő választás:\n" + +"- kattintva növelheti az időt\n" + +"- shift-tel kattintva csökkentheti\n" + +"- lenyomva tartva és húzva gyorsabban kiválaszthatja."; + +Calendar._TT["PREV_YEAR"] = "Előző év (tartsa nyomva a menühöz)"; +Calendar._TT["PREV_MONTH"] = "Előző hónap (tartsa nyomva a menühöz)"; +Calendar._TT["GO_TODAY"] = "Mai napra ugrás"; +Calendar._TT["NEXT_MONTH"] = "Köv. hónap (tartsa nyomva a menühöz)"; +Calendar._TT["NEXT_YEAR"] = "Köv. év (tartsa nyomva a menühöz)"; +Calendar._TT["SEL_DATE"] = "Válasszon dátumot"; +Calendar._TT["DRAG_TO_MOVE"] = "Húzza a mozgatáshoz"; +Calendar._TT["PART_TODAY"] = " (ma)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "%s legyen a hét első napja"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Bezár"; +Calendar._TT["TODAY"] = "Ma"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikk vagy húzás az érték változtatásához"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%b %e, %a"; + +Calendar._TT["WK"] = "hét"; +Calendar._TT["TIME"] = "idő:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-it.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-it.js new file mode 100644 index 0000000..7f84cde --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-it.js @@ -0,0 +1,124 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Translator: Fabio Di Bernardini, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domenica", + "Lunedì", + "Martedì", + "Mercoledì", + "Giovedì", + "Venerdì", + "Sabato", + "Domenica"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dom", + "Lun", + "Mar", + "Mer", + "Gio", + "Ven", + "Sab", + "Dom"); + +// full month names +Calendar._MN = new Array +("Gennaio", + "Febbraio", + "Marzo", + "Aprile", + "Maggio", + "Giugno", + "Luglio", + "Augosto", + "Settembre", + "Ottobre", + "Novembre", + "Dicembre"); + +// short month names +Calendar._SMN = new Array +("Gen", + "Feb", + "Mar", + "Apr", + "Mag", + "Giu", + "Lug", + "Ago", + "Set", + "Ott", + "Nov", + "Dic"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Informazioni sul calendario"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Per gli aggiornamenti: http://www.dynarch.com/projects/calendar/\n" + +"Distribuito sotto licenza GNU LGPL. Vedi http://gnu.org/licenses/lgpl.html per i dettagli." + +"\n\n" + +"Selezione data:\n" + +"- Usa \xab, \xbb per selezionare l'anno\n" + +"- Usa " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " per i mesi\n" + +"- Tieni premuto a lungo il mouse per accedere alle funzioni di selezione veloce."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selezione orario:\n" + +"- Clicca sul numero per incrementarlo\n" + +"- o Shift+click per decrementarlo\n" + +"- o click e sinistra o destra per variarlo."; + +Calendar._TT["PREV_YEAR"] = "Anno prec.(clicca a lungo per il menĂą)"; +Calendar._TT["PREV_MONTH"] = "Mese prec. (clicca a lungo per il menĂą)"; +Calendar._TT["GO_TODAY"] = "Oggi"; +Calendar._TT["NEXT_MONTH"] = "Pross. mese (clicca a lungo per il menĂą)"; +Calendar._TT["NEXT_YEAR"] = "Pross. anno (clicca a lungo per il menĂą)"; +Calendar._TT["SEL_DATE"] = "Seleziona data"; +Calendar._TT["DRAG_TO_MOVE"] = "Trascina per spostarlo"; +Calendar._TT["PART_TODAY"] = " (oggi)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostra prima %s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Chiudi"; +Calendar._TT["TODAY"] = "Oggi"; +Calendar._TT["TIME_PART"] = "(Shift-)Click o trascina per cambiare il valore"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a:%b:%e"; + +Calendar._TT["WK"] = "set"; +Calendar._TT["TIME"] = "Ora:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-jp.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-jp.js new file mode 100644 index 0000000..3bca7eb --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-jp.js @@ -0,0 +1,45 @@ +// ** I18N +Calendar._DN = new Array +("“ú", + "ŚŽ", + "‰Î", + "…", + "–Ř", + "‹ŕ", + "“y", + "“ú"); +Calendar._MN = new Array +("1ŚŽ", + "2ŚŽ", + "3ŚŽ", + "4ŚŽ", + "5ŚŽ", + "6ŚŽ", + "7ŚŽ", + "8ŚŽ", + "9ŚŽ", + "10ŚŽ", + "11ŚŽ", + "12ŚŽ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "ŹT‚ĚŤĹŹ‰‚Ě—j“ú‚đŘ‚č‘Ö‚¦"; +Calendar._TT["PREV_YEAR"] = "‘O”N"; +Calendar._TT["PREV_MONTH"] = "‘OŚŽ"; +Calendar._TT["GO_TODAY"] = "Ťˇ“ú"; +Calendar._TT["NEXT_MONTH"] = "—‚ŚŽ"; +Calendar._TT["NEXT_YEAR"] = "—‚”N"; +Calendar._TT["SEL_DATE"] = "“ú•t‘I‘đ"; +Calendar._TT["DRAG_TO_MOVE"] = "EB“hE‚ĚÚ“®"; +Calendar._TT["PART_TODAY"] = " (Ťˇ“ú)"; +Calendar._TT["MON_FIRST"] = "ŚŽ—j“ú‚đć“Ş‚É"; +Calendar._TT["SUN_FIRST"] = "“ú—j“ú‚đć“Ş‚É"; +Calendar._TT["CLOSE"] = "•Â‚¶‚é"; +Calendar._TT["TODAY"] = "Ťˇ“ú"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd"; +Calendar._TT["TT_DATE_FORMAT"] = "%mŚŽ %d“ú (%a)"; + +Calendar._TT["WK"] = "ŹT"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ko-utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ko-utf8.js new file mode 100644 index 0000000..035dd74 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ko-utf8.js @@ -0,0 +1,120 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Translation: Yourim Yi +// Encoding: EUC-KR +// lang : ko +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names + +Calendar._DN = new Array +("일요일", + "월요일", + "화요일", + "ě요일", + "목요일", + "ę¸ěš”일", + "토요일", + "일요일"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("일", + "ě›”", + "í™”", + "ě", + "목", + "ę¸", + "토", + "일"); + +// full month names +Calendar._MN = new Array +("1ě›”", + "2ě›”", + "3ě›”", + "4ě›”", + "5ě›”", + "6ě›”", + "7ě›”", + "8ě›”", + "9ě›”", + "10ě›”", + "11ě›”", + "12ě›”"); + +// short month names +Calendar._SMN = new Array +("1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "calendar ě— ëŚ€í•´ě„ś"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"\n"+ +"최신 버전을 받으시려면 http://www.dynarch.com/projects/calendar/ ě— ë°©ë¬¸í•ě„¸ěš”\n" + +"\n"+ +"GNU LGPL 라이센스로 배포ë©ë‹ë‹¤. \n"+ +"ëťĽěť´ě„ĽěŠ¤ě— ëŚ€í•ś ěžě„¸í•ś 내용은 http://gnu.org/licenses/lgpl.html ěť„ 읽으세요." + +"\n\n" + +"날짜 ě„ íť:\n" + +"- 연도를 ě„ íťí•ë ¤ë©´ \xab, \xbb 버튼을 사용합ë‹ë‹¤\n" + +"- 달을 ě„ íťí•ë ¤ë©´ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " 버튼을 ë„르세요\n" + +"- 계속 ë„르고 ěžěśĽë©´ ěś„ 값들을 빠르게 ě„ íťí•ě‹¤ ě ěžěŠµë‹ë‹¤."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"ě‹śę°„ ě„ íť:\n" + +"- ë§ěš°ěŠ¤ëˇś ë„르면 ě‹śę°„ěť´ 증가합ë‹ë‹¤\n" + +"- Shift 키와 í•¨ę» ë„르면 ę°ě†Śí•©ë‹ë‹¤\n" + +"- ë„른 ěíśě—ě„ś ë§ěš°ěŠ¤ëĄĽ 움ě§ěť´ë©´ 좀 더 빠르게 ę°’ěť´ 변합ë‹ë‹¤.\n"; + +Calendar._TT["PREV_YEAR"] = "지난 í•´ (길게 ë„르면 목록)"; +Calendar._TT["PREV_MONTH"] = "지난 달 (길게 ë„르면 목록)"; +Calendar._TT["GO_TODAY"] = "ě¤ëŠ 날짜로"; +Calendar._TT["NEXT_MONTH"] = "다음 달 (길게 ë„르면 목록)"; +Calendar._TT["NEXT_YEAR"] = "다음 í•´ (길게 ë„르면 목록)"; +Calendar._TT["SEL_DATE"] = "날짜를 ě„ íťí•ě„¸ěš”"; +Calendar._TT["DRAG_TO_MOVE"] = "ë§ěš°ěŠ¤ ë“śëžę·¸ëˇś 이동 í•ě„¸ěš”"; +Calendar._TT["PART_TODAY"] = " (ě¤ëŠ)"; +Calendar._TT["MON_FIRST"] = "월요일을 í•ś ěŁĽěť ě‹śěž‘ 요일로"; +Calendar._TT["SUN_FIRST"] = "일요일을 í•ś ěŁĽěť ě‹śěž‘ 요일로"; +Calendar._TT["CLOSE"] = "닫기"; +Calendar._TT["TODAY"] = "ě¤ëŠ"; +Calendar._TT["TIME_PART"] = "(Shift-)í´ë¦­ ë는 ë“śëžę·¸ í•ě„¸ěš”"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%b/%e [%a]"; + +Calendar._TT["WK"] = "주"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ko.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ko.js new file mode 100644 index 0000000..8cddf58 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ko.js @@ -0,0 +1,120 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Translation: Yourim Yi +// Encoding: EUC-KR +// lang : ko +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names + +Calendar._DN = new Array +("ŔĎżäŔĎ", + "żůżäŔĎ", + "Č­żäŔĎ", + "ĽöżäŔĎ", + "¸ńżäŔĎ", + "±ÝżäŔĎ", + "ĹäżäŔĎ", + "ŔĎżäŔĎ"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("ŔĎ", + "żů", + "Č­", + "Ľö", + "¸ń", + "±Ý", + "Ĺä", + "ŔĎ"); + +// full month names +Calendar._MN = new Array +("1żů", + "2żů", + "3żů", + "4żů", + "5żů", + "6żů", + "7żů", + "8żů", + "9żů", + "10żů", + "11żů", + "12żů"); + +// short month names +Calendar._SMN = new Array +("1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "calendar żˇ ´ëÇŘĽ­"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"\n"+ +"ĂֽŠąöŔüŔ» ąŢŔ¸˝Ă·Á¸é http://www.dynarch.com/projects/calendar/ żˇ ąćą®ÇĎĽĽżä\n" + +"\n"+ +"GNU LGPL ¶óŔĚĽľ˝ş·Î ąčĆ÷µË´Ď´Ů. \n"+ +"¶óŔĚĽľ˝şżˇ ´ëÇŃ ŔÚĽĽÇŃ ł»żëŔş http://gnu.org/licenses/lgpl.html Ŕ» ŔĐŔ¸ĽĽżä." + +"\n\n" + +"łŻÂĄ Ľ±ĹĂ:\n" + +"- ż¬µµ¸¦ Ľ±ĹĂÇĎ·Á¸é \xab, \xbb ąöĆ°Ŕ» »çżëÇŐ´Ď´Ů\n" + +"- ´ŢŔ» Ľ±ĹĂÇĎ·Á¸é " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ąöĆ°Ŕ» ´©¸ŁĽĽżä\n" + +"- °čĽÓ ´©¸Ł°í ŔÖŔ¸¸é Ŕ§ °ŞµéŔ» şü¸Ł°Ô Ľ±ĹĂÇĎ˝Ç Ľö ŔÖ˝Ŕ´Ď´Ů."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"˝Ă°Ł Ľ±ĹĂ:\n" + +"- ¸¶żě˝ş·Î ´©¸Ł¸é ˝Ă°ŁŔĚ Áő°ˇÇŐ´Ď´Ů\n" + +"- Shift Ĺ°żÍ ÇÔ˛˛ ´©¸Ł¸é °¨ĽŇÇŐ´Ď´Ů\n" + +"- ´©¸Ą »óĹÂżˇĽ­ ¸¶żě˝ş¸¦ żňÁ÷Ŕ̸é Á» ´ő şü¸Ł°Ô °ŞŔĚ şŻÇŐ´Ď´Ů.\n"; + +Calendar._TT["PREV_YEAR"] = "Áöł­ ÇŘ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["PREV_MONTH"] = "Áöł­ ´Ţ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["GO_TODAY"] = "żŔ´Ă łŻÂĄ·Î"; +Calendar._TT["NEXT_MONTH"] = "´ŮŔ˝ ´Ţ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["NEXT_YEAR"] = "´ŮŔ˝ ÇŘ (±ć°Ô ´©¸Ł¸é ¸ń·Ď)"; +Calendar._TT["SEL_DATE"] = "łŻÂĄ¸¦ Ľ±ĹĂÇĎĽĽżä"; +Calendar._TT["DRAG_TO_MOVE"] = "¸¶żě˝ş µĺ·ˇ±×·Î Ŕ̵ż ÇĎĽĽżä"; +Calendar._TT["PART_TODAY"] = " (żŔ´Ă)"; +Calendar._TT["MON_FIRST"] = "żůżäŔĎŔ» ÇŃ ÁÖŔÇ ˝ĂŔŰ żäŔĎ·Î"; +Calendar._TT["SUN_FIRST"] = "ŔĎżäŔĎŔ» ÇŃ ÁÖŔÇ ˝ĂŔŰ żäŔĎ·Î"; +Calendar._TT["CLOSE"] = "´Ý±â"; +Calendar._TT["TODAY"] = "żŔ´Ă"; +Calendar._TT["TIME_PART"] = "(Shift-)Ŭ¸Ż ¶Ç´Â µĺ·ˇ±× ÇĎĽĽżä"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%b/%e [%a]"; + +Calendar._TT["WK"] = "ÁÖ"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lt-utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lt-utf8.js new file mode 100644 index 0000000..d39653b --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lt-utf8.js @@ -0,0 +1,114 @@ +// ** I18N + +// Calendar LT language +// Author: Martynas Majeris, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sekmadienis", + "Pirmadienis", + "Antradienis", + "TreÄŤiadienis", + "Ketvirtadienis", + "Pentadienis", + "Ĺ eštadienis", + "Sekmadienis"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sek", + "Pir", + "Ant", + "Tre", + "Ket", + "Pen", + "Ĺ eš", + "Sek"); + +// full month names +Calendar._MN = new Array +("Sausis", + "Vasaris", + "Kovas", + "Balandis", + "Gegužė", + "BirĹľelis", + "Liepa", + "RugpjĹ«tis", + "RugsÄ—jis", + "Spalis", + "Lapkritis", + "Gruodis"); + +// short month names +Calendar._SMN = new Array +("Sau", + "Vas", + "Kov", + "Bal", + "Geg", + "Bir", + "Lie", + "Rgp", + "Rgs", + "Spa", + "Lap", + "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Apie kalendoriĹł"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"NaujausiÄ… versijÄ… rasite: http://www.dynarch.com/projects/calendar/\n" + +"Platinamas pagal GNU LGPL licencijÄ…. Aplankykite http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"Datos pasirinkimas:\n" + +"- MetĹł pasirinkimas: \xab, \xbb\n" + +"- MÄ—nesio pasirinkimas: " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "\n" + +"- Nuspauskite ir laikykite pelÄ—s klavišą greitesniam pasirinkimui."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Laiko pasirinkimas:\n" + +"- Spustelkite ant valandĹł arba minuÄŤiĹł - skaiÄŤius padidÄ—s vienetu.\n" + +"- Jei spausite kartu su Shift, skaiÄŤius sumažės.\n" + +"- Greitam pasirinkimui spustelkite ir pajudinkite pelÄ™."; + +Calendar._TT["PREV_YEAR"] = "Ankstesni metai (laikykite, jei norite meniu)"; +Calendar._TT["PREV_MONTH"] = "Ankstesnis mÄ—nuo (laikykite, jei norite meniu)"; +Calendar._TT["GO_TODAY"] = "Pasirinkti šiandienÄ…"; +Calendar._TT["NEXT_MONTH"] = "Kitas mÄ—nuo (laikykite, jei norite meniu)"; +Calendar._TT["NEXT_YEAR"] = "Kiti metai (laikykite, jei norite meniu)"; +Calendar._TT["SEL_DATE"] = "Pasirinkite datÄ…"; +Calendar._TT["DRAG_TO_MOVE"] = "Tempkite"; +Calendar._TT["PART_TODAY"] = " (šiandien)"; +Calendar._TT["MON_FIRST"] = "Pirma savaitÄ—s diena - pirmadienis"; +Calendar._TT["SUN_FIRST"] = "Pirma savaitÄ—s diena - sekmadienis"; +Calendar._TT["CLOSE"] = "UĹľdaryti"; +Calendar._TT["TODAY"] = "Ĺ iandien"; +Calendar._TT["TIME_PART"] = "Spustelkite arba tempkite jei norite pakeisti"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %Y-%m-%d"; + +Calendar._TT["WK"] = "sav"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lt.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lt.js new file mode 100644 index 0000000..43b93d6 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lt.js @@ -0,0 +1,114 @@ +// ** I18N + +// Calendar LT language +// Author: Martynas Majeris, +// Encoding: Windows-1257 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Sekmadienis", + "Pirmadienis", + "Antradienis", + "Trečiadienis", + "Ketvirtadienis", + "Pentadienis", + "Đeđtadienis", + "Sekmadienis"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sek", + "Pir", + "Ant", + "Tre", + "Ket", + "Pen", + "Đeđ", + "Sek"); + +// full month names +Calendar._MN = new Array +("Sausis", + "Vasaris", + "Kovas", + "Balandis", + "Geguţë", + "Birţelis", + "Liepa", + "Rugpjűtis", + "Rugsëjis", + "Spalis", + "Lapkritis", + "Gruodis"); + +// short month names +Calendar._SMN = new Array +("Sau", + "Vas", + "Kov", + "Bal", + "Geg", + "Bir", + "Lie", + "Rgp", + "Rgs", + "Spa", + "Lap", + "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Apie kalendoriř"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Naujausiŕ versijŕ rasite: http://www.dynarch.com/projects/calendar/\n" + +"Platinamas pagal GNU LGPL licencijŕ. Aplankykite http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"Datos pasirinkimas:\n" + +"- Metř pasirinkimas: \xab, \xbb\n" + +"- Mënesio pasirinkimas: " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "\n" + +"- Nuspauskite ir laikykite pelës klaviđŕ greitesniam pasirinkimui."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Laiko pasirinkimas:\n" + +"- Spustelkite ant valandř arba minučiř - skaičus padidës vienetu.\n" + +"- Jei spausite kartu su Shift, skaičius sumaţës.\n" + +"- Greitam pasirinkimui spustelkite ir pajudinkite pelć."; + +Calendar._TT["PREV_YEAR"] = "Ankstesni metai (laikykite, jei norite meniu)"; +Calendar._TT["PREV_MONTH"] = "Ankstesnis mënuo (laikykite, jei norite meniu)"; +Calendar._TT["GO_TODAY"] = "Pasirinkti điandienŕ"; +Calendar._TT["NEXT_MONTH"] = "Kitas mënuo (laikykite, jei norite meniu)"; +Calendar._TT["NEXT_YEAR"] = "Kiti metai (laikykite, jei norite meniu)"; +Calendar._TT["SEL_DATE"] = "Pasirinkite datŕ"; +Calendar._TT["DRAG_TO_MOVE"] = "Tempkite"; +Calendar._TT["PART_TODAY"] = " (điandien)"; +Calendar._TT["MON_FIRST"] = "Pirma savaitës diena - pirmadienis"; +Calendar._TT["SUN_FIRST"] = "Pirma savaitës diena - sekmadienis"; +Calendar._TT["CLOSE"] = "Uţdaryti"; +Calendar._TT["TODAY"] = "Điandien"; +Calendar._TT["TIME_PART"] = "Spustelkite arba tempkite jei norite pakeisti"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %Y-%m-%d"; + +Calendar._TT["WK"] = "sav"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lv.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lv.js new file mode 100644 index 0000000..407699d --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-lv.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar LV language +// Author: Juris Valdovskis, +// Encoding: cp1257 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Svçtdiena", + "Pirmdiena", + "Otrdiena", + "Tređdiena", + "Ceturdiena", + "Piektdiena", + "Sestdiena", + "Svçtdiena"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sv", + "Pr", + "Ot", + "Tr", + "Ce", + "Pk", + "Se", + "Sv"); + +// full month names +Calendar._MN = new Array +("Janvâris", + "Februâris", + "Marts", + "Aprîlis", + "Maijs", + "Jűnijs", + "Jűlijs", + "Augusts", + "Septembris", + "Oktobris", + "Novembris", + "Decembris"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Mai", + "Jűn", + "Jűl", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Par kalendâru"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Datuma izvçle:\n" + +"- Izmanto \xab, \xbb pogas, lai izvçlçtos gadu\n" + +"- Izmanto " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "pogas, lai izvçlçtos mçnesi\n" + +"- Turi nospiestu peles pogu uz jebkuru no augstâk minçtajâm pogâm, lai paâtrinâtu izvçli."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Laika izvçle:\n" + +"- Uzklikđíini uz jebkuru no laika daďâm, lai palielinâtu to\n" + +"- vai Shift-klikđíis, lai samazinâtu to\n" + +"- vai noklikđíini un velc uz attiecîgo virzienu lai mainîtu âtrâk."; + +Calendar._TT["PREV_YEAR"] = "Iepr. gads (turi izvçlnei)"; +Calendar._TT["PREV_MONTH"] = "Iepr. mçnesis (turi izvçlnei)"; +Calendar._TT["GO_TODAY"] = "Đodien"; +Calendar._TT["NEXT_MONTH"] = "Nâkođais mçnesis (turi izvçlnei)"; +Calendar._TT["NEXT_YEAR"] = "Nâkođais gads (turi izvçlnei)"; +Calendar._TT["SEL_DATE"] = "Izvçlies datumu"; +Calendar._TT["DRAG_TO_MOVE"] = "Velc, lai pârvietotu"; +Calendar._TT["PART_TODAY"] = " (đodien)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Attçlot %s kâ pirmo"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "1,7"; + +Calendar._TT["CLOSE"] = "Aizvçrt"; +Calendar._TT["TODAY"] = "Đodien"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikđíis vai pârvieto, lai mainîtu"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Laiks:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-nl.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-nl.js new file mode 100644 index 0000000..a1dea94 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-nl.js @@ -0,0 +1,73 @@ +// ** I18N +Calendar._DN = new Array +("Zondag", + "Maandag", + "Dinsdag", + "Woensdag", + "Donderdag", + "Vrijdag", + "Zaterdag", + "Zondag"); + +Calendar._SDN_len = 2; + +Calendar._MN = new Array +("Januari", + "Februari", + "Maart", + "April", + "Mei", + "Juni", + "Juli", + "Augustus", + "September", + "Oktober", + "November", + "December"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Info"; + +Calendar._TT["ABOUT"] = +"DHTML Datum/Tijd Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + +"Ga voor de meest recente versie naar: http://www.dynarch.com/projects/calendar/\n" + +"Verspreid onder de GNU LGPL. Zie http://gnu.org/licenses/lgpl.html voor details." + +"\n\n" + +"Datum selectie:\n" + +"- Gebruik de \xab \xbb knoppen om een jaar te selecteren\n" + +"- Gebruik de " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " knoppen om een maand te selecteren\n" + +"- Houd de muis ingedrukt op de genoemde knoppen voor een snellere selectie."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Tijd selectie:\n" + +"- Klik op een willekeurig onderdeel van het tijd gedeelte om het te verhogen\n" + +"- of Shift-klik om het te verlagen\n" + +"- of klik en sleep voor een snellere selectie."; + +//Calendar._TT["TOGGLE"] = "Selecteer de eerste week-dag"; +Calendar._TT["PREV_YEAR"] = "Vorig jaar (ingedrukt voor menu)"; +Calendar._TT["PREV_MONTH"] = "Vorige maand (ingedrukt voor menu)"; +Calendar._TT["GO_TODAY"] = "Ga naar Vandaag"; +Calendar._TT["NEXT_MONTH"] = "Volgende maand (ingedrukt voor menu)"; +Calendar._TT["NEXT_YEAR"] = "Volgend jaar (ingedrukt voor menu)"; +Calendar._TT["SEL_DATE"] = "Selecteer datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Klik en sleep om te verplaatsen"; +Calendar._TT["PART_TODAY"] = " (vandaag)"; +//Calendar._TT["MON_FIRST"] = "Toon Maandag eerst"; +//Calendar._TT["SUN_FIRST"] = "Toon Zondag eerst"; + +Calendar._TT["DAY_FIRST"] = "Toon %s eerst"; + +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Sluiten"; +Calendar._TT["TODAY"] = "(vandaag)"; +Calendar._TT["TIME_PART"] = "(Shift-)Klik of sleep om de waarde te veranderen"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b %Y"; + +Calendar._TT["WK"] = "wk"; +Calendar._TT["TIME"] = "Tijd:"; \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-no.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-no.js new file mode 100644 index 0000000..d9297d1 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-no.js @@ -0,0 +1,114 @@ +// ** I18N + +// Calendar NO language +// Author: Daniel Holmen, +// Encoding: UTF-8 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Søndag", + "Mandag", + "Tirsdag", + "Onsdag", + "Torsdag", + "Fredag", + "Lørdag", + "Søndag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Søn", + "Man", + "Tir", + "Ons", + "Tor", + "Fre", + "Lør", + "Søn"); + +// full month names +Calendar._MN = new Array +("Januar", + "Februar", + "Mars", + "April", + "Mai", + "Juni", + "Juli", + "August", + "September", + "Oktober", + "November", + "Desember"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Mai", + "Jun", + "Jul", + "Aug", + "Sep", + "Okt", + "Nov", + "Des"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Om kalenderen"; + +Calendar._TT["ABOUT"] = +"DHTML Dato-/Tidsvelger\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For nyeste versjon, gĂĄ til: http://www.dynarch.com/projects/calendar/\n" + +"Distribuert under GNU LGPL. Se http://gnu.org/licenses/lgpl.html for detaljer." + +"\n\n" + +"Datovalg:\n" + +"- Bruk knappene \xab og \xbb for ĂĄ velge ĂĄr\n" + +"- Bruk knappene " + String.fromCharCode(0x2039) + " og " + String.fromCharCode(0x203a) + " for ĂĄ velge mĂĄned\n" + +"- Hold inne musknappen eller knappene over for raskere valg."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Tidsvalg:\n" + +"- Klikk pĂĄ en av tidsdelene for ĂĄ øke den\n" + +"- eller Shift-klikk for ĂĄ senke verdien\n" + +"- eller klikk-og-dra for raskere valg.."; + +Calendar._TT["PREV_YEAR"] = "Forrige. ĂĄr (hold for meny)"; +Calendar._TT["PREV_MONTH"] = "Forrige. mĂĄned (hold for meny)"; +Calendar._TT["GO_TODAY"] = "GĂĄ til idag"; +Calendar._TT["NEXT_MONTH"] = "Neste mĂĄned (hold for meny)"; +Calendar._TT["NEXT_YEAR"] = "Neste ĂĄr (hold for meny)"; +Calendar._TT["SEL_DATE"] = "Velg dato"; +Calendar._TT["DRAG_TO_MOVE"] = "Dra for ĂĄ flytte"; +Calendar._TT["PART_TODAY"] = " (idag)"; +Calendar._TT["MON_FIRST"] = "Vis mandag først"; +Calendar._TT["SUN_FIRST"] = "Vis søndag først"; +Calendar._TT["CLOSE"] = "Lukk"; +Calendar._TT["TODAY"] = "Idag"; +Calendar._TT["TIME_PART"] = "(Shift-)Klikk eller dra for ĂĄ endre verdi"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d.%m.%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "uke"; \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pl-utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pl-utf8.js new file mode 100644 index 0000000..6b8ca67 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pl-utf8.js @@ -0,0 +1,93 @@ +// ** I18N + +// Calendar PL language +// Author: Dariusz Pietrzak, +// Author: Janusz Piwowarski, +// Encoding: utf-8 +// Distributed under the same terms as the calendar itself. + +Calendar._DN = new Array +("Niedziela", + "PoniedziaĹ‚ek", + "Wtorek", + "Ĺšroda", + "Czwartek", + "PiÄ…tek", + "Sobota", + "Niedziela"); +Calendar._SDN = new Array +("Nie", + "Pn", + "Wt", + "Ĺšr", + "Cz", + "Pt", + "So", + "Nie"); +Calendar._MN = new Array +("StyczeĹ„", + "Luty", + "Marzec", + "KwiecieĹ„", + "Maj", + "Czerwiec", + "Lipiec", + "SierpieĹ„", + "WrzesieĹ„", + "PaĹşdziernik", + "Listopad", + "GrudzieĹ„"); +Calendar._SMN = new Array +("Sty", + "Lut", + "Mar", + "Kwi", + "Maj", + "Cze", + "Lip", + "Sie", + "Wrz", + "PaĹş", + "Lis", + "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O kalendarzu"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Aby pobrać najnowszÄ… wersjÄ™, odwiedĹş: http://www.dynarch.com/projects/calendar/\n" + +"DostÄ™pny na licencji GNU LGPL. Zobacz szczegóły na http://gnu.org/licenses/lgpl.html." + +"\n\n" + +"WybĂłr daty:\n" + +"- UĹĽyj przyciskĂłw \xab, \xbb by wybrać rok\n" + +"- UĹĽyj przyciskĂłw " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " by wybrać miesiÄ…c\n" + +"- Przytrzymaj klawisz myszy nad jednym z powyĹĽszych przyciskĂłw dla szybszego wyboru."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"WybĂłr czasu:\n" + +"- Kliknij na jednym z pĂłl czasu by zwiÄ™kszyć jego wartość\n" + +"- lub kliknij trzymajÄ…c Shift by zmiejszyć jego wartość\n" + +"- lub kliknij i przeciÄ…gnij dla szybszego wyboru."; + +//Calendar._TT["TOGGLE"] = "ZmieĹ„ pierwszy dzieĹ„ tygodnia"; +Calendar._TT["PREV_YEAR"] = "Poprzedni rok (przytrzymaj dla menu)"; +Calendar._TT["PREV_MONTH"] = "Poprzedni miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["GO_TODAY"] = "IdĹş do dzisiaj"; +Calendar._TT["NEXT_MONTH"] = "NastÄ™pny miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["NEXT_YEAR"] = "NastÄ™pny rok (przytrzymaj dla menu)"; +Calendar._TT["SEL_DATE"] = "Wybierz datÄ™"; +Calendar._TT["DRAG_TO_MOVE"] = "PrzeciÄ…gnij by przesunąć"; +Calendar._TT["PART_TODAY"] = " (dzisiaj)"; +Calendar._TT["MON_FIRST"] = "WyĹ›wietl poniedziaĹ‚ek jako pierwszy"; +Calendar._TT["SUN_FIRST"] = "WyĹ›wietl niedzielÄ™ jako pierwszÄ…"; +Calendar._TT["CLOSE"] = "Zamknij"; +Calendar._TT["TODAY"] = "Dzisiaj"; +Calendar._TT["TIME_PART"] = "(Shift-)Kliknij lub przeciÄ…gnij by zmienić wartość"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%e %B, %A"; + +Calendar._TT["WK"] = "ty"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pl.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pl.js new file mode 100644 index 0000000..76e0551 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pl.js @@ -0,0 +1,56 @@ +// ** I18N +// Calendar PL language +// Author: Artur Filipiak, +// January, 2004 +// Encoding: UTF-8 +Calendar._DN = new Array +("Niedziela", "PoniedziaĹ‚ek", "Wtorek", "Ĺšroda", "Czwartek", "PiÄ…tek", "Sobota", "Niedziela"); + +Calendar._SDN = new Array +("N", "Pn", "Wt", "Ĺšr", "Cz", "Pt", "So", "N"); + +Calendar._MN = new Array +("StyczeĹ„", "Luty", "Marzec", "KwiecieĹ„", "Maj", "Czerwiec", "Lipiec", "SierpieĹ„", "WrzesieĹ„", "PaĹşdziernik", "Listopad", "GrudzieĹ„"); + +Calendar._SMN = new Array +("Sty", "Lut", "Mar", "Kwi", "Maj", "Cze", "Lip", "Sie", "Wrz", "PaĹş", "Lis", "Gru"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O kalendarzu"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"WybĂłr daty:\n" + +"- aby wybrać rok uĹĽyj przyciskĂłw \xab, \xbb\n" + +"- aby wybrać miesiÄ…c uĹĽyj przyciskĂłw " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + "\n" + +"- aby przyspieszyć wybĂłr przytrzymaj wciĹ›niÄ™ty przycisk myszy nad ww. przyciskami."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"WybĂłr czasu:\n" + +"- aby zwiÄ™kszyć wartość kliknij na dowolnym elemencie selekcji czasu\n" + +"- aby zmniejszyć wartość uĹĽyj dodatkowo klawisza Shift\n" + +"- moĹĽesz rĂłwnieĹĽ poruszać myszkÄ™ w lewo i prawo wraz z wciĹ›niÄ™tym lewym klawiszem."; + +Calendar._TT["PREV_YEAR"] = "Poprz. rok (przytrzymaj dla menu)"; +Calendar._TT["PREV_MONTH"] = "Poprz. miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["GO_TODAY"] = "PokaĹĽ dziĹ›"; +Calendar._TT["NEXT_MONTH"] = "Nast. miesiÄ…c (przytrzymaj dla menu)"; +Calendar._TT["NEXT_YEAR"] = "Nast. rok (przytrzymaj dla menu)"; +Calendar._TT["SEL_DATE"] = "Wybierz datÄ™"; +Calendar._TT["DRAG_TO_MOVE"] = "PrzesuĹ„ okienko"; +Calendar._TT["PART_TODAY"] = " (dziĹ›)"; +Calendar._TT["MON_FIRST"] = "PokaĹĽ PoniedziaĹ‚ek jako pierwszy"; +Calendar._TT["SUN_FIRST"] = "PokaĹĽ NiedzielÄ™ jako pierwszÄ…"; +Calendar._TT["CLOSE"] = "Zamknij"; +Calendar._TT["TODAY"] = "DziĹ›"; +Calendar._TT["TIME_PART"] = "(Shift-)klik | drag, aby zmienić wartość"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y.%m.%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "wk"; \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pt.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pt.js new file mode 100644 index 0000000..deee8a1 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-pt.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar pt_BR language +// Author: Adalberto Machado, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Segunda", + "Terca", + "Quarta", + "Quinta", + "Sexta", + "Sabado", + "Domingo"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Dom", + "Seg", + "Ter", + "Qua", + "Qui", + "Sex", + "Sab", + "Dom"); + +// full month names +Calendar._MN = new Array +("Janeiro", + "Fevereiro", + "Marco", + "Abril", + "Maio", + "Junho", + "Julho", + "Agosto", + "Setembro", + "Outubro", + "Novembro", + "Dezembro"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Fev", + "Mar", + "Abr", + "Mai", + "Jun", + "Jul", + "Ago", + "Set", + "Out", + "Nov", + "Dez"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Sobre o calendario"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Ultima versao visite: http://www.dynarch.com/projects/calendar/\n" + +"Distribuido sobre GNU LGPL. Veja http://gnu.org/licenses/lgpl.html para detalhes." + +"\n\n" + +"Selecao de data:\n" + +"- Use os botoes \xab, \xbb para selecionar o ano\n" + +"- Use os botoes " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para selecionar o mes\n" + +"- Segure o botao do mouse em qualquer um desses botoes para selecao rapida."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selecao de hora:\n" + +"- Clique em qualquer parte da hora para incrementar\n" + +"- ou Shift-click para decrementar\n" + +"- ou clique e segure para selecao rapida."; + +Calendar._TT["PREV_YEAR"] = "Ant. ano (segure para menu)"; +Calendar._TT["PREV_MONTH"] = "Ant. mes (segure para menu)"; +Calendar._TT["GO_TODAY"] = "Hoje"; +Calendar._TT["NEXT_MONTH"] = "Prox. mes (segure para menu)"; +Calendar._TT["NEXT_YEAR"] = "Prox. ano (segure para menu)"; +Calendar._TT["SEL_DATE"] = "Selecione a data"; +Calendar._TT["DRAG_TO_MOVE"] = "Arraste para mover"; +Calendar._TT["PART_TODAY"] = " (hoje)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostre %s primeiro"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Fechar"; +Calendar._TT["TODAY"] = "Hoje"; +Calendar._TT["TIME_PART"] = "(Shift-)Click ou arraste para mudar valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d/%m/%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e %b"; + +Calendar._TT["WK"] = "sm"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ro.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ro.js new file mode 100644 index 0000000..116e358 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ro.js @@ -0,0 +1,66 @@ +// ** I18N +Calendar._DN = new Array +("DuminicÄ", + "Luni", + "MarĹŁi", + "Miercuri", + "Joi", + "Vineri", + "SâmbÄtÄ", + "DuminicÄ"); +Calendar._SDN_len = 2; +Calendar._MN = new Array +("Ianuarie", + "Februarie", + "Martie", + "Aprilie", + "Mai", + "Iunie", + "Iulie", + "August", + "Septembrie", + "Octombrie", + "Noiembrie", + "Decembrie"); + +// tooltips +Calendar._TT = {}; + +Calendar._TT["INFO"] = "Despre calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Pentru ultima versiune vizitaĹŁi: http://www.dynarch.com/projects/calendar/\n" + +"Distribuit sub GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"SelecĹŁia datei:\n" + +"- FolosiĹŁi butoanele \xab, \xbb pentru a selecta anul\n" + +"- FolosiĹŁi butoanele " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pentru a selecta luna\n" + +"- TineĹŁi butonul mouse-ului apÄsat pentru selecĹŁie mai rapidÄ."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"SelecĹŁia orei:\n" + +"- Click pe ora sau minut pentru a mÄri valoarea cu 1\n" + +"- Sau Shift-Click pentru a micĹźora valoarea cu 1\n" + +"- Sau Click Ĺźi drag pentru a selecta mai repede."; + +Calendar._TT["PREV_YEAR"] = "Anul precedent (lung pt menu)"; +Calendar._TT["PREV_MONTH"] = "Luna precedentÄ (lung pt menu)"; +Calendar._TT["GO_TODAY"] = "Data de azi"; +Calendar._TT["NEXT_MONTH"] = "Luna urmÄtoare (lung pt menu)"; +Calendar._TT["NEXT_YEAR"] = "Anul urmÄtor (lung pt menu)"; +Calendar._TT["SEL_DATE"] = "SelecteazÄ data"; +Calendar._TT["DRAG_TO_MOVE"] = "Trage pentru a miĹźca"; +Calendar._TT["PART_TODAY"] = " (astÄzi)"; +Calendar._TT["DAY_FIRST"] = "AfiĹźeazÄ %s prima zi"; +Calendar._TT["WEEKEND"] = "0,6"; +Calendar._TT["CLOSE"] = "ĂŽnchide"; +Calendar._TT["TODAY"] = "AstÄzi"; +Calendar._TT["TIME_PART"] = "(Shift-)Click sau drag pentru a selecta"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%d-%m-%Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %d %B"; + +Calendar._TT["WK"] = "spt"; +Calendar._TT["TIME"] = "Ora:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ru.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ru.js new file mode 100644 index 0000000..9f75a6a --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ru.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar RU language +// Translation: Sly Golovanov, http://golovanov.net, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("воŃкреŃенье", + "понедельник", + "вторник", + "Ńреда", + "четверг", + "пятница", + "ŃŃббота", + "воŃкреŃенье"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("вŃĐş", + "пон", + "втр", + "Ńрд", + "чет", + "пят", + "ŃŃб", + "вŃĐş"); + +// full month names +Calendar._MN = new Array +("январь", + "февраль", + "март", + "апрель", + "ĐĽĐ°Đą", + "июнь", + "июль", + "авгŃŃŃ‚", + "Ńентябрь", + "октябрь", + "ноябрь", + "декабрь"); + +// short month names +Calendar._SMN = new Array +("янв", + "фев", + "ĐĽĐ°Ń€", + "апр", + "ĐĽĐ°Đą", + "июн", + "июл", + "авг", + "Ńен", + "окт", + "ноя", + "дек"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Đž календаре..."; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Как выбрать Đ´Đ°Ń‚Ń:\n" + +"- При помощи кнопок \xab, \xbb можно выбрать год\n" + +"- При помощи кнопок " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " можно выбрать меŃяц\n" + +"- Подержите эти кнопки нажатыми, чтобы появилоŃŃŚ меню быŃтрого выбора."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Как выбрать время:\n" + +"- При клике на чаŃĐ°Ń… или минŃŃ‚Đ°Ń… они ŃвеличиваютŃŃŹ\n" + +"- при клике Ń Đ˝Đ°Đ¶Đ°Ń‚ĐľĐą клавиŃей Shift они ŃменьŃĐ°ŃŽŃ‚ŃŃŹ\n" + +"- еŃли нажать и двигать ĐĽŃ‹Ńкой влево/вправо, они бŃĐ´ŃŃ‚ менятьŃŃŹ быŃтрее."; + +Calendar._TT["PREV_YEAR"] = "На год назад (Ńдерживать для меню)"; +Calendar._TT["PREV_MONTH"] = "На меŃяц назад (Ńдерживать для меню)"; +Calendar._TT["GO_TODAY"] = "Сегодня"; +Calendar._TT["NEXT_MONTH"] = "На меŃяц вперед (Ńдерживать для меню)"; +Calendar._TT["NEXT_YEAR"] = "На год вперед (Ńдерживать для меню)"; +Calendar._TT["SEL_DATE"] = "Выберите Đ´Đ°Ń‚Ń"; +Calendar._TT["DRAG_TO_MOVE"] = "ПеретаŃкивайте ĐĽŃ‹Ńкой"; +Calendar._TT["PART_TODAY"] = " (Ńегодня)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Первый день недели бŃдет %s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Закрыть"; +Calendar._TT["TODAY"] = "Сегодня"; +Calendar._TT["TIME_PART"] = "(Shift-)клик или нажать и двигать"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a"; + +Calendar._TT["WK"] = "нед"; +Calendar._TT["TIME"] = "Время:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ru_win_.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ru_win_.js new file mode 100644 index 0000000..de455af --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-ru_win_.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar RU language +// Translation: Sly Golovanov, http://golovanov.net, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("âîńęđĺńĺíüĺ", + "ďîíĺäĺëüíčę", + "âňîđíčę", + "ńđĺäŕ", + "÷ĺňâĺđă", + "ď˙ňíčöŕ", + "ńóááîňŕ", + "âîńęđĺńĺíüĺ"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("âńę", + "ďîí", + "âňđ", + "ńđä", + "÷ĺň", + "ď˙ň", + "ńóá", + "âńę"); + +// full month names +Calendar._MN = new Array +("˙íâŕđü", + "ôĺâđŕëü", + "ěŕđň", + "ŕďđĺëü", + "ěŕé", + "čţíü", + "čţëü", + "ŕâăóńň", + "ńĺíň˙áđü", + "îęň˙áđü", + "íî˙áđü", + "äĺęŕáđü"); + +// short month names +Calendar._SMN = new Array +("˙íâ", + "ôĺâ", + "ěŕđ", + "ŕďđ", + "ěŕé", + "čţí", + "čţë", + "ŕâă", + "ńĺí", + "îęň", + "íî˙", + "äĺę"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Î ęŕëĺíäŕđĺ..."; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Ęŕę âűáđŕňü äŕňó:\n" + +"- Ďđč ďîěîůč ęíîďîę \xab, \xbb ěîćíî âűáđŕňü ăîä\n" + +"- Ďđč ďîěîůč ęíîďîę " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " ěîćíî âűáđŕňü ěĺń˙ö\n" + +"- Ďîäĺđćčňĺ ýňč ęíîďęč íŕćŕňűěč, ÷ňîáű ďî˙âčëîńü ěĺíţ áűńňđîăî âűáîđŕ."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Ęŕę âűáđŕňü âđĺě˙:\n" + +"- Ďđč ęëčęĺ íŕ ÷ŕńŕő čëč ěčíóňŕő îíč óâĺëč÷čâŕţňń˙\n" + +"- ďđč ęëčęĺ ń íŕćŕňîé ęëŕâčřĺé Shift îíč óěĺíüřŕţňń˙\n" + +"- ĺńëč íŕćŕňü č äâčăŕňü ěűřęîé âëĺâî/âďđŕâî, îíč áóäóň ěĺí˙ňüń˙ áűńňđĺĺ."; + +Calendar._TT["PREV_YEAR"] = "Íŕ ăîä íŕçŕä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["PREV_MONTH"] = "Íŕ ěĺń˙ö íŕçŕä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["GO_TODAY"] = "Ńĺăîäí˙"; +Calendar._TT["NEXT_MONTH"] = "Íŕ ěĺń˙ö âďĺđĺä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["NEXT_YEAR"] = "Íŕ ăîä âďĺđĺä (óäĺđćčâŕňü äë˙ ěĺíţ)"; +Calendar._TT["SEL_DATE"] = "Âűáĺđčňĺ äŕňó"; +Calendar._TT["DRAG_TO_MOVE"] = "Ďĺđĺňŕńęčâŕéňĺ ěűřęîé"; +Calendar._TT["PART_TODAY"] = " (ńĺăîäí˙)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Ďĺđâűé äĺíü íĺäĺëč áóäĺň %s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Çŕęđűňü"; +Calendar._TT["TODAY"] = "Ńĺăîäí˙"; +Calendar._TT["TIME_PART"] = "(Shift-)ęëčę čëč íŕćŕňü č äâčăŕňü"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%e %b, %a"; + +Calendar._TT["WK"] = "íĺä"; +Calendar._TT["TIME"] = "Âđĺě˙:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-si.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-si.js new file mode 100644 index 0000000..cb3dfb9 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-si.js @@ -0,0 +1,94 @@ +/* Slovenian language file for the DHTML Calendar version 0.9.2 +* Author David Milost , January 2004. +* Feel free to use this script under the terms of the GNU Lesser General +* Public License, as long as you do not remove or alter this notice. +*/ + // full day names +Calendar._DN = new Array +("Nedelja", + "Ponedeljek", + "Torek", + "Sreda", + "ÄŚetrtek", + "Petek", + "Sobota", + "Nedelja"); + // short day names + Calendar._SDN = new Array +("Ned", + "Pon", + "Tor", + "Sre", + "ÄŚet", + "Pet", + "Sob", + "Ned"); +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Maj", + "Jun", + "Jul", + "Avg", + "Sep", + "Okt", + "Nov", + "Dec"); + // full month names +Calendar._MN = new Array +("Januar", + "Februar", + "Marec", + "April", + "Maj", + "Junij", + "Julij", + "Avgust", + "September", + "Oktober", + "November", + "December"); + +// tooltips +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O koledarju"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Za zadnjo verzijo pojdine na naslov: http://www.dynarch.com/projects/calendar/\n" + +"Distribuirano pod GNU LGPL. Poglejte http://gnu.org/licenses/lgpl.html za podrobnosti." + +"\n\n" + +"Izbor datuma:\n" + +"- Uporabite \xab, \xbb gumbe za izbor leta\n" + +"- Uporabite " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " gumbe za izbor meseca\n" + +"- ZadrĹľite klik na kateremkoli od zgornjih gumbov za hiter izbor."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Izbor ćasa:\n" + +"- Kliknite na katerikoli del ćasa za poveć. le-tega\n" + +"- ali Shift-click za zmanj. le-tega\n" + +"- ali kliknite in povlecite za hiter izbor."; + +Calendar._TT["TOGGLE"] = "Spremeni dan s katerim se prićne teden"; +Calendar._TT["PREV_YEAR"] = "Predhodnje leto (dolg klik za meni)"; +Calendar._TT["PREV_MONTH"] = "Predhodnji mesec (dolg klik za meni)"; +Calendar._TT["GO_TODAY"] = "Pojdi na tekoći dan"; +Calendar._TT["NEXT_MONTH"] = "Naslednji mesec (dolg klik za meni)"; +Calendar._TT["NEXT_YEAR"] = "Naslednje leto (dolg klik za meni)"; +Calendar._TT["SEL_DATE"] = "Izberite datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Pritisni in povleci za spremembo pozicije"; +Calendar._TT["PART_TODAY"] = " (danes)"; +Calendar._TT["MON_FIRST"] = "PrikaĹľi ponedeljek kot prvi dan"; +Calendar._TT["SUN_FIRST"] = "PrikaĹľi nedeljo kot prvi dan"; +Calendar._TT["CLOSE"] = "Zapri"; +Calendar._TT["TODAY"] = "Danes"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; + +Calendar._TT["WK"] = "Ted"; \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sk.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sk.js new file mode 100644 index 0000000..4fe6a3c --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sk.js @@ -0,0 +1,99 @@ +// ** I18N + +// Calendar SK language +// Author: Peter Valach (pvalach@gmx.net) +// Encoding: utf-8 +// Last update: 2003/10/29 +// Distributed under the same terms as the calendar itself. + +// full day names +Calendar._DN = new Array +("NedeĂ„Äľa", + "Pondelok", + "Utorok", + "Streda", + "Ĺ tvrtok", + "Piatok", + "Sobota", + "NedeĂ„Äľa"); + +// short day names +Calendar._SDN = new Array +("Ned", + "Pon", + "Uto", + "Str", + "Ĺ tv", + "Pia", + "Sob", + "Ned"); + +// full month names +Calendar._MN = new Array +("Január", + "Február", + "Marec", + "AprĂ­l", + "Máj", + "JÄ‚Ĺźn", + "JÄ‚Ĺźl", + "August", + "September", + "OktÄ‚Ĺ‚ber", + "November", + "December"); + +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "Máj", + "JÄ‚Ĺźn", + "JÄ‚Ĺźl", + "Aug", + "Sep", + "Okt", + "Nov", + "Dec"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "O kalendári"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + +"PoslednÄ‚Ĺź verziu nájdete na: http://www.dynarch.com/projects/calendar/\n" + +"DistribuovanĂ© pod GNU LGPL. ViĂ„Ĺą http://gnu.org/licenses/lgpl.html pre detaily." + +"\n\n" + +"VÄ‚Ëťber dátumu:\n" + +"- PouĹľite tlaÄŤidlá \xab, \xbb pre vÄ‚Ëťber roku\n" + +"- PouĹľite tlaÄŤidlá " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " pre vÄ‚Ëťber mesiaca\n" + +"- Ak ktorĂ©koĂ„Äľvek z tÄ‚Ëťchto tlaÄŤidiel podržíte dlhšie, zobrazĂ­ sa rÄ‚Ëťchly vÄ‚Ëťber."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"VÄ‚Ëťber ÄŤasu:\n" + +"- Kliknutie na niektorÄ‚Ĺź poloĹľku ÄŤasu ju zvýši\n" + +"- Shift-klik ju znĂ­Ĺľi\n" + +"- Ak podržíte tlaÄŤĂ­tko stlaÄŤenĂ©, posÄ‚ĹźvanĂ­m menĂ­te hodnotu."; + +Calendar._TT["PREV_YEAR"] = "PredošlÄ‚Ëť rok (podrĹľte pre menu)"; +Calendar._TT["PREV_MONTH"] = "PredošlÄ‚Ëť mesiac (podrĹľte pre menu)"; +Calendar._TT["GO_TODAY"] = "PrejsĹĄ na dnešok"; +Calendar._TT["NEXT_MONTH"] = "Nasl. mesiac (podrĹľte pre menu)"; +Calendar._TT["NEXT_YEAR"] = "Nasl. rok (podrĹľte pre menu)"; +Calendar._TT["SEL_DATE"] = "ZvoĂ„Äľte dátum"; +Calendar._TT["DRAG_TO_MOVE"] = "PodrĹľanĂ­m tlaÄŤĂ­tka zmenĂ­te polohu"; +Calendar._TT["PART_TODAY"] = " (dnes)"; +Calendar._TT["MON_FIRST"] = "ZobraziĹĄ pondelok ako prvÄ‚Ëť"; +Calendar._TT["SUN_FIRST"] = "ZobraziĹĄ nedeĂ„Äľu ako prvÄ‚Ĺź"; +Calendar._TT["CLOSE"] = "ZavrieĹĄ"; +Calendar._TT["TODAY"] = "Dnes"; +Calendar._TT["TIME_PART"] = "(Shift-)klik/ĹĄahanie zmenĂ­ hodnotu"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "$d. %m. %Y"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %e. %b"; + +Calendar._TT["WK"] = "týž"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sp.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sp.js new file mode 100644 index 0000000..239d1b3 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sp.js @@ -0,0 +1,110 @@ +// ** I18N + +// Calendar SP language +// Author: Rafael Velasco +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("Domingo", + "Lunes", + "Martes", + "Miercoles", + "Jueves", + "Viernes", + "Sabado", + "Domingo"); + +Calendar._SDN = new Array +("Dom", + "Lun", + "Mar", + "Mie", + "Jue", + "Vie", + "Sab", + "Dom"); + +// full month names +Calendar._MN = new Array +("Enero", + "Febrero", + "Marzo", + "Abril", + "Mayo", + "Junio", + "Julio", + "Agosto", + "Septiembre", + "Octubre", + "Noviembre", + "Diciembre"); + +// short month names +Calendar._SMN = new Array +("Ene", + "Feb", + "Mar", + "Abr", + "May", + "Jun", + "Jul", + "Ago", + "Sep", + "Oct", + "Nov", + "Dic"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Información del Calendario"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"Nuevas versiones en: http://www.dynarch.com/projects/calendar/\n" + +"Distribuida bajo licencia GNU LGPL. Para detalles vea http://gnu.org/licenses/lgpl.html ." + +"\n\n" + +"Selección de Fechas:\n" + +"- Use \xab, \xbb para seleccionar el ańo\n" + +"- Use " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " para seleccionar el mes\n" + +"- Mantenga presionado el botón del ratón en cualquiera de las opciones superiores para un acceso rapido ."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Selección del Reloj:\n" + +"- Seleccione la hora para cambiar el reloj\n" + +"- o presione Shift-click para disminuirlo\n" + +"- o presione click y arrastre del ratón para una selección rapida."; + +Calendar._TT["PREV_YEAR"] = "Ańo anterior (Presione para menu)"; +Calendar._TT["PREV_MONTH"] = "Mes Anterior (Presione para menu)"; +Calendar._TT["GO_TODAY"] = "Ir a Hoy"; +Calendar._TT["NEXT_MONTH"] = "Mes Siguiente (Presione para menu)"; +Calendar._TT["NEXT_YEAR"] = "Ańo Siguiente (Presione para menu)"; +Calendar._TT["SEL_DATE"] = "Seleccione fecha"; +Calendar._TT["DRAG_TO_MOVE"] = "Arrastre y mueva"; +Calendar._TT["PART_TODAY"] = " (Hoy)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "Mostrar %s primero"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "Cerrar"; +Calendar._TT["TODAY"] = "Hoy"; +Calendar._TT["TIME_PART"] = "(Shift-)Click o arrastra para cambar el valor"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%dd-%mm-%yy"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %e de %B de %Y"; + +Calendar._TT["WK"] = "Sm"; +Calendar._TT["TIME"] = "Hora:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sv.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sv.js new file mode 100644 index 0000000..db1f4b8 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-sv.js @@ -0,0 +1,93 @@ +// ** I18N + +// Calendar SV language (Swedish, svenska) +// Author: Mihai Bazon, +// Translation team: +// Translator: Leonard Norrgĺrd +// Last translator: Leonard Norrgĺrd +// Encoding: iso-latin-1 +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("söndag", + "mĺndag", + "tisdag", + "onsdag", + "torsdag", + "fredag", + "lördag", + "söndag"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. +Calendar._SDN_len = 2; +Calendar._SMN_len = 3; + +// full month names +Calendar._MN = new Array +("januari", + "februari", + "mars", + "april", + "maj", + "juni", + "juli", + "augusti", + "september", + "oktober", + "november", + "december"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "Om kalendern"; + +Calendar._TT["ABOUT"] = +"DHTML Datum/tid-väljare\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"För senaste version gĺ till: http://www.dynarch.com/projects/calendar/\n" + +"Distribueras under GNU LGPL. Se http://gnu.org/licenses/lgpl.html för detaljer." + +"\n\n" + +"Val av datum:\n" + +"- Använd knapparna \xab, \xbb för att välja ĺr\n" + +"- Använd knapparna " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " för att välja mĺnad\n" + +"- Hĺll musknappen nedtryckt pĺ nĺgon av ovanstĺende knappar för snabbare val."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Val av tid:\n" + +"- Klicka pĺ en del av tiden för att öka den delen\n" + +"- eller skift-klicka för att minska den\n" + +"- eller klicka och drag för snabbare val."; + +Calendar._TT["PREV_YEAR"] = "Föregĺende ĺr (hĺll för menu)"; +Calendar._TT["PREV_MONTH"] = "Föregĺende mĺnad (hĺll för menu)"; +Calendar._TT["GO_TODAY"] = "Gĺ till dagens datum"; +Calendar._TT["NEXT_MONTH"] = "Följande mĺnad (hĺll för menu)"; +Calendar._TT["NEXT_YEAR"] = "Följande ĺr (hĺll för menu)"; +Calendar._TT["SEL_DATE"] = "Välj datum"; +Calendar._TT["DRAG_TO_MOVE"] = "Drag för att flytta"; +Calendar._TT["PART_TODAY"] = " (idag)"; +Calendar._TT["MON_FIRST"] = "Visa mĺndag först"; +Calendar._TT["SUN_FIRST"] = "Visa söndag först"; +Calendar._TT["CLOSE"] = "Stäng"; +Calendar._TT["TODAY"] = "Idag"; +Calendar._TT["TIME_PART"] = "(Skift-)klicka eller drag för att ändra tid"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A %d %b %Y"; + +Calendar._TT["WK"] = "vecka"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-tr.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-tr.js new file mode 100644 index 0000000..2164687 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-tr.js @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// Turkish Translation by Nuri AKMAN +// Location: Ankara/TURKEY +// e-mail : nuriakman@hotmail.com +// Date : April, 9 2003 +// +// Note: if Turkish Characters does not shown on you screen +// please include falowing line your html code: +// +// +// +////////////////////////////////////////////////////////////////////////////////////////////// + +// ** I18N +Calendar._DN = new Array +("Pazar", + "Pazartesi", + "Salý", + "Çarţamba", + "Perţembe", + "Cuma", + "Cumartesi", + "Pazar"); +Calendar._MN = new Array +("Ocak", + "Ţubat", + "Mart", + "Nisan", + "Mayýs", + "Haziran", + "Temmuz", + "Ađustos", + "Eylül", + "Ekim", + "Kasým", + "Aralýk"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["TOGGLE"] = "Haftanýn ilk gününü kaydýr"; +Calendar._TT["PREV_YEAR"] = "Önceki Yýl (Menü için basýlý tutunuz)"; +Calendar._TT["PREV_MONTH"] = "Önceki Ay (Menü için basýlý tutunuz)"; +Calendar._TT["GO_TODAY"] = "Bugün'e git"; +Calendar._TT["NEXT_MONTH"] = "Sonraki Ay (Menü için basýlý tutunuz)"; +Calendar._TT["NEXT_YEAR"] = "Sonraki Yýl (Menü için basýlý tutunuz)"; +Calendar._TT["SEL_DATE"] = "Tarih seçiniz"; +Calendar._TT["DRAG_TO_MOVE"] = "Taţýmak için sürükleyiniz"; +Calendar._TT["PART_TODAY"] = " (bugün)"; +Calendar._TT["MON_FIRST"] = "Takvim Pazartesi gününden baţlasýn"; +Calendar._TT["SUN_FIRST"] = "Takvim Pazar gününden baţlasýn"; +Calendar._TT["CLOSE"] = "Kapat"; +Calendar._TT["TODAY"] = "Bugün"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "dd-mm-y"; +Calendar._TT["TT_DATE_FORMAT"] = "d MM y, DD"; + +Calendar._TT["WK"] = "Hafta"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-zh.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-zh.js new file mode 100644 index 0000000..4a0feb6 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/calendar-zh.js @@ -0,0 +1,119 @@ +// ** I18N + +// Calendar ZH language +// Author: muziq, +// Encoding: GB2312 or GBK +// Distributed under the same terms as the calendar itself. + +// full day names +Calendar._DN = new Array +("ĐÇĆÚČŐ", + "ĐÇĆÚŇ»", + "ĐÇĆÚ¶ţ", + "ĐÇĆÚČý", + "ĐÇĆÚËÄ", + "ĐÇĆÚÎĺ", + "ĐÇĆÚÁů", + "ĐÇĆÚČŐ"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("ČŐ", + "Ň»", + "¶ţ", + "Čý", + "ËÄ", + "Îĺ", + "Áů", + "ČŐ"); + +// full month names +Calendar._MN = new Array +("Ň»ÔÂ", + "¶ţÔÂ", + "ČýÔÂ", + "ËÄÔÂ", + "ÎĺÔÂ", + "ÁůÔÂ", + "ĆßÔÂ", + "°ËÔÂ", + "ľĹÔÂ", + "Ę®ÔÂ", + "ʮһÔÂ", + "Ę®¶ţÔÂ"); + +// short month names +Calendar._SMN = new Array +("Ň»ÔÂ", + "¶ţÔÂ", + "ČýÔÂ", + "ËÄÔÂ", + "ÎĺÔÂ", + "ÁůÔÂ", + "ĆßÔÂ", + "°ËÔÂ", + "ľĹÔÂ", + "Ę®ÔÂ", + "ʮһÔÂ", + "Ę®¶ţÔÂ"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "°ďÖú"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"ѡÔńČŐĆÚ:\n" + +"- µă»÷ \xab, \xbb °´ĹĄŃˇÔńÄę·Ý\n" + +"- µă»÷ " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " °´ĹĄŃˇÔńÔ·Ý\n" + +"- ł¤°´ŇÔÉĎ°´ĹĄżÉ´Ó˛ËµĄÖĐżěËŮѡÔńÄę·Ý»ňÔ·Ý"; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"ѡÔńʱĽä:\n" + +"- µă»÷Сʱ»ň·ÖÖÓżÉĘą¸ÄĘýÖµĽÓŇ»\n" + +"- °´×ˇShiftĽüµă»÷Сʱ»ň·ÖÖÓżÉĘą¸ÄĘýÖµĽőŇ»\n" + +"- µă»÷Í϶ŻĘó±ężÉ˝řĐĐżěËŮѡÔń"; + +Calendar._TT["PREV_YEAR"] = "ÉĎŇ»Äę (°´×ˇłö˛ËµĄ)"; +Calendar._TT["PREV_MONTH"] = "ÉĎһԠ(°´×ˇłö˛ËµĄ)"; +Calendar._TT["GO_TODAY"] = "תµ˝˝ńČŐ"; +Calendar._TT["NEXT_MONTH"] = "ĎÂһԠ(°´×ˇłö˛ËµĄ)"; +Calendar._TT["NEXT_YEAR"] = "ĎÂŇ»Äę (°´×ˇłö˛ËµĄ)"; +Calendar._TT["SEL_DATE"] = "ѡÔńČŐĆÚ"; +Calendar._TT["DRAG_TO_MOVE"] = "Í϶Ż"; +Calendar._TT["PART_TODAY"] = " (˝ńČŐ)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "×î×ó±ßĎÔĘľ%s"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "ąŘ±Ő"; +Calendar._TT["TODAY"] = "˝ńČŐ"; +Calendar._TT["TIME_PART"] = "(Shift-)µă»÷Ęó±ę»ňÍ϶Ż¸Ä±äÖµ"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%A, %b %eČŐ"; + +Calendar._TT["WK"] = "ÖÜ"; +Calendar._TT["TIME"] = "ʱĽä:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/cn_utf8.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/cn_utf8.js new file mode 100644 index 0000000..a0ef7c6 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/lang/cn_utf8.js @@ -0,0 +1,123 @@ +// ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Translator : Niko +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names +Calendar._DN = new Array +("\u5468\u65e5",//\u5468\u65e5 + "\u5468\u4e00",//\u5468\u4e00 + "\u5468\u4e8c",//\u5468\u4e8c + "\u5468\u4e09",//\u5468\u4e09 + "\u5468\u56db",//\u5468\u56db + "\u5468\u4e94",//\u5468\u4e94 + "\u5468\u516d",//\u5468\u516d + "\u5468\u65e5");//\u5468\u65e5 + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("\u5468\u65e5", + "\u5468\u4e00", + "\u5468\u4e8c", + "\u5468\u4e09", + "\u5468\u56db", + "\u5468\u4e94", + "\u5468\u516d", + "\u5468\u65e5"); + +// full month names +Calendar._MN = new Array +("\u4e00\u6708", + "\u4e8c\u6708", + "\u4e09\u6708", + "\u56db\u6708", + "\u4e94\u6708", + "\u516d\u6708", + "\u4e03\u6708", + "\u516b\u6708", + "\u4e5d\u6708", + "\u5341\u6708", + "\u5341\u4e00\u6708", + "\u5341\u4e8c\u6708"); + +// short month names +Calendar._SMN = new Array +("\u4e00\u6708", + "\u4e8c\u6708", + "\u4e09\u6708", + "\u56db\u6708", + "\u4e94\u6708", + "\u516d\u6708", + "\u4e03\u6708", + "\u516b\u6708", + "\u4e5d\u6708", + "\u5341\u6708", + "\u5341\u4e00\u6708", + "\u5341\u4e8c\u6708"); + +// tooltips +Calendar._TT = {}; +Calendar._TT["INFO"] = "\u5173\u4e8e"; + +Calendar._TT["ABOUT"] = +" DHTML \u65e5\u8d77/\u65f6\u95f4\u9009\u62e9\u63a7\u4ef6\n" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: \u6700\u65b0\u7248\u672c\u8bf7\u767b\u9646http://www.dynarch.com/projects/calendar/\u5bdf\u770b\n" + +"\u9075\u5faaGNU LGPL. \u7ec6\u8282\u53c2\u9605 http://gnu.org/licenses/lgpl.html" + +"\n\n" + +"\u65e5\u671f\u9009\u62e9:\n" + +"- \u70b9\u51fb\xab(\xbb)\u6309\u94ae\u9009\u62e9\u4e0a(\u4e0b)\u4e00\u5e74\u5ea6.\n" + +"- \u70b9\u51fb" + String.fromCharCode(0x2039) + "(" + String.fromCharCode(0x203a) + ")\u6309\u94ae\u9009\u62e9\u4e0a(\u4e0b)\u4e2a\u6708\u4efd.\n" + +"- \u957f\u65f6\u95f4\u6309\u7740\u6309\u94ae\u5c06\u51fa\u73b0\u66f4\u591a\u9009\u62e9\u9879."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"\u65f6\u95f4\u9009\u62e9:\n" + +"-\u5728\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)\u4e0a\u5355\u51fb\u9f20\u6807\u5de6\u952e\u6765\u589e\u52a0\u5f53\u524d\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)\n" + +"-\u5728\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)\u4e0a\u6309\u4f4fShift\u952e\u540e\u5355\u51fb\u9f20\u6807\u5de6\u952e\u6765\u51cf\u5c11\u5f53\u524d\u65f6\u95f4\u90e8\u5206(\u5206\u6216\u8005\u79d2)."; + +Calendar._TT["PREV_YEAR"] = "\u4e0a\u4e00\u5e74"; +Calendar._TT["PREV_MONTH"] = "\u4e0a\u4e2a\u6708"; +Calendar._TT["GO_TODAY"] = "\u5230\u4eca\u5929"; +Calendar._TT["NEXT_MONTH"] = "\u4e0b\u4e2a\u6708"; +Calendar._TT["NEXT_YEAR"] = "\u4e0b\u4e00\u5e74"; +Calendar._TT["SEL_DATE"] = "\u9009\u62e9\u65e5\u671f"; +Calendar._TT["DRAG_TO_MOVE"] = "\u62d6\u52a8"; +Calendar._TT["PART_TODAY"] = " (\u4eca\u5929)"; + +// the following is to inform that "%s" is to be the first day of week +// %s will be replaced with the day name. +Calendar._TT["DAY_FIRST"] = "%s\u4e3a\u8fd9\u5468\u7684\u7b2c\u4e00\u5929"; + +// This may be locale-dependent. It specifies the week-end days, as an array +// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1 +// means Monday, etc. +Calendar._TT["WEEKEND"] = "0,6"; + +Calendar._TT["CLOSE"] = "\u5173\u95ed"; +Calendar._TT["TODAY"] = "\u4eca\u5929"; +Calendar._TT["TIME_PART"] = "(\u6309\u7740Shift\u952e)\u5355\u51fb\u6216\u62d6\u52a8\u6539\u53d8\u503c"; + +// date formats +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e\u65e5"; + +Calendar._TT["WK"] = "\u5468"; +Calendar._TT["TIME"] = "\u65f6\u95f4:"; diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-blue.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-blue.css new file mode 100644 index 0000000..9a4f7ce --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-blue.css @@ -0,0 +1,232 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #556; + font-size: 11px; + color: #000; + cursor: default; + background: #eef; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ +} + +.calendar .nav { + background: #778 url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #fff; + color: #000; + padding: 2px; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ + background: #778; + color: #fff; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #bdf; +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #556; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #a66; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #aaf; + color: #000; + border: 1px solid #04f; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #77c; + padding: 2px 0px 0px 2px; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + color: #456; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #bdf; +} + +.calendar tbody .rowhilite td { + background: #def; +} + +.calendar tbody .rowhilite td.wn { + background: #eef; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #def; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #cde; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fff; + color: #000; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #a66; +} + +.calendar tbody td.today { /* Cell showing selected date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #556; + color: #fff; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #fff; + color: #445; + border-top: 1px solid #556; + padding: 1px; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #aaf; + border: 1px solid #04f; + color: #000; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #77c; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #def; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #acf; +} + +.calendar .combo .active { + border-top: 1px solid #46a; + border-bottom: 1px solid #46a; + background: #eef; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #667; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-blue2.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-blue2.css new file mode 100644 index 0000000..2cf7387 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-blue2.css @@ -0,0 +1,236 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #206A9B; + font-size: 11px; + color: #000; + cursor: default; + background: #F1F8FC; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ +} + +.calendar .nav { + background: #007ED1 url(/bundles/dynarch_calendar/images/menuarrow2.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #000; + color: #fff; + padding: 2px; +} + +.calendar thead tr { /* Row containing navigation buttons */ + background: #007ED1; + color: #fff; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #C7E1F3; +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #206A9B; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #a66; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #34ABFA; + color: #000; + border: 1px solid #016DC5; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #006AA9; + border: 1px solid #008AFF; + padding: 2px 0px 0px 2px; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + color: #456; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #C7E1F3; +} + +.calendar tbody .rowhilite td { + background: #def; +} + +.calendar tbody .rowhilite td.wn { + background: #F1F8FC; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #def; + padding: 1px 3px 1px 1px; + border: 1px solid #8FC4E8; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #cde; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fff; + color: #000; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #a66; +} + +.calendar tbody td.today { /* Cell showing selected date */ + font-weight: bold; + color: #D50000; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #206A9B; + color: #fff; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #000; + color: #fff; + border-top: 1px solid #206A9B; + padding: 1px; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #B8DAF0; + border: 1px solid #178AEB; + color: #000; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #006AA9; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #def; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #34ABFA; + border-top: 1px solid #46a; + border-bottom: 1px solid #46a; + font-weight: bold; +} + +.calendar .combo .active { + border-top: 1px solid #46a; + border-bottom: 1px solid #46a; + background: #F1F8FC; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #E3F0F9; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #F1F8FC; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #267DB7; + color: #fff; +} + +.calendar td.time span.active { + border-color: red; + background-color: #000; + color: #A5FF00; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-brown.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-brown.css new file mode 100644 index 0000000..222ab8e --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-brown.css @@ -0,0 +1,225 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #655; + font-size: 11px; + color: #000; + cursor: default; + background: #ffd; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ +} + +.calendar .nav { + background: #edc url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #654; + color: #fed; + padding: 2px; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ + background: #edc; + color: #000; +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #655; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #faa; + color: #000; + border: 1px solid #f40; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #c77; + padding: 2px 0px 0px 2px; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #fed; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #fed; +} + +.calendar tbody .rowhilite td { + background: #ddf; +} + +.calendar tbody .rowhilite td.wn { + background: #efe; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #ffe; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #ddc; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fea; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { font-weight: bold; } + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #988; + color: #000; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + border-top: 1px solid #655; + background: #dcb; + color: #840; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #faa; + border: 1px solid #f40; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #c77; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #ffe; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #fc8; +} + +.calendar .combo .active { + border-top: 1px solid #a64; + border-bottom: 1px solid #a64; + background: #fee; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #a88; + padding: 1px 0px; + text-align: center; + background-color: #fed; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #988; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #866; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-green.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-green.css new file mode 100755 index 0000000..da4c935 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-green.css @@ -0,0 +1,229 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #565; + font-size: 11px; + color: #000; + cursor: default; + background: #efe; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ + background: #676; + color: #fff; + font-size: 90%; +} + +.calendar .nav { + background: #676 url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + padding: 2px; + background: #250; + color: #efa; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #565; + padding: 2px; + text-align: center; + color: #000; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #a66; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #afa; + color: #000; + border: 1px solid #084; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #7c7; + padding: 2px 0px 0px 2px; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #dfb; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + color: #564; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #bbb; +} +.calendar tbody .day.othermonth.oweekend { + color: #fbb; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #8a8; + background: #dfb; +} + +.calendar tbody .rowhilite td { + background: #dfd; +} + +.calendar tbody .rowhilite td.wn { + background: #efe; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #efd; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #dec; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #f8fff8; + color: #000; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #a66; +} + +.calendar tbody td.today { font-weight: bold; color: #0a0; } + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #565; + color: #fff; +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + padding: 2px; + background: #250; + color: #efa; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #afa; + border: 1px solid #084; + color: #000; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #7c7; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #565; + background: #efd; + color: #000; + font-size: 90%; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .hilite { + background: #af8; +} + +.calendar .combo .active { + border-top: 1px solid #6a4; + border-bottom: 1px solid #6a4; + background: #efe; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #8a8; + padding: 1px 0px; + text-align: center; + background-color: #dfb; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #898; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #686; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-system.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-system.css new file mode 100644 index 0000000..5d1ec79 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-system.css @@ -0,0 +1,251 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border: 1px solid; + border-color: #fff #000 #000 #fff; + font-size: 11px; + cursor: default; + background: Window; + color: WindowText; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border: 1px solid; + border-color: #fff #000 #000 #fff; + font-size: 11px; + cursor: default; + background: Window; + color: WindowText; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border: 1px solid; + border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight; + background: ButtonFace; +} + +.calendar .nav { + background: ButtonFace url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: ActiveCaption; + color: CaptionText; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid ButtonShadow; + padding: 2px; + text-align: center; + background: ButtonFace; + color: ButtonText; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border: 2px solid; + padding: 0px; + border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + border-width: 1px; + padding: 2px 0px 0px 2px; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid ButtonShadow; + background: ButtonFace; + color: ButtonText; +} + +.calendar tbody .rowhilite td { + background: Highlight; + color: HighlightText; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border: 1px solid; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; + padding: 2px 2px 0px 2px; + background: ButtonFace; + color: ButtonText; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody td.disabled { color: GrayText; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: ButtonFace; + padding: 1px; + border: 1px solid; + border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow; + color: ButtonText; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #e4e0d8; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border: 1px solid; + border-color: ButtonHighlight ButtonShadow ButtonShadow ButtonHighlight; + background: Menu; + color: MenuText; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + padding: 0px; + border: 1px solid #000; +} + +.calendar .combo .hilite { + background: Highlight; + color: HighlightText; +} + +.calendar td.time { + border-top: 1px solid ButtonShadow; + padding: 1px 0px; + text-align: center; + background-color: ButtonFace; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: Menu; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: Highlight; + color: HighlightText; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-tas.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-tas.css new file mode 100644 index 0000000..33c8951 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-tas.css @@ -0,0 +1,239 @@ +/* The main calendar widget. DIV containing a table. */ + +div.calendar { position: relative; } + +.calendar, .calendar table { + border: 1px solid #655; + font-size: 11px; + color: #000; + cursor: default; + background: #ffd; + font-family: tahoma,verdana,sans-serif; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#DDDCCC,EndColorStr=#FFFFFF); +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; /* They are the navigation buttons */ + padding: 2px; /* Make the buttons seem like they're pressing */ + color:#363636; +} + +.calendar .nav { + background: #edc url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; /* Pressing it will take you to the current date */ + text-align: center; + background: #654; + color: #363636; + padding: 2px; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#dddccc); +} + +.calendar thead .headrow { /* Row containing navigation buttons */ + /*background: #3B86A0;*/ + color: #363636; + font-weight: bold; +filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#3b86a0); +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #655; + padding: 2px; + text-align: center; + color: #363636; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#DDDCCC,EndColorStr=#FFFFFF); +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + background-color: #ffcc86; + color: #000; + border: 1px solid #b59345; + padding: 1px; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + background-color: #c77; + padding: 2px 0px 0px 2px; +} + +.calendar thead .daynames { /* Row containing the day names */ + background: #fed; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #fed; +} + +.calendar tbody .rowhilite td { + background: #ddf; + +} + +.calendar tbody .rowhilite td.wn { + background: #efe; +} + +.calendar tbody td.hilite { /* Hovered cells */ + background: #ffe; + padding: 1px 3px 1px 1px; + border: 1px solid #bbb; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + background: #ddc; + padding: 2px 2px 0px 2px; +} + +.calendar tbody td.selected { /* Cell showing today date */ + font-weight: bold; + border: 1px solid #000; + padding: 1px 3px 1px 1px; + background: #fea; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { font-weight: bold; } + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ + text-align: center; + background: #988; + color: #000; + +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + border-top: 1px solid #655; + background: #dcb; + color: #363636; + font-weight: bold; + filter: +progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#FFFFFF,EndColorStr=#DDDCCC); +} +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + background: #faa; + border: 1px solid #f40; + padding: 1px; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + background: #c77; + padding: 2px 0px 0px 2px; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.combo { + position: absolute; + display: none; + top: 0px; + left: 0px; + width: 4em; + cursor: default; + border: 1px solid #655; + background: #ffe; + color: #000; + font-size: smaller; + z-index: 100; +} + +.combo .label, +.combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.combo .label-IEfix { + width: 4em; +} + +.combo .hilite { + background: #fc8; +} + +.combo .active { + border-top: 1px solid #a64; + border-bottom: 1px solid #a64; + background: #fee; + font-weight: bold; +} + +.calendar td.time { + border-top: 1px solid #a88; + padding: 1px 0px; + text-align: center; + background-color: #fed; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #988; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #866; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-1.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-1.css new file mode 100644 index 0000000..eacf359 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-1.css @@ -0,0 +1,271 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #d4d0c8; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #d4d0c8; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #848078; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #f4f0e8; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #e4e0d8; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #c4c0b8; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #f4f0e8; +} + +.calendar tbody .rowhilite td { + background: #e4e0d8; +} + +.calendar tbody .rowhilite td.wn { + background: #d4d0c8; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + padding: 2px 2px 0px 2px; + background: #e4e0d8; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #f4f0e8; + padding: 1px; + border: 1px solid #000; + background: #848078; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #e4e0d8; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #e4e0d8; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #c4c0b8; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #048; + color: #fea; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #766; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-2.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-2.css new file mode 100644 index 0000000..e204b43 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-2.css @@ -0,0 +1,271 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #d4c8d0; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #d4c8d0; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #847880; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #f4e8f0; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #e4d8e0; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #c4b8c0; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #f4e8f0; +} + +.calendar tbody .rowhilite td { + background: #e4d8e0; +} + +.calendar tbody .rowhilite td.wn { + background: #d4c8d0; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + padding: 2px 2px 0px 2px; + background: #e4d8e0; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #f4e8f0; + padding: 1px; + border: 1px solid #000; + background: #847880; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #e4d8e0; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #e4d8e0; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #d4c8d0; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #408; + color: #fea; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #766; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-1.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-1.css new file mode 100644 index 0000000..3e2079d --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-1.css @@ -0,0 +1,265 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d0d4; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d0d4; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #788084; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #e8f0f4; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #d8e0e4; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #b8c0c4; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #e8f4f0; +} + +.calendar tbody .rowhilite td { + background: #d8e4e0; +} + +.calendar tbody .rowhilite td.wn { + background: #c8d4d0; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border: 1px solid; + border-color: #fff #000 #000 #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: #000 #fff #fff #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + padding: 2px 2px 0px 2px; + border: 1px solid; + border-color: #000 #fff #fff #000; + background: #d8e0e4; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #e8f0f4; + padding: 1px; + border: 1px solid #000; + background: #788084; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #d8e0e4; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #d8e0e4; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #c8d0d4; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #048; + color: #aef; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #e8f0f4; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #667; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-2.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-2.css new file mode 100644 index 0000000..d4313d4 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar-win2k-cold-2.css @@ -0,0 +1,271 @@ +/* The main calendar widget. DIV containing a table. */ + +.calendar { + position: relative; + display: none; + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d4d0; + font-family: tahoma,verdana,sans-serif; +} + +.calendar table { + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + font-size: 11px; + color: #000; + cursor: default; + background: #c8d4d0; + font-family: tahoma,verdana,sans-serif; +} + +/* Header part -- contains navigation buttons and day names. */ + +.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */ + text-align: center; + padding: 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar .nav { + background: transparent url(/bundles/dynarch_calendar/images/menuarrow.gif) no-repeat 100% 100%; +} + +.calendar thead .title { /* This holds the current "month, year" */ + font-weight: bold; + padding: 1px; + border: 1px solid #000; + background: #788480; + color: #fff; + text-align: center; +} + +.calendar thead .headrow { /* Row containing navigation buttons */ +} + +.calendar thead .daynames { /* Row containing the day names */ +} + +.calendar thead .name { /* Cells containing the day names */ + border-bottom: 1px solid #000; + padding: 2px; + text-align: center; + background: #e8f4f0; +} + +.calendar thead .weekend { /* How a weekend day name shows in header */ + color: #f00; +} + +.calendar thead .hilite { /* How do the buttons in header appear when hover */ + border-top: 2px solid #fff; + border-right: 2px solid #000; + border-bottom: 2px solid #000; + border-left: 2px solid #fff; + padding: 0px; + background-color: #d8e4e0; +} + +.calendar thead .active { /* Active (pressed) buttons in header */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + background-color: #b8c4c0; +} + +/* The body part -- contains all the days in month. */ + +.calendar tbody .day { /* Cells containing month days dates */ + width: 2em; + text-align: right; + padding: 2px 4px 2px 2px; +} +.calendar tbody .day.othermonth { + font-size: 80%; + color: #aaa; +} +.calendar tbody .day.othermonth.oweekend { + color: #faa; +} + +.calendar table .wn { + padding: 2px 3px 2px 2px; + border-right: 1px solid #000; + background: #e8f4f0; +} + +.calendar tbody .rowhilite td { + background: #d8e4e0; +} + +.calendar tbody .rowhilite td.wn { + background: #c8d4d0; +} + +.calendar tbody td.hilite { /* Hovered cells */ + padding: 1px 3px 1px 1px; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; +} + +.calendar tbody td.active { /* Active (pressed) cells */ + padding: 2px 2px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar tbody td.selected { /* Cell showing selected date */ + font-weight: bold; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; + padding: 2px 2px 0px 2px; + background: #d8e4e0; +} + +.calendar tbody td.weekend { /* Cells showing weekend days */ + color: #f00; +} + +.calendar tbody td.today { /* Cell showing today date */ + font-weight: bold; + color: #00f; +} + +.calendar tbody .disabled { color: #999; } + +.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */ + visibility: hidden; +} + +.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */ + display: none; +} + +/* The footer part -- status bar and "Close" button */ + +.calendar tfoot .footrow { /* The in footer (only one right now) */ +} + +.calendar tfoot .ttip { /* Tooltip (status bar) cell */ + background: #e8f4f0; + padding: 1px; + border: 1px solid #000; + background: #788480; + color: #fff; + text-align: center; +} + +.calendar tfoot .hilite { /* Hover style for buttons in footer */ + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + padding: 1px; + background: #d8e4e0; +} + +.calendar tfoot .active { /* Active (pressed) style for buttons in footer */ + padding: 2px 0px 0px 2px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +/* Combo boxes (menus that display months/years for direct selection) */ + +.calendar .combo { + position: absolute; + display: none; + width: 4em; + top: 0px; + left: 0px; + cursor: default; + border-top: 1px solid #fff; + border-right: 1px solid #000; + border-bottom: 1px solid #000; + border-left: 1px solid #fff; + background: #d8e4e0; + font-size: 90%; + padding: 1px; + z-index: 100; +} + +.calendar .combo .label, +.calendar .combo .label-IEfix { + text-align: center; + padding: 1px; +} + +.calendar .combo .label-IEfix { + width: 4em; +} + +.calendar .combo .active { + background: #c8d4d0; + padding: 0px; + border-top: 1px solid #000; + border-right: 1px solid #fff; + border-bottom: 1px solid #fff; + border-left: 1px solid #000; +} + +.calendar .combo .hilite { + background: #048; + color: #aef; +} + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #e8f0f4; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #667; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar.css b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar.css new file mode 100644 index 0000000..c8fbbc3 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/stylesheets/calendar.css @@ -0,0 +1,106 @@ +/********** + syncPEOPLE calendar specific styles +*/ +.calendar { + width: 80%; + border: 0px; + border-style: none; + margin-top: 20px; + margin-left: auto; + margin-right: auto; +} +/*.calendar td { + border-style: none; +}*/ +.calendar th { + border-style: none; +} +.calendar_top { + width: 100%; + border-style: none; +} +.calendar_title { + font-size: 14pt; + font-weight: bold; + text-align: center; +} +.calendar_control { + text-align: center; +} +.calendar_table { + width: 100%; + border-style: outset; + border-width: 3px; + padding-top:0px; + padding-right:2px; + padding-bottom:0px + padding-left:2px; +} +.calendar_day_row0 { +} +.calendar_day_row1 { + background-color: #C0C0C0; +} +.calendar_time { + width: 20%; + text-align:right +} +.calendar_appointment { + width: 80%; +} +.calendar_today { + font-weight: bold; +} +.week_cell { + width: 25%; + height: 18ex; + vertical-align: top; + border: thin solid grey; +} +.week_row { +} +.month_day_in { + width: 13%; + height: 10ex; + vertical-align: top; + border: thin solid grey; + overflow: hidden; +} +.month_day_out { + width: 13%; + height: 10ex; + vertical-align: top; + border: thin solid grey; + background-color: #C0C0C0; + overflow: hidden; +} +.month_week { + width: 9%; + height: 10ex; + text-align: center; + border: none; +} +.month_row { +} +#appointment { + text-align: center; +} +#appoinment_edit { + margin-left: auto; + margin-right: auto; + +} +#appoinment_edit th { + text-align: right; + vertical-align: top; +} +#appoinment_edit td { + text-align: left; + border-style: none; + vertical-align: top; +} +.notice { + border: red solid; + background-color:mlight-red; + text-align: center; +} \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/jstree.rb b/vendor/plugins/bundled_resource/bundles/jstree.rb new file mode 100644 index 0000000..1744f8e --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/jstree.rb @@ -0,0 +1,27 @@ +# Example HTML: +# +=begin +
      + +
    • Root node 2
        +
      • Node 2.1
          +
        • Node 2.1.4
        • +
      • +
    • +
    +=end + +module BundledResource::Jstree + def bundle + require_stylesheet "/bundles/jstree/stylesheets/resizable" + + require_javascript "prototype" + require_javascript "effects" + require_javascript "/bundles/jstree/javascripts/behaviour" + require_javascript "/bundles/jstree/javascripts/jstree" + end +end diff --git a/vendor/plugins/bundled_resource/bundles/jstree/javascripts/behaviour.js b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/behaviour.js new file mode 100644 index 0000000..bc5504f --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/behaviour.js @@ -0,0 +1,254 @@ +/* + Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work + of Simon Willison (see comments by Simon below). + + Description: + + Uses css selectors to apply javascript behaviours to enable + unobtrusive javascript in html documents. + + Usage: + + var myrules = { + 'b.someclass' : function(element){ + element.onclick = function(){ + alert(this.innerHTML); + } + }, + '#someid u' : function(element){ + element.onmouseover = function(){ + this.innerHTML = "BLAH!"; + } + } + }; + + Behaviour.register(myrules); + + // Call Behaviour.apply() to re-apply the rules (if you + // update the dom, etc). + + License: + + My stuff is BSD licensed. Not sure about Simon's. + + More information: + + http://ripcord.co.nz/behaviour/ + +*/ + +var Behaviour = { + list : new Array, + + register : function(sheet){ + Behaviour.list.push(sheet); + }, + + start : function(){ + Behaviour.addLoadEvent(function(){ + Behaviour.apply(); + }); + }, + + apply : function(){ + for (h=0;sheet=Behaviour.list[h];h++){ + for (selector in sheet){ + list = document.getElementsBySelector(selector); + + if (!list){ + continue; + } + + for (i=0;element=list[i];i++){ + sheet[selector](element); + } + } + } + }, + + addLoadEvent : function(func){ + var oldonload = window.onload; + + if (typeof window.onload != 'function') { + window.onload = func; + } else { + window.onload = function() { + oldonload(); + func(); + } + } + } +} + +Behaviour.start(); + +/* + The following code is Copyright (C) Simon Willison 2004. + + document.getElementsBySelector(selector) + - returns an array of element objects from the current document + matching the CSS selector. Selectors can contain element names, + class names and ids and can be nested. For example: + + elements = document.getElementsBySelect('div#main p a.external') + + Will return an array of all 'a' elements with 'external' in their + class attribute that are contained inside 'p' elements that are + contained inside the 'div' element which has id="main" + + New in version 0.4: Support for CSS2 and CSS3 attribute selectors: + See http://www.w3.org/TR/css3-selectors/#attribute-selectors + + Version 0.4 - Simon Willison, March 25th 2003 + -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows + -- Opera 7 fails +*/ + +function getAllChildren(e) { + // Returns all children of element. Workaround required for IE5/Windows. Ugh. + return e.all ? e.all : e.getElementsByTagName('*'); +} + +document.getElementsBySelector = function(selector) { + // Attempt to fail gracefully in lesser browsers + if (!document.getElementsByTagName) { + return new Array(); + } + // Split selector in to tokens + var tokens = selector.split(' '); + var currentContext = new Array(document); + for (var i = 0; i < tokens.length; i++) { + token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');; + if (token.indexOf('#') > -1) { + // Token is an ID selector + var bits = token.split('#'); + var tagName = bits[0]; + var id = bits[1]; + var element = document.getElementById(id); + if (tagName && element.nodeName.toLowerCase() != tagName) { + // tag with that ID not found, return false + return new Array(); + } + // Set currentContext to contain just this element + currentContext = new Array(element); + continue; // Skip to next token + } + if (token.indexOf('.') > -1) { + // Token contains a class selector + var bits = token.split('.'); + var tagName = bits[0]; + var className = bits[1]; + if (!tagName) { + tagName = '*'; + } + // Get elements matching tag, filter them for class selector + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) { + currentContext[currentContextIndex++] = found[k]; + } + } + continue; // Skip to next token + } + // Code to deal with attribute selectors + if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { + var tagName = RegExp.$1; + var attrName = RegExp.$2; + var attrOperator = RegExp.$3; + var attrValue = RegExp.$4; + if (!tagName) { + tagName = '*'; + } + // Grab all of the tagName elements within current context + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements; + if (tagName == '*') { + elements = getAllChildren(currentContext[h]); + } else { + elements = currentContext[h].getElementsByTagName(tagName); + } + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = new Array; + var currentContextIndex = 0; + var checkFunction; // This function will be used to filter the elements + switch (attrOperator) { + case '=': // Equality + checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); }; + break; + case '~': // Match one of space seperated words + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); }; + break; + case '|': // Match start with value followed by optional hyphen + checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); }; + break; + case '^': // Match starts with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); }; + break; + case '$': // Match ends with value - fails with "Warning" in Opera 7 + checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); }; + break; + case '*': // Match ends with value + checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); }; + break; + default : + // Just test for existence of attribute + checkFunction = function(e) { return e.getAttribute(attrName); }; + } + currentContext = new Array; + var currentContextIndex = 0; + for (var k = 0; k < found.length; k++) { + if (checkFunction(found[k])) { + currentContext[currentContextIndex++] = found[k]; + } + } + // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); + continue; // Skip to next token + } + + if (!currentContext[0]){ + return; + } + + // If we get here, token is JUST an element (not a class or ID selector) + tagName = token; + var found = new Array; + var foundCount = 0; + for (var h = 0; h < currentContext.length; h++) { + var elements = currentContext[h].getElementsByTagName(tagName); + for (var j = 0; j < elements.length; j++) { + found[foundCount++] = elements[j]; + } + } + currentContext = found; + } + return currentContext; +} + +/* That revolting regular expression explained +/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ + \---/ \---/\-------------/ \-------/ + | | | | + | | | The value + | | ~,|,^,$,* or = + | Attribute + Tag +*/ diff --git a/vendor/plugins/bundled_resource/bundles/jstree/javascripts/jstree.js b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/jstree.js new file mode 100644 index 0000000..333bd38 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/jstree.js @@ -0,0 +1,124 @@ +JSTree = Class.create(); +Object.extend(JSTree.prototype, { + initialize: function(tree) { + this.element = $(tree); + this.options = Object.extend({ + duration: 0.25, + collapseAll: false, + collapsedClass: 'collapsed', + expandedClass: 'expanded', + imagePath: '.', + imageCollapse: 'minus.gif', + imageExpand: 'plus.gif' + }, arguments[1] || {}); + + this._setup(); + }, + + isCollapsed: function(el) { + el = $(el); + return el.className == this.options.collapsedClass; + }, + + collapse: function(el, duration) { + el = $(el); + if (duration == undefined) duration = this.options.duration; + var recurse = (arguments[2] == undefined) ? true : arguments[2]; + + el.className = this.options.collapsedClass; + + var a = el.firstChild; + if ((a.nodeType == 1) && (a.className == 'jstreeControl')) { + a.style.backgroundImage = this._imageUrl(this.options.imageExpand); + } + + if (recurse) { + var children = el.getElementsByTagName('li'); + for (var i = 0; i < children.length; i++) { + var c = children[i]; + this.collapse(c, duration, false); + } + + var sublists = el.getElementsByTagName('ul') + for (var j = 0; j < sublists.length; j++) { + var list = sublists[j]; + Effect.BlindUp(list, { 'duration': duration }); + } + + } + }, + + expand: function(el, duration) { + el = $(el); + if (duration == undefined) duration = this.options.duration; + + el.className = this.options.expandedClass; + + var a = el.firstChild; + if ((a.nodeType == 1) && (a.className == 'jstreeControl')) { + a.style.backgroundImage = this._imageUrl(this.options.imageCollapse); + } + + var children = el.childNodes; + for (i = 0; i < children.length; i++) { + var child = children[i]; + if ((child.nodeType == 1) && (child.tagName == 'UL')) { + Effect.BlindDown(child, { 'duration': duration }); + } + } + }, + + _imageUrl: function(image) { + var path = this.options.imagePath + '/' + image; + return 'url("' + path +'")'; + }, + + _toggle: function() { + if (this.treeControl.isCollapsed(this)) { + this.treeControl.expand(this); + } else { + this.treeControl.collapse(this); + } + }, + + _setup: function() { + var tree = this.element; + tree.setAttribute('style', 'list-style: none; ' + tree.style); + + lists = tree.getElementsByTagName('ul'); + for (var i = 0; i < lists.length; i++) { + var sublist = lists[i]; + sublist.setAttribute('style', 'list-style: none; ' + sublist.style); + } + + var items = tree.getElementsByTagName('li'); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var collapsable = item.getElementsByTagName('ul').length > 0; + if (collapsable) { + var collapsed = this.isCollapsed(item); + var img = this._imageUrl(collapsed ? this.options.imageExpand : this.options.imageCollapse); + var style = 'padding-left: 15px; background: ' + img + ' left no-repeat;'; + var a = document.createElement('a'); +a.innerHTML = ' '; + a.setAttribute('style', style); + a.setAttribute('class', 'jstreeControl'); + a.onclick = this._toggle.bindAsEventListener(item); + item.insertBefore(a, item.firstChild); + item.treeControl = this; + + if (collapsed || this.options.collapseAll) { + this.collapse(item, 0); + } + } + } + } +}); + +Behaviour.register({ + 'ul.jstree': function(list) { + // new JSTree(list, { 'collapseAll': true, 'duration': 0 }); + // new JSTree(list, { 'collapseAll': true, 'duration': 3 }); + new JSTree(list, { 'collapseAll': true }); + } +}); diff --git a/vendor/plugins/bundled_resource/bundles/lightbox.rb b/vendor/plugins/bundled_resource/bundles/lightbox.rb new file mode 100644 index 0000000..43a3019 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/lightbox.rb @@ -0,0 +1,20 @@ +# == Usage == +# require_bundle :lightbox +# +# Surround a thumbnail image inside of an anchor tag like this: +# +# +# +# +# And your thumbnails will turn in to full-sized images within the browser window when clicked on! +# +# == Full Lightbox Documenation == +# http://www.huddletogether.com/projects/lightbox/ + +module BundledResource::Lightbox + def bundle + require_javascript "/bundles/lightbox/javascripts/lightbox" + + require_stylesheet "/bundles/lightbox/stylesheets/lightbox" + end +end \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/images/loading.gif b/vendor/plugins/bundled_resource/bundles/lightbox/images/loading.gif new file mode 100644 index 0000000..fbe57be Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/lightbox/images/loading.gif differ diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/images/overlay.png b/vendor/plugins/bundled_resource/bundles/lightbox/images/overlay.png new file mode 100644 index 0000000..7cee298 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/lightbox/images/overlay.png differ diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/javascripts/lightbox.js b/vendor/plugins/bundled_resource/bundles/lightbox/javascripts/lightbox.js new file mode 100644 index 0000000..b5589eb --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/lightbox/javascripts/lightbox.js @@ -0,0 +1,318 @@ +/* + Lightbox JS: Fullsize Image Overlays + by Lokesh Dhakar - http://www.huddletogether.com + + For more information on this script, visit: + http://huddletogether.com/projects/lightbox/ + + Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/ + (basically, do anything you want, just leave my name and link) + + Table of Contents + ----------------- + Configuration + + Functions + - getPageScroll() + - getPageSize() + - pause() + - showLightbox() + - hideLightbox() + - initLightbox() + - addLoadEvent() + + Function Calls + - addLoadEvent(initLightbox) + +*/ + + + +// +// Configuration +// + +// If you would like to use a loading image, point to it in the next line, otherwise leave as-is. +var loadingImage = '/bundles/lightbox/images/loading.gif'; + + + + + +// +// getPageScroll() +// Returns array with x,y page scroll values. +// Core code from - quirksmode.org +// +function getPageScroll(){ + + var yScroll; + + if (self.pageYOffset) { + yScroll = self.pageYOffset; + } else if (document.documentElement && document.documentElement.scrollTop){ // Explorer 6 Strict + yScroll = document.documentElement.scrollTop; + } else if (document.body) {// all other Explorers + yScroll = document.body.scrollTop; + } + + arrayPageScroll = new Array('',yScroll) + return arrayPageScroll; +} + + + +// +// getPageSize() +// Returns array with page width, height and window width, height +// Core code from - quirksmode.org +// +function getPageSize(){ + + var xScroll, yScroll; + + if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac + xScroll = document.body.scrollWidth; + yScroll = document.body.scrollHeight; + } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari + xScroll = document.body.offsetWidth; + yScroll = document.body.offsetHeight; + } + + var windowWidth, windowHeight; + if (self.innerHeight) { // all except Explorer + windowWidth = self.innerWidth; + windowHeight = self.innerHeight; + } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode + windowWidth = document.documentElement.clientWidth; + windowHeight = document.documentElement.clientHeight; + } else if (document.body) { // other Explorers + windowWidth = document.body.clientWidth; + windowHeight = document.body.clientHeight; + } + + // for small pages with total height less then height of the viewport + if(yScroll < windowHeight){ + pageHeight = windowHeight; + } else { + pageHeight = yScroll; + } + + // for small pages with total width less then width of the viewport + if(xScroll < windowWidth){ + pageWidth = windowWidth; + } else { + pageWidth = xScroll; + } + + + arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight) + return arrayPageSize; +} + +// +// pause(numberMillis) +// Pauses code execution for specified time. Uses busy code, not good. +// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602 +// +function pause(numberMillis) { + var now = new Date(); + var exitTime = now.getTime() + numberMillis; + while (true) { + now = new Date(); + if (now.getTime() > exitTime) + return; + } +} + + + + + +// +// showLightbox() +// Preloads images. Pleaces new image in lightbox then centers and displays. +// +function showLightbox(objLink) +{ + // prep objects + var objOverlay = document.getElementById('overlay'); + var objLightbox = document.getElementById('lightbox'); + var objImage = document.getElementById('lightboxImage'); + var objLoadingImage = document.getElementById('loadingImage'); + + var arrayPageSize = getPageSize(); + var arrayPageScroll = getPageScroll(); + + // center loadingImage if it exists + if (objLoadingImage) { + objLoadingImage.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - objLoadingImage.height) / 2) + 'px'); + objLoadingImage.style.left = (((arrayPageSize[0] - 40 - objLoadingImage.width) / 2) + 'px'); + } + + // set height of Overlay to take up whole page and show + objOverlay.style.height = (arrayPageSize[1] + 'px'); + objOverlay.style.display = 'block'; + + // preload image + imgPreload = new Image(); + + imgPreload.onload=function(){ + objImage.src = objLink.href; + + // center lightbox + objLightbox.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - imgPreload.height) / 2) + 'px'); + objLightbox.style.left = (((arrayPageSize[0] - 40 - imgPreload.width) / 2) + 'px'); + + // A small pause between the image loading and displaying is required with IE, + // this prevents the previous image displaying for a short burst causing flicker. + if (navigator.appVersion.indexOf("MSIE")!=-1){ + pause(250); + } + + objLightbox.style.display = 'block'; + + return false; + } + + imgPreload.src = objLink.href; + +} + + + + + +// +// hideLightbox() +// +function hideLightbox() +{ + // get objects + objOverlay = document.getElementById('overlay'); + objLightbox = document.getElementById('lightbox'); + + // hide lightbox and overlay + objOverlay.style.display = 'none'; + objLightbox.style.display = 'none'; +} + + + + +// +// initLightbox() +// Function runs on window load, going through link tags looking for rel="lightbox". +// These links receive onclick events that enable the lightbox display for their targets. +// The function also inserts html markup at the top of the page which will be used as a +// container for the overlay pattern and the inline image. +// +function initLightbox() +{ + + if (!document.getElementsByTagName){ return; } + var anchors = document.getElementsByTagName("a"); + + // loop through all anchor tags + for (var i=0; i + // + + var objBody = document.getElementsByTagName("body").item(0); + + // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file) + var objOverlay = document.createElement("div"); + objOverlay.setAttribute('id','overlay'); + objOverlay.style.display = 'none'; + objOverlay.style.position = 'absolute'; + objOverlay.style.top = '0'; + objOverlay.style.left = '0'; + objOverlay.style.zIndex = '90'; + objOverlay.style.width = '100%'; + objBody.insertBefore(objOverlay, objBody.firstChild); + + var arrayPageSize = getPageSize(); + var arrayPageScroll = getPageScroll(); + + // preload and create loader image + var imgPreloader = new Image(); + + // if loader image found, create link to hide lightbox and create loadingimage + imgPreloader.onload=function(){ + + var objLoadingImageLink = document.createElement("a"); + objLoadingImageLink.setAttribute('href','#'); + objLoadingImageLink.onclick = function () {hideLightbox(); return false;} + objOverlay.appendChild(objLoadingImageLink); + + var objLoadingImage = document.createElement("img"); + objLoadingImage.src = loadingImage; + objLoadingImage.setAttribute('id','loadingImage'); + objLoadingImage.style.position = 'absolute'; + objLoadingImage.style.zIndex = '150'; + objLoadingImageLink.appendChild(objLoadingImage); + + imgPreloader.onload=function(){}; // clear onLoad, as IE will flip out w/animated gifs + + return false; + } + + imgPreloader.src = loadingImage; + + // create lightbox div, same note about styles as above + var objLightbox = document.createElement("div"); + objLightbox.setAttribute('id','lightbox'); + objLightbox.style.display = 'none'; + objLightbox.style.position = 'absolute'; + objLightbox.style.zIndex = '100'; + objBody.insertBefore(objLightbox, objOverlay.nextSibling); + + // create link + var objLink = document.createElement("a"); + objLink.setAttribute('href','#'); + objLink.onclick = function () {hideLightbox(); return false;} + objLightbox.appendChild(objLink); + + // create image + var objImage = document.createElement("img"); + objImage.setAttribute('id','lightboxImage'); + objLink.appendChild(objImage); +} + + + + +// +// addLoadEvent() +// Adds event to window.onload without overwriting currently assigned onload functions. +// Function found at Simon Willison's weblog - http://simon.incutio.com/ +// +function addLoadEvent(func) +{ + var oldonload = window.onload; + if (typeof window.onload != 'function'){ + window.onload = func; + } else { + window.onload = function(){ + oldonload(); + func(); + } + } + +} + + + + +addLoadEvent(initLightbox); // run initLightbox onLoad \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/stylesheets/lightbox.css b/vendor/plugins/bundled_resource/bundles/lightbox/stylesheets/lightbox.css new file mode 100644 index 0000000..99de44b --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/lightbox/stylesheets/lightbox.css @@ -0,0 +1,15 @@ +#lightbox{ + background-color:#eee; + padding: 10px; + border-bottom: 2px solid #666; + border-right: 2px solid #666; + } + +#overlay{ background-image: url(/bundles/lightbox/images/overlay.png); } + +* html #overlay{ + background-color: #333; + back\ground-color: transparent; + background-image: url(/bundles/lightbox/images/blank.gif); + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/bundles/lightbox/images/overlay.png", sizingMethod="scale"); + } \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/qforms.rb b/vendor/plugins/bundled_resource/bundles/qforms.rb new file mode 100644 index 0000000..8dc488e --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms.rb @@ -0,0 +1,29 @@ +# == Usage == +# require_bundle :qforms +# +# 1. Name your form, e.g. +#
    ...
    +# +# 2. Create a qForms object from the named HTML form: +# +# +# == Full qForms Documentation == +# http://pengoworks.com/qforms/docs/index.htm + +module BundledResource::Qforms + def bundle + require_javascript "/bundles/qforms/javascripts/qforms" + # require_javascript "/bundles/qforms/javascripts/qforms/field" + # require_javascript "/bundles/qforms/javascripts/qforms/functions" + # require_javascript "/bundles/qforms/javascripts/qforms/validation" + require_javascript "/bundles/qforms/javascripts/qforms_init" + end +end \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms.js new file mode 100755 index 0000000..cab3723 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms.js @@ -0,0 +1,1143 @@ +/****************************************************************************** + qForm JavaScript API + + Author: Dan G. Switzer, II + Date: December 10, 2000 + Build: 139 + + Description: + This library provides a API to forms on your page. This simplifies retrieval + of field values by providing methods to retrieve the values from fields, + without having to do complicate coding. + + To contribute money to further the development of the qForms API, see: + http://www.pengoworks.com/qForms/donations/ + + GNU License + --------------------------------------------------------------------------- + This library provides common methods for interacting with HTML forms + Copyright (C) 2001 Dan G. Switzer, II + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for mser details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +******************************************************************************/ +// find out which version of JavaScript the user has +var _jsver = 11; +for( var z=2; z < 6; z++ ) document.write("_jsver = 1" + z + ";"); + +/****************************************************************************** + qForm API Initialization +******************************************************************************/ +// define _a object +function _a(){ + // qForm's Version info + this.version = "139"; + + // initialize the number of qForm instances + this.instances = 0; + // initialize an object to use for pointers + this.objects = new Object(); + // the path where the external library components are found + this.librarypath = ""; + // specifies whether the browser should autodetect the version of JavaScript being used + this.autodetect = true; + // this specifies the default modules to load when the wildcard ("*") is specified + this.modules = new Array("field", "functions|12", "validation"); + // this is the name of the modules that have been loaded, libraries will not be loaded more then once + this.packages = new Object(); + // this is a list of validators that has loaded + this.validators = new Array(); + // this contains a list of the original contents of a container, when the setValue() method is used on a container, then the containers object is checked to see if the key exists + this.containers = new Object(); + // this structure defines the version of JavaScript being used + this.jsver = new Object(); + for( var z=1; z < 9; z++ ) this.jsver["1" + z] = "1." + z; + + // this is background color style to use when a form field validation error has occurred + this.errorColor = "red"; + // the style attribute to adjust when throwing an error + this.styleAttribute = "backgroundColor"; + // this specifies whether or not to use error color coding (by default browser that support it use it) + this.useErrorColorCoding = (document.all || document.getElementById) ? true : false; + // this specifies whether all qForm objects should be validated upon a form submission, or just the form being submitted. By default only the form being submitted is validated. + this.validateAll = false; + // this specifies whether or not a form can be submitted if validation errors occurred. If set to false, the user gets an alert box, if set to true, the user receives a confirm box. + this.allowSubmitOnError = false; + // the place holder for the number of custom validators that have been initialized + this.customValidators = 0; + // specify whether the reset method should be run when the form object is initialized + this.resetOnInit = false; + // determine whether to show status bar messages + this.showStatusMsgs = true; + + // set the regular expression attributes + this.reAttribs = "gi"; + return true; +} +qFormAPI = new _a(); + +// define _a setLibraryPath(); prototype +function _a_setLibraryPath(path){ + if( path.substring(path.length-1) != '/' ) path += '/'; + this.librarypath = path; + return true; +} +_a.prototype.setLibraryPath = _a_setLibraryPath; + +// define _a include(); prototype +function _a_include(src, path, ver){ + var source = src; + if( !source ) return true; + if( !path ) var path = this.librarypath + "qforms/"; + if( !ver ) var ver = ""; + + if( source.substring(source.length-3) != ".js" ) source += ".js"; + var thisPackage = source.substring(0,source.length-3); + + var strJS = ""; + + // if the package is already loaded, then kill method + if( this.packages[thisPackage] ) return true; + + if( thisPackage == "*" ){ + for( var i=0; i < this.modules.length; i++ ){ + var source = this.modules[i]; + var ver = "99"; + if( source.indexOf("|") > -1 ){ + ver = source.substring(source.indexOf("|") + 1); + source = source.substring(0, source.indexOf("|")); + } + if( _jsver > ver && this.autodetect ){ + document.write(strJS + this.jsver[ver] + "\" src=\"" + path + source + "_js" + ver + ".js" + strEJS); + } else { + document.write(strJS + "\" src=\"" + path + source + ".js" + strEJS); + } + this.packages[source] = true; + } + } else { + if( !this.autodetect || _jsver < 12 || ver.length == 0 ){ + document.write(strJS + "\" src=\"" + path + source + strEJS); + } else if( this.autodetect && (parseInt(_jsver, 10) >= parseInt(ver, 10)) ){ + source = source.substring(0,source.length-3) + "_js" + ver + source.substring(source.length-3); + document.write(strJS + this.jsver[ver] + "\" src=\"" + path + source + strEJS); + } else { + document.write(strJS + "\" src=\"" + path + source + strEJS); + } + } + + this.packages[thisPackage] = true; + return true; +} +_a.prototype.include = _a_include; + +function _a_unload(){ + var isFramed = false; + // loop through all the forms and reset the status of the form to idle + for( obj in qFormAPI.objects ){ + qFormAPI.objects[obj]._status = "idle"; + if( !!qFormAPI.objects[obj]._frame ) isFramed = true; + } + // some psuedo garbage collection to destroy some of the pointers if in a framed environment + if( isFramed ){ + // kill the objects if using frames + this.objects = new Object(); + // kill the containers if using frames + this.containers = new Object(); + } + return true; +} +_a.prototype.unload = _a_unload; + +// define _a validate(); prototype +function _a_validate(qForm){ + // if just validate a single form, then validate now and exit + if( !this.validateAll ) return qFormAPI.objects[qForm].validate(); + + var aryErrors = new Array(); + + // loop through all the forms + for( obj in qFormAPI.objects ){ + // check the form for errors + qFormAPI.objects[obj].checkForErrors(); + // add the errors from this form t adde queue + for( var i=0; i < qFormAPI.objects[obj]._queue.errors.length; i++ ){ + aryErrors[aryErrors.length] = qFormAPI.objects[obj]._queue.errors[i]; + } + } + + // if there are no errors then return true + if( aryErrors.length == 0 ) return true; + + var strError = "The following error(s) occurred:\n"; + for( var i=0; i < aryErrors.length; i++ ) strError += " - " + aryErrors[i] + "\n"; + + var result = false; + // check to see if the user is allowed to submit the form even if an error occurred + if( this._allowSubmitOnError && this._showAlerts ) result = confirm(strError + "\nAre you sure you want to continue?"); + // if the form can be submitted with errors and errors should not be alerted set a hidden field equal to the errors + else if( this._allowSubmitOnError && !this._showAlerts ) result = true; + // otherwise, just display the error + else alert(strError); + + return result; +} +_a.prototype.validate = _a_validate; + +function _a_reset(hardReset){ + // loop through all the forms and reset the properties + for( obj in qFormAPI.objects ) qFormAPI.objects[obj].reset(hardReset); + return true; +} +_a.prototype.reset = _a_reset; + +// define _a getFields(); prototype +function _a_getFields(){ + stcAllData = new Object(); + + // loop through all the forms + for( obj in qFormAPI.objects ){ + // check the form for errors + var tmpStruct = qFormAPI.objects[obj].getFields(); + // add the value from this form to the structure + for( field in tmpStruct ){ + if( !stcAllData[field] ){ + stcAllData[field] = tmpStruct[field]; + } else { + stcAllData[field] += "," + tmpStruct[field]; + } + } + } + + // return all the form data + return stcAllData; +} +_a.prototype.getFields = _a_getFields; + +// define _a setFields(); prototype +function _a_setFields(struct, rd, ra){ + // loop through each form and populate the fields + for( obj in qFormAPI.objects ) qFormAPI.objects[obj].setFields(struct, rd, ra); +} +_a.prototype.setFields = _a_setFields; + +// define _a dump(); prototype +function _a_dump(){ + var str = ""; + formData = this.getFields(); + for( field in formData ) str += field + " = " + formData[field] + "\n"; + alert(str); +} +_a.prototype.dump = _a_dump; + + +/****************************************************************************** + qForm Object +******************************************************************************/ +// define qForm object +function qForm(name, parent, frame){ + if( name == null ) return true; + if( !name ) return alert("No form specified."); + // increase the instance counter + qFormAPI.instances++; + // make sure the unload event is called + if( qFormAPI.instances == 1 ) window.onunload = new Function(_functionToString(window.onunload, ";qFormAPI.unload();")); + this._name = name; + this._parent = (!!parent) ? parent : null; + this._frame = (!!frame) ? frame : null; + this._status = null; + this._queue = new Object(); + this._queue.errorFields = ","; + this._queue.errors = new Array(); + this._queue.validation = new Array(); + this._showAlerts = true; + this._allowSubmitOnError = qFormAPI.allowSubmitOnError; + this._locked = false; + this._skipValidation = false; + qFormAPI.objects[this._name] = this; + // this is a string pointer to the qFormAPI object copy of this object + this._pointer = "qFormAPI.objects['" + this._name + "']"; + this.init(); + return true; +} +// initialize dummy qForm object so that NS will initialize the prototype object +new qForm(null, null, null); + +// define qForm init(); prototype +function _q_init(){ + if( !this._name ) return false; + + // if this is NS4 and the form is in a layer + if( this._parent && document.layers ) this._form = this._parent + ".document." + this._name; + // otherwise point to the form + else this._form = "document." + this._name; + + // if the form is in a frame, then add path to the frame + if( this._frame ) this._form = this._frame + "." + this._form; + + // create a pointer to the form object + this.obj = eval(this._form); + + // if the object doesn't exist, thrown an error + if( !this.obj ) return alert("The form \"" + this._name + "\" does not exist. This error \nwill occur if the Form object was initialized before the form \nhas been created or if it simply doesn't exist. Please make \nsure to initialize the Form object after page loads to avoid \npotential problems."); + + // set the onSubmit method equal to whatever the current onSubmit is + // this function is then run whenever the submitCheck determines it's ok to submit the form + this.onSubmit = new Function(_functionToString(this.obj.onsubmit, "")); + // replace the form's onSubmit event and just run the submitCheck() method + var strSubmitCheck = this._pointer + ".submitCheck();"; + if( this._frame ) strSubmitCheck = "top." + strSubmitCheck; + this.obj.onsubmit = new Function("return " + strSubmitCheck); + + // loop through form elements + this._fields = new Array(); + this._pointers = new Object(); + for( var j=0; j < this.obj.elements.length; j++ ) this.addField(this.obj.elements[j].name); + this._status = "initialized"; + + // reset the form + if( qFormAPI.resetOnInit ) this.reset(); + + return true; +} +qForm.prototype.init = _q_init; + +// define qForm addField prototype +function _q_addField(field){ + if( typeof field == "undefined" || field.length == 0 ) return false; + o = this.obj[field]; + if( typeof o == "undefined" ) return false; + // if the field is an array + if( typeof o.type == "undefined" ) o = o[0]; + if( (!!o.type) && (typeof this[field] == "undefined") && (field.length > 0) ){ + this[field] = new Field(o, field, this._name); + this._fields[this._fields.length] = field; + this._pointers[field.toLowerCase()] = this[field]; + } + return true; +} +qForm.prototype.addField = _q_addField; + +// define qForm removeField prototype +function _q_removeField(field){ + // this function requires a JS1.2 browser + + // currently, events attached to a form field are not + // deleted. this means you'll need to manually remove + // the field from the DOM, or errors will occur + if( typeof this[field] == "undefined" ) return false; + + var f = this._fields; + // find the field in the fields array and remove it + for( var i=0; i < f.length; i++ ){ + if( f[i] == field ){ + var fp = i; + break; + } + } + + if( _jsver >= 12 ){ + delete this[field]; + f.splice(fp,1); + delete this._pointers[field.toLowerCase()]; + + var q = this._queue.validation; + // loop through validation queue, and remove references of + for( var j=0; j < q.length; j++ ){ + if( q[j][0] == field ){ + q.splice(j,1); + j--; + } + } + } + return true; +} +qForm.prototype.removeField = _q_removeField; + + +// define qForm submitCheck prototype +function _q_submitCheck(){ + // make sure the form is submitted more then once + if( this._status == "submitting" || this._status == "validating" ) return false; + this._status = "submitting"; + + // validate the form + var result = qFormAPI.validate(this._name); + // if no errors occurred, run the onSubmit() method + if( result ){ + // run the custom onSubmit method + var x = this.onSubmit(); + // if a boolean value was passed back, then update the result value + if( typeof x == "boolean" ) result = x; + } + + // if the form shouldn't be submitted, then reset the form's status + if( !result ){ + // if any validation errors occur or the form is not to be submitted because the + // onSubmit() event return false, then set the reset the form's status + this._status = "idle"; + // run any processing that should be done before submitting the form + } else { + // make sure to select all "container" objects so the values are included when submitted + _setContainerValues(this); + } + return result; +} +qForm.prototype.submitCheck = _q_submitCheck; + +// define qForm onSubmit(); prototype +qForm.prototype.onSubmit = new Function(""); + + +// define qForm addMethod(); prototype +function _q_addMethod(name, fn, type){ + if( arguments.length < 2 ) return alert("To create a new method, you must specify \nboth a name and function to run: \n obj.addMethod(\"checkTime\", _isTime);"); + var type = _param(arguments[2], "from").toLowerCase(); + + // set the object to attach the prototype method to + if( type == "field" ) type = "Field"; + else type = "qForm"; + + // if adding a predefined function, then add it now + if( typeof fn == "function" ){ + strFN = fn.toString(); + strFN = strFN.substring(strFN.indexOf(" "), strFN.indexOf("(")); + eval(type + ".prototype." + name + " = " + strFN); + + // if creating a new function, then add it now + } else { + var fnTemp = new Function(fn); + eval(type + ".prototype." + name + " = fnTemp;"); + } + return true; +} +qForm.prototype.addMethod = _q_addMethod; + +// define qForm addEvent(); prototype +function _q_addEvent(event, cmd, append){ + if( arguments.length < 2 ) return alert("Invalid arguments. Please use the format \naddEvent(event, command, [append])."); + var append = _param(arguments[2], true, "boolean"); + _addEvent(this._pointer + ".obj", arguments[0], arguments[1], append); + return true; +} +qForm.prototype.addEvent = _q_addEvent; + +// define qForm required(); prototype +function _q_required(fields, value){ + var value = _param(arguments[1], true, "boolean"); + aryField = _removeSpaces(fields).split(","); + + for( var i=0; i < aryField.length; i++ ){ + if( !this[aryField[i]] ) return alert("The form field \"" + aryField[i] + "\" does not exist."); + this[aryField[i]].required = value; + } + return true; +} +qForm.prototype.required = _q_required; + +// define qForm optional(); prototype +function _q_optional(fields){ + // turn the fields off + this.required(fields, false); + return true; +} +qForm.prototype.optional = _q_optional; + + +// define qForm forceValidation(); prototype +function _q_forceValidation(fields, value){ + var value = _param(arguments[1], true, "boolean"); + aryField = _removeSpaces(fields).split(","); + + for( var i=0; i < aryField.length; i++ ){ + if( !this[aryField[i]] ) return alert("The form field \"" + aryField[i] + "\" does not exist."); + this[aryField[i]].validate = value; + } + return true; +} +qForm.prototype.forceValidation = _q_forceValidation; + + +// define qForm submit(); prototype +function _q_submit(){ + var x = false; + // do not submit the form more then once + if( this._status == "submitting" ) return false; + if( this.obj.onsubmit() ) x = this.obj.submit(); + return (typeof x == "undefined") ? true : x; +} +qForm.prototype.submit = _q_submit; + +// define qForm disabled(); prototype +function _q_disabled(status){ + var objExists = (typeof this.obj.disabled == "boolean") ? true : false; + if( arguments.length == 0 ) var status = (this.obj.disabled) ? false : true; + // if the "disabled" var doesn't exist, then use the build in "locked" feature + if( !objExists ) this._locked = status; + // switch the status of the disabled property + else this.obj.disabled = status; + return true; +} +qForm.prototype.disabled = _q_disabled; + +// define qForm reset(); prototype +function _q_reset(hardReset){ + if( this._status == null ) return false; + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + // reset the value for this field + this[this._fields[j]].setValue(((!!hardReset) ? null : this[this._fields[j]].defaultValue), true, false); + // enforce any depencies of the current field + if( this[this._fields[j]]._queue.dependencies.length > 0 ) this[this._fields[j]].enforceDependency(); + } + return true; +} +qForm.prototype.reset = _q_reset; + +// define qForm getFields(); prototype +function _q_getFields(){ + if( this._status == null ) return false; + struct = new Object(); + // loop through form elements + for( var j=0; j < this._fields.length; j++ ) struct[this._fields[j]] = this[this._fields[j]].getValue(); + return struct; +} +qForm.prototype.getFields = _q_getFields; + +// define qForm setFields(); prototype +function _q_setFields(struct, rd, ra){ + if( this._status == null ) return false; + // if you need to reset the default values of the fields + var resetDefault = _param(arguments[1], false, "boolean"); + var resetAll = _param(arguments[2], true, "boolean"); + // reset the form + if( resetAll ) this.reset(); + // loop through form elements + for( key in struct ){ + var obj = this._pointers[key.toLowerCase()]; + if( obj ){ + obj.setValue(struct[key], true, false); + if(resetDefault) obj.defaultValue = struct[key]; + } + } + return true; +} +qForm.prototype.setFields = _q_setFields; + +// define qForm hasChanged(); prototype +function _q_hasChanged(){ + if( this._status == null ) return false; + var b = false; + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + if( this[this._fields[j]].getValue() != this[this._fields[j]].defaultValue ){ + b = true; + break; + } + } + return b; +} +qForm.prototype.hasChanged = _q_hasChanged; + +// define qForm changedFields(); prototype +function _q_changedFields(){ + if( this._status == null ) return false; + struct = new Object(); + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + if( this[this._fields[j]].getValue() != this[this._fields[j]].defaultValue ){ + struct[this._fields[j]] = this[this._fields[j]].getValue(); + } + } + return struct; +} +qForm.prototype.changedFields = _q_changedFields; + +// define qForm dump(); prototype +function _q_dump(){ + var str = ""; + var f = this.getFields(); + for( fld in f ) str += fld + " = " + f[fld] + "\n"; + alert(str); +} +qForm.prototype.dump = _q_dump; + +/****************************************************************************** + Field Object +******************************************************************************/ +// define Field object +function Field(form, field, formName, init){ + if( arguments.length > 3 ) return true; + this._queue = new Object(); + this._queue.dependencies = new Array(); + this._queue.validation = new Array(); + this.qForm = qFormAPI.objects[formName]; + this.name = field; + this.path = this.qForm._form + "['" + field + "']"; + this.pointer = this.qForm._pointer + "['" + field + "']"; + this.obj = eval(this.path); + this.locked = false; + this.description = field.toLowerCase(); + this.required = false; + this.validate = false; + this.container = false; + this.type = (!this.obj.type && !!this.obj[0]) ? this.obj[0].type : this.obj.type; + this.validatorAttached = false; + + var value = this.getValue(); + this.defaultValue = value; + this.lastValue = value; + + // initialize the field object + this.init(); + + return true; +} +new Field(null, null, null, true); + +// define Field init(); prototype +function _f_init(){ + if( qFormAPI.useErrorColorCoding && this.obj.style ) this.styleValue = (!!this.obj.style[qFormAPI.styleAttribute]) ? this.obj.style[qFormAPI.styleAttribute].toLowerCase() : ""; + + if( document.layers && (this.type == "radio" || this.type == "checkbox") && !!this.obj[0] ){ + this.addEvent("onclick", "return " + this.pointer + ".allowFocus();"); + } else { + this.addEvent("onfocus", "return " + this.pointer + ".allowFocus();"); + } +} +Field.prototype.init = _f_init; + +// define Field allowFocus(); prototype +function _f_allowFocus(){ + // if the background color equals the error color, then reset the style to the original background + if( qFormAPI.useErrorColorCoding && this.obj.style ){ + if( this.qForm._queue.errorFields.indexOf(","+this.name+",") > -1 ) this.obj.style[qFormAPI.styleAttribute] = this.styleValue; + } + // store the current value in the lastValue property + this.lastValue = this.getValue(); + // check to see if the field is locked + var result = this.checkIfLocked(); + + // if the field is locked, and we have a select box, we need to reset the value of the field + // and call the onblur method to remove focus + if( (this.type.indexOf("select") > -1) && !result ){ + this.resetLast(); + this.blur(); + } + + // if the field isn't locked, run the onFocus event + if( !result ) this.onFocus(); + // return the result of the checkIfLocked() method + return result; +} +Field.prototype.allowFocus = _f_allowFocus; + +// define qForm onFocus(); prototype +Field.prototype.onFocus = new Function(""); + +// define Field addEvent(); prototype +function _f_addEvent(event, cmd, append){ + if( arguments.length < 2 ) return alert("Invalid arguments. Please use the format \naddEvent(event, command, [append])."); + var append = _param(arguments[2], true, "boolean"); + + // if the field is a multi-array element, then apply the event to all items in the array + if( (this.type == "radio" || this.type == "checkbox") && !!this.obj[0] ){ + for( var i=0; i < this.obj.length; i++ ) _addEvent(this.path + "[" + i + "]", arguments[0], arguments[1], append); + } else { + _addEvent(this.path, arguments[0], arguments[1], append); + } + return true; +} +Field.prototype.addEvent = _f_addEvent; + +// define Field disabled(); prototype +function _f_disabled(s){ + var status = arguments[0]; + var oField = (this.type == "radio") ? this.obj[0] : this.obj; + var objExists = (typeof oField.disabled == "boolean") ? true : false; + if( arguments.length == 0 ) var status = (oField.disabled) ? false : true; + // if the "disabled" var doesn't exist, then use the build in "locked" feature + if( !objExists ) this.locked = status; + // switch the status of the disabled property + else { + if( !!this.obj[0] && this.type.indexOf("select") == -1 ) for( var i=0; i < this.obj.length; i++ ) this.obj[i].disabled = status; + else this.obj.disabled = status; + } + return true; +} +Field.prototype.disabled = _f_disabled; + +// define Field checkIfLocked(); prototype +function _f_checkIfLocked(showMsg){ + var bShowMsg = _param(arguments[0], this.qForm._showAlerts); + // if the value isn't equal to the key, then don't relocate the user + if( this.isLocked() ){ + this.blur(); + if( bShowMsg ) alert("This field is disabled."); + return false; + } + return true; +} +Field.prototype.checkIfLocked = _f_checkIfLocked; + +// define Field isLocked(); prototype +function _f_isLocked(){ + var isLocked = this.locked; + if( this.qForm._locked ) isLocked = true; // if the entire form is locked + return isLocked; +} +Field.prototype.isLocked = _f_isLocked; + +// define Field isDisabled(); prototype +function _f_isDisabled(){ + // if the disabled object exists, then get its status + if( typeof this.obj.disabled == "boolean" ){ + var isDisabled = this.obj.disabled; + if( this.qForm.obj.disabled ) isDisabled = true; // if the entire form is locked + return isDisabled; + // otherwise, return false (saying it's not disabled) + } else { + return false; + } +} +Field.prototype.isDisabled = _f_isDisabled; + +// define Field focus(); prototype +function _f_focus(){ + if( !!this.obj.focus ) this.obj.focus(); +} +Field.prototype.focus = _f_focus; + +// define Field blur(); prototype +function _f_blur(){ + if( !!this.obj.blur ) this.obj.blur(); +} +Field.prototype.blur = _f_blur; + +// define Field select(); prototype +function _f_select(){ + if( !!this.obj.select ) this.obj.select(); +} +Field.prototype.select = _f_select; + +// define Field reset(); prototype +function _f_reset(){ + this.setValue(this.defaultValue, true, false); +} +Field.prototype.reset = _f_reset; + +// define Field getValue(); prototype +function _f_getValue(){ + var type = (this.type.substring(0,6) == "select") ? "select" : this.type; + var value = new Array(); + + if( type == "select" ){ + if( this.type == "select-one" && !this.container ){ + value[value.length] = (this.obj.selectedIndex == -1) ? "" : this.obj[this.obj.selectedIndex].value; + } else { + // loop through all element in the array for this field + for( var i=0; i < this.obj.length; i++ ){ + // if the element is selected, get the selected values (unless it's a dummy container) + if( (this.obj[i].selected || this.container) && (!this.dummyContainer) ){ + // append the selected value, if the value property doesn't exist, use the text + value[value.length] = this.obj[i].value; + } + } + } + } else if( (type == "checkbox") || (type == "radio") ){ + // if more then one checkbox + if( !!this.obj[0] && !this.obj.value ){ + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ) if( this.obj[i].checked ) value[value.length] = this.obj[i].value; + // otherwise, store the value of the field (if checkmarked) into the list + } else if( this.obj.checked ){ + value[value.length] = this.obj.value; + } + } else { + value[value.length] = this.obj.value; + } + return value.join(","); +} +Field.prototype.getValue = _f_getValue; + +// define Field setValue(); prototype +function _f_setValue(value, bReset, doEvents){ + this.lastValue = this.getValue(); + var reset = _param(arguments[1], true, "boolean"); + var doEvents = _param(arguments[2], true, "boolean"); + var type = (this.type.substring(0,6) == "select") ? "select" : this.type; + var v; + + if( type == "select" ){ + var bSelectOne = (this.type == "select-one") ? true : false; + var orig = value; + value = "," + value + ","; + bLookForFirst = true; // if select-one type, then only select the first value found + // if the select box is not a container + if( !this.container ){ + // loop through all element in the array for this field + for( var i=0; i < this.obj.length; i++ ){ + v = this.obj[i].value; + bSelectItem = (value.indexOf("," + v + ",") > -1) ? true : false; + if( bSelectItem && (bLookForFirst || !bSelectOne) ) this.obj[i].selected = true; + else if( reset || bSelectOne) this.obj[i].selected = false; + if( bSelectItem && bLookForFirst ) bLookForFirst = false; + } + // if a select-one box and nothing selected, then try to select the default value + if( bSelectOne && bLookForFirst ){ + if( this.defaultValue == orig ) if( this.obj.length > 0 ) this.obj[0].selected = true; + else this.setValue(this.defaultValue); + } + // if the select box is a container, then search through the container's original contents + } else { + newValues = new Object(); + for( var i=0; i < this.boundContainers.length; i++ ){ + var sCName = this.qForm._name + "_" + this.boundContainers[i]; + // check to see if the container exists, if it does check for the value + if( qFormAPI.containers[sCName] ){ + // loop through all the container objects + for( key in qFormAPI.containers[sCName] ){ + // if the key is in the container, then make sure to add the value + if( value.indexOf("," + key + ",") > -1 ){ + newValues[key] = qFormAPI.containers[sCName][key]; + } + } + } + } + // populate the container values + this.populate(newValues, reset) + } + + } else if( (type == "checkbox") || (type == "radio") ){ + // if more then one checkbox + if( !!this.obj[0] && !this.obj.value ){ + // surround the value by commas for detection + value = "," + value + ","; + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ){ + if( value.indexOf("," + this.obj[i].value + ",") > -1 ) this.obj[i].checked = true; + else if( reset ) this.obj[i].checked = false; + } + // otherwise, store the value of the field (if checkmarked) into the list + } else if( this.obj.value == value ){ + this.obj.checked = true; + } else if( reset ){ + this.obj.checked = false; + } + + } else { + this.obj.value = (!value) ? "" : value; + } + + // run the trigger events + if( doEvents ){ + this.triggerEvent("onblur"); + // run the onchange event if the value has changed + if( this.lastValue != value ) this.triggerEvent("onchange"); + } + // run the onSetValue method + this.onSetValue(); + + return true; +} +Field.prototype.setValue = _f_setValue; + +// define Field onSetValue(); prototype +Field.prototype.onSetValue = new Function(""); + +// define Field triggerEvent(); prototype +function _f_triggerEvent(event){ + oEvent = eval("this.obj." + event); + if( (this.obj.type == "checkbox") || (this.obj.type == "radio") && !!this.obj[0] ){ + for( var k=0; k < this.obj.length; k++ ){ + oEvent = eval("this.obj[k]." + event); + if( typeof oEvent == "function" ) oEvent(); + } + } else if( typeof oEvent == "function" ){ + oEvent(); + } +} +Field.prototype.triggerEvent = _f_triggerEvent; + +/****************************************************************************** + Validation Object +******************************************************************************/ +// define qForm addValidator(); prototype +function _q_addValidator(name, fn){ + if( arguments.length < 2 ) return alert("To create a new validation object, you must specify \nboth a name and function to run: \n obj.addValidator(\"isTime\", __isTime);"); + if( typeof fn == "string" ){ + var _func = new Function(fn); + _addValidator(name, _func); + } else { + _addValidator(name, fn); + } + return true; +} +qForm.prototype.addValidator = _q_addValidator; + +// define Field validateExp(); prototype +function _f_validateExp(expression, error, cmd){ + var expression = _param(arguments[0], "false"); + var error = _param(arguments[1], "An error occurred on the field '\" + this.description + \"'."); + var cmd = _param(arguments[2]); + + var strFn = "if( " + expression + " ){ this.error = \"" + error + "\";}"; + if( cmd.length > 0 ) strFn += cmd; + strValidateExp = "_validateExp" + qFormAPI.customValidators; + _addValidator(strValidateExp, new Function(strFn)); + eval(this.pointer + ".validate" + strValidateExp + "();"); + qFormAPI.customValidators++; +} +Field.prototype.validateExp = _f_validateExp; + +function _addValidator(name, fn, alwaysRun){ + var alwaysRun = _param(arguments[2], false, "boolean"); + + if( arguments.length < 2 ) return alert("To create a new validation object, you must specify \nboth a name and function to run: \n _addValidator(\"isTime\", __isTime);"); + // strip "is" out of name if present + if( name.substring(0,2).toLowerCase() == "is" ) name = name.substring(2); + + // if the validator has already been loaded, do not load it + for( var a=0; a < qFormAPI.validators.length; a++ ) if( qFormAPI.validators[a] == name ) return alert("The " + name + " validator has already been loaded."); + + // add the validator to the array of validators + qFormAPI.validators[qFormAPI.validators.length] = name; + + // if not registering a simple expression evaluator, then update the status bar + if( qFormAPI.showStatusMsgs && name.substring(0,12) != "_validateExp" ){ + // update the status bar with the initialization request + window.status = "Initializing the validate" + name + "() and is" + name + "() validation scripts..."; + // clear the status bar + setTimeout("window.status = ''", 100); + } + + var strFN = fn.toString(); + var strName = strFN.substring(strFN.indexOf(" "), strFN.indexOf("(")); + var strArguments = strFN.substring( strFN.indexOf("(")+1, strFN.indexOf(")") ); + // remove spaces from the arguments + while( strArguments.indexOf(" ") > -1 ) strArguments = strArguments.substring( 0, strArguments.indexOf(" ") ) + strArguments.substring( strArguments.indexOf(" ")+1 ); + + // add rountine to check to see if the validation method should be processed + // if displaying errors, but the field is locked then return false immediately + var strBody = "var display = (this.qForm._status == 'validating') ? false : true;\n"; + strBody += "if( (display && this.isLocked()) || this.qForm._status.substring(0,5) == 'error') return false;\n this.value = this.getValue();"; + if( !alwaysRun ) strBody += "if( !display && this.value.length == 0 && !this.required ) return false;\n"; + strBody += "this.error = '';\n"; + + // get the body of the custom function + strBody += strFN.substring( strFN.indexOf("{")+1, strFN.lastIndexOf("}") ); + + // if alerting the user to the error + strBody += "if( this.error.length > 0 && !!errorMsg) this.error = errorMsg;\n"; + strBody += "if( display && this.error.length > 0 ){\n"; + strBody += "if( this.qForm._status.indexOf('_ShowError') > -1 ){\n"; + strBody += "this.qForm._status = 'error';\n"; + // if the user has specified an error message, then display the custom message + strBody += "alert(this.error);\n"; + strBody += "setTimeout(this.pointer + \".focus();\", 1);\n"; + strBody += "setTimeout(this.pointer + \".qForm._status = 'idle';\", 100);\n"; + strBody += "} return false;\n"; + strBody += "} else if ( display ){ return true; } return this.error;\n"; + + // start build a string to create the new function + var strNewFN = "new Function("; + var aryArguments = strArguments.split(","); + for( var i=0; i < aryArguments.length; i++ ){ + if(aryArguments[i] != "") strNewFN += "\"" + aryArguments[i] + "\","; + } + var strRuleFN = strNewFN; + + strNewFN += "\"errorMsg\",strBody);"; + + // create the Field prototype for validation + eval("Field.prototype.is" + name + " = " + strNewFN); + + // create validation rule, the validation rule must loop through the arguments provided + // and create a string to stick in the validation queue. This string will be eval() later + // on to check for errors + var strRule = "var cmd = this.pointer + '.is" + name + "';\n"; + strRule += "cmd += '( ';\n"; + strRule += "for( i=0; i < arguments.length; i++ ){ \n"; + strRule += "if( typeof arguments[i] == 'string' ) cmd += '\"' + arguments[i] + '\",';\n"; + strRule += "else cmd += arguments[i] + ',';\n"; + strRule += "}\n"; + strRule += "cmd = cmd.substring(0, cmd.length-1);\n"; + strRule += "cmd += ')';\n"; + strRule += "this.qForm._queue.validation[this.qForm._queue.validation.length] = new Array(this.name, cmd);\n"; + strRule += "this._queue.validation[this._queue.validation.length] = cmd;\n"; + strRule += "if( !this.validatorAttached ){ this.addEvent('onblur', this.pointer + '.checkForErrors()');"; + strRule += "this.validatorAttached = true;}\n"; + strRule += "return true;\n"; + strRuleFN += "\"errorMsg\",strRule);"; + eval("Field.prototype.validate" + name + " = " + strRuleFN); + + return true; +} + +// define Field checkForErrors(); prototype +function _f_checkForErrors(){ + if( !this.validate || this.qForms._skipValidation ) return true; + // change the status of the form + this.qForm._status += "_ShowError"; + // loop through the validation queue and validation each item, if the item has already been validated, don't validate again + for( var i=0; i < this._queue.validation.length; i++ ) if( !eval(this._queue.validation[i]) ) break; + // reset the status to idle + setTimeout(this.pointer + ".qForm._status = 'idle';", 100); + return true; +} +Field.prototype.checkForErrors = _f_checkForErrors; + +// define qForm validate(); prototype +function _q_validate(){ + // if validation library hasn't been loaded, then return true + if( !qFormAPI.packages.validation || this._skipValidation ) return true; + + // check the form for errors + this.checkForErrors(); + + // if there are no errors then return true + if( this._queue.errors.length == 0 ) return true; + + // run the custom onError event, if it returns false, cancel request + var result = this.onError(); + if( result == false ) return true; + + var strError = "The following error(s) occurred:\n"; + for( var i=0; i < this._queue.errors.length; i++ ) strError += " - " + this._queue.errors[i] + "\n"; + + var result = false; + // check to see if the user is allowed to submit the form even if an error occurred + if( this._allowSubmitOnError && this._showAlerts ) result = confirm(strError + "\nAre you sure you want to continue?"); + // if the form can be submitted with errors and errors should not be alerted set a hidden field equal to the errors + else if( this._allowSubmitOnError && !this._showAlerts ) result = true; + // otherwise, just display the error + else alert(strError); + + return result; +} +qForm.prototype.validate = _q_validate; + +// define qForm checkForErrors(); prototype +function _q_checkForErrors(){ + var status = this._status; // copy the current form's status + this._status = "validating"; // set form's status to validating + this._queue.errors = new Array(); // clear the current error queue + aryQueue = new Array(); // create a local queue for the required fields + this._queue.errorFields = ","; + + + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + // if the current field is required, then check to make sure it's value isn't blank + if( this[this._fields[j]].required ) aryQueue[aryQueue.length] = new Array(this._fields[j], this._pointer + "['" + this._fields[j] + "'].isNotEmpty(\"The " + this[this._fields[j]].description + " field is required.\");"); + // reset the CSS settings on the field + if( qFormAPI.useErrorColorCoding && this[this._fields[j]].obj.style ) this[this._fields[j]].obj.style[qFormAPI.styleAttribute] = this[this._fields[j]].styleValue; + } + + // loop through the required fields queue, if the field throws an error, don't validate later + for( var i=0; i < aryQueue.length; i++ ) this[aryQueue[i][0]].throwError(eval(aryQueue[i][1])); + + // loop through the validation queue and validation each item, if the item has already been validated, don't validate again + for( var i=0; i < this._queue.validation.length; i++ ) this[this._queue.validation[i][0]].throwError(eval(this._queue.validation[i][1])); + + // run the custom validation routine + this.onValidate(); + + // set form's status back to it's last status + this._status = status; + + return true; +} +qForm.prototype.checkForErrors = _q_checkForErrors; + +// define qForm onValidate(); prototype +qForm.prototype.onValidate = new Function(""); + +// define qForm onError(); prototype +qForm.prototype.onError = new Function(""); + +// define Field throwError() prototype +function _f_throwError(error){ + var q = this.qForm; + // if the error msg is a valid string and this field hasn't errored already, then queue msg + if( (typeof error == "string") && (error.length > 0) && (q._queue.errorFields.indexOf("," + this.name + ",") == -1) ){ + q._queue.errors[q._queue.errors.length] = error; + q._queue.errorFields += this.name + ","; + // change the background color of failed validation fields to red + if( qFormAPI.useErrorColorCoding && this.obj.style ) this.obj.style[qFormAPI.styleAttribute] = qFormAPI.errorColor; + return true; + } + return false; +} +Field.prototype.throwError = _f_throwError; + +/****************************************************************************** + Required Functions +******************************************************************************/ +// define the addEvent() function +function _addEvent(obj, event, cmd, append){ + if( arguments.length < 3 ) return alert("Invalid arguments. Please use the format \n_addEvent(object, event, command, [append])."); + var append = _param(arguments[3], true, "boolean"); + var event = arguments[0] + "." + arguments[1].toLowerCase(); + var objEvent = eval(event); + var strEvent = (objEvent) ? objEvent.toString() : ""; + // strip out the body of the function + strEvent = strEvent.substring(strEvent.indexOf("{")+1, strEvent.lastIndexOf("}")); + strEvent = (append) ? (strEvent + cmd) : (cmd + strEvent); + strEvent += "\n"; + eval(event + " = new Function(strEvent)"); + return true; +} + +// define the _functionToString() function +function _functionToString(fn, cmd, append){ + if( arguments.length < 1 ) return alert("Invalid arguments. Please use the format \n_functionToString(function, [command], [append])."); + var append = _param(arguments[2], true, "boolean"); + var strFunction = (!fn) ? "" : fn.toString(); + // strip out the body of the function + strFunction = strFunction.substring(strFunction.indexOf("{")+1, strFunction.lastIndexOf("}")); + if( cmd ) strFunction = (append) ? (strFunction + cmd + "\n") : (cmd + strFunction + "\n"); + return strFunction; +} + +// define the _param(value, default, type) function +function _param(v, d, t){ + // if no default value is present, use an empty string + if( typeof d == "undefined" ) d = ""; + // if no type value is present, use "string" + if( typeof t == "undefined" ) t = "string"; + // if datatype should be a number and it's a string, convert it to a number + if( t == "number" && typeof v == "string" ) var v = parseFloat(arguments[0]); + // get the value to return, if the v param is not equal to the type, use default value + var value = (typeof v != "undefined" && typeof v == t.toLowerCase()) ? v : d; + return value; +} + +// define the _removeSpaces(value) function +function _removeSpaces(v){ + // remove all spaces + while( v.indexOf(" ") > -1 ) v = v.substring( 0, v.indexOf(" ") ) + v.substring( v.indexOf(" ")+1 ); + return v; +} + +// defined the _setContainerValues(obj) function +function _setContainerValues(obj){ + // loop through form elements + for( var i=0; i < obj._fields.length; i++ ){ + if( obj[obj._fields[i]].container && obj[obj._fields[i]].type.substring(0,6) == "select" ){ + for( var x=0; x < obj[obj._fields[i]].obj.length; x++ ){ + obj[obj._fields[i]].obj[x].selected = (!obj[obj._fields[i]].dummyContainer); + } + } + } +} diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/bits.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/bits.js new file mode 100755 index 0000000..7c8bdbf --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/bits.js @@ -0,0 +1,54 @@ +/****************************************************************************** + qForm JSAPI: Bits Extensions Library + + Author: Dan G. Switzer, II + Build: 101 +******************************************************************************/ +// define Field getBits(); prototype +function _Field_getBits(useValue){ + var isCheckbox = (this.type == "checkbox") ? true : false; + var isSelect = (this.type == "select-multiple") ? true : false; + + if( !isCheckbox && !isSelect && (this.obj.length > 0) ) return alert("This method is only available to checkboxes or select boxes with multiple options."); + var useValue = _param(arguments[0], false, "boolean"); + + var iBit = 0; + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ){ + // if the option is checked, then add the 2 ^ i to the existing value + if( isCheckbox && this.obj[i].checked ){ + // append the selected value + iBit += (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i); + } else if( isSelect && this.obj.options[i].selected ){ + iBit += (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i); + } + } + return iBit; +} +Field.prototype.getBits = _Field_getBits; + +// define Field setBits(); prototype +function _Field_setBits(value, useValue){ + var isCheckbox = (this.type == "checkbox") ? true : false; + var isSelect = (this.type == "select-multiple") ? true : false; + + if( !isCheckbox && !isSelect && (this.obj.length > 0) ) return alert("This method is only available to checkboxes or select boxes with multiple options."); + var value = _param(arguments[0], "0"); + var useValue = _param(arguments[1], false, "boolean"); + + var value = parseInt(value, 10); + // loop through all checkbox elements, and if a checkbox is checked, grab the value + for( var i=0; i < this.obj.length; i++ ){ + // if the bitand returns the same as the value being checked, then the current + // checkbox should be checked + var j = (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i); + var result = ( (value & j) == j) ? true : false; + if( isCheckbox ) this.obj[i].checked = result; + else if( isSelect ) this.obj.options[i].selected = result; + } + // if the value provided is greater then the last bit value, return false to indicate an error + // otherwise return true to say everything is ok + return (value < Math.pow(2, i)) ? true : false; +} +Field.prototype.setBits = _Field_setBits; + diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cfform.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cfform.js new file mode 100755 index 0000000..4c79ee7 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cfform.js @@ -0,0 +1,191 @@ +/****************************************************************************** + qForm JSAPI: CFFORM Validation Library + + Author: Dan G. Switzer, II + Build: 100 +******************************************************************************/ +qFormAPI.packages.cfform = true; + +_addValidator( + "isBoolean", + function (){ + if( + (this.value.toUpperCase() != "TRUE") + || (this.value.toUpperCase() != "FALSE") + || (this.value.toUpperCase() != "YES") + || (this.value.toUpperCase() != "NO") + || (this.value != "0") + || (this.value != "1") + ){ + this.error = "The " + this.description + " field does not contain a boolean value."; + } + } +); + +/** + * A string GUID value is required. A GUID is a string + * of length 36 formatted as XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where X is a + * hexadecimal digit (0-9 or A-F). + */ +_addValidator( + "isGUID", + function (){ + var v = _trim(this.value); + if( !(/[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{12,12}/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid GUID."; + } + } +); + +/** + * A string UUID value is required. A UUID is a string + * of length 35 formatted as XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX, where X is a + * hexadecimal digit (0-9 or A-F). + */ +_addValidator( + "isUUID", + function (){ + var v = _trim(this.value); + if( !(/[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{16,16}/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid GUID."; + } + } +); + + +/** + * validate that the value is formatted correctly for a http/https/ftp url + * This pattern will match http/https/ftp urls. + * + * Matches: http://www.mm.com/index.cfm + * HTTP://WWW.MM.COM + * http://www.mm.com/index.cfm?userid=1&name=mike+nimer + * http://www.mm.com/index.cfm/userid/1/name/mike+nimer - trick used by cf developers so search engines can parse their sites (search engines ignore query strings) + * ftp://www.mm.com/ + * ftp://uname:pass@www.mm.com/ + * mailto:email@address.com + * news:rec.gardening + * news:rec.gardening + * http://a/ + * file://ftp.yoyodyne.com/pub/files/foobar.txt + * Non-Matches: www.yahoo.com + * http:www.mm.com + * + */ +_addValidator( + "isURL", + function (){ + var v = _trim(this.value).toLowerCase(); + if( !(/^((http|https|ftp|file)\:\/\/([a-zA-Z0-0]*:[a-zA-Z0-0]*(@))?[a-zA-Z0-9-\.]+(\.[a-zA-Z]{2,3})?(:[a-zA-Z0-9]*)?\/?([a-zA-Z0-9-\._\?\,\'\/\+&%\$#\=~])*)|((mailto)\:[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z0-9]{2,7})|((news)\:[a-zA-Z0-9\.]*)$/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid URL."; + } + } +); + + +/** + * A string UUID value is required. A UUID is a string + * of length 35 formatted as XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX, where X is a + * hexadecimal digit (0-9 or A-F). + */ +_addValidator( + "isRegEx", + function (r){ + var v = _trim(this.value); + if( !(r.test(v)) ){ + this.error = "The " + this.description + " field contains invalid data."; + } + } +); + +/** + * validate that the value is formatted as a telephone correctly + * This pattern matches any US Telephone Number. + * This regular expression excludes the first number, after the area code,from being 0 or 1; + * it also allows an extension to be added where it does not have to be prefixed by 'x'. + * + * Matches: + * 617.219.2000 + * 219-2000 + * (617)283-3599 x234 + * 1(222)333-4444 + * 1 (222) 333-4444 + * 222-333-4444 + * 1-222-333-4444 + * Non-Matches: + * 44-1344-458606 + * +44-1344-458606 + * +34-91-397-6611 + * 7-095-940-2000 + * +7-095-940-2000 + * +49-(0)-889-748-5516 +*/ +_addValidator( + "isUSPhone", + function (){ + var v = _trim(this.value); + if( !(/^(((1))?[ ,\-,\.]?([\\(]?([1-9][0-9]{2})[\\)]?))?[ ,\-,\.]?([^0-1]){1}([0-9]){2}[ ,\-,\.]?([0-9]){4}(( )((x){0,1}([0-9]){1,5}){0,1})?$/.test(v)) ){ + this.error = "The " + this.description + " field does not contain a valid phone number."; + } + } +); + + +_addValidator( + "isTime", + function (){ + var aTime = this.value.split(":"); + var isTime = true; + + if( (this.value.length == 0) || (aTime.length != 2) ) isTime = false; + + if( isTime ){ + var sHour = aTime[0]; + var iHour = parseInt(sHour, 10); + var sMinute = aTime[1]; + var iMinute = parseInt(sMinute, 10); + + if( (sHour != String(iHour)) || (sMinute != String(iMinute)) ) isTime = false; + else if( (iHour < 0) || (iHour > 23) ) isTime = false; + else if( (iMinute < 0) || (iMinute > 59) ) isTime = false; + } + + if( !isTime ){ + this.error = "The " + this.description + " field does not contain a valid time."; + } + } +); + +_addValidator( + "isInt", + function (){ + var v = this.value; + var isNumeric = (v == String(parseFloat(v, 10))); + if( !isNumeric || (parseInt(v, 10) != parseFloat(v, 10)) ){ + this.error = "The " + this.description + " field must contain an integer value."; + } + } +); + +_addValidator( + "isFloat", + function (){ + var v = this.value; + var isNumeric = (v == String(parseFloat(v, 10))); + if( !isNumeric || (parseInt(v, 10) == parseFloat(v, 10)) ){ + this.error = "The " + this.description + " field must contain an floating number."; + } + } +); + +_addValidator( + "isNumber", + function (){ + var v = this.value; + if( v != String(parseFloat(v, 10)) ){ + this.error = "The " + this.description + " field must contain a valid number."; + } + } +); + + diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cookies.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cookies.js new file mode 100755 index 0000000..f61f98e --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cookies.js @@ -0,0 +1,89 @@ +/****************************************************************************** + qForm JSAPI: Cookie Library + + Author: Dan G. Switzer, II + Build: 105 +******************************************************************************/ +// initialize workspace variables +var _c_dToday = new Date(); +var _c_iExpiresIn = 90; +var _c_strName = self.location.pathname; + +/****************************************************************************** + Required Functions +******************************************************************************/ +// retrieve a cookie from the browser +function _getCookie(name){ + var iStart = document.cookie.indexOf(name + "="); + var iLength = iStart + name.length + 1; + if( (iStart == -1) || (!iStart && (name == document.cookie.substring(0)) ) ) return null; + var iEnd = document.cookie.indexOf(";", iLength); + if( iEnd == -1 ) iEnd = document.cookie.length; + return unescape(document.cookie.substring(iLength, iEnd)); +} + +// set a cookie to the browser +function _setCookie(name, value, expires, path, domain, secure){ + document.cookie = name + "=" + escape(value) + + ( (expires) ? ";expires=" + expires.toGMTString() : "") + + ( (path) ? ";path=" + path : "") + + ( (domain) ? ";domain=" + domain : "") + + ( (secure) ? ";secure" : ""); +} + +function _deleteCookie(name, path, domain){ + if (Get_Cookie(name)) document.cookie = name + "=" + + ( (path) ? ";path=" + path : "") + + ( (domain) ? ";domain=" + domain : "") + + ";expires=Thu, 01-Jan-1970 00:00:01 GMT"; +} + +function _createCookiePackage(struct){ + var cookie = ""; + for( key in struct ){ + if( cookie.length > 0 ) cookie += "&"; + cookie += key + ":" + escape(struct[key]); + } + return cookie; +} + +function _readCookiePackage(pkg){ + struct = new Object(); + // break the package into key/value pairs + var a = pkg.split("&"); + // loop through the array and seperate the key/value pairs + for( var i=0; i < a.length; i++ ) a[i] = a[i].split(":"); + // convert the values into a structure + for( var i=0; i < a.length; i++ ) struct[a[i][0]] = unescape(a[i][1]); + // return the structure + return struct; +} + +/****************************************************************************** + qForm Methods +******************************************************************************/ +// define qForm loadFields(); prototype +function _qForm_loadFields(){ + var strPackage = _getCookie("qForm_" + this._name + "_" + _c_strName); + // there is no form saved + if( strPackage == null ) return false; + + this.setFields(_readCookiePackage(strPackage), null, true); +} +qForm.prototype.loadFields = _qForm_loadFields; + +// define qForm saveFields(); prototype +function _qForm_saveFields(){ + var expires = new Date(_c_dToday.getTime() + (_c_iExpiresIn * 86400000)); + var strPackage = _createCookiePackage(this.getFields()); + _setCookie("qForm_" + this._name + "_" + _c_strName, strPackage, expires); +} +qForm.prototype.saveFields = _qForm_saveFields; + +// define qForm saveOnSubmit(); prototype +function _qForm_saveOnSubmit(){ + // grab the current onSubmit() method and append the saveFields() method to it + var fn = _functionToString(this.onSubmit, "this.saveFields();"); + this.onSubmit = new Function(fn); +} +qForm.prototype.saveOnSubmit = _qForm_saveOnSubmit; diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/field.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/field.js new file mode 100755 index 0000000..dbcd296 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/field.js @@ -0,0 +1,281 @@ +/****************************************************************************** + qForm JSAPI: Field Extensions Library + + Author: Dan G. Switzer, II + Build: 109 +******************************************************************************/ +// define Field makeContainer(); prototype +function _Field_makeContainer(bindTo){ + lstContainers = (arguments.length == 0) ? this.name : this.name + "," + arguments[0]; + this.container = true; + this.defaultValue = this.getValue(); + this.lastValue = this.defaultValue; + this.dummyContainer = false; + this.boundContainers = _listToArray(lstContainers.toLowerCase()); + var thisKey = this.qForm._name + "_" + this.name.toLowerCase(); + + // copy objects from the select box into the container object + qFormAPI.containers[thisKey] = new Object(); + for( var i=0; i < this.obj.options.length; i++ ){ + qFormAPI.containers[thisKey][this.obj.options[i].value] = this.obj.options[i].text; + } +} +Field.prototype.makeContainer = _Field_makeContainer; + +// define Field resetLast(); prototype +function _Field_resetLast(){ + this.setValue(this.lastValue, null, false); + return true; +} +Field.prototype.resetLast = _Field_resetLast; + +// define Field toUpperCase(); prototype +function _Field_toUpperCase(){ + this.setValue(this.getValue().toUpperCase(), null, false); + return true; +} +Field.prototype.toUpperCase = _Field_toUpperCase; + +// define Field toLowerCase(); prototype +function _Field_toLowerCase(){ + this.setValue(this.getValue().toLowerCase(), null, false); + return true; +} +Field.prototype.toLowerCase = _Field_toLowerCase; + +// define Field ltrim(); prototype +function _Field_ltrim(){ + this.setValue(_ltrim(this.getValue()), null, false); + return true; +} +Field.prototype.ltrim = _Field_ltrim; + +// define Field rtrim(); prototype +function _Field_rtrim(){ + this.setValue(_rtrim(this.getValue()), null, false); + return true; +} +Field.prototype.rtrim = _Field_rtrim; + +// define Field trim(); prototype +function _Field_trim(){ + this.setValue(_trim(this.getValue()), null, false); + return true; +} +Field.prototype.trim = _Field_trim; + +// define Field compare(); prototype +function _Field_compare(field){ + if( this.getValue() == this.qForm[field].getValue() ){ + return true; + } else { + return false; + } + return true; +} +Field.prototype.compare = _Field_compare; + +// define Field mirrorTo(); prototype +function _Field_mirrorTo(objName){ + // test to see if the object is qForm object + isQForm = ( objName.indexOf(".") > -1 ) ? !eval("!objName.substring(0,objName.indexOf('.'))") : false; + + // if it's a qForm object, then set the value of the field to the current field when updated + if( isQForm ){ + var strCommand = objName + ".setValue(" + this.pointer + ".getValue()" + ", null, false);"; + // otherwise, set the local variable + } else { + var strCommand = objName + " = " + this.pointer + ".getValue();"; + } + + // add an onblur event so that when the field is updated, the requested field + // is updated with the value + this.addEvent(_getEventType(this.type), strCommand, false); +} +Field.prototype.mirrorTo = _Field_mirrorTo; + +// define Field createDependencyTo(); prototype +function _Field_createDependencyTo(field, condition){ + var condition = (arguments.length > 1) ? "\"" + arguments[1] + "\"" : null; + var otherField = this.qForm._pointer + "['" + field + "']"; + if( !eval(otherField) ) return alert("The " + field + " field does not exist. The dependency \nto " + this.name + " can not be created."); + // add an onblur event so that when the field is updated, the requested field + // is updated with the value + if( this.qForm[field]._queue.dependencies.length == 0 ) this.qForm[field].addEvent(_getEventType(this.qForm[field].type), otherField + ".enforceDependency();", false); + this.qForm[field]._queue.dependencies[this.qForm[field]._queue.dependencies.length] = otherField + ".isDependent('" + this.name + "', " + condition + ");"; + return true; +} +Field.prototype.createDependencyTo = _Field_createDependencyTo; + +// *this is an internal method that should only be used by the API* +// define Field isDependent(); prototype +function _Field_isDependent(field, condition){ + var condition = _param(arguments[1], null); + this.value = this.getValue(); + + // if the current field is empty or not equal to the specified value, then the + // dependent field is not required, otherwise the dependency is enforced + if( condition == null ){ + var result = (this.isNotEmpty() || this.required); + } else { + // if there's a space in the condition, assume you're to evaluate the string + if( condition.indexOf("this.") > -1 || condition == "true" || condition == "false" ){ + var result = eval(condition); + // otherwise, you're doing a simple value compare + } else { + var result = (this.value == condition); + } + } + // return both the field and the result + var o = null; + o = new Object(); + o.field = field; + o.result = result; + return o; +} +Field.prototype.isDependent = _Field_isDependent; + +// *this is an internal method that should only be used by the API* +// define Field enforceDependency(); prototype +function _Field_enforceDependency(e){ + var lstExcludeFields = _param(arguments[0], ","); + var lstFieldsChecked = ","; + var lstFieldsRequired = ","; + // loop through all the dependency and run each one + for( var i=0; i < this._queue.dependencies.length; i++ ){ + var s = eval(this._queue.dependencies[i]); + // keep a unique list of field checked + if( lstFieldsChecked.indexOf("," + s.field + ",") == -1 ) lstFieldsChecked += s.field + ","; + // keep a unique list of fields that now should be required + if( s.result && lstFieldsRequired.indexOf("," + s.field + ",") == -1 ) lstFieldsRequired += s.field + ","; + } + // create an array of the field checked + aryFieldsChecked = lstFieldsChecked.split(","); + // loop through the array skipping the first and last elements + for( var j=1; j < aryFieldsChecked.length-1; j++ ){ + // determine if the field is required + var result = (lstFieldsRequired.indexOf("," + aryFieldsChecked[j] + ",") > -1); + // update it's status + this.qForm[aryFieldsChecked[j]].required = result; + // now go check the dependencies for the field whose required status was changed + // if the dependency rules for the field have already been run, then don't run + // them again + if( lstExcludeFields.indexOf("," + aryFieldsChecked[j] + ",") == -1 ) setTimeout(this.qForm._pointer + "." + aryFieldsChecked[j] + ".enforceDependency('" + lstExcludeFields + this.name + ",')", 1); + } +} +Field.prototype.enforceDependency = _Field_enforceDependency; + + +// define Field location(); prototype +function _Field_location(target, key){ + var target = _param(arguments[0], "self"); + var key = _param(arguments[1]); + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return this.setValue(key, null, false); + + var value = this.getValue(); + this.setValue(key, null, false); + // if the value isn't equal to the key, then don't relocate the user + if( value != key ) eval(target + ".location = value"); + + return true; +} +Field.prototype.location = _Field_location; + +// define Field format(); prototype +function _Field_format(mask, type){ + var type = _param(arguments[1], "numeric").toLowerCase(); + this.validate = true; + this.validateFormat(mask, type); +} +Field.prototype.format = _Field_format; + + +// define Field populate(); prototype +function _Field_populate(struct, reset, sort, prefix){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return false; + + var reset = _param(arguments[1], true, "boolean"); + var sort = _param(arguments[2], false, "boolean"); + var prefix = _param(arguments[3], null, "object"); + + if( this.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes."); + + // clear the select box + if( reset ) this.obj.length = 0; + + // if prefixing options + if( !!prefix ) for( key in prefix ) this.obj.options[this.obj.length] = new Option(prefix[key], key); + + // populate the select box + for( key in struct ) this.obj.options[this.obj.length] = new Option(struct[key], key); + + // if the user wishes to sort the options in the select box + if( sort ) _sortOptions(this.obj); + return true; +} +Field.prototype.populate = _Field_populate; + +// define Field transferTo(); prototype +function _Field_transferTo(field, sort, type, selectItems, reset){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return false; + var sort = _param(arguments[1], true, "boolean"); + var type = _param(arguments[2], "selected"); + var selectItems = _param(arguments[3], true, "boolean"); + var reset = _param(arguments[4], false, "boolean"); + + _transferOptions(this.obj, this.qForm[field].obj, sort, type, selectItems, reset); + return true; +} +Field.prototype.transferTo = _Field_transferTo; + +// define Field transferFrom(); prototype +function _Field_transferFrom(field, sort, type, selectItems, reset){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() ) return false; + var sort = _param(arguments[1], true, "boolean"); + var type = _param(arguments[2], "selected"); + var selectItems = _param(arguments[3], true, "boolean"); + var reset = _param(arguments[4], false, "boolean"); + + _transferOptions(this.qForm[field].obj, this.obj, sort, type, selectItems, reset); + return true; +} +Field.prototype.transferFrom = _Field_transferFrom; + +// define Field moveUp(); prototype +function _Field_moveUp(){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() || this.type.substring(0,6) != "select" ) return false; + + var oOptions = this.obj.options; + // rearrange + for( var i=1; i < oOptions.length; i++ ){ + // swap options + if( oOptions[i].selected ){ + _swapOptions(oOptions[i], oOptions[i-1]); + } + } + return true; +} +Field.prototype.moveUp = _Field_moveUp; + +// define Field moveDown(); prototype +function _Field_moveDown(){ + // if the current field is disabled or locked, then kill the method + if( this.isLocked() || this.isDisabled() || this.type.substring(0,6) != "select" ) return false; + + var oOptions = this.obj.options; + // rearrange + for( var i=oOptions.length-2; i > -1; i-- ){ + // swap options + if( oOptions[i].selected ){ + _swapOptions(oOptions[i+1], oOptions[i]); + } + } + return true; +} +Field.prototype.moveDown = _Field_moveDown; + diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions.js new file mode 100755 index 0000000..af931ea --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions.js @@ -0,0 +1,200 @@ +/****************************************************************************** + qForm JSAPI: Functions Library + + Author: Dan G. Switzer, II + Build: 108 +******************************************************************************/ +function _trim(s){ return _rtrim(_ltrim(s)); } + +function _ltrim(s){ + var w = " \n\t\f"; + // remove all beginning white space + while( w.indexOf(s.charAt(0)) != -1 && s.length != 0 ) s = s.substring(1); + return s; +} + +function _rtrim(s){ + var w = " \n\t\f"; + // remove all ending white space + while( w.indexOf(s.charAt(s.length-1)) != -1 && s.length != 0 ) s = s.substring(0, s.length-1); + return s; +} + +function _listToArray(string,delim){ + var delim = _param(arguments[1], ","); + tmp = string.split(delim); + for( var i=0; i < tmp.length; i++ ) tmp[i] = _trim(tmp[i]); + return tmp; +} + +function _listSum(string,delim){ + var delim = _param(arguments[1], ","); + tmp = _listToArray(string,delim); + iValue = 0; + for( var i=0; i < tmp.length; i++ ) iValue += parseInt(tmp[i], 10); + return iValue; +} + +function _stripInvalidChars(string, type){ + var string = _param(arguments[0]); + var type = _param(arguments[1], "numeric").toLowerCase(); + var validchars = type; + + stcTypes = new Object(); + stcTypes.numeric = "1234567890"; + stcTypes.alpha = "abcdefghijklmnopqrstuvwxyz"; + stcTypes.alphnumeric = "abcdefghijklmnopqrstuvwxyz1234567890"; + + if( stcTypes[type] ) validchars = stcTypes[type]; + + var tmp = ""; + var lc = string.toLowerCase(); + // loop through the string an make sure each character is an alpha character + for( var i=0;i < string.length;i++ ){ + if (validchars.indexOf(lc.charAt(i)) != -1) tmp += string.charAt(i); + } + return tmp; +} + +function _isLength(string, len, type){ + var string = _param(arguments[0]); + var len = parseInt(_param(arguments[1], 10, "number"), 10); + var type = _param(arguments[2], "numeric"); + + var tmp = _stripInvalidChars(string, type); + return (tmp.length == len) ? true : false; +} + +function _getState(abbr){ + var abbr = _param(arguments[0]).toLowerCase(); + _s = new Object(); _s.al = "Alabama"; _s.ak = "Alaska"; _s.as = "American Samoa"; _s.az = "Arizona"; _s.ar = "Arkansas"; _s.ca = "California"; _s.co = "Colorado"; _s.ct = "Connecticut"; _s.de = "Delaware"; _s.dc = "District of Columbia"; _s.fm = "Federal States of Micronesia"; _s.fl = "Florida"; _s.ga = "Georgia"; _s.gu = "Guam"; _s.hi = "Hawaii"; _s.id = "Idaho"; _s.il = "Illinois"; _s["in"] = "Indiana"; _s.ia = "Iowa"; _s.ks = "Kansas"; _s.ky = "Kentucky"; _s.la = "Louisana"; _s.me = "Maine"; _s.mh = "Marshall Islands"; _s.md = "Maryland"; _s.ma = "Massachusetts"; _s.mi = "Michigan"; _s.mn = "Minnesota"; _s.ms = "Mississippi"; _s.mo = "Missouri"; _s.mt = "Montana"; _s.ne = "Nebraska"; _s.nv = "Nevada"; _s.nh = "New Hampshire"; _s.nj = "New Jersey"; _s.nm = "New Mexico"; _s.ny = "New York"; _s.nc = "North Carolina"; _s.nd = "North Dakota"; _s.mp = "Northern Mariana Islands"; _s.oh = "Ohio"; _s.ok = "Oklahoma"; _s.or = "Oregon"; _s.pw = "Palau"; _s.pa = "Pennsylvania"; _s.pr = "Puerto Rico"; _s.ri = "Rhode Island"; _s.sc = "South Carolina"; _s.sd = "South Dakota"; _s.tn = "Tennessee"; _s.tx = "Texas"; _s.ut = "Utah"; _s.vt = "Vermont"; _s.vi = "Virgin Islands"; _s.va = "Virginia"; _s.wa = "Washington"; _s.wv = "West Virginia"; _s.wi = "Wisconsin"; _s.wy = "Wyoming"; _s.aa = "Armed Forces Americas"; _s.ae = "Armed Forces Africa/Europe/Middle East"; _s.ap = "Armed Forces Pacific"; + if( !_s[abbr] ){ + return null; + } else { + return _s[abbr]; + } +} + +// define the default properties for the sort +qFormAPI.sortOptions = new Object(); +qFormAPI.sortOptions.order = "asc"; +qFormAPI.sortOptions.byText = true; +function _sortOptions(obj, order, byText){ + var order = _param(arguments[1], qFormAPI.sortOptions.order); + if( order != "asc" && order != "desc" ) order = "asc"; + var byText = _param(arguments[2], qFormAPI.sortOptions.byText, "boolean"); + var orderAsc = (order == "asc") ? true : false; + + // loop through all the options and sort them asc + for( var i=0; i < obj.options.length; i++ ){ + for( var j=0; j < obj.options.length-1; j++ ){ + // if an option is greater than the next option, swap them + if( orderAsc && (byText && obj.options[j].text > obj.options[j+1].text) || (!byText && obj.options[j].value > obj.options[j+1].value) ){ + tmpTxt = obj.options[j].text; + tmpVal = obj.options[j].value; + tmpSel = obj.options[j].selected; + obj.options[j].text = obj.options[j+1].text ; + obj.options[j].value = obj.options[j+1].value; + obj.options[j].selected = obj.options[j+1].selected; + obj.options[j+1].text = tmpTxt ; + obj.options[j+1].value = tmpVal; + obj.options[j+1].selected = tmpSel; + } else if( !orderAsc && (byText && obj.options[j].text < obj.options[j+1].text) || (!byText && obj.options[j].value < obj.options[j+1].value) ){ + tmpTxt = obj.options[j].text; + tmpVal = obj.options[j].value; + tmpSel = obj.options[j].selected; + obj.options[j].text = obj.options[j+1].text ; + obj.options[j].value = obj.options[j+1].value; + obj.options[j].selected = obj.options[j+1].selected; + obj.options[j+1].text = tmpTxt ; + obj.options[j+1].value = tmpVal; + obj.options[j+1].selected = tmpSel; + } + } + } + return true; +} + +function _transferOptions(field1, field2, sort, type, selectItems, reset){ + var sort = _param(arguments[2], true, "boolean"); + var type = _param(arguments[3].toLowerCase(), "selected"); + if( type != "all" && type != "selected" ) type = "selected"; + var selectItems = _param(arguments[4], true, "boolean"); + var reset = _param(arguments[5], false, "boolean"); + var doAll = (type == "all") ? true : false; + + if( field1.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field1.name + "\" is not a select box."); + if( field2.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field2.name + "\" is not a select box."); + + // clear the select box + if( reset ) field2.length = 0; + + for( var i=0; i < field1.length; i++ ){ + // if the current option is selected, move it + if( doAll || field1.options[i].selected ){ + field2.options[field2.length] = new Option(field1.options[i].text, field1.options[i].value, false, selectItems); + field1.options[i] = null; + i--; // since you just deleted a option, redo this array position next loop + } + } + + // if sorting the fields + if( sort ) _sortOptions(field2); + return true; +} + + +function _getURLParams(){ + struct = new Object(); + var strURL = document.location.href; + var iPOS = strURL.indexOf("?"); + // if there are some query string params, split them into an array + if( iPOS != -1 ){ + var strQS = strURL.substring(iPOS + 1); + var aryQS = strQS.split("&"); + + // otherwise, return the empty structure + } else { + return struct; + } + + // loop through the array + for( var i=0; i < aryQS.length; i++ ){ + iPOS = aryQS[i].indexOf("="); + // if no equal sign is found, then the value is null + if( iPOS == -1 ){ + struct[aryQS[i]] = null; + // otherwise grab the variable name and it's value and stick it in structure + } else { + var key = aryQS[i].substring(0, iPOS); + var value = unescape(aryQS[i].substring(iPOS+1)); + // if the value doesn't exist, then create a new key + if( !struct[key] ) struct[key] = value; + // otherwise, append the value + else struct[key] += "," + value; + } + } + + return struct; +} + +function _createFields(struct, type){ + var type = _param(arguments[1], "hidden"); + if( this.status == null ) return false; + // loop through form elements + for( key in struct ){ + document.write(""); + } + return true; +} + +function _getEventType(type){ + // the default event type + var strEvent = "onblur"; + // if this is a checkbox & radio button, then mirror value on click + if( type == "checkbox" || type == "radio" ) strEvent = "onclick"; + // if this is a select box, then mirror value when the value changes + else if( type.substring(0,6) == "select" ) strEvent = "onchange"; + return strEvent; +} + diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions_js12.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions_js12.js new file mode 100755 index 0000000..f96487e --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions_js12.js @@ -0,0 +1,189 @@ +/****************************************************************************** + qForm JSAPI: Functions Library + + Author: Dan G. Switzer, II + Build: 110 +******************************************************************************/ +function _trim(s){ + return _rtrim(_ltrim(s)); +} + +function _ltrim(s){ + // remove all beginning white space + return (s.length == 0) ? s : s.replace(new RegExp("^\\s+", qFormAPI.reAttribs), ""); +} + +function _rtrim(s){ + // remove all ending white space + return (s.length == 0) ? s : s.replace(new RegExp("\\s+$", qFormAPI.reAttribs), ""); +} + +function _listToArray(string,delim){ + var delim = _param(arguments[1], ","); + tmp = string.split(delim); + for( var i=0; i < tmp.length; i++ ) tmp[i] = _trim(tmp[i]); + return tmp; +} + +function _listSum(string,delim){ + var delim = _param(arguments[1], ","); + tmp = _listToArray(string,delim); + iValue = 0; + for( var i=0; i < tmp.length; i++ ) iValue += parseInt(_trim(tmp[i]), 10); + return iValue; +} + +function _stripInvalidChars(_s, _t){ + var s = _param(arguments[0]); + var t = _param(arguments[1], "numeric").toLowerCase(); + var r; + + if( t == "numeric" ) r = new RegExp("([^0-9.]*)(^-?\\d*\\.?\\d*)(\\D*)", qFormAPI.reAttribs); + else if( t == "alpha" ) r = new RegExp("[^A-Za-z]+", qFormAPI.reAttribs); + else if( t == "alphanumeric" ) r = new RegExp("\\W+", qFormAPI.reAttribs); + else r = new RegExp("[^" + t + "]+", qFormAPI.reAttribs); + + if( t == "numeric" ) s = s.replace(r, "$2"); + else s = s.replace(r, ""); + + return s; +} + +function _isLength(string, len, type){ + var string = _param(arguments[0]); + var len = parseInt(_param(arguments[1], 10, "number"), 10); + var type = _param(arguments[2], "numeric"); + + var tmp = _stripInvalidChars(string, type); + return (tmp.length == len) ? true : false; +} + +function _getState(abbr){ + var abbr = _param(arguments[0]).toLowerCase(); + _s = new Object(); _s.al = "Alabama"; _s.ak = "Alaska"; _s.as = "American Samoa"; _s.az = "Arizona"; _s.ar = "Arkansas"; _s.ca = "California"; _s.co = "Colorado"; _s.ct = "Connecticut"; _s.de = "Delaware"; _s.dc = "District of Columbia"; _s.fm = "Federal States of Micronesia"; _s.fl = "Florida"; _s.ga = "Georgia"; _s.gu = "Guam"; _s.hi = "Hawaii"; _s.id = "Idaho"; _s.il = "Illinois"; _s["in"] = "Indiana"; _s.ia = "Iowa"; _s.ks = "Kansas"; _s.ky = "Kentucky"; _s.la = "Louisana"; _s.me = "Maine"; _s.mh = "Marshall Islands"; _s.md = "Maryland"; _s.ma = "Massachusetts"; _s.mi = "Michigan"; _s.mn = "Minnesota"; _s.ms = "Mississippi"; _s.mo = "Missouri"; _s.mt = "Montana"; _s.ne = "Nebraska"; _s.nv = "Nevada"; _s.nh = "New Hampshire"; _s.nj = "New Jersey"; _s.nm = "New Mexico"; _s.ny = "New York"; _s.nc = "North Carolina"; _s.nd = "North Dakota"; _s.mp = "Northern Mariana Islands"; _s.oh = "Ohio"; _s.ok = "Oklahoma"; _s.or = "Oregon"; _s.pw = "Palau"; _s.pa = "Pennsylvania"; _s.pr = "Puerto Rico"; _s.ri = "Rhode Island"; _s.sc = "South Carolina"; _s.sd = "South Dakota"; _s.tn = "Tennessee"; _s.tx = "Texas"; _s.ut = "Utah"; _s.vt = "Vermont"; _s.vi = "Virgin Islands"; _s.va = "Virginia"; _s.wa = "Washington"; _s.wv = "West Virginia"; _s.wi = "Wisconsin"; _s.wy = "Wyoming"; _s.aa = "Armed Forces Americas"; _s.ae = "Armed Forces Africa/Europe/Middle East"; _s.ap = "Armed Forces Pacific"; + if( !_s[abbr] ){ + return null; + } else { + return _s[abbr]; + } +} + +// define the default properties for the sort +qFormAPI.sortOptions = new Object(); +qFormAPI.sortOptions.order = "asc"; +qFormAPI.sortOptions.byText = true; + +function _sortOptions(obj, order, byText){ + var order = _param(arguments[1], qFormAPI.sortOptions.order); + if( order != "asc" && order != "desc" ) order = "asc"; + var byText = _param(arguments[2], qFormAPI.sortOptions.byText, "boolean"); + var orderAsc = (order == "asc") ? true : false; + + // loop through all the options and sort them asc + for( var i=0; i < obj.options.length; i++ ){ + for( var j=0; j < obj.options.length-1; j++ ){ + // if an option is greater than the next option, swap them + if( orderAsc && (byText && obj.options[j].text > obj.options[j+1].text) || (!byText && obj.options[j].value > obj.options[j+1].value) ){ + _swapOptions(obj.options[j], obj.options[j+1]); + } else if( !orderAsc && (byText && obj.options[j].text < obj.options[j+1].text) || (!byText && obj.options[j].value < obj.options[j+1].value) ){ + _swapOptions(obj.options[j], obj.options[j+1]); + } + } + } + return true; +} + +function _swapOptions(o1, o2){ + var sText = o1.text; + var sValue = o1.value; + var sSelected = o1.selected; + o1.text = o2.text ; + o1.value = o2.value; + o1.selected = o2.selected; + o2.text = sText; + o2.value = sValue; + o2.selected = sSelected; +} + +function _transferOptions(field1, field2, sort, type, selectItems, reset){ + var sort = _param(arguments[2], true, "boolean"); + var type = _param(arguments[3], "selected").toLowerCase(); + if( type != "all" && type != "selected" ) type = "selected"; + var selectItems = _param(arguments[4], true, "boolean"); + var reset = _param(arguments[5], false, "boolean"); + var doAll = (type == "all") ? true : false; + + if( field1.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field1.name + "\" is not a select box."); + if( field2.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field2.name + "\" is not a select box."); + + // clear the select box + if( reset ) field2.length = 0; + + for( var i=0; i < field1.length; i++ ){ + // if the current option is selected, move it + if( doAll || field1.options[i].selected ){ + field2.options[field2.length] = new Option(field1.options[i].text, field1.options[i].value, false, selectItems); + field1.options[i] = null; + i--; // since you just deleted a option, redo this array position next loop + } + } + + // if sorting the fields + if( sort ) _sortOptions(field2); + return true; +} + + +function _getURLParams(){ + struct = new Object(); + var strURL = document.location.href; + var iPOS = strURL.indexOf("?"); + // if there are some query string params, split them into an array + if( iPOS != -1 ){ + var strQS = strURL.substring(iPOS + 1); + var aryQS = strQS.split("&"); + // otherwise, return the empty structure + } else { + return struct; + } + + // loop through the array + for( var i=0; i < aryQS.length; i++ ){ + iPOS = aryQS[i].indexOf("="); + // if no equal sign is found, then the value is null + if( iPOS == -1 ){ + struct[aryQS[i]] = null; + // otherwise grab the variable name and it's value and stick it in structure + } else { + var key = aryQS[i].substring(0, iPOS); + var value = unescape(aryQS[i].substring(iPOS+1)); + // if the value doesn't exist, then create a new key + if( !struct[key] ) struct[key] = value; + // otherwise, append the value + else struct[key] += "," + value; + } + } + return struct; +} + +function _createFields(struct, type){ + var type = _param(arguments[1], "hidden"); + if( this.status == null ) return false; + // loop through form elements + for( key in struct ){ + document.write(""); + } + return true; +} + +function _getEventType(type){ + // the default event type + var strEvent = "onblur"; + // if this is a checkbox & radio button, then mirror value on click + if( type == "checkbox" || type == "radio" ) strEvent = "onclick"; + // if this is a select box, then mirror value when the value changes + else if( type.substring(0,6) == "select" ) strEvent = "onchange"; + return strEvent; +} + + diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation.js new file mode 100755 index 0000000..963cf70 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation.js @@ -0,0 +1,404 @@ +/****************************************************************************** + qForm JSAPI: Validation Library + + Author: Dan G. Switzer, II + Build: 113 +******************************************************************************/ +qFormAPI.packages.validation = true; + +// define Field isNotNull(); prototype +function _Field_isNotNull(){ + // check for blank field + if( this.value.length == 0 ){ + this.error = "You must specify a(n) " + this.description + "."; + } +} +_addValidator("isNotNull", _Field_isNotNull, true); + +// define Field isNotEmpty(); prototype +function _Field_isNotEmpty(){ + // check for blank field + if( _ltrim(this.value).length == 0 ){ + this.error = "You must specify a(n) " + this.description + "."; + } +} +_addValidator("isNotEmpty", _Field_isNotEmpty); + +// define Field isEmail(); prototype +function _Field_isEmail(){ + // check for @ . or blank field + if( this.value.indexOf(" ") != -1 ){ + this.error = "Invalid " + this.description + " address. An e-mail address should not contain a space."; + } else if( this.value.indexOf("@") == -1 ){ + this.error = "Invalid " + this.description + " address. An e-mail address must contain the @ symbol."; + } else if( this.value.indexOf("@") == 0 ){ + this.error = "Invalid " + this.description + " address. The @ symbol can not be the first character of an e-mail address."; + } else if( this.value.substring(this.value.indexOf("@")+2).indexOf(".") == -1 ){ + this.error = "Invalid " + this.description + " address. An e-mail address must contain at least one period after the @ symbol."; + } else if( this.value.lastIndexOf("@") == this.value.length-1 ){ + this.error = "Invalid " + this.description + " address. The @ symbol can not be the last character of an e-mail address."; + } else if( this.value.lastIndexOf(".") == this.value.length-1 ){ + this.error = "Invalid " + this.description + " address. A period can not be the last character of an e-mail address."; + } +} +_addValidator("isEmail", _Field_isEmail); + +// define Field isPassword(); prototype +function _Field_isPassword(field, minlen, maxlen){ + var minlen = _param(arguments[1], 1, "number"); // default minimum length of password + var maxlen = _param(arguments[2], 255, "number"); // default maximum length of password + + if( field != null && !this.compare(field) ){ + this.error = "The " + this.description + " and " + this.qForm[field].description + " values do not match."; + } + + // if there aren't an errors yet + if( this.error.length == 0 ){ + if( (this.value.length < minlen) || (this.value.length > maxlen) ){ + this.error = "The " + this.description + " field must be between " + minlen.toString() + " and " + maxlen.toString() + " characters long."; + } + } +} +_addValidator("isPassword", _Field_isPassword); + +// define Field isSame(); prototype +function _Field_isSame(field){ + if( !this.compare(field) ){ + this.error = "The " + this.description + " and " + this.qForm[field].description + " values do not match."; + } +} +_addValidator("isSame", _Field_isSame); + +// define Field isDifferent(); prototype +function _Field_isDifferent(field){ + if( this.compare(field) ){ + this.error = "The " + this.description + " and " + this.qForm[field].description + " must be different."; + } +} +_addValidator("isDifferent", _Field_isDifferent); + +// define Field isRange(); prototype +function _Field_isRange(low, high){ + var low = _param(arguments[0], 0, "number"); + var high = _param(arguments[1], 9999999, "number"); + var iValue = parseInt(this.value, 10); + if( isNaN(iValue) ) iValue = 0; + + // check to make sure the number is within the valid range + if( ( low > iValue ) || ( high < iValue ) ){ + this.error = "The " + this.description + " field does not contain a\nvalue between " + low + " and " + high + "."; + } +} +_addValidator("isRange", _Field_isRange); + +// define Field isInteger(); prototype +function _Field_isInteger(){ + var i = parseInt(this.value, 10); + // make sure the user specified a numeric value + if( isNaN(i) || (String(i) != this.value) ){ + this.error = "The value for " + this.description + " is not a numeric value. This field requires a numeric value."; + } +} +_addValidator("isInteger", _Field_isInteger); + +// define Field isNumeric(); prototype +function _Field_isNumeric(){ + var i = parseFloat(this.value, 10); + // make sure the user specified a numeric value + if( isNaN(i) || (String(i) != this.value) ){ + this.error = "The value for " + this.description + " is not a numeric value. This field requires a numeric value."; + } +} +_addValidator("isNumeric", _Field_isNumeric); + +// define Field isAlpha(); prototype +function _Field_isAlpha(){ + if( !_isLength(this.value, this.value.length, "alpha") ){ + this.error = "The value for " + this.description + " must contain only alpha characters."; + } +} +_addValidator("isAlpha", _Field_isAlpha); + +// define Field isAlphaNumeric(); prototype +function _Field_isAlphaNumeric(){ + if( !_isLength(this.value, this.value.length, "alphanumeric") ){ + this.error = "The value for " + this.description + " must contain only alpha-numeric characters."; + } +} +_addValidator("isAlphaNumeric", _Field_isAlphaNumeric); + +// define Field isDate(); prototype +function _Field_isDate(mask){ + var strMask = _param(arguments[0], "mm/dd/yyyy"); + var iMaskMonth = strMask.lastIndexOf("m") - strMask.indexOf("m") + 1; + var iMaskDay = strMask.lastIndexOf("d") - strMask.indexOf("d") + 1; + var iMaskYear = strMask.lastIndexOf("y") - strMask.indexOf("y") + 1; + + var strDate = this.value; + + // find the delimiter + var delim = "", lstMask = "mdy"; + for( var i=0; i < strMask.length; i++ ){ + if (lstMask.indexOf(strMask.substring(i, i+1)) == -1){ + delim = strMask.substring(i, i+1); + break; + } + } + aMask = strMask.split(delim); + if( aMask.length == 3 ){ + dt = this.value.split(delim); + if( dt.length != 3 ) this.error = "An invalid date was provided for " + this.description + " field."; + for( i=0; i < aMask.length; i++ ){ + if( aMask[i].indexOf("m") > -1 ) var sMonth = dt[i]; + else if( aMask[i].indexOf("d") > -1 ) var sDay = dt[i]; + else if( aMask[i].indexOf("y") > -1 ) var sYear = dt[i]; + } + } else if( mask.length == 1 ){ + var sMonth = this.value.substring(strMask.indexOf("m")-1, strMask.lastIndexOf("m")); + var sDay = this.value.substring(strMask.indexOf("d")-1, strMask.lastIndexOf("d")); + var sYear = this.value.substring(strMask.indexOf("y")-1, strMask.lastIndexOf("y")); + } else { + this.error = "An invalid date mask was provided for " + this.description + " field."; + } + + var iMonth = parseInt(sMonth, 10); + var iDay = parseInt(sDay, 10); + var iYear = parseInt(sYear, 10); + + if( isNaN(iMonth) || sMonth.length > iMaskMonth ) iMonth = 0; + if( isNaN(iDay) || sDay.length > iMaskDay ) iDay = 0; + if( isNaN(sYear) || sYear.length != iMaskYear ) sYear = null; + + lst30dayMonths = ",4,6,9,11,"; + + if( sYear == null ){ + this.error = "An invalid year was provided for the " + this.description + " field. The year \n should be a " + iMaskYear + " digit number."; + } else if( (iMonth < 1) || (iMonth > 12 ) ){ + this.error = "An invalid month was provided for " + this.description + " field."; + } else { + if( iYear < 100 ) var iYear = iYear + ((iYear > 20) ? 1900 : 2000); + var iYear = (sYear.length == 4) ? parseInt(sYear, 10) : parseInt("20" + sYear, 10); + if( lst30dayMonths.indexOf("," + iMonth + ",") > -1 ){ + if( (iDay < 1) || (iDay > 30 ) ) this.error = "An invalid day was provided for the " + this.description + " field."; + } else if( iMonth == 2 ){ + if( (iDay < 1) || (iDay > 28 && !( (iDay == 29) && (iYear%4 == 0 ) ) ) ) this.error = "An invalid day was provided for the " + this.description + " field."; + } else { + if( (iDay < 1) || (iDay > 31 ) ) this.error = "An invalid day was provided for the " + this.description + " field."; + } + } + +} +_addValidator("isDate", _Field_isDate); + + +// define Field isCreditCard(); prototype +function _Field_isCreditCard(){ + var strCC = _stripInvalidChars(this.value, "numeric").toString(); + var isNumeric = (strCC.length > 0) ? true : false; + + if( isNumeric ){ + // now check mod10 + var dd = (strCC.length % 2 == 1) ? false : true; + var cd = 0; + var td; + + for( var i=0; i < strCC.length; i++ ){ + td = parseInt(strCC.charAt(i), 10); + if( dd ){ + td *= 2; + cd += (td % 10); + if ((td / 10) >= 1.0) cd++; + dd = false; + } else { + cd += td; + dd = true; + } + } + if( (cd % 10) != 0 ) this.error = "The credit card number entered in the " + this.description + " field is invalid."; + } else { + this.error = "The credit card number entered in the " + this.description + " field is invalid."; + } +} +_addValidator("isCreditCard", _Field_isCreditCard); + +// define Field isPhoneNumber(); prototype +function _Field_isPhoneNumber(len){ + var len = parseInt(_param(arguments[0], 10, "number"), 10); + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + + // check to make sure the phone is the correct length + if( !_isLength(this.value, len) ){ + this.error = "The " + description + " field must include " + len + " digits."; + } +} +_addValidator("isPhoneNumber", _Field_isPhoneNumber); + +// define Field isLength(); prototype +function _Field_isLength(len, type){ + var len = parseInt(_param(arguments[0], 10, "number"), 10); + var type = _param(arguments[1], "numeric"); + + // check to make sure the phone is the correct length + if( !_isLength(this.value, len, type) ){ + this.error = "The " + this.description + " field must include " + len + " " + type + " characters."; + } +} +_addValidator("isLength", _Field_isLength); + +// define Field isSSN(); prototype +function _Field_isSSN(){ + var description = (this.description == this.name.toLowerCase()) ? "social security" : this.description; + + // check to make sure the phone is the correct length + if( !_isLength(this.value, 9) ){ + this.error = "The " + description + " field must include 9 digits."; + } +} +_addValidator("isSSN", _Field_isSSN); + + +// define Field isState(); prototype +function _Field_isState(){ + // check to make sure the phone is the correct length + if( _getState(this.value) == null ){ + this.error = "The " + this.description + " field must contain a valid 2-digit state abbreviation."; + } +} +_addValidator("isState", _Field_isState); + +// define Field isZipCode(); prototype +function _Field_isZipCode(){ + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + + iZipLen = _stripInvalidChars(this.value).length; + + // check to make sure the zip code is the correct length + if( iZipLen != 5 && iZipLen != 9 ){ + this.error = "The " + description + " field must contain either 5 or 9 digits."; + } +} +_addValidator("isZipCode", _Field_isZipCode); + +// define Field isFormat(); prototype +function _Field_isFormat(mask, type){ + var mask = _param(arguments[0]); + var type = _param(arguments[1], "numeric").toLowerCase(); + var strErrorMsg = ""; + + var strMaskLC = mask.toLowerCase(); + // define quick masks + if( strMaskLC == "ssn" ){ + mask = "xxx-xx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "social security number" : this.description; + strErrorMsg = "The " + description + " field must contain 9 digits and \nshould be in the format: " + mask; + + } else if( (strMaskLC == "phone") || (strMaskLC == "phone1") ){ + mask = "(xxx) xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "phone2" ){ + mask = "xxx-xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "phone3" ){ + mask = "xxx/xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "phone7" ){ + mask = "xxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description; + strErrorMsg = "The " + description + " field must contain 7 digits and \nshould be in the format: " + mask; + + } else if( strMaskLC == "zip" ){ + if( _stripInvalidChars(this.value).length < 6 ){ + mask = "xxxxx"; + } else { + mask = "xxxxx-xxxx"; + } + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + strErrorMsg = "The " + description + " field should contain either 5 or 9 digits \nand be in the format: xxxxx or xxxxx-xxxx"; + + } else if( strMaskLC == "zip5" ){ + mask = "xxxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + strErrorMsg = "The " + description + " field should contain 5 digits \nand be in the format: " + mask; + + } else if( strMaskLC == "zip9" ){ + mask = "xxxxx-xxxx"; + type = "numeric"; + var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description; + strErrorMsg = "The " + description + " field should contain 9 digits \nand be in the format: " + mask; + } else { + var description = this.description; + } + + var string = _stripInvalidChars(this.value, type); + var masklen = _stripInvalidChars(mask, "x").length; + + // check to make sure the string contains the correct number of characters + if( string.length != masklen && this.value.length > 0){ + if( strErrorMsg.length == 0 ) strErrorMsg = "This field requires at least " + masklen + " valid characters. Please \nmake sure to enter the value in the format: \n " + mask + "\n(where 'x' is a valid character.)"; + this.error = strErrorMsg; + + // else re-format the string as defined by the mask + } else if( string.length == masklen ){ + // find the position of all non "X" characters + var stcMask = new Object(); + var lc = mask.toLowerCase(); + // loop through the string an make sure each character is an valid character + for( var i=0; i < mask.length; i++ ){ + if( lc.charAt(i) != "x" ) stcMask[i] = mask.charAt(i); + } + + // put all the non-"X" characters back into the parsed string + var iLastChar = 0; + var newString = ""; + var i = 0; + for( var pos in stcMask ){ + pos = parseInt(pos, 10); + if( pos > iLastChar ){ + newString += string.substring(iLastChar, pos-i) + stcMask[pos]; + iLastChar = pos-i; + } else { + newString += stcMask[pos]; + } + i++; + } + if( i == 0 ){ + newString = string; + } else { + newString += string.substring(iLastChar); + } + + // set the value of the field to the new string--make sure not to perform the onBlur event + this.setValue(newString, true, false); + } +} +_addValidator("isFormat", _Field_isFormat); + +// define Field isLengthGT(); prototype +function _Field_isLengthGT(len){ + if( this.obj.value.length <= len){ + this.error = "The " + this.description + " field must be greater than " + len + " characters."; + } +} +_addValidator("isLengthGT", _Field_isLengthGT); + +// define Field isLengthLT(); prototype +function _Field_isLengthLT(len){ + if( this.obj.value.length >= len){ + this.error = "The " + this.description + " field must be less than " + len + " characters."; + } +} +_addValidator("isLengthLT", _Field_isLengthLT); + diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation_addon.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation_addon.js new file mode 100755 index 0000000..3ac7f29 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation_addon.js @@ -0,0 +1,34 @@ +/****************************************************************************** + qForm JSAPI: Add-on Validation Library + + Author: Dan G. Switzer, II + Build: 100 +******************************************************************************/ +qFormAPI.packages.validation = true; + +// [start] validation routine +function _f_isAtLeastOne(_f){ + var sFields = this.name + ((typeof _f == "string") ? "," + _removeSpaces(_f) : ""); + var aFields = sFields.split(","), v = new Array(), d = new Array(), x = ","; + + for( var i=0; i < aFields.length; i++ ){ + if( !this.qForm[aFields[i]] ) return alert("The field name \"" + aFields[i] + "\" does not exist."); + // store the value in an array + v[v.length] = this.qForm[aFields[i]].getValue(); + // if the field name is already in the list, don't add it + if( x.indexOf("," + aFields[i] + ",") == -1 ){ + d[d.length] = this.qForm[aFields[i]].description; + x += aFields[i] + ","; + } + } + + // if all of the form fields has empty lengths, then throw + // an error message to the page + if( v.join("").length == 0 ){ + this.error = "At least one of the following fields is required:\n " + d.join(", "); + for( i=0; i < aFields.length; i++ ){ + if( qFormAPI.useErrorColorCoding && this.qForm[aFields[i]].obj.style ) this.qForm[aFields[i]].obj.style[qFormAPI.styleAttribute] = qFormAPI.errorColor; + } + } +} +_addValidator("isAtLeastOne", _f_isAtLeastOne, true); diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/wddx.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/wddx.js new file mode 100755 index 0000000..a3f0a12 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/wddx.js @@ -0,0 +1,102 @@ +/****************************************************************************** + qForm JSAPI: WDDX Mod Library + + Author: Dan G. Switzer, II + Build: 101 +******************************************************************************/ + +/****************************************************************************** + Required Functions +******************************************************************************/ +function __serializeStruct(struct){ + // open packet + var aWDDX = new Array("
    "); + for( var key in struct ) aWDDX[aWDDX.length] = "" + __wddxValue(struct[key]) + ""; + // close packet + aWDDX[aWDDX.length] = ""; + return aWDDX.join(""); +} + + +function __wddxValue(str){ + var aValue = new Array(); + for( var i=0; i < str.length; ++i) aValue[aValue.length] = _encoding.table[str.charAt(i)]; + return aValue.join(""); +} + +function _wddx_Encoding(){ + // Encoding table for strings (CDATA) + var et = new Array(); + + // numbers to characters table + var n2c = new Array(); + + for( var i=0; i < 256; ++i ){ + // build a character from octal code + var d1 = Math.floor(i/64); + var d2 = Math.floor((i%64)/8); + var d3 = i%8; + var c = eval("\"\\" + d1.toString(10) + d2.toString(10) + d3.toString(10) + "\""); + + // modify character-code conversion tables + n2c[i] = c; + + // modify encoding table + if( i < 32 && i != 9 && i != 10 && i != 13 ){ + // control characters that are not tabs, newlines, and carriage returns + + // create a two-character hex code representation + var hex = i.toString(16); + if( hex.length == 1 ) hex = "0" + hex; + + et[n2c[i]] = ""; + + } else if( i < 128 ){ + // low characters that are not special control characters + et[n2c[i]] = n2c[i]; + } else { + // high characters + et[n2c[i]] = "&#x" + i.toString(16) + ";"; + } + } + + // special escapes for CDATA encoding + et["<"] = "<"; + et[">"] = ">"; + et["&"] = "&"; + + this.table = et; +} +_encoding = new _wddx_Encoding(); + + +/****************************************************************************** + qForm Methods +******************************************************************************/ +function _a_serialize(exclude){ + // if you need to reset the default values of the fields + var lstExclude = (arguments.length > 0) ? "," + _removeSpaces(arguments[0]) + "," : ""; + struct = new Object(); + stcAllFields = qFormAPI.getFields(); + // loop through form elements + for( key in stcAllFields ){ + if( lstExclude.indexOf("," + key + ",") == -1 ) struct[key] = stcAllFields[key]; + } + // create & return the serialized object + return __serializeStruct(struct); +} +_a.prototype.serialize = _a_serialize; + +// define qForm serialize(); prototype +function _q_serialize(exclude){ + // if you need to reset the default values of the fields + var lstExclude = (arguments.length > 0) ? "," + _removeSpaces(arguments[0]) + "," : ""; + struct = new Object(); + // loop through form elements + for( var j=0; j < this._fields.length; j++ ){ + if( lstExclude.indexOf("," + this._fields[j] + ",") == -1 ) struct[this._fields[j]] = this[this._fields[j]].getValue(); + } + // create & return the serialized object + return __serializeStruct(struct); +} +qForm.prototype.serialize = _q_serialize; diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms_init.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms_init.js new file mode 100755 index 0000000..68ccea8 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms_init.js @@ -0,0 +1,9 @@ +// set the path to the qForms directory +qFormAPI.setLibraryPath("/bundles/qforms/javascripts/"); +// this loads all the default libraries +qFormAPI.include("*"); + + +/******************************************************************** + load custom validation scripts here +********************************************************************/ diff --git a/vendor/plugins/bundled_resource/bundles/stateful_form.rb b/vendor/plugins/bundled_resource/bundles/stateful_form.rb new file mode 100644 index 0000000..5e1a66b --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/stateful_form.rb @@ -0,0 +1,74 @@ +# == Usage == +# require_bundle :stateful_form +# +# +# == Full Stateful Form Documentation == +# ? + +module BundledResource::StatefulForm + def bundle + require_javascript "/bundles/stateful_form/javascripts/stateful_form" + end + + module Helper + # Returns true or false depending on the visible state of the element. + def visible_state(element_id) + (params[:visible_state][element_id.to_sym] == '1') rescue visible_state_get_default(element_id) + end + + # Returns 'block' or 'none' depending on the visible state of the element. + def css_visible_state(element_id) + visible_state(element_id) ? 'block' : 'none' + end + + # Outputs a hidden_state_tag for each element that has a known state. The element's + # state is 'known' if, + # a) the params[:visible_state] hash includes the element's id, or + # b) the element's default state has been stored via a previous all to visible_state_set_default + # + # Call perpetuate_visible_states once within of a view's form tags to perpetuate the + # visible (or hidden) states of sections of the form. + def perpetuate_visible_states + buffer = "" + known_states = [] + known_states |= params[:visible_state].keys if params[:visible_state] + known_states |= @visible_state_default.keys if @visible_state_default + for element_id in known_states + logger.warn "(Stateful Form) Perpetuating visible state #{element_id}: #{visible_state(element_id)}" + buffer << hidden_state_tag(element_id) + end + buffer + end + + def visible_state_set_default(element_id, state = true) + @visible_state_default ||= Hash.new + @visible_state_default[element_id.to_sym] = state + end + + def visible_state_get_default(element_id) + @visible_state_default ||= Hash.new + @visible_state_default[element_id.to_sym] || false + end + + # Show an html input tag (type='hidden') to preserve state information + # about some other element's visible state. Calling hidden_state_tag + # multiple times on the same element_id will result in only ONE tag being + # written unless force_write is set to true. + # If +boolean_value+ is nil, the the current visible_state will be used. + def hidden_state_tag(element_id, boolean_value = nil, force_write = false) + @visible_state_perpetuated ||= [] + if boolean_value.nil? + value = visible_state(element_id) ? 1 : 0 + else + value = boolean_value ? 1 : 0 + end + buffer = "" + if force_write or !@visible_state_perpetuated.include?(element_id) + buffer = hidden_field_tag("visible_state[#{element_id}]", value, :id => "visible_state_#{element_id}") + @visible_state_perpetuated << element_id + end + buffer + end + end +end + diff --git a/vendor/plugins/bundled_resource/bundles/stateful_form/javascripts/stateful_form.js b/vendor/plugins/bundled_resource/bundles/stateful_form/javascripts/stateful_form.js new file mode 100644 index 0000000..00cb433 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/stateful_form/javascripts/stateful_form.js @@ -0,0 +1,18 @@ +function remember_visible_state_of(element) { + var visible_state = (Element.visible(element) ? 1 : 0); + // Allow optional second argument to override the state + if (arguments.length > 1) visible_state = (arguments[1] == true || arguments[1] == 1) ? 1 : 0; + + // See if the element has a hidden field where we will store its state + element = $(element); + var hidden_field_id = 'visible_state_' + element.id + var hidden_field = $(hidden_field_id); + + if (hidden_field) + hidden_field.value = visible_state; + else + new Insertion.Top(element, + ""); +} diff --git a/vendor/plugins/bundled_resource/bundles/textarea.rb b/vendor/plugins/bundled_resource/bundles/textarea.rb new file mode 100644 index 0000000..cc0550b --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/textarea.rb @@ -0,0 +1,8 @@ +module BundledResource::Textarea + def bundle + require_stylesheet "/bundles/textarea/stylesheets/resizable" + + require_javascript "/bundles/textarea/javascripts/resizable" + require_javascript "/bundles/textarea/javascripts/wordcount" + end +end \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/areaLess.png b/vendor/plugins/bundled_resource/bundles/textarea/images/areaLess.png new file mode 100755 index 0000000..bae913c Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/areaLess.png differ diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/areaMore.png b/vendor/plugins/bundled_resource/bundles/textarea/images/areaMore.png new file mode 100755 index 0000000..910e18f Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/areaMore.png differ diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/fontDown.png b/vendor/plugins/bundled_resource/bundles/textarea/images/fontDown.png new file mode 100755 index 0000000..48dfe73 Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/fontDown.png differ diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/fontUp.png b/vendor/plugins/bundled_resource/bundles/textarea/images/fontUp.png new file mode 100755 index 0000000..13523bf Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/fontUp.png differ diff --git a/vendor/plugins/bundled_resource/bundles/textarea/javascripts/resizable.js b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/resizable.js new file mode 100755 index 0000000..ef49338 --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/resizable.js @@ -0,0 +1,130 @@ +document.getElementsByClassName = function (needle) +{ + var my_array = document.getElementsByTagName("*"); + var retvalue = new Array(); + var i; + var j; + + for (i = 0, j = 0; i < my_array.length; i++) + { + var c = " " + my_array[i].className + " "; + if (c.indexOf(" " + needle + " ") != -1) + retvalue[j++] = my_array[i]; + } + return retvalue; +} + +function addEvent(obj, evType, fn) +{ + if (obj.addEventListener) + { + obj.addEventListener(evType, fn, true); + return true; + } + else if (obj.attachEvent) + { + var r = obj.attachEvent("on"+evType, fn); + return r; + } + else + { + return false; + } +} + +addEvent(window, 'load', function() +{ + var resizables = document.getElementsByClassName('resizable'); + var i, ta, wrapper, bigger, smaller, biggerFont, smallerFont, biggerSpan, smallerSpan, biggerFontSpan, smallerFontSpan; + for (i=0; i 30) + this.resizer.style.height = (parseInt(this.resizer.style.height) - 30) + "px"; + return false; + } + + biggerFont.onclick = function() + { + if (this.resizer.style.fontSize == '') + this.resizer.style.fontSize = '1em'; + + this.resizer.style.fontSize = (parseFloat(this.resizer.style.fontSize) + 0.1) + "em"; + + return false; + } + + smallerFont.onclick = function() + { + if (this.resizer.style.fontSize == '') + this.resizer.style.fontSize = '1em'; + + if ((parseFloat(this.resizer.style.fontSize) - 0.1) > 0.5) + this.resizer.style.fontSize = (parseFloat(this.resizer.style.fontSize) - 0.1) + "em"; + + return false; + } + + wrapper.appendChild(bigger); + wrapper.appendChild(smaller); + wrapper.appendChild(biggerFont); + wrapper.appendChild(smallerFont); + } +}); diff --git a/vendor/plugins/bundled_resource/bundles/textarea/javascripts/wordcount.js b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/wordcount.js new file mode 100755 index 0000000..ef6c32f --- /dev/null +++ b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/wordcount.js @@ -0,0 +1,29 @@ + +addEvent(window, 'load', function() +{ + var counts = document.getElementsByClassName('wordcount'); + var i, countHolder; + + for (i=0; i 0 and (ENV['RAILS_BUNDLES'].nil? or ENV['RAILS_BUNDLES'].upcase == 'YES') + # We need to load each ruby file in the bundles directory. Each file should + # contain a module within the BundledResource module, e.g. + # module BundledResource::Qforms + # def bundle + # require_javascript '...' # require whatever resources are necessary + # end + # end + # + # Note that the single 'bundle' method can have optional parameters if needed. + + require 'require_resource' + require 'bundled_resource' + + RAILS_DEFAULT_LOGGER.debug "Resource Bundles: #{bundle_files.size}" + bundle_files.each do |filename| + name = File.basename(filename) + name = name[0...-3] if name =~ /\.rb$/ + + RAILS_DEFAULT_LOGGER.debug "Requiring Bundle\n #{filename}" + require filename + + # Rename the generic 'bundle' method in to something that doesn't conflict with + # the other module method names. + bundle_module = BundledResource.const_get(name.to_s.camelize) + bundle_module.module_eval "alias bundle_#{name} bundle" + bundle_module.send :undef_method, :bundle + + # Then include the bundle module in to the base module, so that the methods will + # be available inside ActionView::Base + BundledResource.send(:include, bundle_module) + + # Check for optional Controller module + if bundle_module.const_defined? 'Controller' + controller_addon = bundle_module.const_get('Controller') + RAILS_DEFAULT_LOGGER.debug "Including #{name} bundle's Controller module" + ActionController::Base.send(:include, controller_addon) + end + + # Check for optional Helper module + if bundle_module.const_defined? 'Helper' + helper_addon = bundle_module.const_get('Helper') + RAILS_DEFAULT_LOGGER.debug "Including #{name} bundle's Helper module" + ActionView::Base.send(:include, helper_addon) + end + end + + public_bundle_directory = File.expand_path(File.join(RAILS_ROOT, "public", "bundles")) + + BundledResource.create_public_bundle_directory(public_bundle_directory) + BundledResource.copy_bundles(bundle_directory_root, public_bundle_directory) + + ActionView::Base.send(:include, RequireResource) + ActionView::Base.send(:include, BundledResource) +end \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/lib/bundled_resource.rb b/vendor/plugins/bundled_resource/lib/bundled_resource.rb new file mode 100644 index 0000000..eb96b32 --- /dev/null +++ b/vendor/plugins/bundled_resource/lib/bundled_resource.rb @@ -0,0 +1,49 @@ +require 'fileutils' + +BUNDLE_RESOURCE_README = <<-END +Files in this directory are automatically generated from your Rails bundles. +They are copied from the directories of each bundle into this directory +each time Rails starts (server, console). Any edits you make will NOT +persist across the next server restart; instead you should edit the files within +the bundle directory itself (e.g. vendor/plugins/bundle_resource/bundles/*). +END + +module BundledResource + def require_bundle(name, *args) + method = "bundle_#{name}" + send(method, *args) + end + + def self.copy_bundles(bundle_directory_root, public_bundle_directory) + RAILS_DEFAULT_LOGGER.debug "Copying bundles from\n #{bundle_directory_root} to\n #{public_bundle_directory}" + Dir.new(bundle_directory_root).each do |entry| + full_path = File.join(bundle_directory_root, entry) + if File.directory?(full_path) + unless ['.', '..', '.svn'].include?(entry) + BundledResource.copy_files(full_path, public_bundle_directory) + end + end + end + RAILS_DEFAULT_LOGGER.debug "Done copying bundles." + end + + def self.create_public_bundle_directory(public_bundle_dir) + if File.exists?(public_bundle_dir) + RAILS_DEFAULT_LOGGER.debug "Removing bundles directory\n #{public_bundle_dir}" + FileUtils.rm_rf(public_bundle_dir) + end + + RAILS_DEFAULT_LOGGER.debug "Creating bundles directory\n #{public_bundle_dir}" + # create the public/engines directory, with a warning message in it. + FileUtils.mkdir(public_bundle_dir) + File.open(File.join(public_bundle_dir, "README"), "w") do |f| + f.puts BUNDLE_RESOURCE_README + end + end + + # Replicates the subdirectories under the bundle's directory into + # the application's public directory. + def self.copy_files(source, destination) + FileUtils.cp_r(source, destination) rescue nil + end +end \ No newline at end of file diff --git a/vendor/plugins/bundled_resource/lib/require_resource.rb b/vendor/plugins/bundled_resource/lib/require_resource.rb new file mode 100644 index 0000000..95948b8 --- /dev/null +++ b/vendor/plugins/bundled_resource/lib/require_resource.rb @@ -0,0 +1,124 @@ +# RequireResource v.1.4 by Duane Johnson +# +# Makes inclusion of javascript and stylesheet resources easier via automatic or explicit +# calls. e.g. require_javascript 'popup' is an explicit call. +# +# The simplest way to make use of this functionality is to add +# <%= resource_tags %> +# to your layout (usually in the section). This will automatically add +# bundle support to your application, as well as enable simple javascript and stylesheet +# dependencies for your views. +# +# Note that this can easily be turned in to a helper on its own. +module RequireResource + mattr_accessor :path_prefix + + # Write out all javascripts & stylesheets, including default javascripts (unless :defaults => false) + def resource_tags(options = {}) + options = {:auto => true, :defaults => true}.update(options) + require_defaults if options[:defaults] + stylesheet_auto_link_tags(:auto => options[:auto]) + + javascript_auto_include_tags(:auto => options[:auto]) + end + + # Write out the tags themselves based on the array of stylesheets to be included + def stylesheet_auto_link_tags(options = {}) + options = {:auto => true}.update(options) + ensure_resource_is_initialized(:stylesheet) + autorequire(:stylesheet) if options[:auto] + @stylesheets.uniq.inject("") do |buffer, css| + buffer << stylesheet_link_tag(css) + "\n " + end + end + + # Write out the + # + # engine_javascript "my_engine", "another_file", "one_more" => + # + # + # + # + # Any options supplied as a Hash as the last argument will be processed as in + # javascript_include_tag. + # + def engine_javascript(engine_name, *sources) + javascript_include_tag(*convert_public_sources(engine_name, :javascript, sources)) + end + + # Returns a image tag based on the parameters passed to it + # Required option is option[:engine] in order to correctly idenfity the correct engine location + # + # engine_image 'rails-engines.png', :engine => 'my_engine', :alt => 'My Engine' => + # My Engine />
+      #
+      # Any options supplied as a Hash as the last argument will be processed as in
+      # image_tag.
+      #
+      def engine_image(src, options = {})
+      	return if !src
+
+      	image_src = engine_image_src(src, options)
+
+      	options.delete(:engine)
+
+      	image_tag(image_src, options)
+      end
+
+      # Alias for engine_image
+      def engine_image_tag(src, options = {})
+        engine_image(src, options)
+      end
+
+      # Returns a path to the image stored within the engine_files
+      # Required option is option[:engine] in order to correctly idenfity the correct engine location
+      #
+      #   engine_image_src 'rails-engines.png', :engine => 'my_engine' =>
+      #   loading from engine '#{engine.name}'") + rails_pre_engines_require_or_load(engine_file_name) + found = true + end + end + end + end + + # finally, load any application-specific controller classes using the 'proper' + # rails load mechanism, EXCEPT when we're testing engines and could load this file + # from an engine + rails_pre_engines_require_or_load(file_name) unless Engines.disable_app_code_mixing && found + end + + def rails_1_0_require_or_load(file_name) + file_name = $1 if file_name =~ /^(.*)\.rb$/ + + Engines.log.debug "Engines 1.0.0 require_or_load '#{file_name}'" + + # if the file_name ends in "_controller" or "_controller.rb", strip all + # path information out of it except for module context, and load it. Ditto + # for helpers. + found = if file_name =~ /_controller(.rb)?$/ + require_engine_files(file_name, 'controller') + elsif file_name =~ /_helper(.rb)?$/ # any other files we can do this with? + require_engine_files(file_name, 'helper') + end + + # finally, load any application-specific controller classes using the 'proper' + # rails load mechanism, EXCEPT when we're testing engines and could load this file + # from an engine + Engines.log.debug("--> loading from application: '#{file_name}'") + rails_pre_engines_require_or_load(file_name) unless Engines.disable_app_code_mixing && found + Engines.log.debug("--> Done loading.") + end + + # Load the given file (which should be a path to be matched from the root of each + # engine) from all active engines which have that file. + # NOTE! this method automagically strips file_name up to and including the first + # instance of '/app/controller'. This should correspond to the app/controller folder + # under RAILS_ROOT. However, if you have your Rails application residing under a + # path which includes /app/controller anyway, such as: + # + # /home/username/app/controller/my_web_application # == RAILS_ROOT + # + # then you might have trouble. Sorry, just please don't have your web application + # running under a path like that. + def require_engine_files(file_name, type='') + found = false + Engines.log.debug "requiring #{type} file '#{file_name}'" + processed_file_name = file_name.gsub(/[\w\W\/\.]*app\/#{type}s\//, '') + Engines.log.debug "--> rewrote to '#{processed_file_name}'" + Engines.each(:load_order) do |engine| + engine_file_name = File.join(engine.root, 'app', "#{type}s", processed_file_name) + engine_file_name += '.rb' unless ! load? || engine_file_name[-3..-1] == '.rb' + Engines.log.debug "--> checking '#{engine.name}' for #{engine_file_name}" + if File.exist?(engine_file_name) || + (engine_file_name[-3..-1] != '.rb' && File.exist?(engine_file_name + '.rb')) + Engines.log.debug "--> found, loading from engine '#{engine.name}'" + rails_pre_engines_require_or_load(engine_file_name) + found = true + end + end + found + end +end + + +# We only need to deal with LoadingModules in Rails 1.0.0 +if Rails::VERSION::STRING =~ /^1.0/ && !Engines.config(:edge) + module ::Dependencies + class RootLoadingModule < LoadingModule + # hack to allow adding to the load paths within the Rails Dependencies mechanism. + # this allows Engine classes to be unloaded and loaded along with standard + # Rails application classes. + def add_path(path) + @load_paths << (path.kind_of?(ConstantLoadPath) ? path : ConstantLoadPath.new(path)) + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/engines/lib/engines/migration_extensions.rb b/vendor/plugins/engines/lib/engines/migration_extensions.rb new file mode 100644 index 0000000..0653261 --- /dev/null +++ b/vendor/plugins/engines/lib/engines/migration_extensions.rb @@ -0,0 +1,53 @@ +#require 'active_record/connection_adapters/abstract/schema_statements' + +module ::ActiveRecord::ConnectionAdapters::SchemaStatements + alias :old_initialize_schema_information :initialize_schema_information + def initialize_schema_information + # create the normal schema stuff + old_initialize_schema_information + + # create the engines schema stuff. + begin + execute "CREATE TABLE #{engine_schema_info_table_name} (engine_name #{type_to_sql(:string)}, version #{type_to_sql(:integer)})" + rescue ActiveRecord::StatementInvalid + # Schema has been initialized + end + end + + def engine_schema_info_table_name + ActiveRecord::Base.wrapped_table_name "engine_schema_info" + end +end + + +require 'breakpoint' +module ::Engines + class EngineMigrator < ActiveRecord::Migrator + + # We need to be able to set the 'current' engine being migrated. + cattr_accessor :current_engine + + class << self + + def schema_info_table_name + ActiveRecord::Base.wrapped_table_name "engine_schema_info" + end + + def current_version + result = ActiveRecord::Base.connection.select_one("SELECT version FROM #{schema_info_table_name} WHERE engine_name = '#{current_engine.name}'") + if result + result["version"].to_i + else + # There probably isn't an entry for this engine in the migration info table. + # We need to create that entry, and set the version to 0 + ActiveRecord::Base.connection.execute("INSERT INTO #{schema_info_table_name} (version, engine_name) VALUES (0,'#{current_engine.name}')") + 0 + end + end + end + + def set_schema_version(version) + ActiveRecord::Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i} WHERE engine_name = '#{self.current_engine.name}'") + end + end +end diff --git a/vendor/plugins/engines/lib/engines/routing_extensions.rb b/vendor/plugins/engines/lib/engines/routing_extensions.rb new file mode 100644 index 0000000..00f6b63 --- /dev/null +++ b/vendor/plugins/engines/lib/engines/routing_extensions.rb @@ -0,0 +1,28 @@ +module ActionController + module Routing + + class << self + # This holds the global list of valid controller paths + attr_accessor :controller_paths + end + + class ControllerComponent + class << self + protected + def safe_load_paths #:nodoc: + if defined?(RAILS_ROOT) + paths = $LOAD_PATH.select do |base| + base = File.expand_path(base) + # Check that the path matches one of the allowed paths in controller_paths + base.match(/^#{ActionController::Routing.controller_paths.map { |p| File.expand_path(p) } * '|'}/) + end + Engines.log.debug "Engines safe_load_paths: #{paths.inspect}" + paths + else + $LOAD_PATH + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/engines/lib/engines/ruby_extensions.rb b/vendor/plugins/engines/lib/engines/ruby_extensions.rb new file mode 100644 index 0000000..189ca46 --- /dev/null +++ b/vendor/plugins/engines/lib/engines/ruby_extensions.rb @@ -0,0 +1,113 @@ +#-- +# Add these methods to the top-level module so that they are available in all +# modules, etc +#++ +class ::Module + # Defines a constant within a module/class ONLY if that constant does + # not already exist. + # + # This can be used to implement defaults in plugins/engines/libraries, e.g. + # if a plugin module exists: + # module MyPlugin + # default_constant :MyDefault, "the_default_value" + # end + # + # then developers can override this default by defining that constant at + # some point *before* the module/plugin gets loaded (such as environment.rb) + def default_constant(name, value) + if !(name.is_a?(String) or name.is_a?(Symbol)) + raise "Cannot use a #{name.class.name} ['#{name}'] object as a constant name" + end + if !self.const_defined?(name) + self.class_eval("#{name} = #{value.inspect}") + end + end + + # A mechanism for defining configuration of Modules. With this + # mechanism, default values for configuration can be provided within shareable + # code, and the end user can customise the configuration without having to + # provide all values. + # + # Example: + # + # module MyModule + # config :param_one, "some value" + # config :param_two, 12345 + # end + # + # Those values can now be accessed by the following method + # + # MyModule.config :param_one + # => "some value" + # MyModule.config :param_two + # => 12345 + # + # ... or, if you have overrriden the method 'config' + # + # MyModule::CONFIG[:param_one] + # => "some value" + # MyModule::CONFIG[:param_two] + # => 12345 + # + # Once a value is stored in the configuration, it will not be altered + # by subsequent assignments, unless a special flag is given: + # + # (later on in your code, most likely in another file) + # module MyModule + # config :param_one, "another value" + # config :param_two, 98765, :force + # end + # + # The configuration is now: + # + # MyModule.config :param_one + # => "some value" # not changed + # MyModule.config :param_two + # => 98765 + # + # Configuration values can also be given as a Hash: + # + # MyModule.config :param1 => 'value1', :param2 => 'value2' + # + # Setting of these values can also be forced: + # + # MyModule.config :param1 => 'value3', :param2 => 'value4', :force => true + # + # A value of anything other than false or nil given for the :force key will + # result in the new values *always* being set. + def config(*args) + + raise "config expects at least one argument" if args.empty? + + # extract the arguments + if args[0].is_a?(Hash) + override = args[0][:force] + args[0].delete(:force) + args[0].each { |key, value| _handle_config(key, value, override)} + else + _handle_config(*args) + end + end + + private + # Actually set the config values + def _handle_config(name, value=nil, override=false) + if !self.const_defined?("CONFIG") + self.class_eval("CONFIG = {}") + end + + if value != nil + if override or self::CONFIG[name] == nil + self::CONFIG[name] = value + end + else + # if we pass an array of config keys to config(), + # get the array of values back + if name.is_a? Array + name.map { |c| self::CONFIG[c] } + else + self::CONFIG[name] + end + end + end +end diff --git a/vendor/plugins/engines/lib/engines/testing_extensions.rb b/vendor/plugins/engines/lib/engines/testing_extensions.rb new file mode 100644 index 0000000..6d9755d --- /dev/null +++ b/vendor/plugins/engines/lib/engines/testing_extensions.rb @@ -0,0 +1,327 @@ +# The Engines testing extensions enable developers to load fixtures into specific +# tables irrespective of the name of the fixtures file. This work is heavily based on +# patches made by Duane Johnson (canadaduane), viewable at +# http://dev.rubyonrails.org/ticket/1911 +# +# Engine developers should supply fixture files in the /test/fixtures directory +# as normal. Within their tests, they should load the fixtures using the 'fixture' command +# (rather than the normal 'fixtures' command). For example: +# +# class UserTest < Test::Unit::TestCase +# fixture :users, :table_name => LoginEngine.config(:user_table), :class_name => "User" +# +# ... +# +# This will ensure that the fixtures/users.yml file will get loaded into the correct +# table, and will use the correct model object class. + + + +# A FixtureGroup is a set of fixtures identified by a name. Normally, this is the name of the +# corresponding fixture filename. For example, when you declare the use of fixtures in a +# TestUnit class, like so: +# fixtures :users +# you are creating a FixtureGroup whose name is 'users', and whose defaults are set such that the +# +class_name+, +file_name+ and +table_name+ are guessed from the FixtureGroup's name. +class FixtureGroup + attr_accessor :table_name, :class_name, :connection + attr_reader :group_name, :file_name + + def initialize(file_name, optional_names = {}) + self.file_name = file_name + self.group_name = optional_names[:group_name] || file_name + if optional_names[:table_name] + self.table_name = optional_names[:table_name] + self.class_name = optional_names[:class_name] || Inflector.classify(@table_name.to_s.gsub('.','_')) + elsif optional_names[:class_name] + self.class_name = optional_names[:class_name] + if Object.const_defined?(@class_name) + model_class = Object.const_get(@class_name) + self.table_name = ActiveRecord::Base.table_name_prefix + model_class.table_name + ActiveRecord::Base.table_name_suffix + end + end + + # In case either :table_name or :class_name was not set: + self.table_name ||= ActiveRecord::Base.table_name_prefix + @group_name.to_s + ActiveRecord::Base.table_name_suffix + self.class_name ||= Inflector.classify(@table_name.to_s.gsub('.','_')) + end + + def file_name=(name) + @file_name = name.to_s + end + + def group_name=(name) + @group_name = name.to_sym + end + + def class_file_name + Inflector.underscore(@class_name) + end + + # Instantiate an array of FixtureGroup objects from an array of strings (table_names) + def self.array_from_names(names) + names.collect { |n| FixtureGroup.new(n) } + end + + def hash + @group_name.hash + end + + def eql?(other) + @group_name.eql? other.group_name + end +end + +class Fixtures < YAML::Omap + + def self.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances=true) + old_logger_level = ActiveRecord::Base.logger.level + ActiveRecord::Base.logger.level = Logger::ERROR + + # table_name.to_s.gsub('.','_') replaced by 'fixture_group_name' + object.instance_variable_set "@#{fixture_group_name}", fixtures + if load_instances + ActiveRecord::Base.silence do + fixtures.each do |name, fixture| + begin + if model = fixture.find + object.instance_variable_set "@#{name}", model + end + rescue FixtureClassNotFound + # Let's hope the developer has included it himself + end + end + end + end + + ActiveRecord::Base.logger.level = old_logger_level + end + + # this doesn't really need to be overridden... + def self.instantiate_all_loaded_fixtures(object, load_instances=true) + all_loaded_fixtures.each do |fixture_group_name, fixtures| + Fixtures.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances) + end + end + + def self.create_fixtures(fixtures_directory, *fixture_groups) + connection = block_given? ? yield : ActiveRecord::Base.connection + fixture_groups.flatten! + + # Backwards compatibility: Allow an array of table names to be passed in, but just use them + # to create an array of FixtureGroup objects + if not fixture_groups.empty? and fixture_groups.first.is_a?(String) + fixture_groups = FixtureGroup.array_from_names(fixture_groups) + end + + ActiveRecord::Base.silence do + fixtures_map = {} + fixtures = fixture_groups.map do |group| + fixtures_map[group.group_name] = Fixtures.new(connection, fixtures_directory, group) + end + # Make sure all refs to all_loaded_fixtures use group_name as hash index, not table_name + 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!) + fixture_groups.each do |fg| + connection.reset_pk_sequence!(fg.table_name) + end + end + end + + return fixtures.size > 1 ? fixtures : fixtures.first + end + end + + + attr_accessor :connection, :fixtures_directory, :file_filter + attr_accessor :fixture_group + + def initialize(connection, fixtures_directory, fixture_group, file_filter = DEFAULT_FILTER_RE) + @connection, @fixtures_directory = connection, fixtures_directory + @fixture_group = fixture_group + @file_filter = file_filter + read_fixture_files + end + + def delete_existing_fixtures + @connection.delete "DELETE FROM #{@fixture_group.table_name}", 'Fixture Delete' + end + + def insert_fixtures + values.each do |fixture| + @connection.execute "INSERT INTO #{@fixture_group.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' + end + end + + private + def read_fixture_files + if File.file?(yaml_file_path) + read_yaml_fixture_files + elsif File.file?(csv_file_path) + read_csv_fixture_files + elsif File.file?(deprecated_yaml_file_path) + raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}" + elsif File.directory?(single_file_fixtures_path) + read_standard_fixture_files + else + raise Fixture::FixtureError, "Couldn't find a yaml, csv or standard file to load at #{@fixtures_directory} (#{@fixture_group.file_name})." + end + end + + def read_yaml_fixture_files + # YAML fixtures + begin + if yaml = YAML::load(erb_render(IO.read(yaml_file_path))) + yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value) + yaml.each do |name, data| + self[name] = Fixture.new(data, fixture_group.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 + end + + def read_csv_fixture_files + # 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["#{@fixture_group.class_file_name}_#{i+=1}"]= Fixture.new(data, @fixture_group.class_name) + end + end + + def read_standard_fixture_files + # Standard fixtures + path = File.join(@fixtures_directory, @fixture_group.file_name) + Dir.entries(path).each do |file| + path = File.join(@fixtures_directory, @fixture_group.file_name, file) + if File.file?(path) and file !~ @file_filter + self[file] = Fixture.new(path, @fixture_group.class_name) + end + end + end + + def yaml_file_path + fixture_path_with_extension ".yml" + end + + def deprecated_yaml_file_path + fixture_path_with_extension ".yaml" + end + + def csv_file_path + fixture_path_with_extension ".csv" + end + + def single_file_fixtures_path + fixture_path_with_extension "" + end + + def fixture_path_with_extension(ext) + File.join(@fixtures_directory, @fixture_group.file_name + ext) + end + + def erb_render(fixture_content) + ERB.new(fixture_content).result + end + +end + +module Test #:nodoc: + module Unit #:nodoc: + class TestCase #:nodoc: + cattr_accessor :fixtures_directory + class_inheritable_accessor :fixture_groups + class_inheritable_accessor :fixture_table_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_groups = [] + self.use_transactional_fixtures = false + self.use_instantiated_fixtures = true + self.pre_loaded_fixtures = false + + @@already_loaded_fixtures = {} + + # Backwards compatibility + def self.fixture_path=(path); self.fixtures_directory = path; end + def self.fixture_path; self.fixtures_directory; end + def fixture_group_names; fixture_groups.collect { |g| g.group_name }; end + def fixture_table_names; fixture_group_names; end + + def self.fixture(file_name, options = {}) + self.fixture_groups |= [FixtureGroup.new(file_name, options)] + require_fixture_classes + setup_fixture_accessors + end + + def self.fixtures(*file_names) + self.fixture_groups |= FixtureGroup.array_from_names(file_names.flatten) + require_fixture_classes + setup_fixture_accessors + end + + def self.require_fixture_classes(fixture_groups_override = nil) + (fixture_groups_override || fixture_groups).each do |group| + begin + require group.class_file_name + rescue LoadError + # Let's hope the developer has included it himself + end + end + end + + def self.setup_fixture_accessors(fixture_groups_override=nil) + (fixture_groups_override || fixture_groups).each do |group| + define_method(group.group_name) do |fixture, *optionals| + force_reload = optionals.shift + @fixture_cache[group.group_name] ||= Hash.new + @fixture_cache[group.group_name][fixture] = nil if force_reload + @fixture_cache[group.group_name][fixture] ||= @loaded_fixtures[group.group_name][fixture.to_s].find + end + end + end + + private + def load_fixtures + @loaded_fixtures = {} + fixtures = Fixtures.create_fixtures(fixtures_directory, fixture_groups) + unless fixtures.nil? + if fixtures.instance_of?(Fixtures) + @loaded_fixtures[fixtures.fixture_group.group_name] = fixtures + else + fixtures.each { |f| @loaded_fixtures[f.fixture_group.group_name] = f } + end + end + end + + def instantiate_fixtures + if pre_loaded_fixtures + raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty? + unless @@required_fixture_classes + groups = Fixtures.all_loaded_fixtures.values.collect { |f| f.group_name } + self.class.require_fixture_classes groups + @@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 |fixture_group_name, fixtures| + Fixtures.instantiate_fixtures(self, fixture_group_name, fixtures, load_instances?) + end + end + end + end + end +end diff --git a/vendor/plugins/engines/tasks/deprecated_engines.rake b/vendor/plugins/engines/tasks/deprecated_engines.rake new file mode 100644 index 0000000..33e2a28 --- /dev/null +++ b/vendor/plugins/engines/tasks/deprecated_engines.rake @@ -0,0 +1,7 @@ +# Old-style engines rake tasks. +# NOTE: THESE ARE DEPRICATED! PLEASE USE THE NEW STYLE! + +task :engine_info => "engines:info" +task :engine_migrate => "db:migrate:engines" +task :enginedoc => "doc:engines" +task :load_plugin_fixtures => "db:fixtures:engines:load" \ No newline at end of file diff --git a/vendor/plugins/engines/tasks/engines.rake b/vendor/plugins/engines/tasks/engines.rake new file mode 100644 index 0000000..21bb763 --- /dev/null +++ b/vendor/plugins/engines/tasks/engines.rake @@ -0,0 +1,176 @@ +module Engines + module RakeTasks + def self.all_engines + # An engine is informally defined as any subdirectory in vendor/plugins + # which ends in '_engine', '_bundle', or contains an 'init_engine.rb' file. + engine_base_dirs = ['vendor/plugins'] + # The engine root may be different; if possible try to include + # those directories too + if Engines.const_defined?(:CONFIG) + engine_base_dirs << Engines::CONFIG[:root] + end + engine_base_dirs.map! {|d| [d + '/*_engine/*', + d + '/*_bundle/*', + d + '/*/init_engine.rb']}.flatten! + engine_dirs = FileList.new(*engine_base_dirs) + engine_dirs.map do |engine| + File.basename(File.dirname(engine)) + end.uniq + end + end +end + + +namespace :engines do + desc "Display version information about active engines" + task :info => :environment do + if ENV["ENGINE"] + e = Engines.get(ENV["ENGINE"]) + header = "Details for engine '#{e.name}':" + puts header + puts "-" * header.length + puts "Version: #{e.version}" + puts "Details: #{e.info}" + else + puts "Engines plugin: #{Engines.version}" + Engines.each do |e| + puts "#{e.name}: #{e.version}" + end + end + end +end + +namespace :db do + namespace :fixtures do + namespace :engines do + + desc "Load plugin/engine fixtures into the current environment's database." + task :load => :environment do + require 'active_record/fixtures' + ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym) + plugin = ENV['ENGINE'] || '*' + Dir.glob(File.join(RAILS_ROOT, 'vendor', 'plugins', plugin, 'test', 'fixtures', '*.yml')).each do |fixture_file| + Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*')) + end + end + + end + end + + + namespace :migrate do + + desc "Migrate all engines. Target specific version with VERSION=x, specific engine with ENGINE=x" + task :engines => :environment do + engines_to_migrate = ENV["ENGINE"] ? [Engines.get(ENV["ENGINE"])].compact : Engines.active + if engines_to_migrate.empty? + puts "Couldn't find an engine called '#{ENV["ENGINE"]}'" + else + if ENV["VERSION"] && !ENV["ENGINE"] + # ignore the VERSION, since it makes no sense in this context; we wouldn't + # want to revert ALL engines to the same version because of a misttype + puts "Ignoring the given version (#{ENV["VERSION"]})." + puts "To control individual engine versions, use the ENGINE= argument" + else + engines_to_migrate.each do |engine| + Engines::EngineMigrator.current_engine = engine + migration_directory = File.join(engine.root, 'db', 'migrate') + if File.exist?(migration_directory) + puts "Migrating engine '#{engine.name}'" + Engines::EngineMigrator.migrate(migration_directory, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) + else + puts "The db/migrate directory for engine '#{engine.name}' appears to be missing." + puts "Should be: #{migration_directory}" + end + end + if ActiveRecord::Base.schema_format == :ruby && !engines_to_migrate.empty? + Rake::Task[:db_schema_dump].invoke + end + end + end + end + + namespace :engines do + Engines::RakeTasks.all_engines.each do |engine_name| + desc "Migrate the '#{engine_name}' engine. Target specific version with VERSION=x" + task engine_name => :environment do + ENV['ENGINE'] = engine_name; Rake::Task['db:migrate:engines'].invoke + end + end + end + + end +end + + +# this is just a rip-off from the plugin stuff in railties/lib/tasks/documentation.rake, +# because the default plugindoc stuff doesn't support subdirectories like /app or +# /component. +namespace :doc do + + desc "Generate documation for all installed engines" + task :engines => Engines::RakeTasks.all_engines.map {|engine| "doc:engines:#{engine}"} + + namespace :engines do + # Define doc tasks for each engine + Engines::RakeTasks.all_engines.each do |engine_name| + desc "Generation documentation for the '#{engine_name}' engine" + task engine_name => :environment do + engine_base = "vendor/plugins/#{engine_name}" + options = [] + files = Rake::FileList.new + options << "-o doc/plugins/#{engine_name}" + options << "--title '#{engine_name.titlecase} Documentation'" + options << '--line-numbers --inline-source' + options << '--all' # include protected methods + options << '-T html' + + files.include("#{engine_base}/lib/**/*.rb") + files.include("#{engine_base}/app/**/*.rb") # include the app directory + files.include("#{engine_base}/components/**/*.rb") # include the components directory + if File.exists?("#{engine_base}/README") + files.include("#{engine_base}/README") + options << "--main '#{engine_base}/README'" + end + files.include("#{engine_base}/CHANGELOG") if File.exists?("#{engine_base}/CHANGELOG") + + options << files.to_s + + sh %(rdoc #{options * ' '}) + end + end + end +end + +namespace :test do + desc "Run the engine tests in vendor/plugins/**/test (or specify with ENGINE=name)" + # NOTE: we're using the Rails 1.0 non-namespaced task here, just to maintain + # compatibility with Rails 1.0 + # TODO: make this work with Engines.config(:root) + + namespace :engines do + Engines::RakeTasks.all_engines.each do |engine_name| + desc "Run the engine tests for '#{engine_name}'" + Rake::TestTask.new(engine_name => :prepare_test_database) do |t| + t.libs << 'test' + t.pattern = "vendor/plugins/#{engine_name}/test/**/*_test.rb" + t.verbose = true + end + end + end + + Rake::TestTask.new(:engines => [:warn_about_multiple_engines_testing, :prepare_test_database]) do |t| + t.libs << "test" + engines = ENV['ENGINE'] || '**' + t.pattern = "vendor/plugins/#{engines}/test/**/*_test.rb" + t.verbose = true + end + + task :warn_about_multiple_engines_testing do + puts %{-~============== A Moste Polite Warninge ==================~- +You may experience issues testing multiple engines at once. +Please test engines individual for the moment. +-~===============( ... as you were ... )===================~- +} + end +end \ No newline at end of file diff --git a/vendor/plugins/engines/test/action_view_extensions_test.rb b/vendor/plugins/engines/test/action_view_extensions_test.rb new file mode 100644 index 0000000..8951785 --- /dev/null +++ b/vendor/plugins/engines/test/action_view_extensions_test.rb @@ -0,0 +1,9 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + '/../../../../config/environment') +require 'test_help' + +class ActionViewExtensionsTest < Test::Unit::TestCase + def test_stylesheet_path + assert true + end +end \ No newline at end of file diff --git a/vendor/plugins/engines/test/ruby_extensions_test.rb b/vendor/plugins/engines/test/ruby_extensions_test.rb new file mode 100644 index 0000000..c904f67 --- /dev/null +++ b/vendor/plugins/engines/test/ruby_extensions_test.rb @@ -0,0 +1,115 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + '/../../../../config/environment') +require 'test_help' + +class RubyExtensionsTest < Test::Unit::TestCase + + def setup + # create the module to be used for config testing + eval "module TestModule end" + end + + def teardown + # remove the TestModule constant from our scope + self.class.class_eval { remove_const :TestModule } + end + + + # + # Module.config + # + + def test_config_no_arguments + assert_raise(RuntimeError) { TestModule.config } + end + + def test_config_array_arguments + TestModule.config :monkey, 123 + assert_equal(123, TestModule.config(:monkey)) + end + + def test_config_hash_arguments + TestModule.config :monkey => 123, :donkey => 456 + assert_equal(123, TestModule.config(:monkey)) + assert_equal(456, TestModule.config(:donkey)) + end + + def test_config_can_store_hash + TestModule.config :hash, :key1 => 'val1', :key2 => 'val2' + assert_equal({:key1 => 'val1', :key2 => 'val2'}, TestModule.config(:hash)) + end + + def test_config_cant_overwrite_existing_config_values + TestModule.config :monkey, 123 + assert_equal(123, TestModule.config(:monkey)) + TestModule.config :monkey, 456 + assert_equal(123, TestModule.config(:monkey)) + + TestModule.config :monkey => 456 + assert_equal(123, TestModule.config(:monkey)) + + # in this case, the resulting Hash only has {:baboon => "goodbye!"} - that's Ruby, users beware. + TestModule.config :baboon => "hello", :baboon => "goodbye!" + assert_equal("goodbye!", TestModule.config(:baboon)) + end + + def test_config_force_new_value + TestModule.config :monkey, 123 + TestModule.config :man, 321 + assert_equal(123, TestModule.config(:monkey)) + assert_equal(321, TestModule.config(:man)) + TestModule.config :monkey, 456, :force + assert_equal(456, TestModule.config(:monkey)) + TestModule.config :monkey => 456, :man => 654, :force => true + assert_equal(456, TestModule.config(:monkey)) + assert_equal(654, TestModule.config(:man)) + TestModule.config :monkey => 789, :man => 987, :force => false + assert_equal(456, TestModule.config(:monkey)) + assert_equal(654, TestModule.config(:man)) + + TestModule.config :hash, :key1 => 'val1', :key2 => 'val2' + assert_equal({:key1 => 'val1', :key2 => 'val2'}, TestModule.config(:hash)) + TestModule.config :hash => {:key1 => 'val3', :key2 => 'val4'}, :force => true + assert_equal({:key1 => 'val3', :key2 => 'val4'}, TestModule.config(:hash)) + end + + # this test is somewhat redundant, but it might be an idea to havbe it explictly anyway + def test_config_get_values + TestModule.config :monkey, 123 + assert_equal(123, TestModule.config(:monkey)) + end + + def test_config_get_multiple_values + TestModule.config :monkey, 123 + TestModule.config :donkey, 456 + assert_equal([123, 456], TestModule.config([:monkey, :donkey])) + end + + + # + # Module.default_constant + # + + def test_default_constant_set + TestModule.default_constant :Monkey, 123 + assert_equal(123, TestModule::Monkey) + TestModule.default_constant "Hello", 456 + assert_equal(456, TestModule::Hello) + end + + def test_default_constant_cannot_set_again + TestModule.default_constant :Monkey, 789 + assert_equal(789, TestModule::Monkey) + TestModule.default_constant :Monkey, 456 + assert_equal(789, TestModule::Monkey) + end + + def test_default_constant_bad_arguments + # constant names must be Captialized + assert_raise(NameError) { TestModule.default_constant :lowercase_name, 123 } + + # constant names should be given as Strings or Symbols + assert_raise(RuntimeError) { TestModule.default_constant 123, 456 } + assert_raise(RuntimeError) { TestModule.default_constant Object.new, 456 } + end +end diff --git a/vendor/plugins/exception_notification/README b/vendor/plugins/exception_notification/README new file mode 100644 index 0000000..9a47c41 --- /dev/null +++ b/vendor/plugins/exception_notification/README @@ -0,0 +1,111 @@ += Exception Notifier Plugin for Rails + +The Exception Notifier plugin provides a mailer object and a default set of +templates for sending email notifications when errors occur in a Rails +application. The plugin is configurable, allowing programmers to specify: + +* the sender address of the email +* the recipient addresses +* the text used to prefix the subject line + +The email includes information about the current request, session, and +environment, and also gives a backtrace of the exception. + +== Usage + +First, include the ExceptionNotifiable mixin in whichever controller you want +to generate error emails (typically ApplicationController): + + class ApplicationController < ActionController::Base + include ExceptionNotifiable + ... + end + +Then, specify the email recipients in your environment: + + ExceptionNotifier.exception_recipients = %w(joe@schmoe.com bill@schmoe.com) + +And that's it! The defaults take care of the rest. + +== Configuration + +You can tweak other values to your liking, as well. In your environment file, +just set any or all of the following values: + + # defaults to exception.notifier@default.com + ExceptionNotifier.sender_address = + %("Application Error" ) + + # defaults to "[ERROR] " + ExceptionNotifier.email_prefix = "[APP] " + +Email notifications will only occur when the IP address is determined not to +be local. You can specify certain addresses to always be local so that you'll +get a detailed error instead of the generic error page. You do this in your +controller (or even per-controller): + + consider_local "64.72.18.143", "14.17.21.25" + +You can specify subnet masks as well, so that all matching addresses are +considered local: + + consider_local "64.72.18.143/24" + +The address "127.0.0.1" is always considered local. If you want to completely +reset the list of all addresses (for instance, if you wanted "127.0.0.1" to +NOT be considered local), you can simply do, somewhere in your controller: + + local_addresses.clear + +== Customization + +By default, the notification email includes four parts: request, session, +environment, and backtrace (in that order). You can customize how each of those +sections are rendered by placing a partial named for that part in your +app/views/exception_notifier directory (e.g., _session.rhtml). Each partial has +access to the following variables: + +* @controller: the controller that caused the error +* @request: the current request object +* @exception: the exception that was raised +* @host: the name of the host that made the request +* @backtrace: a sanitized version of the exception's backtrace +* @rails_root: a sanitized version of RAILS_ROOT +* @data: a hash of optional data values that were passed to the notifier +* @sections: the array of sections to include in the email + +You can reorder the sections, or exclude sections completely, by altering the +ExceptionNotifier.sections variable. You can even add new sections that +describe application-specific data--just add the section's name to the list +(whereever you'd like), and define the corresponding partial. Then, if your +new section requires information that isn't available by default, make sure +it is made available to the email using the exception_data macro: + + class ApplicationController < ActionController::Base + ... + protected + exception_data :additional_data + + def additional_data + { :document => @document, + :person => @person } + end + ... + end + +In the above case, @document and @person would be made available to the email +renderer, allowing your new section(s) to access and display them. See the +existing sections defined by the plugin for examples of how to write your own. + +== Advanced Customization + +By default, the email notifier will only notify on critical errors. For +ActiveRecord::RecordNotFound and ActionController::UnknownAction, it will +simply render the contents of your public/404.html file. Other exceptions +will render public/500.html and will send the email notification. If you want +to use different rules for the notification, you will need to implement your +own rescue_action_in_public method. You can look at the default implementation +in ExceptionNotifiable for an example of how to go about that. + + +Copyright (c) 2005 Jamis Buck, released under the MIT license \ No newline at end of file diff --git a/vendor/plugins/exception_notification/init.rb b/vendor/plugins/exception_notification/init.rb new file mode 100644 index 0000000..b39bd95 --- /dev/null +++ b/vendor/plugins/exception_notification/init.rb @@ -0,0 +1 @@ +require "action_mailer" diff --git a/vendor/plugins/exception_notification/lib/exception_notifiable.rb b/vendor/plugins/exception_notification/lib/exception_notifiable.rb new file mode 100644 index 0000000..60875d2 --- /dev/null +++ b/vendor/plugins/exception_notification/lib/exception_notifiable.rb @@ -0,0 +1,90 @@ +require 'ipaddr' + +# Copyright (c) 2005 Jamis Buck +# +# 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. +module ExceptionNotifiable + def self.included(target) + target.extend(ClassMethods) + end + + module ClassMethods + def consider_local(*args) + local_addresses.concat(args.flatten.map { |a| IPAddr.new(a) }) + end + + def local_addresses + addresses = read_inheritable_attribute(:local_addresses) + unless addresses + addresses = [IPAddr.new("127.0.0.1")] + write_inheritable_attribute(:local_addresses, addresses) + end + addresses + end + + def exception_data(deliverer=self) + if deliverer == self + read_inheritable_attribute(:exception_data) + else + write_inheritable_attribute(:exception_data, deliverer) + end + end + end + + def local_request? + remote = IPAddr.new(request.remote_ip) + !self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil? + end + + def render_404 + respond_to do |type| + type.html { render :file => "#{RAILS_ROOT}/public/404.html", :status => "404 Not Found" } + type.all { render :nothing => true, :status => "404 Not Found" } + end + end + + def render_500 + respond_to do |type| + type.html { render :file => "#{RAILS_ROOT}/public/500.html", :status => "500 Error" } + type.all { render :nothing => true, :status => "500 Error" } + end + end + + def rescue_action_in_public(exception) + case exception + when ActiveRecord::RecordNotFound, ActionController::UnknownController, ActionController::UnknownAction + render_404 + + else + render_500 + + deliverer = self.class.exception_data + data = case deliverer + when nil then {} + when Symbol then send(deliverer) + when Proc then deliverer.call(self) + end + + ExceptionNotifier.deliver_exception_notification(exception, self, + request, data) + end + end + +end diff --git a/vendor/plugins/exception_notification/lib/exception_notifier.rb b/vendor/plugins/exception_notification/lib/exception_notifier.rb new file mode 100644 index 0000000..0518c3d --- /dev/null +++ b/vendor/plugins/exception_notification/lib/exception_notifier.rb @@ -0,0 +1,67 @@ +require 'pathname' + +# Copyright (c) 2005 Jamis Buck +# +# 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. +class ExceptionNotifier < ActionMailer::Base + @@sender_address = %("Exception Notifier" ) + cattr_accessor :sender_address + + @@exception_recipients = [] + cattr_accessor :exception_recipients + + @@email_prefix = "[ERROR] " + cattr_accessor :email_prefix + + @@sections = %w(request session environment backtrace) + cattr_accessor :sections + + def self.reloadable?; false; end + + def exception_notification(exception, controller, request, data={}) + subject "#{email_prefix}#{controller.controller_name}##{controller.action_name} (#{exception.class}) #{exception.message.inspect}" + + recipients exception_recipients + from sender_address + + body data.merge({ :controller => controller, :request => request, + :exception => exception, :host => request.env["HTTP_HOST"], + :backtrace => sanitize_backtrace(exception.backtrace), + :rails_root => rails_root, :data => data, + :sections => sections }) + end + + def template_root + "#{File.dirname(__FILE__)}/../views" + end + + private + + def sanitize_backtrace(trace) + re = Regexp.new(/^#{Regexp.escape(rails_root)}/) + trace.map { |line| Pathname.new(line.gsub(re, "[RAILS_ROOT]")).cleanpath.to_s } + end + + def rails_root + return @rails_root if @rails_root + @rails_root = Pathname.new(RAILS_ROOT).cleanpath.to_s + end + +end diff --git a/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb b/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb new file mode 100644 index 0000000..4e4c48b --- /dev/null +++ b/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb @@ -0,0 +1,63 @@ +require 'pp' + +# Copyright (c) 2005 Jamis Buck +# +# 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. +module ExceptionNotifierHelper + VIEW_PATH = "views/exception_notifier" + APP_PATH = "#{RAILS_ROOT}/app/#{VIEW_PATH}" + + def render_section(section) + RAILS_DEFAULT_LOGGER.info("rendering section #{section.inspect}") + summary = render_overridable(section).strip + unless summary.blank? + title = render_overridable(:title, :locals => { :title => section }).strip + "#{title}\n\n#{summary.gsub(/^/, " ")}\n\n" + end + end + + def render_overridable(partial, options={}) + if File.exist?(path = "#{APP_PATH}/_#{partial}.rhtml") + render(options.merge(:file => path, :use_full_path => false)) + elsif File.exist?(path = "#{File.dirname(__FILE__)}/../#{VIEW_PATH}/_#{partial}.rhtml") + render(options.merge(:file => path, :use_full_path => false)) + else + "" + end + end + + def inspect_model_object(model, locals={}) + render_overridable(:inspect_model, + :locals => { :inspect_model => model, + :show_instance_variables => true, + :show_attributes => true }.merge(locals)) + end + + def inspect_value(value) + len = 512 + result = object_to_yaml(value).gsub(/\n/, "\n ").strip + result = result[0,len] + "... (#{result.length-len} bytes more)" if result.length > len+20 + result + end + + def object_to_yaml(object) + object.to_yaml.sub(/^---\s*/m, "") + end +end diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml new file mode 100644 index 0000000..7d13ba0 --- /dev/null +++ b/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml @@ -0,0 +1 @@ +<%= @backtrace.join "\n" %> diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml new file mode 100644 index 0000000..f426b54 --- /dev/null +++ b/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml @@ -0,0 +1,7 @@ +<% max = @request.env.keys.max { |a,b| a.length <=> b.length } -%> +<% @request.env.keys.sort.each do |key| -%> +* <%= "%*-s: %s" % [max.length, key, @request.env[key].to_s.strip] %> +<% end -%> + +* Process: <%= $$ %> +* Server : <%= `hostname -s`.chomp %> diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml new file mode 100644 index 0000000..82f9526 --- /dev/null +++ b/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml @@ -0,0 +1,16 @@ +<% if show_attributes -%> +[attributes] +<% attrs = inspect_model.attributes -%> +<% max = attrs.keys.max { |a,b| a.length <=> b.length } -%> +<% attrs.keys.sort.each do |attr| -%> +* <%= "%*-s: %s" % [max.length, attr, object_to_yaml(attrs[attr]).gsub(/\n/, "\n ")] %> +<% end -%> +<% end -%> + +<% if show_instance_variables -%> +[instance variables] +<% inspect_model.instance_variables.sort.each do |variable| -%> +<%- next if variable == "@attributes" -%> +* <%= variable %>: <%= inspect_value(inspect_model.instance_variable_get(variable)) %> +<% end -%> +<% end -%> diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml new file mode 100644 index 0000000..6df4520 --- /dev/null +++ b/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml @@ -0,0 +1,3 @@ +* URL: <%= @request.protocol %><%= @host %><%= @request.request_uri %> +* Parameters: <%= @request.parameters.inspect %> +* Rails root: <%= @rails_root %> diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml new file mode 100644 index 0000000..8bee57d --- /dev/null +++ b/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml @@ -0,0 +1,4 @@ +<% for variable in @request.session.instance_variables -%> +<% next if variable =~ /^@db/ -%> +* <%= variable %>: <%= PP.pp(@request.session.instance_variable_get(variable),"").gsub(/\n/, "\n ").strip %> +<% end -%> diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml new file mode 100644 index 0000000..1ed5a3f --- /dev/null +++ b/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml @@ -0,0 +1,3 @@ +------------------------------- +<%= title.to_s.humanize %>: +------------------------------- diff --git a/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml new file mode 100644 index 0000000..ec30c4a --- /dev/null +++ b/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml @@ -0,0 +1,6 @@ +A <%= @exception.class %> occurred in <%= @controller.controller_name %>#<%= @controller.action_name %>: + + <%= @exception.message %> + <%= @backtrace.first %> + +<%= @sections.map { |section| render_section(section) }.join %> diff --git a/vendor/plugins/login_engine/CHANGELOG b/vendor/plugins/login_engine/CHANGELOG new file mode 100644 index 0000000..3764ef2 --- /dev/null +++ b/vendor/plugins/login_engine/CHANGELOG @@ -0,0 +1,22 @@ += v1.0.2 +* Added version +* Removed errant requires no longer needed (murray.steele@gmail.com, Ticket #156, Ticket #157, Ticket #158) +# Removed documentation/rake tasks that refer the schema.rb (Ticket #155) +# Verified cannot be assigned via URL parameters. If more security is required, users should override the signup action itself (Ticket #169) +# Minor view/flash message cleanup +# Authentication by token now respects primary key prefixes (Ticket #140) + += v1.0.1 + * Added CHANGELOG + * Changed wording for when password forgotten to 'reset', rather than 'retrieve'. (snowblink@gmail.com) + * Fixed new location of engines testing extensions. (lazyatom@gmail.com) + * Removed schema.db from Login Engine; migrations should be used instead. (snowblink@gmail.com) + * Updated User Controller tests to parse the user_id and email out of the URL in the email body. (snowblink@gmail.com) + * Ticket #89 (lazyatom@gmail.com) User creation halts the after_save callback chain. + * Ticket #97 (dcorbin@machturtle.com) The forgotten_password view generates invalid HTML + * Ticket #112 (segabor@gmail.com) Authentication system will break even on successful login + * Added simple email validation to the User model. (snowblink@gmail.com) + This should also take care of the unit test failures detailed in Ticket #114 (morris@wolfman.com) + * Ticket #118 (augustz@augustz.com) SVN source for login_engine not found + * Ticket #119 (Goynang) Unit tests for engines fail after default install + * Ticket #126 (lazyatom@gmail.com) Add install.rb to login engine diff --git a/vendor/plugins/login_engine/README b/vendor/plugins/login_engine/README new file mode 100644 index 0000000..69b2981 --- /dev/null +++ b/vendor/plugins/login_engine/README @@ -0,0 +1,344 @@ += Before we start + +This is a Rails Engine version of the Salted Login Generator, a most excellent login system which is sufficient for most simple cases. For the most part, this code has not been altered from its generator form, with the following notable exceptions + +* Localization has been removed. +* The 'welcome' page has been changed to the 'home' page +* A few new functions have been thrown in +* It's... uh.... a Rails Engine now ;-) + +However, what I'm trying to say is that 99.9999% of the credit for this should go to Joe Hosteny, Tobias Luetke (xal) and the folks that worked on the original Salted Login generator code. I've just wrapped it into something runnable with the Rails Engine system. + +Please also bear in mind that this is a work in progress, and things like testing are wildly up in the air... but they will fall into place very soon. And now, on with the show. + + += Installation + +Installing the Login Engine is fairly simple. + +Your options are: + 1. Install as a rails plugin: + $ script/plugin install login_engine + 2. Use svn:externals + $ svn propedit svn:externals vendor/plugins + + You can choose to use the latest stable release: + login_engine http://svn.rails-engines.org/plugins/login_engine + + Or a tagged release (recommended for releases of your code): + login_engine http://svn.rails-engines.org/logine_engine/tags/ + +There are a few configuration steps that you'll need to take to get everything running smoothly. Listed below are the changes to your application you will need to make. + +=== Setup your Rails application + +Edit your database.yml, most importantly! You might also want to move public/index.html out of the way, and set up some default routes in config/routes.rb. + +=== Add configuration and start engine + +Add the following to the bottom of environment.rb: + + module LoginEngine + config :salt, "your-salt-here" + end + + Engines.start :login + +You'll probably want to change the Salt value to something unique. You can also override any of the configuration values defined at the top of lib/user_system.rb in a similar way. Note that you don't need to start the engine with Engines.start :login_engine - instead, :login (or any name) is sufficient if the engine is a directory named _engine. + + +=== Add the filters + +Next, edit your app/controllers/application.rb file. The beginning of your ApplicationController should look something like this: + + require 'login_engine' + + class ApplicationController < ActionController::Base + include LoginEngine + helper :user + model :user + + before_filter :login_required + +If you don't want ALL actions to require a login, you need to read further below to learn how to restrict only certain actions. + +Add the following to your ApplicationHelper: + + module ApplicationHelper + include LoginEngine + end + +This ensures that the methods to work with users in your views are available + +=== Set up ActionMailer + +If you want to disable email functions within the Login Engine, simple set the :use_email_notification config flag to false in your environment.rb file: + + module LoginEngine + + # ... other options... + config :use_email_notification, false + + end + +You should note that retrieving forgotten passwords automatically isn't possible when the email functions are disabled. Instead, the user is presented with a message instructing them to contact the system administrator + +If you wish you use email notifications and account creation verification, you must properly configure ActionMailer for your mail settings. For example, you could add the following in config/environments/development.rb (for a .Mac account, and with your own username and password, obviously): + +ActionMailer::Base.server_settings = { + :address => "smtp.mac.com", + :port => 25, + :domain => "smtp.mac.com", + :user_name => "", + :password => "", + :authentication => :login +} + +You'll need to configure it properly so that email can be sent. One of the easiest ways to test your configuration is to temporarily reraise exceptions from the signup method (so that you get the actual mailer exception string). In the rescue statement, put a single "raise" statement in. Once you've debugged any setting problems, remove that statement to get the proper flash error handling back. + + +=== Create the DB schema + +After you have done the modifications the the ApplicationController and its helper, you can import the user model into the database. Migration information in login_engine/db/migrate/. + +You *MUST* check that these files aren't going to interfere with anything in your application. + +You can change the table name used by adding + + module LoginEngine + + # ... other options... + config :user_table, "your_table_name" + + end + +...to the LoginEngine configuration in environment.rb. Then run from the root of your project: + + rake db:migrate:engines ENGINE=login + +to import the schema into your database. + + +== Include stylesheets + +If you want the default stylesheet, add the following line to your layout: + + <%= engine_stylesheet 'login_engine' %> + +... somewhere in the section of your HTML layout file. + +== Integrate flash messages into your layout + +LoginEngine does not display any flash messages in the views it contains, and thus you must display them yourself. This allows you to integrate any flash messages into your existing layout. LoginEngine adheres to the emerging flash usage standard, namely: + +* :warning - warning (failure) messages +* :notice - success messages +* :message - neutral (reminder, informational) messages + +This gives you the flexibility to theme the different message classes separately. In your layout you should check for and display flash[:warning], flash[:notice] and flash[:message]. For example: + + <% for name in [:notice, :warning, :message] %> + <% if flash[name] %> + <%= "
    #{flash[name]}
    " %> + <% end %> + <% end %> + +Alternately, you could look at using the flash helper plugin (available from https://opensvn.csie.org/traccgi/flash_helper_plugin/trac.cgi/), which supports the same naming convention. + + += How to use the Login Engine + +Now you can go around and happily add "before_filter :login_required" to the controllers which you would like to protect. + +After integrating the login system with your rails application navigate to your new controller's signup method. There you can create a new account. After you are done you should have a look at your DB. Your freshly created user will be there but the password will be a sha1 hashed 40 digit mess. I find this should be the minimum of security which every page offering login & password should give its customers. Now you can move to one of those controllers which you protected with the before_filter :login_required snippet. You will automatically be re-directed to your freshly created login controller and you are asked for a password. After entering valid account data you will be taken back to the controller which you requested earlier. Simple huh? + +=== Protection using before_filter + +Adding the line before_filter :login_required to your app/controllers/application.rb file will protect *all* of your applications methods, in every controller. If you only want to control access to specific controllers, remove this line from application.rb and add it to the controllers that you want to secure. + +Within individual controllers you can restrict which methods the filter runs on in the usual way: + + before_filter :login_required, :only => [:myaccount, :changepassword] + before_filter :login_required, :except => [:index] + +=== Protection using protect?() + +Alternatively, you can leave the before_filter in the global application.rb file, and control which actions are restricted in individual controllers by defining a protect?() method in that controller. + +For instance, in the UserController we want to allow everyone access to the 'login', 'signup' and 'forgot_password' methods (otherwise noone would be able to access our site!). So a protect?() method is defined in user_controller.rb as follows: + + def protect?(action) + if ['login', 'signup', 'forgot_password'].include?(action) + return false + else + return true + end + end + +Of course, you can override this Engine behaviour in your application - see below. + +== Configuration + +The following configuration variables are set in lib/login_engine.rb. If you wish to override them, you should set them BEFORE calling Engines.start (it is possible to set them after, but it's simpler to just do it before. Please refer to the Engine documentation for the #config method for more information). + +For example, the following might appear at the bottom of /config/environment.rb: + + module LoginEngine + config :salt, 'my salt' + config :app_name, 'My Great App' + config :app_url, 'http://www.wow-great-domain.com' + end + + Engines.start + +=== Configuration Options + ++email_from+:: The email from which registration/administration emails will appear to + come from. Defaults to 'webmaster@your.company'. ++admin_email+:: The email address users are prompted to contact if passwords cannot + be emailed. Defaults to 'webmaster@your.company'. ++app_url+:: The URL of the site sent to users for signup/forgotten passwords, etc. + Defaults to 'http://localhost:3000/'. ++app_name+:: The application title used in emails. Defaults to 'TestApp'. ++mail_charset+:: The charset used in emails. Defaults to 'utf-8'. ++security_token_life_hours+:: The life span of security tokens, in hours. If a security + token is older than this when it is used to try and authenticate + a user, it will be discarded. In other words, the amount of time + new users have between signing up and clicking the link they + are sent. Defaults to 24 hours. ++two_column_input+:: If true, forms created with the UserHelper#form_input method will + use a two-column table. Defaults to true. ++changeable_fields+:: An array of fields within the user model which the user + is allowed to edit. The Salted Hash Login generator documentation + states that you should NOT include the email field in this + array, although I am not sure why. Defaults to +[ 'firstname', 'lastname' ]+. ++delayed_delete+:: Set to true to allow delayed deletes (i.e., delete of record + doesn't happen immediately after user selects delete account, + but rather after some expiration of time to allow this action + to be reverted). Defaults to false. ++delayed_delete_days+:: The time delay used for the 'delayed_delete' feature. Defaults to + 7 days. ++user_table+:: The table to store User objects in. Defaults to "users" (or "user" if + ActiveRecord pluralization is disabled). ++use_email_notification+:: If false, no emails will be sent to the user. As a consequence, + users who signup are immediately verified, and they cannot request + forgotten passwords. Defaults to true. ++confirm_account+:: An overriding flag to control whether or not user accounts must be + verified by email. This overrides the +user_email_notification+ flag. + Defaults to true. + +== Overriding controllers and views + +The standard home page is almost certainly not what you want to present to your users. Because this login system is a Rails Engine, overriding the default behaviour couldn't be simpler. To change the RHTML template shown for the home action, simple create a new file in RAILS_ROOT/app/views/user/home.rhtml (you'll probably need to create the directory user at the same time). This new view file will be used instead of the one provided in the Login Engine. Easy! + + +== Tips & Tricks + +How do I... + + ... access the user who is currently logged in + + A: You can get the user object from the session using session[:user] + Example: + Welcome <%= session[:user].name %> + + You can also use the 'current_user' method provided by UserHelper: + Example: + Welcome <%= current_user.name %> + + + ... restrict access to only a few methods? + + A: Use before_filters build in scoping. + Example: + before_filter :login_required, :only => [:myaccount, :changepassword] + before_filter :login_required, :except => [:index] + + ... check if a user is logged-in in my views? + + A: session[:user] will tell you. Here is an example helper which you can use to make this more pretty: + Example: + def user? + !session[:user].nil? + end + + ... return a user to the page they came from before logging in? + + A: The user will be send back to the last url which called the method "store_location" + Example: + User was at /articles/show/1, wants to log in. + in articles_controller.rb, add store_location to the show function and + send the user to the login form. + After he logs in he will be send back to /articles/show/1 + +You can find more help at http://wiki.rubyonrails.com/rails/show/SaltedLoginGenerator + +== Troubleshooting + +One of the more common problems people have seen is that after verifying an account by following the emailed URL, they are unable to login via the normal login method since the verified field is not properly set in the user model's row in the DB. + +The most common cause of this problem is that the DB and session get out of sync. In particular, it always happens for me after recreating the DB if I have run the server previously. To fix the problem, remove the /tmp/ruby* session files (from wherever they are for your installation) while the server is stopped, and then restart. This usually is the cause of the problem. + += Notes + +=== Database Schemas & Testing + +Currently, since not all databases appear to support structure cloning, the tests will load the entire schema into your test database, potentially blowing away any other test structures you might have. If this presents an issue for your application, comment out the line in test/test_helper.rb + + += Database Schema Details + +You need a database table corresponding to the User model. This is provided as a Rails Schema file, but the schema is presented below for information. Note the table type for MySQL. Whatever DB you use, it must support transactions. If it does not, the functional tests will not work properly, nor will the application in the face of failures during certain DB creates and updates. + + mysql syntax: + CREATE TABLE users ( + id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + login VARCHAR(80) NOT NULL, + salted_password VARCHAR(40) NOT NULL, + email VARCHAR(60) NOT NULL, + firstname VARCHAR(40), + lastname VARCHAR(40), + salt CHAR(40) NOT NULL, + verified INT default 0, + role VARCHAR(40) default NULL, + security_token CHAR(40) default NULL, + token_expiry DATETIME default NULL, + deleted INT default 0, + delete_after DATETIME default NULL + ) TYPE=InnoDB DEFAULT CHARSET=utf8; + + postgres: + CREATE TABLE "users" ( + id SERIAL PRIMARY KEY + login VARCHAR(80) NOT NULL, + salted_password VARCHAR(40) NOT NULL, + email VARCHAR(60) NOT NULL, + firstname VARCHAR(40), + lastname VARCHAR(40), + salt CHAR(40) NOT NULL, + verified INT default 0, + role VARCHAR(40) default NULL, + security_token CHAR(40) default NULL, + token_expiry TIMESTAMP default NULL, + deleted INT default 0, + delete_after TIMESTAMP default NULL + ) WITH OIDS; + + sqlite: + CREATE TABLE 'users' ( + id INTEGER PRIMARY KEY, + login VARCHAR(80) NOT NULL, + salted_password VARCHAR(40) NOT NULL, + email VARCHAR(60) NOT NULL, + firstname VARCHAR(40), + lastname VARCHAR(40), + salt CHAR(40) NOT NULL, + verified INT default 0, + role VARCHAR(40) default NULL, + security_token CHAR(40) default NULL, + token_expiry DATETIME default NULL, + deleted INT default 0, + delete_after DATETIME default NULL + ); + +Of course your user model can have any amount of extra fields. This is just a starting point. diff --git a/vendor/plugins/login_engine/app/controllers/user_controller.rb b/vendor/plugins/login_engine/app/controllers/user_controller.rb new file mode 100644 index 0000000..397f06b --- /dev/null +++ b/vendor/plugins/login_engine/app/controllers/user_controller.rb @@ -0,0 +1,261 @@ +class UserController < ApplicationController + model :user + + # Override this function in your own application to define a custom home action. + def home + if user? + @fullname = "#{current_user.firstname} #{current_user.lastname}" + else + @fullname = "Not logged in..." + end # this is a bit of a hack since the home action is used to verify user + # keys, where noone is logged in. We should probably create a unique + # 'validate_key' action instead. + end + + # The action used to log a user in. If the user was redirected to the login page + # by the login_required method, they should be sent back to the page they were + # trying to access. If not, they will be sent to "/user/home". + def login + return if generate_blank + @user = User.new(params[:user]) + if session[:user] = User.authenticate(params[:user][:login], params[:user][:password]) + session[:user].logged_in_at = Time.now + session[:user].save + flash[:notice] = 'Login successful' + redirect_to_stored_or_default :action => 'home' + else + @login = params[:user][:login] + flash.now[:warning] = 'Login unsuccessful' + end + end + + # Register as a new user. Upon successful registration, the user will be sent to + # "/user/login" to enter their details. + def signup + return if generate_blank + params[:user].delete('form') + params[:user].delete('verified') # you CANNOT pass this as part of the request + @user = User.new(params[:user]) + begin + User.transaction(@user) do + @user.new_password = true + unless LoginEngine.config(:use_email_notification) and LoginEngine.config(:confirm_account) + @user.verified = 1 + end + if @user.save + key = @user.generate_security_token + url = url_for(:action => 'home', :user_id => @user.id, :key => key) + flash[:notice] = 'Signup successful!' + if LoginEngine.config(:use_email_notification) and LoginEngine.config(:confirm_account) + UserNotify.deliver_signup(@user, params[:user][:password], url) + flash[:notice] << ' Please check your registered email account to verify your account registration and continue with the login.' + else + flash[:notice] << ' Please log in.' + end + redirect_to :action => 'login' + end + end + rescue Exception => e + flash.now[:notice] = nil + flash.now[:warning] = 'Error creating account: confirmation email not sent' + logger.error "Unable to send confirmation E-Mail:" + logger.error e + end + end + + def logout + session[:user] = nil + redirect_to :action => 'login' + end + + def change_password + return if generate_filled_in + if do_change_password_for(@user) + # since sometimes we're changing the password from within another action/template... + #redirect_to :action => params[:back_to] if params[:back_to] + redirect_back_or_default :action => 'change_password' + end + end + + protected + def do_change_password_for(user) + begin + User.transaction(user) do + user.change_password(params[:user][:password], params[:user][:password_confirmation]) + if user.save + if LoginEngine.config(:use_email_notification) + UserNotify.deliver_change_password(user, params[:user][:password]) + flash[:notice] = "Updated password emailed to #{@user.email}" + else + flash[:notice] = "Password updated." + end + return true + else + flash[:warning] = 'There was a problem saving the password. Please retry.' + return false + end + end + rescue + flash[:warning] = 'Password could not be changed at this time. Please retry.' + end + end + + public + + + def forgot_password + # Always redirect if logged in + if user? + flash[:message] = 'You are currently logged in. You may change your password now.' + redirect_to :action => 'change_password' + return + end + + # Email disabled... we are unable to provide the password + if !LoginEngine.config(:use_email_notification) + flash[:message] = "Please contact the system admin at #{LoginEngine.config(:admin_email)} to reset your password." + redirect_back_or_default :action => 'login' + return + end + + # Render on :get and render + return if generate_blank + + # Handle the :post + if params[:user][:email].empty? + flash.now[:warning] = 'Please enter a valid email address.' + elsif (user = User.find_by_email(params[:user][:email])).nil? + flash.now[:warning] = "We could not find a user with the email address #{params[:user][:email]}" + else + begin + User.transaction(user) do + key = user.generate_security_token + url = url_for(:action => 'change_password', :user_id => user.id, :key => key) + UserNotify.deliver_forgot_password(user, url) + flash[:notice] = "Instructions on resetting your password have been emailed to #{params[:user][:email]}" + end + unless user? + redirect_to :action => 'login' + return + end + redirect_back_or_default :action => 'home' + rescue + flash.now[:warning] = "Your password could not be emailed to #{params[:user][:email]}" + end + end + end + + def edit + return if generate_filled_in + do_edit_user(@user) + end + + protected + def do_edit_user(user) + begin + User.transaction(user) do + user.attributes = params[:user].delete_if { |k,v| not LoginEngine.config(:changeable_fields).include?(k) } + if user.save + flash[:notice] = "User details updated" + else + flash[:warning] = "Details could not be updated! Please retry." + end + end + rescue + flash.now[:warning] = "Error updating user details. Please try again later." + end + end + + public + + def delete + get_user_to_act_on + if do_delete_user(@user) + logout + else + redirect_back_or_default :action => 'home' + end + end + + protected + def do_delete_user(user) + begin + if LoginEngine.config(:delayed_delete) + User.transaction(user) do + key = user.set_delete_after + if LoginEngine.config(:use_email_notification) + url = url_for(:action => 'restore_deleted', :user_id => user.id, :key => key) + UserNotify.deliver_pending_delete(user, url) + end + end + else + destroy(@user) + end + return true + rescue + if LoginEngine.config(:use_email_notification) + flash.now[:warning] = 'The delete instructions were not sent. Please try again later.' + else + flash.now[:notice] = 'The account has been scheduled for deletion. It will be removed in #{LoginEngine.config(:delayed_delete_days)} days.' + end + return false + end + end + + public + + def restore_deleted + get_user_to_act_on + @user.deleted = 0 + if not @user.save + flash.now[:warning] = "The account for #{@user['login']} was not restored. Please try the link again." + redirect_to :action => 'login' + else + redirect_to :action => 'home' + end + end + + protected + + def destroy(user) + UserNotify.deliver_delete(user) if LoginEngine.config(:use_email_notification) + flash[:notice] = "The account for #{user['login']} was successfully deleted." + user.destroy() + end + + def protect?(action) + if ['login', 'signup', 'forgot_password'].include?(action) + return false + else + return true + end + end + + # Generate a template user for certain actions on get + def generate_blank + case request.method + when :get + @user = User.new + render + return true + end + return false + end + + # Generate a template user for certain actions on get + def generate_filled_in + get_user_to_act_on + case request.method + when :get + render + return true + end + return false + end + + # returns the user object this method should act upon; only really + # exists for other engines operating on top of this one to redefine... + def get_user_to_act_on + @user = session[:user] + end +end diff --git a/vendor/plugins/login_engine/app/helpers/user_helper.rb b/vendor/plugins/login_engine/app/helpers/user_helper.rb new file mode 100644 index 0000000..2dad7d8 --- /dev/null +++ b/vendor/plugins/login_engine/app/helpers/user_helper.rb @@ -0,0 +1,88 @@ +module UserHelper + + # Abstraction to make views a little cleaner + def form_input(helper_method, prompt, field_name=nil, options = {}, form_name = nil) + form_name = "user" if form_name.nil? + case helper_method.to_s + when 'hidden_field' + self.hidden_field(form_name, field_name, options) + when /^.*button$/ + #prompt = l(:"#{@controller.controller_name}_#{field_name}_button") + <<-EOL + + #{self.send(helper_method, form_name, prompt, options)} + + EOL + else + field = ( + case helper_method + when :select + self.send(helper_method, form_name, field_name, options.delete('values'), options) + when :password_field + options[:value] = "" + self.send(helper_method, form_name, field_name, options) + else + self.send(helper_method, form_name, field_name, options) + end) +# lname = "#{form_name}_#{field_name}_form" +# prompt = l(:"#{lname}") + if LoginEngine.config(:two_column_input) +<<-EOL + + + #{field} + + EOL + else +<<-EOL + + #{field} + EOL + end + end + end + +# def button_helper(name, options = {}) +# label = l(:"#{@controller.controller_name}_#{name}_button") +# "#{self.send(:submit_tag, label, options)}" +# end + +# def link_helper(name, options = {}) +# raise ArgumentError if name.nil? +# label = l(:"#{@controller.controller_name}_#{name}_link") +# "#{self.send(:link_to, label, options)}" +# end + + def title_helper + "#{@controller.controller_class_name} #{@controller.action_name}" + end + +# def message_helper(name) +# l(:"#{@controller.controller_name}_#{name}_message") +# end + + def start_form_tag_helper(options = {}) + url = url_for(:action => "#{@controller.action_name}") + "#{self.send(:start_form_tag, url, options)}" + end + + def attributes(hash) + hash.keys.inject("") { |attrs, key| attrs + %{#{key}="#{h(hash[key])}" } } + end + + def read_only_field(form_name, field_name, html_options) + "#{instance_variable_get('@' + form_name)[field_name]}" + end + + def submit_button(form_name, prompt, html_options) + %{} + end + + def changeable(user, field) + if user.new_record? or LoginEngine.config(:changeable_fields).include?(field) + :text_field + else + :read_only_field + end + end +end diff --git a/vendor/plugins/login_engine/app/models/user.rb b/vendor/plugins/login_engine/app/models/user.rb new file mode 100644 index 0000000..6a313fa --- /dev/null +++ b/vendor/plugins/login_engine/app/models/user.rb @@ -0,0 +1,7 @@ +class User < ActiveRecord::Base + include LoginEngine::AuthenticatedUser + + # all logic has been moved into login_engine/lib/login_engine/authenticated_user.rb + +end + diff --git a/vendor/plugins/login_engine/app/models/user_notify.rb b/vendor/plugins/login_engine/app/models/user_notify.rb new file mode 100644 index 0000000..0767842 --- /dev/null +++ b/vendor/plugins/login_engine/app/models/user_notify.rb @@ -0,0 +1,75 @@ +class UserNotify < ActionMailer::Base + def signup(user, password, url=nil) + setup_email(user) + + # Email header info + @subject += "Welcome to #{LoginEngine.config(:app_name)}!" + + # Email body substitutions + @body["name"] = "#{user.firstname} #{user.lastname}" + @body["login"] = user.login + @body["password"] = password + @body["url"] = url || LoginEngine.config(:app_url).to_s + @body["app_name"] = LoginEngine.config(:app_name).to_s + end + + def forgot_password(user, url=nil) + setup_email(user) + + # Email header info + @subject += "Forgotten password notification" + + # Email body substitutions + @body["name"] = "#{user.firstname} #{user.lastname}" + @body["login"] = user.login + @body["url"] = url || LoginEngine.config(:app_url).to_s + @body["app_name"] = LoginEngine.config(:app_name).to_s + end + + def change_password(user, password, url=nil) + setup_email(user) + + # Email header info + @subject += "Changed password notification" + + # Email body substitutions + @body["name"] = "#{user.firstname} #{user.lastname}" + @body["login"] = user.login + @body["password"] = password + @body["url"] = url || LoginEngine.config(:app_url).to_s + @body["app_name"] = LoginEngine.config(:app_name).to_s + end + + def pending_delete(user, url=nil) + setup_email(user) + + # Email header info + @subject += "Delete user notification" + + # Email body substitutions + @body["name"] = "#{user.firstname} #{user.lastname}" + @body["url"] = url || LoginEngine.config(:app_url).to_s + @body["app_name"] = LoginEngine.config(:app_name).to_s + @body["days"] = LoginEngine.config(:delayed_delete_days).to_s + end + + def delete(user, url=nil) + setup_email(user) + + # Email header info + @subject += "Delete user notification" + + # Email body substitutions + @body["name"] = "#{user.firstname} #{user.lastname}" + @body["url"] = url || LoginEngine.config(:app_url).to_s + @body["app_name"] = LoginEngine.config(:app_name).to_s + end + + def setup_email(user) + @recipients = "#{user.email}" + @from = LoginEngine.config(:email_from).to_s + @subject = "[#{LoginEngine.config(:app_name)}] " + @sent_on = Time.now + @headers['Content-Type'] = "text/plain; charset=#{LoginEngine.config(:mail_charset)}; format=flowed" + end +end diff --git a/vendor/plugins/login_engine/app/views/user/_edit.rhtml b/vendor/plugins/login_engine/app/views/user/_edit.rhtml new file mode 100644 index 0000000..e963d1c --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/_edit.rhtml @@ -0,0 +1,11 @@ +
    + + <%= form_input changeable(user, "firstname"), "First Name", "firstname" %> + <%= form_input changeable(user, "lastname"), "Last Name","lastname" %> + <%= form_input changeable(user, "login"), "Login ID", "login", :size => 30 %>
    + <%= form_input changeable(user, "email"), "Email", "email" %> + <% if submit %> + <%= form_input :submit_button, (user.new_record? ? 'Signup' : 'Change Settings'), :class => 'two_columns' %> + <% end %> +
    +
    diff --git a/vendor/plugins/login_engine/app/views/user/_password.rhtml b/vendor/plugins/login_engine/app/views/user/_password.rhtml new file mode 100644 index 0000000..27139f3 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/_password.rhtml @@ -0,0 +1,9 @@ +
    + + <%= form_input :password_field, "Password", "password", :size => 30 %> + <%= form_input :password_field, "Password Confirmation", "password_confirmation", :size => 30 %> + <% if submit %> + <%= form_input :submit_button, 'Change password' %> + <% end %> +
    +
    \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user/change_password.rhtml b/vendor/plugins/login_engine/app/views/user/change_password.rhtml new file mode 100644 index 0000000..3b5abe4 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/change_password.rhtml @@ -0,0 +1,17 @@ +
    +

    Change Password

    + + <%= error_messages_for 'user' %> + +
    +

    Enter your new password in the fields below and click 'Change Password' to have a new password sent to your email inbox.

    + + <%= start_form_tag :action => 'change_password' %> + <%= render_partial 'password', :user => @user, :submit => false %> +
    + <%= submit_tag 'Change password' %> + <%= link_to 'Cancel', :action => 'home' %> +
    + <%= end_form_tag %> +
    +
    \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user/edit.rhtml b/vendor/plugins/login_engine/app/views/user/edit.rhtml new file mode 100644 index 0000000..efe7953 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/edit.rhtml @@ -0,0 +1,23 @@ +
    +

    Edit user

    + + <%= error_messages_for 'user' %> + + <%= start_form_tag :action => 'edit' %> + <%= render_partial 'edit', :user => @user, :submit => true %> + <%= end_form_tag %> +
    + <%= start_form_tag :action => 'change_password' %> + <%= hidden_field_tag "back_to", "edit" %> + <%= render_partial 'password', :submit => true %> + <%= end_form_tag %> + + <%= start_form_tag :action => 'delete' %> +
    + <%= hidden_field 'user', 'form', :value => 'delete' %> + + <%= form_input :submit_button, 'Delete Account' %> +
    + <%= end_form_tag %> +
    + \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user/forgot_password.rhtml b/vendor/plugins/login_engine/app/views/user/forgot_password.rhtml new file mode 100644 index 0000000..66c7ce9 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/forgot_password.rhtml @@ -0,0 +1,18 @@ +
    +

    Forgotten Password

    + + <%= error_messages_for 'user' %> + +
    +

    Enter your email address in the field below and click 'Reset Password' to have instructions on how to retrieve your forgotten password emailed to you.

    + + <%= start_form_tag_helper %> + <%= text_field("user", "email", "size" => 30) %> + +
    + <%= submit_tag 'Reset Password' %> + <%= link_to 'Cancel', :action => 'login' %> +
    + <%= end_form_tag %> +
    +
    diff --git a/vendor/plugins/login_engine/app/views/user/home.rhtml b/vendor/plugins/login_engine/app/views/user/home.rhtml new file mode 100644 index 0000000..019b865 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/home.rhtml @@ -0,0 +1,7 @@ +
    +

    Welcome

    +

    You are now logged into the system, <%= @fullname %>...

    +

    Since you are here it's safe to assume the application never called store_location, otherwise you would have been redirected somewhere else after a successful login.

    + + <%= link_to '« logout', :action => 'logout' %> +
    diff --git a/vendor/plugins/login_engine/app/views/user/login.rhtml b/vendor/plugins/login_engine/app/views/user/login.rhtml new file mode 100644 index 0000000..50a6eb3 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/login.rhtml @@ -0,0 +1,17 @@ +
    +

    Please Login

    + +
    + <%= start_form_tag :action => 'login' %> + + <%= form_input :text_field, "Login ID", "login", :size => 30 %>
    + <%= form_input :password_field, "Password", "password", :size => 30 %>
    +
    + +
    + <%= submit_tag 'Login' %> + <%= link_to 'Register for an account', :action => 'signup' %> | + <%= link_to 'Forgot my password', :action => 'forgot_password' %>
    + <%= end_form_tag %> +
    +
    diff --git a/vendor/plugins/login_engine/app/views/user/logout.rhtml b/vendor/plugins/login_engine/app/views/user/logout.rhtml new file mode 100644 index 0000000..18f8b3d --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/logout.rhtml @@ -0,0 +1,8 @@ +
    +

    Logoff

    + +

    You are now logged out of the system...

    + + <%= link_to '« login', :action => 'login' %> +
    + diff --git a/vendor/plugins/login_engine/app/views/user/signup.rhtml b/vendor/plugins/login_engine/app/views/user/signup.rhtml new file mode 100644 index 0000000..c3a0cd3 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user/signup.rhtml @@ -0,0 +1,17 @@ +
    +

    Signup

    + + <%= error_messages_for 'user' %> + +
    + <%= start_form_tag :action => 'signup' %> + <%= render_partial 'edit', :user => @user, :submit => false %>
    + <%= render_partial 'password', :submit => false %> + +
    + <%= submit_tag 'Signup' %> + <%= link_to 'Cancel', :action => 'login' %> +
    + <%= end_form_tag %> +
    +
    \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user_notify/change_password.rhtml b/vendor/plugins/login_engine/app/views/user_notify/change_password.rhtml new file mode 100644 index 0000000..7c0933f --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user_notify/change_password.rhtml @@ -0,0 +1,10 @@ +Dear <%= @name %>, + +At your request, <%= @app_name %> has changed your password. If it was not at your request, then you should be aware that someone has access to your account and requested this change. + +Your new login credentials are: + + login: <%= @login %> + password: <%= @password %> + +<%= @url %> \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user_notify/delete.rhtml b/vendor/plugins/login_engine/app/views/user_notify/delete.rhtml new file mode 100644 index 0000000..de91707 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user_notify/delete.rhtml @@ -0,0 +1,5 @@ +Dear <%= @name %>, + +At your request, <%= @app_name %> has permanently deleted your account. + +<%= @url %> \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user_notify/forgot_password.rhtml b/vendor/plugins/login_engine/app/views/user_notify/forgot_password.rhtml new file mode 100644 index 0000000..17f36c3 --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user_notify/forgot_password.rhtml @@ -0,0 +1,11 @@ +Dear <%= @name %>, + +At your request, <%= @app_name %> has sent you the following URL so that you may reset your password. If it was not at your request, then you should be aware that someone has entered your email address as theirs in the forgotten password section of <%= @app_name %>. + +Please click on the following link to go to the change password page: + +Click me! + +It's advisable for you to change your password as soon as you login. It's as simple as navigating to 'Preferences' and clicking on 'Change Password'. + +<%= @url %> \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user_notify/pending_delete.rhtml b/vendor/plugins/login_engine/app/views/user_notify/pending_delete.rhtml new file mode 100644 index 0000000..84e200e --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user_notify/pending_delete.rhtml @@ -0,0 +1,9 @@ +Dear <%= @name %>, + +At your request, <%= @app_name %> has marked your account for deletion. If it was not at your request, then you should be aware that someone has access to your account and requested this change. + +The following link is provided for you to restore your deleted account. If you click on this link within the next <%= @days %> days, your account will not be deleted. Otherwise, simply ignore this email and your account will be permanently deleted after that time. + +Click me! + +<%= @url %> \ No newline at end of file diff --git a/vendor/plugins/login_engine/app/views/user_notify/signup.rhtml b/vendor/plugins/login_engine/app/views/user_notify/signup.rhtml new file mode 100644 index 0000000..ae2626c --- /dev/null +++ b/vendor/plugins/login_engine/app/views/user_notify/signup.rhtml @@ -0,0 +1,12 @@ +Welcome to <%= @app_name %>, <%= @name %>. + +Your login credentials are: + + login: <%= @login %> + password: <%= @password %> + +Please click on the following link to confirm your registration: + +Click me! + +<%= @url %> diff --git a/vendor/plugins/login_engine/db/migrate/001_initial_schema.rb b/vendor/plugins/login_engine/db/migrate/001_initial_schema.rb new file mode 100644 index 0000000..6af212c --- /dev/null +++ b/vendor/plugins/login_engine/db/migrate/001_initial_schema.rb @@ -0,0 +1,25 @@ +class InitialSchema < ActiveRecord::Migration + def self.up + create_table LoginEngine.config(:user_table), :force => true do |t| + t.column "login", :string, :limit => 80, :default => "", :null => false + t.column "salted_password", :string, :limit => 40, :default => "", :null => false + t.column "email", :string, :limit => 60, :default => "", :null => false + t.column "firstname", :string, :limit => 40 + t.column "lastname", :string, :limit => 40 + t.column "salt", :string, :limit => 40, :default => "", :null => false + t.column "verified", :integer, :default => 0 + t.column "role", :string, :limit => 40 + t.column "security_token", :string, :limit => 40 + t.column "token_expiry", :datetime + t.column "created_at", :datetime + t.column "updated_at", :datetime + t.column "logged_in_at", :datetime + t.column "deleted", :integer, :default => 0 + t.column "delete_after", :datetime + end + end + + def self.down + drop_table LoginEngine.config(:user_table) + end +end diff --git a/vendor/plugins/login_engine/init_engine.rb b/vendor/plugins/login_engine/init_engine.rb new file mode 100644 index 0000000..a959da0 --- /dev/null +++ b/vendor/plugins/login_engine/init_engine.rb @@ -0,0 +1,11 @@ +# load up all the required files we need... + +require 'login_engine' + +module LoginEngine::Version + Major = 1 + Minor = 0 + Release = 2 +end + +Engines.current.version = LoginEngine::Version \ No newline at end of file diff --git a/vendor/plugins/login_engine/install.rb b/vendor/plugins/login_engine/install.rb new file mode 100644 index 0000000..523280c --- /dev/null +++ b/vendor/plugins/login_engine/install.rb @@ -0,0 +1,4 @@ +# Install the engines plugin if it has been already +unless File.exist?(File.dirname(__FILE__) + "/../engines") + Commands::Plugin.parse!(['install', 'http://svn.rails-engines.org/plugins/engines']) +end \ No newline at end of file diff --git a/vendor/plugins/login_engine/lib/login_engine.rb b/vendor/plugins/login_engine/lib/login_engine.rb new file mode 100644 index 0000000..6164603 --- /dev/null +++ b/vendor/plugins/login_engine/lib/login_engine.rb @@ -0,0 +1,62 @@ +require 'login_engine/authenticated_user' +require 'login_engine/authenticated_system' + +module LoginEngine + include AuthenticatedSystem # re-include the helper module + + #-- + # Define the configuration values. config sets the value of the + # constant ONLY if it has not already been set, i.e. by the user in + # environment.rb + #++ + + # Source address for user emails + config :email_from, 'webmaster@your.company' + + # Destination email for system errors + config :admin_email, 'webmaster@your.company' + + # Sent in emails to users + config :app_url, 'http://localhost:3000/' + + # Sent in emails to users + config :app_name, 'TestApp' + + # Email charset + config :mail_charset, 'utf-8' + + # Security token lifetime in hours + config :security_token_life_hours, 24 + + # Two column form input + config :two_column_input, true + + # Add all changeable user fields to this array. + # They will then be able to be edited from the edit action. You + # should NOT include the email field in this array. + config :changeable_fields, [ 'firstname', 'lastname' ] + + # Set to true to allow delayed deletes (i.e., delete of record + # doesn't happen immediately after user selects delete account, + # but rather after some expiration of time to allow this action + # to be reverted). + config :delayed_delete, false + + # Default is one week + config :delayed_delete_days, 7 + + # the table to store user information in + if ActiveRecord::Base.pluralize_table_names + config :user_table, "users" + else + config :user_table, "user" + end + + # controls whether or not email is used + config :use_email_notification, true + + # Controls whether accounts must be confirmed after signing up + # ONLY if this and use_email_notification are both true + config :confirm_account, true + +end diff --git a/vendor/plugins/login_engine/lib/login_engine/authenticated_system.rb b/vendor/plugins/login_engine/lib/login_engine/authenticated_system.rb new file mode 100644 index 0000000..cbe3e78 --- /dev/null +++ b/vendor/plugins/login_engine/lib/login_engine/authenticated_system.rb @@ -0,0 +1,113 @@ +module LoginEngine + module AuthenticatedSystem + + protected + + # overwrite this if you want to restrict access to only a few actions + # or if you want to check if the user has the correct rights + # example: + # + # # only allow nonbobs + # def authorize?(user) + # user.login != "bob" + # end + def authorize?(user) + true + end + + # overwrite this method if you only want to protect certain actions of the controller + # example: + # + # # don't protect the login and the about method + # def protect?(action) + # if ['action', 'about'].include?(action) + # return false + # else + # return true + # end + # end + def protect?(action) + true + end + + # login_required filter. add + # + # before_filter :login_required + # + # if the controller should be under any rights management. + # for finer access control you can overwrite + # + # def authorize?(user) + # + def login_required + if not protect?(action_name) + return true + end + + if user? and authorize?(session[:user]) + return true + end + + # store current location so that we can + # come back after the user logged in + store_location + + # call overwriteable reaction to unauthorized access + access_denied + end + + # overwrite if you want to have special behavior in case the user is not authorized + # to access the current operation. + # the default action is to redirect to the login screen + # example use : + # a popup window might just close itself for instance + def access_denied + redirect_to :controller => "/user", :action => "login" + end + + # store current uri in the session. + # we can return to this location by calling return_location + def store_location + session['return-to'] = request.request_uri + end + + # move to the last store_location call or to the passed default one + def redirect_to_stored_or_default(default=nil) + if session['return-to'].nil? + redirect_to default + else + redirect_to_url session['return-to'] + session['return-to'] = nil + end + end + + def redirect_back_or_default(default=nil) + if request.env["HTTP_REFERER"].nil? + redirect_to default + else + redirect_to(request.env["HTTP_REFERER"]) # same as redirect_to :back + end + end + + def user? + # First, is the user already authenticated? + return true if not session[:user].nil? + + # If not, is the user being authenticated by a token? + id = params[:user_id] + key = params[:key] + if id and key + session[:user] = User.authenticate_by_token(id, key) + return true if not session[:user].nil? + end + + # Everything failed + return false + end + + # Returns the current user from the session, if any exists + def current_user + session[:user] + end + end +end diff --git a/vendor/plugins/login_engine/lib/login_engine/authenticated_user.rb b/vendor/plugins/login_engine/lib/login_engine/authenticated_user.rb new file mode 100644 index 0000000..e48be19 --- /dev/null +++ b/vendor/plugins/login_engine/lib/login_engine/authenticated_user.rb @@ -0,0 +1,155 @@ +require 'digest/sha1' + +# this model expects a certain database layout and its based on the name/login pattern. + +module LoginEngine + module AuthenticatedUser + + def self.included(base) + base.class_eval do + + # use the table name given + set_table_name LoginEngine.config(:user_table) + + attr_accessor :new_password + + validates_presence_of :login + validates_length_of :login, :within => 3..40 + validates_uniqueness_of :login + validates_uniqueness_of :email + validates_format_of :email, :with => /^[^@]+@.+$/ + + validates_presence_of :password, :if => :validate_password? + validates_confirmation_of :password, :if => :validate_password? + validates_length_of :password, { :minimum => 5, :if => :validate_password? } + validates_length_of :password, { :maximum => 40, :if => :validate_password? } + + protected + + attr_accessor :password, :password_confirmation + + after_save :falsify_new_password + after_validation :crypt_password + + end + base.extend(ClassMethods) + end + + module ClassMethods + + def authenticate(login, pass) + u = find(:first, :conditions => ["login = ? AND verified = 1 AND deleted = 0", login]) + return nil if u.nil? + find(:first, :conditions => ["login = ? AND salted_password = ? AND verified = 1", login, AuthenticatedUser.salted_password(u.salt, AuthenticatedUser.hashed(pass))]) + end + + def authenticate_by_token(id, token) + # Allow logins for deleted accounts, but only via this method (and + # not the regular authenticate call) + u = find(:first, :conditions => ["#{User.primary_key} = ? AND security_token = ?", id, token]) + return nil if u.nil? or u.token_expired? + return nil if false == u.update_expiry + u + end + + end + + + protected + + def self.hashed(str) + # check if a salt has been set... + if LoginEngine.config(:salt) == nil + raise "You must define a :salt value in the configuration for the LoginEngine module." + end + + return Digest::SHA1.hexdigest("#{LoginEngine.config(:salt)}--#{str}--}")[0..39] + end + + def self.salted_password(salt, hashed_password) + hashed(salt + hashed_password) + end + + public + + # hmmm, how does this interact with the developer's own User model initialize? + # We would have to *insist* that the User.initialize method called 'super' + # + def initialize(attributes = nil) + super + @new_password = false + end + + def token_expired? + self.security_token and self.token_expiry and (Time.now > self.token_expiry) + end + + def update_expiry + write_attribute('token_expiry', [self.token_expiry, Time.at(Time.now.to_i + 600 * 1000)].min) + write_attribute('authenticated_by_token', true) + write_attribute("verified", 1) + update_without_callbacks + end + + def generate_security_token(hours = nil) + if not hours.nil? or self.security_token.nil? or self.token_expiry.nil? or + (Time.now.to_i + token_lifetime / 2) >= self.token_expiry.to_i + return new_security_token(hours) + else + return self.security_token + end + end + + def set_delete_after + hours = LoginEngine.config(:delayed_delete_days) * 24 + write_attribute('deleted', 1) + write_attribute('delete_after', Time.at(Time.now.to_i + hours * 60 * 60)) + + # Generate and return a token here, so that it expires at + # the same time that the account deletion takes effect. + return generate_security_token(hours) + end + + def change_password(pass, confirm = nil) + self.password = pass + self.password_confirmation = confirm.nil? ? pass : confirm + @new_password = true + end + + protected + + def validate_password? + @new_password + end + + + def crypt_password + if @new_password + write_attribute("salt", AuthenticatedUser.hashed("salt-#{Time.now}")) + write_attribute("salted_password", AuthenticatedUser.salted_password(salt, AuthenticatedUser.hashed(@password))) + end + end + + def falsify_new_password + @new_password = false + true + end + + def new_security_token(hours = nil) + write_attribute('security_token', AuthenticatedUser.hashed(self.salted_password + Time.now.to_i.to_s + rand.to_s)) + write_attribute('token_expiry', Time.at(Time.now.to_i + token_lifetime(hours))) + update_without_callbacks + return self.security_token + end + + def token_lifetime(hours = nil) + if hours.nil? + LoginEngine.config(:security_token_life_hours) * 60 * 60 + else + hours * 60 * 60 + end + end + + end +end + diff --git a/vendor/plugins/login_engine/public/stylesheets/login_engine.css b/vendor/plugins/login_engine/public/stylesheets/login_engine.css new file mode 100644 index 0000000..e62f823 --- /dev/null +++ b/vendor/plugins/login_engine/public/stylesheets/login_engine.css @@ -0,0 +1,81 @@ +/* + + This CSS file is basically the scaffold.css file, and is only + included here to demonstrate using CSS files with Engines. + +*/ + +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#ErrorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#ErrorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#ErrorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#ErrorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} + diff --git a/vendor/plugins/login_engine/test/fixtures/users.yml b/vendor/plugins/login_engine/test/fixtures/users.yml new file mode 100644 index 0000000..51fea63 --- /dev/null +++ b/vendor/plugins/login_engine/test/fixtures/users.yml @@ -0,0 +1,41 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +bob: + id: 1000001 + login: bob + salted_password: b1de1d1d2aec05df2be6f02995537c1783f08490 # atest + salt: bf3c47e71c0bfeb6288c9b6b5e24e15256a0e407 + email: bob@test.com + verified: 1 + +existingbob: + id: 1000002 + login: existingbob + salted_password: b1de1d1d2aec05df2be6f02995537c1783f08490 # atest + salt: bf3c47e71c0bfeb6288c9b6b5e24e15256a0e407 + email: existingbob@test.com + verified: 1 + +longbob: + id: 1000003 + login: longbob + salted_password: 53427dca242488e885216a579e362ee888c3ebc1 # alongtest + salt: d35a9cc89af83799d9a938a74cb06a11d295aa9c + email: longbob@test.com + verified: 1 + +deletebob1: + id: 1000004 + login: deletebob1 + salted_password: 53427dca242488e885216a579e362ee888c3ebc1 # alongtest + salt: d35a9cc89af83799d9a938a74cb06a11d295aa9c + email: deletebob1@test.com + verified: 1 + +deletebob2: + id: 1000005 + login: deletebob2 + salted_password: 53427dca242488e885216a579e362ee888c3ebc1 # alongtest + salt: d35a9cc89af83799d9a938a74cb06a11d295aa9c + email: deletebob2@test.com + verified: 1 \ No newline at end of file diff --git a/vendor/plugins/login_engine/test/functional/user_controller_test.rb b/vendor/plugins/login_engine/test/functional/user_controller_test.rb new file mode 100644 index 0000000..92c7c2c --- /dev/null +++ b/vendor/plugins/login_engine/test/functional/user_controller_test.rb @@ -0,0 +1,536 @@ +require File.dirname(__FILE__) + '/../test_helper' +require_dependency 'user_controller' + + +# Raise errors beyond the default web-based presentation +class UserController; def rescue_action(e) raise e end; end + +class UserControllerTest < Test::Unit::TestCase + + # load the fixture into the developer-specified table using the custom + # 'fixture' method. + fixture :users, :table_name => LoginEngine.config(:user_table), :class_name => "User" + + def setup + + LoginEngine::CONFIG[:salt] = "test-salt" + + @controller = UserController.new + @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new + @request.host = "localhost" + end + + + + #========================================================================== + # + # Login/Logout + # + #========================================================================== + + def test_home_without_login + get :home + assert_redirected_to :action => "login" + end + + def test_invalid_login + post :login, :user => { :login => "bob", :password => "wrong_password" } + assert_response :success + + assert_session_has_no :user + assert_template "login" + end + + def test_login + @request.session['return-to'] = "/bogus/location" + + post :login, :user => { :login => "bob", :password => "atest" } + + assert_response 302 # redirect + assert_session_has :user + assert_equal users(:bob), session[:user] + + assert_redirect_url "http://#{@request.host}/bogus/location" + end + + def test_login_logoff + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + get :logout + assert_session_has_no :user + + end + + + #========================================================================== + # + # Signup + # + #========================================================================== + + def test_signup + LoginEngine::CONFIG[:use_email_notification] = true + + ActionMailer::Base.deliveries = [] + + @request.session['return-to'] = "/bogus/location" + + assert_equal 5, User.count + post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "newpassword", :email => "newbob@test.com" } + assert_session_has_no :user + + assert_redirect_url(@controller.url_for(:action => "login")) + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries[0] + assert_equal "newbob@test.com", mail.to_addrs[0].to_s + assert_match /login:\s+\w+\n/, mail.encoded + assert_match /password:\s+\w+\n/, mail.encoded + #mail.encoded =~ /user_id=(.*?)&key=(.*?)"/ + user_id = /user_id=(\d+)/.match(mail.encoded)[1] + key = /key=([a-z0-9]+)/.match(mail.encoded)[1] + + assert_not_nil user_id + assert_not_nil key + + user = User.find_by_email("newbob@test.com") + assert_not_nil user + assert_equal 0, user.verified + + # First past the expiration. + Time.advance_by_days = 1 + get :home, :user_id => "#{user_id}", :key => "#{key}" + Time.advance_by_days = 0 + user = User.find_by_email("newbob@test.com") + assert_equal 0, user.verified + + # Then a bogus key. + get :home, :user_id => "#{user_id}", :key => "boguskey" + user = User.find_by_email("newbob@test.com") + assert_equal 0, user.verified + + # Now the real one. + get :home, :user_id => "#{user_id}", :key => "#{key}" + user = User.find_by_email("newbob@test.com") + assert_equal 1, user.verified + + post :login, :user => { :login => "newbob", :password => "newpassword" } + assert_session_has :user + get :logout + + end + + def test_signup_bad_password + LoginEngine::CONFIG[:use_email_notification] = true + ActionMailer::Base.deliveries = [] + + @request.session['return-to'] = "/bogus/location" + post :signup, :user => { :login => "newbob", :password => "bad", :password_confirmation => "bad", :email => "newbob@test.com" } + assert_session_has_no :user + assert_invalid_column_on_record "user", "password" + assert_success + assert_equal 0, ActionMailer::Base.deliveries.size + end + + def test_signup_bad_email + LoginEngine::CONFIG[:use_email_notification] = true + ActionMailer::Base.deliveries = [] + + @request.session['return-to'] = "/bogus/location" + + ActionMailer::Base.inject_one_error = true + post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "newpassword", :email => "newbob@test.com" } + assert_session_has_no :user + assert_equal 0, ActionMailer::Base.deliveries.size + end + + def test_signup_without_email + LoginEngine::CONFIG[:use_email_notification] = false + + @request.session['return-to'] = "/bogus/location" + + post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "newpassword", :email => "newbob@test.com" } + + assert_redirect_url(@controller.url_for(:action => "login")) + assert_session_has_no :user + assert_match /Signup successful/, flash[:notice] + + assert_not_nil User.find_by_login("newbob") + + user = User.find_by_email("newbob@test.com") + assert_not_nil user + + post :login, :user => { :login => "newbob", :password => "newpassword" } + assert_session_has :user + get :logout + end + + def test_signup_bad_details + @request.session['return-to'] = "/bogus/location" + + # mismatched password + post :signup, :user => { :login => "newbob", :password => "newpassword", :password_confirmation => "wrong" } + assert_invalid_column_on_record "user", "password" + assert_success + + # login not long enough + post :signup, :user => { :login => "yo", :password => "newpassword", :password_confirmation => "newpassword" } + assert_invalid_column_on_record "user", "login" + assert_success + + # both + post :signup, :user => { :login => "yo", :password => "newpassword", :password_confirmation => "wrong" } + assert_invalid_column_on_record "user", ["login", "password"] + assert_success + + # existing user + post :signup, :user => { :login => "bob", :password => "doesnt_matter", :password_confirmation => "doesnt_matter" } + assert_invalid_column_on_record "user", "login" + assert_success + + # existing email + post :signup, :user => { :login => "newbob", :email => "longbob@test.com", :password => "doesnt_matter", :password_confirmation => "doesnt_matter" } + assert_invalid_column_on_record "user", "email" + assert_success + + end + + + #========================================================================== + # + # Edit + # + #========================================================================== + + def test_edit + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + post :edit, :user => { "firstname" => "Bob", "form" => "edit" } + assert_equal @response.session[:user].firstname, "Bob" + + post :edit, :user => { "firstname" => "", "form" => "edit" } + assert_equal @response.session[:user].firstname, "" + + get :logout + end + + + + #========================================================================== + # + # Delete + # + #========================================================================== + + def test_delete + LoginEngine::CONFIG[:use_email_notification] = true + # Immediate delete + post :login, :user => { :login => "deletebob1", :password => "alongtest" } + assert_session_has :user + + LoginEngine.config :delayed_delete, false, :force + post :delete + assert_equal 1, ActionMailer::Base.deliveries.size + assert_session_has_no :user + + # try and login in again, we should fail. + post :login, :user => { :login => "deletebob1", :password => "alongtest" } + assert_session_has_no :user + assert_template_has "login" + + + # Now try delayed delete + ActionMailer::Base.deliveries = [] + + post :login, :user => { :login => "deletebob2", :password => "alongtest" } + assert_session_has :user + + LoginEngine.config :delayed_delete, true, :force + post :delete + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries[0] + user_id = /user_id=(\d+)/.match(mail.encoded)[1] + key = /key=([a-z0-9]+)/.match(mail.encoded)[1] + + post :restore_deleted, :user_id => "#{user_id}", "key" => "badkey" + assert_session_has_no :user + + # Advance the time past the delete date + Time.advance_by_days = LoginEngine.config :delayed_delete_days + post :restore_deleted, :user_id => "#{user_id}", "key" => "#{key}" + assert_session_has_no :user + Time.advance_by_days = 0 + + post :restore_deleted, :user_id => "#{user_id}", "key" => "#{key}" + assert_session_has :user + end + + def test_delete_without_email + LoginEngine::CONFIG[:use_email_notification] = false + ActionMailer::Base.deliveries = [] + + # Immediate delete + post :login, :user => { :login => "deletebob1", :password => "alongtest" } + assert_session_has :user + + LoginEngine.config :delayed_delete, false, :force + post :delete + assert_session_has_no :user + assert_nil User.find_by_login("deletebob1") + + # try and login in again, we should fail. + post :login, :user => { :login => "deletebob1", :password => "alongtest" } + assert_session_has_no :user + assert_template_has "login" + + + # Now try delayed delete + ActionMailer::Base.deliveries = [] + + post :login, :user => { :login => "deletebob2", :password => "alongtest" } + assert_session_has :user + + # delayed delete is not really relevant currently without email. + LoginEngine.config :delayed_delete, true, :force + post :delete + assert_equal 1, User.find_by_login("deletebob2").deleted + end + + + + #========================================================================== + # + # Change Password + # + #========================================================================== + + def test_change_valid_password + + LoginEngine::CONFIG[:use_email_notification] = true + + ActionMailer::Base.deliveries = [] + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + post :change_password, :user => { :password => "changed_password", :password_confirmation => "changed_password" } + + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries[0] + assert_equal "bob@test.com", mail.to_addrs[0].to_s + assert_match /login:\s+\w+\n/, mail.encoded + assert_match /password:\s+\w+\n/, mail.encoded + + post :login, :user => { :login => "bob", :password => "changed_password" } + assert_session_has :user + post :change_password, :user => { :password => "atest", :password_confirmation => "atest" } + get :logout + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + get :logout + end + + def test_change_valid_password_without_email + + LoginEngine::CONFIG[:use_email_notification] = false + + ActionMailer::Base.deliveries = [] + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + post :change_password, :user => { :password => "changed_password", :password_confirmation => "changed_password" } + + assert_redirected_to :action => "change_password" + + post :login, :user => { :login => "bob", :password => "changed_password" } + assert_session_has :user + post :change_password, :user => { :password => "atest", :password_confirmation => "atest" } + get :logout + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + get :logout + end + + def test_change_short_password + LoginEngine::CONFIG[:use_email_notification] = true + ActionMailer::Base.deliveries = [] + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + post :change_password, :user => { :password => "bad", :password_confirmation => "bad" } + assert_invalid_column_on_record "user", "password" + assert_success + assert_equal 0, ActionMailer::Base.deliveries.size + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + get :logout + end + + def test_change_short_password_without_email + LoginEngine::CONFIG[:use_email_notification] = false + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + post :change_password, :user => { :password => "bad", :password_confirmation => "bad" } + assert_invalid_column_on_record "user", "password" + assert_success + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + get :logout + end + + + def test_change_password_with_bad_email + LoginEngine::CONFIG[:use_email_notification] = true + ActionMailer::Base.deliveries = [] + + # log in + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + # change the password, but the email delivery will fail + ActionMailer::Base.inject_one_error = true + post :change_password, :user => { :password => "changed_password", :password_confirmation => "changed_password" } + assert_equal 0, ActionMailer::Base.deliveries.size + assert_match /Password could not be changed/, flash[:warning] + + # logout + get :logout + assert_session_has_no :user + + # ensure we can log in with our original password + # TODO: WHY DOES THIS FAIL!! It looks like the transaction stuff in UserController#change_password isn't actually rolling back changes. + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + get :logout + end + + + + + #========================================================================== + # + # Forgot Password + # + #========================================================================== + + def test_forgot_password + LoginEngine::CONFIG[:use_email_notification] = true + + do_forgot_password(false, false, false) + do_forgot_password(false, false, true) + do_forgot_password(true, false, false) + do_forgot_password(false, true, false) + end + + def do_forgot_password(bad_address, bad_email, logged_in) + ActionMailer::Base.deliveries = [] + + if logged_in + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + end + + @request.session['return-to'] = "/bogus/location" + if not bad_address and not bad_email + post :forgot_password, :user => { :email => "bob@test.com" } + password = "anewpassword" + if logged_in + assert_equal 0, ActionMailer::Base.deliveries.size + assert_redirect_url(@controller.url_for(:action => "change_password")) + post :change_password, :user => { :password => "#{password}", :password_confirmation => "#{password}" } + else + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries[0] + assert_equal "bob@test.com", mail.to_addrs[0].to_s + user_id = /user_id=(\d+)/.match(mail.encoded)[1] + key = /key=([a-z0-9]+)/.match(mail.encoded)[1] + post :change_password, :user => { :password => "#{password}", :password_confirmation => "#{password}"}, :user_id => "#{user_id}", :key => "#{key}" + assert_session_has :user + get :logout + end + elsif bad_address + post :forgot_password, :user => { :email => "bademail@test.com" } + assert_equal 0, ActionMailer::Base.deliveries.size + elsif bad_email + ActionMailer::Base.inject_one_error = true + post :forgot_password, :user => { :email => "bob@test.com" } + assert_equal 0, ActionMailer::Base.deliveries.size + else + # Invalid test case + assert false + end + + if not bad_address and not bad_email + if logged_in + get :logout + else + assert_redirect_url(@controller.url_for(:action => "login")) + end + post :login, :user => { :login => "bob", :password => "#{password}" } + else + # Okay, make sure the database did not get changed + if logged_in + get :logout + end + post :login, :user => { :login => "bob", :password => "atest" } + end + + assert_session_has :user + + # Put the old settings back + if not bad_address and not bad_email + post :change_password, :user => { :password => "atest", :password_confirmation => "atest" } + end + + get :logout + end + + def test_forgot_password_without_email_and_logged_in + LoginEngine::CONFIG[:use_email_notification] = false + + post :login, :user => { :login => "bob", :password => "atest" } + assert_session_has :user + + @request.session['return-to'] = "/bogus/location" + post :forgot_password, :user => { :email => "bob@test.com" } + password = "anewpassword" + assert_redirect_url(@controller.url_for(:action => "change_password")) + post :change_password, :user => { :password => "#{password}", :password_confirmation => "#{password}" } + + get :logout + + post :login, :user => { :login => "bob", :password => "#{password}" } + + assert_session_has :user + + get :logout + end + + def forgot_password_without_email_and_not_logged_in + LoginEngine::CONFIG[:use_email_notification] = false + + @request.session['return-to'] = "/bogus/location" + post :forgot_password, :user => { :email => "bob@test.com" } + password = "anewpassword" + + # wothout email, you can't retrieve your forgotten password... + assert_match /Please contact the system admin/, flash[:message] + assert_session_has_no :user + + assert_redirect_url "http://#{@request.host}/bogus/location" + end +end diff --git a/vendor/plugins/login_engine/test/mocks/mail.rb b/vendor/plugins/login_engine/test/mocks/mail.rb new file mode 100644 index 0000000..2d620b1 --- /dev/null +++ b/vendor/plugins/login_engine/test/mocks/mail.rb @@ -0,0 +1,14 @@ +ActionMailer::Base.class_eval { + @@inject_one_error = false + cattr_accessor :inject_one_error + + private + def perform_delivery_test(mail) + if inject_one_error + ActionMailer::Base::inject_one_error = false + raise "Failed to send email" if raise_delivery_errors + else + deliveries << mail + end + end +} diff --git a/vendor/plugins/login_engine/test/mocks/time.rb b/vendor/plugins/login_engine/test/mocks/time.rb new file mode 100644 index 0000000..e253ada --- /dev/null +++ b/vendor/plugins/login_engine/test/mocks/time.rb @@ -0,0 +1,19 @@ +require 'time' + +Time.class_eval { + if !respond_to? :now_old # somehow this is getting defined many times. + @@advance_by_days = 0 + cattr_accessor :advance_by_days + + class << Time + alias now_old now + def now + if Time.advance_by_days != 0 + return Time.at(now_old.to_i + Time.advance_by_days * 60 * 60 * 24 + 1) + else + now_old + end + end + end + end +} diff --git a/vendor/plugins/login_engine/test/test_helper.rb b/vendor/plugins/login_engine/test/test_helper.rb new file mode 100644 index 0000000..eba8aa1 --- /dev/null +++ b/vendor/plugins/login_engine/test/test_helper.rb @@ -0,0 +1,11 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper') # the default rails helper + +# ensure that the Engines testing enhancements are loaded. +require File.join(Engines.config(:root), "engines", "lib", "engines", "testing_extensions") + +require File.dirname(__FILE__) + '/mocks/time' +require File.dirname(__FILE__) + '/mocks/mail' + +# set up the fixtures location +Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" +$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path) diff --git a/vendor/plugins/login_engine/test/unit/user_test.rb b/vendor/plugins/login_engine/test/unit/user_test.rb new file mode 100644 index 0000000..12fff2d --- /dev/null +++ b/vendor/plugins/login_engine/test/unit/user_test.rb @@ -0,0 +1,114 @@ +require File.dirname(__FILE__) + '/../test_helper' +class UserTest < Test::Unit::TestCase + + # load the fixture into the developer-specified table using the custom + # 'fixture' method. + fixture :users, :table_name => LoginEngine.config(:user_table), :class_name => "User" + + def setup + LoginEngine::CONFIG[:salt] = "test-salt" + end + + def test_auth + assert_equal users(:bob), User.authenticate("bob", "atest") + assert_nil User.authenticate("nonbob", "atest") + end + + + def test_passwordchange + + users(:longbob).change_password("nonbobpasswd") + users(:longbob).save + assert_equal users(:longbob), User.authenticate("longbob", "nonbobpasswd") + assert_nil User.authenticate("longbob", "alongtest") + users(:longbob).change_password("alongtest") + users(:longbob).save + assert_equal users(:longbob), User.authenticate("longbob", "alongtest") + assert_nil User.authenticate("longbob", "nonbobpasswd") + + end + + def test_disallowed_passwords + + u = User.new + u.login = "nonbob" + u.email = "bobs@email.com" + + u.change_password("tiny") + assert !u.save + assert u.errors.invalid?('password') + + u.change_password("hugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehugehuge") + assert !u.save + assert u.errors.invalid?('password') + + u.change_password("") + assert !u.save + assert u.errors.invalid?('password') + + u.change_password("bobs_secure_password") + assert u.save + assert u.errors.empty? + + end + + def test_bad_logins + + u = User.new + u.change_password("bobs_secure_password") + u.email = "bobs@email.com" + + u.login = "x" + assert !u.save + assert u.errors.invalid?('login') + + u.login = "hugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhugebobhug" + assert !u.save + assert u.errors.invalid?('login') + + u.login = "" + assert !u.save + assert u.errors.invalid?('login') + + u.login = "okbob" + assert u.save + assert u.errors.empty? + + end + + + def test_collision + u = User.new + u.login = "existingbob" + u.change_password("bobs_secure_password") + assert !u.save + end + + + def test_create + u = User.new + u.login = "nonexistingbob" + u.change_password("bobs_secure_password") + u.email = "bobs@email.com" + + assert u.save + + end + + def test_email_should_be_nominally_valid + u = User.new + u.login = "email_test" + u.change_password("email_test_password") + + assert !u.save + assert u.errors.invalid?('email') + + u.email = "invalid_email" + assert !u.save + assert u.errors.invalid?('email') + + u.email = "valid@email.com" + assert u.save + end + +end diff --git a/vendor/plugins/simply_helpful/README b/vendor/plugins/simply_helpful/README new file mode 100644 index 0000000..4711fe2 --- /dev/null +++ b/vendor/plugins/simply_helpful/README @@ -0,0 +1,4 @@ +SimplyHelpful +============= + +Description goes here \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/Rakefile b/vendor/plugins/simply_helpful/Rakefile new file mode 100644 index 0000000..efce24d --- /dev/null +++ b/vendor/plugins/simply_helpful/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the simply_helpful plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the simply_helpful plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'SimplyHelpful' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/simply_helpful/init.rb b/vendor/plugins/simply_helpful/init.rb new file mode 100644 index 0000000..85816b2 --- /dev/null +++ b/vendor/plugins/simply_helpful/init.rb @@ -0,0 +1,2 @@ +require 'simply_helpful' +ActionController::Base.helper(SimplyHelpful::RecordIdentificationHelper, SimplyHelpful::RecordTagHelper) \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/lib/simply_helpful.rb b/vendor/plugins/simply_helpful/lib/simply_helpful.rb new file mode 100644 index 0000000..16c90ce --- /dev/null +++ b/vendor/plugins/simply_helpful/lib/simply_helpful.rb @@ -0,0 +1,7 @@ +require 'simply_helpful/record_identification_helper' +require 'simply_helpful/record_identifier' +require 'simply_helpful/record_tag_helper' + +require 'simply_helpful/jsg_extensions' +require 'simply_helpful/av_extensions' +require 'simply_helpful/form_helper_extensions' \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/lib/simply_helpful/av_extensions.rb b/vendor/plugins/simply_helpful/lib/simply_helpful/av_extensions.rb new file mode 100644 index 0000000..d4cd4f9 --- /dev/null +++ b/vendor/plugins/simply_helpful/lib/simply_helpful/av_extensions.rb @@ -0,0 +1,26 @@ +module ActionView + module Partials + def render_partial_with_record_identification(partial_path, local_assigns = nil, deprecated_local_assigns = nil) + if partial_path.is_a?(String) || partial_path.is_a?(Symbol) || partial_path.nil? + render_partial_without_record_identification( + partial_path, local_assigns, deprecated_local_assigns + ) + elsif partial_path.is_a?(Array) + if partial_path.any? + path = SimplyHelpful::RecordIdentifier.partial_path(partial_path.first) + collection = partial_path + render_partial_collection( + path, collection, nil, local_assigns.value + ) + else + "" + end + else + render_partial_without_record_identification( + SimplyHelpful::RecordIdentifier.partial_path(partial_path), local_assigns, deprecated_local_assigns + ) + end + end + alias_method_chain :render_partial, :record_identification + end +end diff --git a/vendor/plugins/simply_helpful/lib/simply_helpful/form_helper_extensions.rb b/vendor/plugins/simply_helpful/lib/simply_helpful/form_helper_extensions.rb new file mode 100644 index 0000000..15ade39 --- /dev/null +++ b/vendor/plugins/simply_helpful/lib/simply_helpful/form_helper_extensions.rb @@ -0,0 +1,48 @@ +module ActionView + module Helpers + module FormHelper + def form_for_with_record_identification(name_or_object, *args, &proc) + form_method_with_record_identification :form_for, name_or_object, *args, &proc + end + + alias_method_chain :form_for, :record_identification + + protected + def form_method_with_record_identification(method_name, name_or_object, *args, &proc) + old_method_name = "#{method_name}_without_record_identification" + case name_or_object + when String, Symbol, NilClass + send(old_method_name, name_or_object, *args, &proc) + else + options = args.first || {} + + object_name = SimplyHelpful::RecordIdentifier.singular_class_name(name_or_object) + object = name_or_object + url = SimplyHelpful::RecordIdentifier.named_route(object, self) + + html_options = if object.new_record? + { :class => dom_class(object, :new), :id => dom_id(object), :method => :post } + else + { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put } + end + + send(old_method_name, + object_name, object, options.merge({ :url => url, :html => html_options.update(options[:html] || {}) }), &proc + ) + end + end + end + end +end + +module ActionView + module Helpers + module PrototypeHelper + def remote_form_for_with_record_identification(name_or_object, *args, &proc) + form_method_with_record_identification :remote_form_for, name_or_object, *args, &proc + end + + alias_method_chain :remote_form_for, :record_identification + end + end +end diff --git a/vendor/plugins/simply_helpful/lib/simply_helpful/jsg_extensions.rb b/vendor/plugins/simply_helpful/lib/simply_helpful/jsg_extensions.rb new file mode 100644 index 0000000..6c9842a --- /dev/null +++ b/vendor/plugins/simply_helpful/lib/simply_helpful/jsg_extensions.rb @@ -0,0 +1,18 @@ +module ActionView + module Helpers + module PrototypeHelper + class JavaScriptGenerator + module GeneratorMethods + def [](id) + case id + when String, Symbol, NilClass + JavaScriptElementProxy.new(self, id) + else + JavaScriptElementProxy.new(self, SimplyHelpful::RecordIdentifier.dom_id(id)) + end + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/lib/simply_helpful/record_identification_helper.rb b/vendor/plugins/simply_helpful/lib/simply_helpful/record_identification_helper.rb new file mode 100644 index 0000000..5b53fc6 --- /dev/null +++ b/vendor/plugins/simply_helpful/lib/simply_helpful/record_identification_helper.rb @@ -0,0 +1,15 @@ +module SimplyHelpful + module RecordIdentificationHelper + def partial_path(*args, &block) + RecordIdentifier.partial_path(*args, &block) + end + + def dom_class(*args, &block) + RecordIdentifier.dom_class(*args, &block) + end + + def dom_id(*args, &block) + RecordIdentifier.dom_id(*args, &block) + end + end +end \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/lib/simply_helpful/record_identifier.rb b/vendor/plugins/simply_helpful/lib/simply_helpful/record_identifier.rb new file mode 100644 index 0000000..861ee4f --- /dev/null +++ b/vendor/plugins/simply_helpful/lib/simply_helpful/record_identifier.rb @@ -0,0 +1,38 @@ +module SimplyHelpful + module RecordIdentifier + extend self + + def named_route(record, url_writer) + record.new_record? ? + url_writer.send(plural_class_name(record) + "_url") : + url_writer.send(singular_class_name(record) + "_url", record) + end + + def partial_path(record_or_class) + klass = class_from_record_or_class(record_or_class) + "#{klass.name.tableize}/#{klass.name.demodulize.underscore}" + end + + def dom_class(record_or_class, prefix = nil) + [ prefix, singular_class_name(record_or_class) ].compact * '_' + end + + def dom_id(record, prefix = nil) + prefix ||= 'new' unless record.id + [ prefix, singular_class_name(record), record.id ].compact * '_' + end + + def plural_class_name(record_or_class) + singular_class_name(record_or_class).pluralize + end + + def singular_class_name(record_or_class) + class_from_record_or_class(record_or_class).name.underscore.tr('/', '_') + end + + private + def class_from_record_or_class(record_or_class) + record_or_class.is_a?(Class) ? record_or_class : record_or_class.class + end + end +end diff --git a/vendor/plugins/simply_helpful/lib/simply_helpful/record_tag_helper.rb b/vendor/plugins/simply_helpful/lib/simply_helpful/record_tag_helper.rb new file mode 100644 index 0000000..60912e1 --- /dev/null +++ b/vendor/plugins/simply_helpful/lib/simply_helpful/record_tag_helper.rb @@ -0,0 +1,29 @@ +module SimplyHelpful + module RecordTagHelper + def div_for(record, *args, &block) + prefix = args.first.is_a?(Hash) ? nil : args.shift + options = args.first.is_a?(Hash) ? args.shift : {} + concat content_tag(:div, capture(&block), + options.merge({ :class => "#{dom_class(record)} #{options[:class]}".strip, :id => dom_id(record, prefix) })), + block.binding + end + end +end + +module ActionView + module Helpers + module UrlHelper + def link_to_with_record_identification(attr_name, record = {}, html_options = nil, *parameters_for_method_reference) + case record + when Hash, String, Symbol, NilClass + link_to_without_record_identification(attr_name, record, html_options, *parameters_for_method_reference) + else + url = SimplyHelpful::RecordIdentifier.named_route(record, self) + link_to_without_record_identification(record.send(attr_name), url, html_options, *parameters_for_method_reference) + end + end + + alias_method_chain :link_to, :record_identification + end + end +end \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/test/form_helper_extensions_test.rb b/vendor/plugins/simply_helpful/test/form_helper_extensions_test.rb new file mode 100644 index 0000000..01b3e0f --- /dev/null +++ b/vendor/plugins/simply_helpful/test/form_helper_extensions_test.rb @@ -0,0 +1,96 @@ +require File.dirname(__FILE__) + '/test_helper' + +class LabelledFormBuilder < ActionView::Helpers::FormBuilder + (field_helpers - %w(hidden_field)).each do |selector| + src = <<-END_SRC + def #{selector}(field, *args, &proc) + " " + super + "
    " + end + END_SRC + class_eval src, __FILE__, __LINE__ + end +end + +class FormHelperExtensionsTest < Test::Unit::TestCase + include ActionView::Helpers::FormHelper + include ActionView::Helpers::FormTagHelper + include ActionView::Helpers::PrototypeHelper + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + include SimplyHelpful::RecordIdentificationHelper + + def setup + @record = Post.new + @controller = Class.new do + attr_reader :url_for_options + def url_for(options, *parameters_for_method_reference) + @url_for_options = options + @url_for_options || "http://www.example.com" + end + end + @controller = @controller.new + end + + def test_form_for_with_record_identification_with_new_record + _erbout = '' + form_for(@record, {:html => { :id => 'create-post' }}) {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + def test_form_for_with_record_identification_with_custom_builder + _erbout = '' + form_for(@record, :builder => LabelledFormBuilder) do |f| + _erbout.concat(f.text_field(:name)) + end + + expected = "
    " + + "" + + "
    " + + "
    " + assert_dom_equal expected, _erbout + end + + def test_form_for_with_record_identification_without_html_options + _erbout = '' + form_for(@record) {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + + def test_form_for_with_record_identification_with_existing_record + @record.save + _erbout = '' + form_for(@record) {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + + def test_remote_form_for_with_record_identification_with_new_record + _erbout = '' + remote_form_for(@record, {:html => { :id => 'create-post' }}) {} + + expected = %(
    ) + assert_dom_equal expected, _erbout + end + + def test_remote_form_for_with_record_identification_without_html_options + _erbout = '' + remote_form_for(@record) {} + + expected = %(
    ) + assert_dom_equal expected, _erbout + end + + def test_remote_form_for_with_record_identification_with_existing_record + @record.save + _erbout = '' + remote_form_for(@record) {} + + expected = %(
    ) + assert_dom_equal expected, _erbout + end +end \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/test/record_identifier_test.rb b/vendor/plugins/simply_helpful/test/record_identifier_test.rb new file mode 100644 index 0000000..516bd55 --- /dev/null +++ b/vendor/plugins/simply_helpful/test/record_identifier_test.rb @@ -0,0 +1,80 @@ +require File.dirname(__FILE__) + '/test_helper' + +class RecordIdentifierTest < Test::Unit::TestCase + include SimplyHelpful + + def setup + @klass = Post + @record = @klass.new + @singular = 'post' + @plural = 'posts' + end + + def test_dom_id_with_new_record + assert_equal "new_#{@singular}", dom_id(@record) + end + + def test_dom_id_with_new_record_and_prefix + assert_equal "custom_prefix_#{@singular}", dom_id(@record, :custom_prefix) + end + + def test_dom_id_with_saved_record + @record.save + assert_equal "#{@singular}_1", dom_id(@record) + end + + def test_dom_id_with_prefix + @record.save + assert_equal "edit_#{@singular}_1", dom_id(@record, :edit) + end + + def test_partial_path + expected = "#{@plural}/#{@singular}" + assert_equal expected, partial_path(@record) + assert_equal expected, partial_path(Post) + end + + def test_dom_class + assert_equal @singular, dom_class(@record) + end + + def test_dom_class_with_prefix + assert_equal "custom_prefix_#{@singular}", dom_class(@record, :custom_prefix) + end + + def test_singular_class_name + assert_equal @singular, singular_class_name(@record) + end + + def test_singular_class_name_for_class + assert_equal @singular, singular_class_name(@klass) + end + + def test_plural_class_name + assert_equal @plural, plural_class_name(@record) + end + + def test_plural_class_name_for_class + assert_equal @plural, plural_class_name(@klass) + end + + private + def method_missing(method, *args) + RecordIdentifier.send(method, *args) + end +end + +class NestedRecordIdentifierTest < RecordIdentifierTest + def setup + @klass = Post::Nested + @record = @klass.new + @singular = 'post_nested' + @plural = 'post_nesteds' + end + + def test_partial_path + expected = "post/nesteds/nested" + assert_equal expected, partial_path(@record) + assert_equal expected, partial_path(Post::Nested) + end +end diff --git a/vendor/plugins/simply_helpful/test/record_tag_helper_test.rb b/vendor/plugins/simply_helpful/test/record_tag_helper_test.rb new file mode 100644 index 0000000..115aa8f --- /dev/null +++ b/vendor/plugins/simply_helpful/test/record_tag_helper_test.rb @@ -0,0 +1,77 @@ +require File.dirname(__FILE__) + '/test_helper' + +class RecordTagHelperTest < Test::Unit::TestCase + include ActionView::Helpers::UrlHelper + include ActionView::Helpers::TagHelper + include ActionView::Helpers::CaptureHelper + include ActionView::Helpers::TextHelper + include SimplyHelpful::RecordTagHelper + include SimplyHelpful::RecordIdentificationHelper + + def setup + @record = Post.new + end + + def test_div_for_with_new_record + _erbout = '' + div_for(@record) {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + + def test_div_for_with_existing_record + @record.save + _erbout = '' + div_for(@record) {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + + def test_div_for_merges_given_class_names + _erbout = '' + div_for(@record, :class => 'foo') {} + + expected = "
    " + assert_dom_equal expected, _erbout + + _erbout = '' + div_for(@record, :class => 'foo bar') {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + + def test_div_for_with_dom_id_prefix_on_new_record + _erbout = '' + div_for(@record, :foo, :class => 'foo') {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + + def test_div_for_with_dom_id_prefix_on_existing_record + @record.save + _erbout = '' + div_for(@record, :foo, :class => 'foo') {} + + expected = "
    " + assert_dom_equal expected, _erbout + end + + def test_link_to_with_new_record + actual = link_to :name, @record + + expected = "new post" + assert_dom_equal expected, actual + end + + def test_link_to_with_existing_record + @record.save + actual = link_to :name, @record + + expected = "post #1" + assert_dom_equal expected, actual + end +end \ No newline at end of file diff --git a/vendor/plugins/simply_helpful/test/simply_helpful_test.rb b/vendor/plugins/simply_helpful/test/simply_helpful_test.rb new file mode 100644 index 0000000..d202d3e --- /dev/null +++ b/vendor/plugins/simply_helpful/test/simply_helpful_test.rb @@ -0,0 +1,6 @@ +require File.dirname(__FILE__) + '/test_helper' + +class SimplyHelpfulTest < Test::Unit::TestCase + def default_test + end +end diff --git a/vendor/plugins/simply_helpful/test/test_helper.rb b/vendor/plugins/simply_helpful/test/test_helper.rb new file mode 100644 index 0000000..c088807 --- /dev/null +++ b/vendor/plugins/simply_helpful/test/test_helper.rb @@ -0,0 +1,25 @@ +RAILS_ENV = 'test' +require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb')) +require 'action_controller/test_process' +require 'breakpoint' + +class Post + attr_reader :id + def save; @id = 1 end + def new_record?; @id.nil? end + def name + @id.nil? ? 'new post' : "post ##{@id}" + end + class Nested < Post; end +end + +class Test::Unit::TestCase + protected + def posts_url + 'http://www.example.com/posts' + end + + def post_url(post) + "http://www.example.com/posts/#{post.id}" + end +end \ No newline at end of file diff --git a/vendor/rails/REVISION_4727 b/vendor/rails/REVISION_4727 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/actionmailer/CHANGELOG b/vendor/rails/actionmailer/CHANGELOG new file mode 100644 index 0000000..15d482b --- /dev/null +++ b/vendor/rails/actionmailer/CHANGELOG @@ -0,0 +1,247 @@ +*SVN* + +* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com] + +* Resolve action naming collision. #5520 [ssinghi@kreeti.com] + +* ActionMailer::Base documentation rewrite. Closes #4991 [Kevin Clark, Marcel Molina Jr.] + +* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.] + +* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.] + +* Correct spurious documentation example code which results in a SyntaxError. [Marcel Molina Jr.] + +*1.2.1* (April 6th, 2006) + +* Be part of Rails 1.1.1 + + +*1.2.0* (March 27th, 2006) + +* Nil charset caused subject line to be improperly quoted in implicitly multipart messages #2662 [ehalvorsen+rails@runbox.com] + +* Parse content-type apart before using it so that sub-parts of the header can be set correctly #2918 [Jamis Buck] + +* Make custom headers work in subparts #4034 [elan@bluemandrill.com] + +* Template paths with dot chars in them no longer mess up implicit template selection for multipart messages #3332 [Chad Fowler] + +* Make sure anything with content-disposition of "attachment" is passed to the attachment presenter when parsing an email body [Jamis Buck] + +* Make sure TMail#attachments includes anything with content-disposition of "attachment", regardless of content-type [Jamis Buck] + + +*1.1.5* (December 13th, 2005) + +* Become part of Rails 1.0 + + +*1.1.4* (December 7th, 2005) + +* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.] + +* Stricter matching for implicitly multipart filenames excludes files ending in unsupported extensions (such as foo.rhtml.bak) and without a two-part content type (such as foo.text.rhtml or foo.text.really.plain.rhtml). #2398 [Dave Burt , Jeremy Kemper] + + +*1.1.3* (November 7th, 2005) + +* Allow Mailers to have custom initialize methods that set default instance variables for all mail actions #2563 [mrj@bigpond.net.au] + + +*1.1.2* (October 26th, 2005) + +* Upgraded to Action Pack 1.10.2 + + +*1.1.1* (October 19th, 2005) + +* Upgraded to Action Pack 1.10.1 + + +*1.1.0* (October 16th, 2005) + +* Update and extend documentation (rdoc) + +* Minero Aoki made TMail available to Rails/ActionMailer under the MIT license (instead of LGPL) [RubyConf '05] + +* Austin Ziegler made Text::Simple available to Rails/ActionMailer under a MIT-like licens [See rails ML, subject "Text::Format Licence Exception" on Oct 15, 2005] + +* Fix vendor require paths to prevent files being required twice + +* Don't add charset to content-type header for a part that contains subparts (for AOL compatibility) #2013 [John Long] + +* Preserve underscores when unquoting message bodies #1930 + +* Encode multibyte characters correctly #1894 + +* Multipart messages specify a MIME-Version header automatically #2003 [John Long] + +* Add a unified render method to ActionMailer (delegates to ActionView::Base#render) + +* Move mailer initialization to a separate (overridable) method, so that subclasses may alter the various defaults #1727 + +* Look at content-location header (if available) to determine filename of attachments #1670 + +* ActionMailer::Base.deliver(email) had been accidentally removed, but was documented in the Rails book #1849 + +* Fix problem with sendmail delivery where headers should be delimited by \n characters instead of \r\n, which confuses some mail readers #1742 [Kent Sibilev] + + +*1.0.1* (11 July, 2005) + +* Bind to Action Pack 1.9.1 + + +*1.0.0* (6 July, 2005) + +* Avoid adding nil header values #1392 + +* Better multipart support with implicit multipart/alternative and sorting of subparts [John Long] + +* Allow for nested parts in multipart mails #1570 [Flurin Egger] + +* Normalize line endings in outgoing mail bodies to "\n" #1536 [John Long] + +* Allow template to be explicitly specified #1448 [tuxie@dekadance.se] + +* Allow specific "multipart/xxx" content-type to be set on multipart messages #1412 [Flurin Egger] + +* Unquoted @ characters in headers are now accepted in spite of RFC 822 #1206 + +* Helper support (borrowed from ActionPack) + +* Silently ignore Errno::EINVAL errors when converting text. + +* Don't cause an error when parsing an encoded attachment name #1340 [lon@speedymac.com] + +* Nested multipart message parts are correctly processed in TMail::Mail#body + +* BCC headers are removed when sending via SMTP #1402 + +* Added 'content_type' accessor, to allow content type to be set on a per-message basis. content_type defaults to "text/plain". + +* Silently ignore Iconv::IllegalSequence errors when converting text #1341 [lon@speedymac.com] + +* Support attachments and multipart messages. + +* Added new accessors for the various mail properties. + +* Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 [Jamis Buck] + +* Fix attachments and content-type problems #1276 [Jamis Buck] + +* Fixed the TMail#body method to look at the content-transfer-encoding header and unquote the body according to the rules it specifies #1265 [Jamis Buck] + +* Added unquoting even if the iconv lib can't be loaded--in that case, only the charset conversion is skipped #1265 [Jamis Buck] + +* Added automatic decoding of base64 bodies #1214 [Jamis Buck] + +* Added that delivery errors are caught in a way so the mail is still returned whether the delivery was successful or not + +* Fixed that email address like "Jamis Buck, M.D." would cause the quoter to generate emails resulting in "bad address" errors from the mail server #1220 [Jamis Buck] + + +*0.9.1* (20th April, 2005) + +* Depend on Action Pack 1.8.1 + + +*0.9.0* (19th April, 2005) + +* Added that deliver_* will now return the email that was sent + +* Added that quoting to UTF-8 only happens if the characters used are in that range #955 [Jamis Buck] + +* Fixed quoting for all address headers, not just to #955 [Jamis Buck] + +* Fixed unquoting of emails that doesn't have an explicit charset #1036 [wolfgang@stufenlos.net] + + +*0.8.1* (27th March, 2005) + +* Fixed that if charset was found that the end of a mime part declaration TMail would throw an error #919 [lon@speedymac.com] + +* Fixed that TMail::Unquoter would fail to recognize quoting method if it was in lowercase #919 [lon@speedymac.com] + +* Fixed that TMail::Encoder would fail when it attempts to parse e-mail addresses which are encoded using something other than the messages encoding method #919 [lon@speedymac.com] + +* Added rescue for missing iconv library and throws warnings if subject/body is called on a TMail object without it instead + + +*0.8.0* (22th March, 2005) + +* Added framework support for processing incoming emails with an Action Mailer class. See example in README. + + +*0.7.1* (7th March, 2005) + +* Bind to newest Action Pack (1.5.1) + + +*0.7.0* (24th February, 2005) + +* Added support for charsets for both subject and body. The default charset is now UTF-8 #673 [Jamis Buck]. Examples: + + def iso_charset(recipient) + @recipients = recipient + @subject = "testing iso charsets" + @from = "system@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" + @body = "Nothing to see here." + @encode_subject = false + @charset = "iso-8859-1" + end + + +*0.6.1* (January 18th, 2005) + +* Fixed sending of emails to use Tmail#from not the deprecated Tmail#from_address + + +*0.6* (January 17th, 2005) + +* Fixed that bcc and cc should be settable through @bcc and @cc -- not just @headers["Bcc"] and @headers["Cc"] #453 [Eric Hodel] + +* Fixed Action Mailer to be "warnings safe" so you can run with ruby -w and not get framework warnings #453 [Eric Hodel] + + +*0.5* + +* Added access to custom headers, like cc, bcc, and reply-to #268 [Andreas Schwarz]. Example: + + def post_notification(recipients, post) + @recipients = recipients + @from = post.author.email_address_with_name + @headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL + @headers["reply-to"] = "notifications@example.com" + @subject = "[#{post.account.name} #{post.title}]" + @body["post"] = post + end + +*0.4* (5) + +* Consolidated the server configuration options into Base#server_settings= and expanded that with controls for authentication and more [Marten] + NOTE: This is an API change that could potentially break your application if you used the old application form. Please do change! + +* Added Base#deliveries as an accessor for an array of emails sent out through that ActionMailer class when using the :test delivery option. [Jeremy Kemper] + +* Added Base#perform_deliveries= which can be set to false to turn off the actual delivery of the email through smtp or sendmail. + This is especially useful for functional testing that shouldn't send off real emails, but still trigger delivery_* methods. + +* Added option to specify delivery method with Base#delivery_method=. Default is :smtp and :sendmail is currently the only other option. + Sendmail is assumed to be present at "/usr/sbin/sendmail" if that option is used. [Kent Sibilev] + +* Dropped "include TMail" as it added to much baggage into the default namespace (like Version) [Chad Fowler] + + +*0.3* + +* First release diff --git a/vendor/rails/actionmailer/MIT-LICENSE b/vendor/rails/actionmailer/MIT-LICENSE new file mode 100644 index 0000000..26f55e7 --- /dev/null +++ b/vendor/rails/actionmailer/MIT-LICENSE @@ -0,0 +1,21 @@ +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. + diff --git a/vendor/rails/actionmailer/README b/vendor/rails/actionmailer/README new file mode 100755 index 0000000..8c85e1a --- /dev/null +++ b/vendor/rails/actionmailer/README @@ -0,0 +1,148 @@ += Action Mailer -- Easy email delivery and testing + +Action Mailer is a framework for designing email-service layers. These layers +are used to consolidate code for sending out forgotten passwords, welcoming +wishes on signup, invoices for billing, and any other use case that requires +a written notification to either a person or another system. + +Additionally, an Action Mailer class can be used to process incoming email, +such as allowing a weblog to accept new posts from an email (which could even +have been sent from a phone). + +== Sending emails + +The framework works by setting up all the email details, except the body, +in methods on the service layer. Subject, recipients, sender, and timestamp +are all set up this way. An example of such a method: + + def signed_up(recipient) + recipients recipient + subject "[Signed up] Welcome #{recipient}" + from "system@loudthinking.com" + + body(:recipient => recipient) + end + +The body of the email is created by using an Action View template (regular +ERb) that has the content of the body hash parameter available as instance variables. +So the corresponding body template for the method above could look like this: + + Hello there, + + Mr. <%= @recipient %> + +And if the recipient was given as "david@loudthinking.com", the email +generated would look like this: + + Date: Sun, 12 Dec 2004 00:00:00 +0100 + From: system@loudthinking.com + To: david@loudthinking.com + Subject: [Signed up] Welcome david@loudthinking.com + + Hello there, + + Mr. david@loudthinking.com + +You never actually call the instance methods like signed_up directly. Instead, +you call class methods like deliver_* and create_* that are automatically +created for each instance method. So if the signed_up method sat on +ApplicationMailer, it would look like this: + + ApplicationMailer.create_signed_up("david@loudthinking.com") # => tmail object for testing + ApplicationMailer.deliver_signed_up("david@loudthinking.com") # sends the email + ApplicationMailer.new.signed_up("david@loudthinking.com") # won't work! + +== Receiving emails + +To receive emails, you need to implement a public instance method called receive that takes a +tmail object as its single parameter. The Action Mailer framework has a corresponding class method, +which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns +into the tmail object and calls the receive instance method. + +Example: + + class Mailman < ActionMailer::Base + def receive(email) + page = Page.find_by_address(email.to.first) + page.emails.create( + :subject => email.subject, :body => email.body + ) + + if email.has_attachments? + for attachment in email.attachments + page.attachments.create({ + :file => attachment, :description => email.subject + }) + end + end + end + end + +This Mailman can be the target for Postfix. In Rails, you would use the runner like this: + + ./script/runner 'Mailman.receive(STDIN.read)' + +== Configuration + +The Base class has the full list of configuration options. Here's an example: + +ActionMailer::Base.server_settings = { + :address=>'smtp.yourserver.com', # default: localhost + :port=>'25', # default: 25 + :user_name=>'user', + :password=>'pass', + :authentication=>:plain # :plain, :login or :cram_md5 +} + +== Dependencies + +Action Mailer requires that the Action Pack is either available to be required immediately +or is accessible as a GEM. + + +== Bundled software + +* tmail 0.10.8 by Minero Aoki released under LGPL + Read more on http://i.loveruby.net/en/prog/tmail.html + +* Text::Format 0.63 by Austin Ziegler released under OpenSource + Read more on http://www.halostatue.ca/ruby/Text__Format.html + + +== Download + +The latest version of Action Mailer can be found at + +* http://rubyforge.org/project/showfiles.php?group_id=361 + +Documentation can be found at + +* http://actionmailer.rubyonrails.org + + +== Installation + +You can install Action Mailer with the following command. + + % [sudo] ruby install.rb + +from its distribution directory. + + +== License + +Action Mailer is released under the MIT license. + + +== Support + +The Action Mailer homepage is http://actionmailer.rubyonrails.org. You can find +the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer. +And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. + +For other information, feel free to ask on the ruby-talk mailing list (which +is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com. diff --git a/vendor/rails/actionmailer/Rakefile b/vendor/rails/actionmailer/Rakefile new file mode 100755 index 0000000..298d919 --- /dev/null +++ b/vendor/rails/actionmailer/Rakefile @@ -0,0 +1,95 @@ +require 'rubygems' +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/contrib/rubyforgepublisher' +require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version') + +PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' +PKG_NAME = 'actionmailer' +PKG_VERSION = ActionMailer::VERSION::STRING + PKG_BUILD +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" + +RELEASE_NAME = "REL #{PKG_VERSION}" + +RUBY_FORGE_PROJECT = "actionmailer" +RUBY_FORGE_USER = "webster132" + +desc "Default Task" +task :default => [ :test ] + +# Run the unit tests +Rake::TestTask.new { |t| + t.libs << "test" + t.pattern = 'test/*_test.rb' + t.verbose = true + t.warning = false +} + + +# Genereate the RDoc documentation +Rake::RDocTask.new { |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "Action Mailer -- Easy email delivery and testing" + rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README', 'CHANGELOG') + rdoc.rdoc_files.include('lib/action_mailer.rb') + rdoc.rdoc_files.include('lib/action_mailer/*.rb') +} + + +# Create compressed packages +spec = Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = PKG_NAME + s.summary = "Service layer for easy email delivery and testing." + s.description = %q{Makes it trivial to test and deliver emails sent from a single service layer.} + s.version = PKG_VERSION + + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.rubyforge_project = "actionmailer" + s.homepage = "http://www.rubyonrails.org" + + s.add_dependency('actionpack', '= 1.12.1' + PKG_BUILD) + + s.has_rdoc = true + s.requirements << 'none' + s.require_path = 'lib' + s.autorequire = 'action_mailer' + + s.files = [ "Rakefile", "install.rb", "README", "CHANGELOG", "MIT-LICENSE" ] + s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) } + s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) } +end + +Rake::GemPackageTask.new(spec) do |p| + p.gem_spec = spec + p.need_tar = true + p.need_zip = true +end + + +desc "Publish the API documentation" +task :pgem => [:package] do + Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload +end + +desc "Publish the API documentation" +task :pdoc => [:rdoc] do + Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/am", "doc").upload +end + +desc "Publish the release files to RubyForge." +task :release => [ :package ] do + `rubyforge login` + + for ext in %w( gem tgz zip ) + release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" + puts release_command + system(release_command) + end +end \ No newline at end of file diff --git a/vendor/rails/actionmailer/install.rb b/vendor/rails/actionmailer/install.rb new file mode 100644 index 0000000..c559edf --- /dev/null +++ b/vendor/rails/actionmailer/install.rb @@ -0,0 +1,30 @@ +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 +} diff --git a/vendor/rails/actionmailer/lib/action_mailer.rb b/vendor/rails/actionmailer/lib/action_mailer.rb new file mode 100755 index 0000000..894bc82 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer.rb @@ -0,0 +1,50 @@ +#-- +# 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. +#++ + +unless defined?(ActionController) + begin + $:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib" + require '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) } diff --git a/vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb b/vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb new file mode 100644 index 0000000..cfd7230 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/adv_attr_accessor.rb @@ -0,0 +1,30 @@ +module ActionMailer + module AdvAttrAccessor #:nodoc: + def self.included(base) + 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/base.rb b/vendor/rails/actionmailer/lib/action_mailer/base.rb new file mode 100644 index 0000000..d667ef1 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/base.rb @@ -0,0 +1,528 @@ +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. + # + # * recipients - Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the To: header. + # * subject - The subject of your email. Sets the Subject: header. + # * from - Who the email you are sending is from. Sets the From: header. + # * cc - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the Cc: header. + # * bcc - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the Bcc header. + # * sent_on - The date on which the message was sent. If not set, the header wil be set by the delivery agent. + # * content_type - Specify the content type of the message. Defaults to text/plain. + # * headers - Specify additional headers to be set for the message, e.g. headers 'X-Mail-Count' => 107370. + # + # The body method has special behavior. It takes a hash which generates an instance variable + # named after each key in the hash containing the value that that key points to. + # + # So, for example, body "account" => recipient would result + # in an instance variable @account with the value of recipient being accessible in the + # 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 .rhtml file with the same name as the method + # in your mailer model. For example, in the mailer defined above, the template at + # app/views/notifier/signup_notification.rhtml 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 + # deliver_ followed by the name of the mailer method that you would + # like to deliver. The signup_notification method defined above is + # delivered by invoking Notifier.deliver_signup_notification. + # + # = HTML Email + # To send mail as HTML, make sure your view (the .rhtml 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 ActionMailer::Base.template_root = "/my/templates" + # + # * template_root - template root determines the base from which template references will be made. + # + # * logger - the logger is used for generating information on the mailing run if available. + # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. + # + # * server_settings - Allows detailed configuration of the server: + # * :address Allows you to use a remote mail server. Just change it from its default "localhost" setting. + # * :port On the off chance that your mail server doesn't run on port 25, you can change it. + # * :domain If you need to specify a HELO domain, you can do it here. + # * :user_name If your mail server requires authentication, set the username in this setting. + # * :password If your mail server requires authentication, set the password in this setting. + # * :authentication If your mail server requires authentication, you need to specify the authentication type here. + # This is a symbol and one of :plain, :login, :cram_md5 + # + # * raise_delivery_errors - whether or not errors should be raised if the email fails to be delivered. + # + # * delivery_method - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test. + # Sendmail is assumed to be present at "/usr/sbin/sendmail". + # + # * perform_deliveries - Determines whether deliver_* methods are actually carried out. By default they are, + # but this can be turned off to help functional testing. + # + # * deliveries - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful + # for unit and functional testing. + # + # * default_charset - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also + # pick a different charset from inside a method with @charset. + # * default_content_type - The default content type used for the main part of the message. Defaults to "text/plain". You + # can also pick a different content type from inside a method with @content_type. + # * default_mime_version - The default mime version used for the message. Defaults to nil. You + # can also pick a different value from inside a method with @mime_version. When multipart messages are in + # use, @mime_version will be set to "1.0" if it is not set inside a method. + # * default_implicit_parts_order - When a message is built implicitly (i.e. multiple parts are assembled from templates + # which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to + # ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client + # and appear last in the mime encoded message. You can also pick a different order from inside a method with + # @implicit_parts_order. + 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 text/plain + # in most cases, but can be automatically set in some situations. + adv_attr_accessor :content_type + + # Specify the from address for the message. + adv_attr_accessor :from + + # Specify 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/helpers.rb b/vendor/rails/actionmailer/lib/action_mailer/helpers.rb new file mode 100644 index 0000000..8176ba8 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/helpers.rb @@ -0,0 +1,111 @@ +module ActionMailer + module Helpers #:nodoc: + def self.included(base) #:nodoc: + # 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_chain :inherited, :helper + end + + # Wrap initialize_template_class to extend new template class + # instances with the master helper module. + alias_method_chain :initialize_template_class, :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 +end \ No newline at end of file diff --git a/vendor/rails/actionmailer/lib/action_mailer/mail_helper.rb b/vendor/rails/actionmailer/lib/action_mailer/mail_helper.rb new file mode 100644 index 0000000..11fd7d7 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/mail_helper.rb @@ -0,0 +1,19 @@ +require '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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/part.rb b/vendor/rails/actionmailer/lib/action_mailer/part.rb new file mode 100644 index 0000000..31f5b44 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/part.rb @@ -0,0 +1,113 @@ +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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/part_container.rb b/vendor/rails/actionmailer/lib/action_mailer/part_container.rb new file mode 100644 index 0000000..3e3d6b9 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/part_container.rb @@ -0,0 +1,51 @@ +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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/quoting.rb b/vendor/rails/actionmailer/lib/action_mailer/quoting.rb new file mode 100644 index 0000000..d6e04e4 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/quoting.rb @@ -0,0 +1,59 @@ +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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/utils.rb b/vendor/rails/actionmailer/lib/action_mailer/utils.rb new file mode 100644 index 0000000..552f695 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/utils.rb @@ -0,0 +1,8 @@ +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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/text/format.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/text/format.rb new file mode 100755 index 0000000..de054db --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/text/format.rb @@ -0,0 +1,1466 @@ +#-- +# 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*:: [] + # Used in:: 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*:: 72 + # Used in:: #format, #paragraphs, + # #center + 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*:: 72 + # Used in:: #format, #paragraphs, + # #center + 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*:: 0 + # Used in:: #format, #paragraphs, + # #center + 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*:: 0 + # Used in:: #format, #paragraphs, + # #center + 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*:: 0 + # Used in:: #format, #paragraphs, + # #center + 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*:: 0 + # Used in:: #format, #paragraphs, + # #center + 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*:: 4 + # Used in:: #format, #paragraphs + 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*:: 4 + # Used in:: #format, #paragraphs + 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*:: 0 + # Used in:: #format, #paragraphs + 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*:: 0 + # Used in:: #format, #paragraphs + 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 #split_words. 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+ + # Used in:: #format, #paragraphs + 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+ + # Used in:: #format, #paragraphs + 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+ + # Used in:: #format, #paragraphs + 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*:: Text::Format::SPLIT_FIXED + # Used in:: #format, #paragraphs + 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., SPLIT_FIXED | + # SPLIT_CONTINUATION) 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*:: Text::Format::SPLIT_FIXED + # Used in:: #format, #paragraphs + 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+ + # Used in:: #format, #paragraphs + 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*:: {} + # Used in:: #format, #paragraphs + attr_accessor :abbreviations + + # Indicates whether the formatting of paragraphs should be done with + # tagged paragraphs. Useful only with #tag_text. + # + # *Default*:: +false+ + # Used in:: #format, #paragraphs + attr_accessor :tag_paragraph + + # The array of text to be placed before each paragraph when + # #tag_paragraph is +true+. When #format() is called, + # only the first element of the array is used. When #paragraphs + # 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 #tag_paragraph is +true+. + # + # *Default*:: [] + # Used in:: #format, #paragraphs + attr_accessor :tag_text + + # Indicates whether or not the non-breaking space feature should be + # used. + # + # *Default*:: +false+ + # Used in:: #format, #paragraphs + 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*:: {} + # Used in:: #format, #paragraphs + attr_accessor :nobreak_regex + + # Indicates the number of spaces that a single tab represents. + # + # *Default*:: 8 + # Used in:: #expand, #unexpand, + # #paragraphs + attr_reader :tabstop + + # Indicates the number of spaces that a single tab represents. + # + # *Default*:: 8 + # Used in:: #expand, #unexpand, + # #paragraphs + 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*:: Text::Format::LEFT_ALIGN + # Used in:: #format, #paragraphs + 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*:: Text::Format::LEFT_ALIGN + # Used in:: #format, #paragraphs + 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+ + # Used in:: #format, #paragraphs + def left_align? + return @format_style == LEFT_ALIGN + end + + # Indicates that the format style is right alignment. + # + # *Default*:: +false+ + # Used in:: #format, #paragraphs + def right_align? + return @format_style == RIGHT_ALIGN + end + + # Indicates that the format style is right fill. + # + # *Default*:: +false+ + # Used in:: #format, #paragraphs + def right_fill? + return @format_style == RIGHT_FILL + end + + # Indicates that the format style is full justification. + # + # *Default*:: +false+ + # Used in:: #format, #paragraphs + 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 #text 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 #first_indent is the same as #body_indent, then + # paragraphs will be separated by a single empty line in the result; + # otherwise, the paragraphs will follow immediately after each other. + # Uses #format 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 #tabstop 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 #tabstop 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 #text. + # 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" + # + # + 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail.rb new file mode 100755 index 0000000..8cea88d --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail.rb @@ -0,0 +1,3 @@ +require 'tmail/info' +require 'tmail/mail' +require 'tmail/mailbox' diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/address.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/address.rb new file mode 100755 index 0000000..235ec76 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/address.rb @@ -0,0 +1,242 @@ +# +# address.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/encode' +require 'tmail/parser' + + +module TMail + + class Address + + include TextUtils + + def Address.parse( str ) + Parser.parse :ADDRESS, str + end + + def address_group? + false + end + + def initialize( local, domain ) + if domain + domain.each do |s| + raise SyntaxError, 'empty word in domain' if s.empty? + end + end + @local = local + @domain = domain + @name = nil + @routes = [] + end + + attr_reader :name + + def name=( str ) + @name = str + @name = nil if str and str.empty? + end + + alias phrase name + alias phrase= name= + + attr_reader :routes + + def inspect + "#<#{self.class} #{address()}>" + end + + def local + return nil unless @local + return '""' if @local.size == 1 and @local[0].empty? + @local.map {|i| quote_atom(i) }.join('.') + end + + def domain + return nil unless @domain + join_domain(@domain) + end + + def spec + s = self.local + d = self.domain + if s and d + s + '@' + d + else + s + end + end + + alias address spec + + + def ==( other ) + other.respond_to? :spec and self.spec == other.spec + end + + alias eql? == + + def hash + @local.hash ^ @domain.hash + end + + def dup + obj = self.class.new(@local.dup, @domain.dup) + obj.name = @name.dup if @name + obj.routes.replace @routes + obj + end + + include StrategyInterface + + def accept( strategy, dummy1 = nil, dummy2 = nil ) + unless @local + strategy.meta '<>' # empty return-path + return + end + + spec_p = (not @name and @routes.empty?) + if @name + strategy.phrase @name + strategy.space + end + tmp = spec_p ? '' : '<' + unless @routes.empty? + tmp << @routes.map {|i| '@' + i }.join(',') << ':' + end + tmp << self.spec + tmp << '>' unless spec_p + strategy.meta tmp + strategy.lwsp '' + end + + end + + + class AddressGroup + + include Enumerable + + def address_group? + true + end + + def initialize( name, addrs ) + @name = name + @addresses = addrs + end + + attr_reader :name + + def ==( other ) + other.respond_to? :to_a and @addresses == other.to_a + end + + alias eql? == + + def hash + map {|i| i.hash }.hash + end + + def []( idx ) + @addresses[idx] + end + + def size + @addresses.size + end + + def empty? + @addresses.empty? + end + + def each( &block ) + @addresses.each(&block) + end + + def to_a + @addresses.dup + end + + alias to_ary to_a + + def include?( a ) + @addresses.include? a + end + + def flatten + set = [] + @addresses.each do |a| + if a.respond_to? :flatten + set.concat a.flatten + else + set.push a + end + end + set + end + + def each_address( &block ) + flatten.each(&block) + end + + def add( a ) + @addresses.push a + end + + alias push add + + def delete( a ) + @addresses.delete a + end + + include StrategyInterface + + def accept( strategy, dummy1 = nil, dummy2 = nil ) + strategy.phrase @name + strategy.meta ':' + strategy.space + first = true + each do |mbox| + if first + first = false + else + strategy.meta ',' + end + strategy.space + mbox.accept strategy + end + strategy.meta ';' + strategy.lwsp '' + end + + end + +end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/attachments.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/attachments.rb new file mode 100644 index 0000000..4d8d106 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/attachments.rb @@ -0,0 +1,39 @@ +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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/base64.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/base64.rb new file mode 100755 index 0000000..8f89a48 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/base64.rb @@ -0,0 +1,71 @@ +# +# base64.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +module TMail + + 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/config.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/config.rb new file mode 100755 index 0000000..b075299 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/config.rb @@ -0,0 +1,69 @@ +# +# config.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +module TMail + + 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/encode.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/encode.rb new file mode 100755 index 0000000..91bd289 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/encode.rb @@ -0,0 +1,467 @@ +# +# encode.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require '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 : <> + # J : <> + # S : <> + # + # 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/facade.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/facade.rb new file mode 100755 index 0000000..1ecd64b --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/facade.rb @@ -0,0 +1,552 @@ +# +# facade.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/utils' + + +module TMail + + class Mail + + def header_string( name, default = nil ) + h = @header[name.downcase] or return default + h.to_s + end + + ### + ### attributes + ### + + include TextUtils + + def set_string_array_attr( key, strs ) + strs.flatten! + if strs.empty? + @header.delete key.downcase + else + store key, strs.join(', ') + end + strs + end + private :set_string_array_attr + + def set_string_attr( key, str ) + if str + store key, str + else + @header.delete key.downcase + end + str + end + private :set_string_attr + + def set_addrfield( name, arg ) + if arg + h = HeaderField.internal_new(name, @config) + h.addrs.replace [arg].flatten + @header[name] = h + else + @header.delete name + end + arg + end + private :set_addrfield + + def addrs2specs( addrs ) + return nil unless addrs + list = addrs.map {|addr| + if addr.address_group? + then addr.map {|a| a.spec } + else addr.spec + end + }.flatten + return nil if list.empty? + list + end + private :addrs2specs + + + # + # date time + # + + def date( default = nil ) + if h = @header['date'] + h.date + else + default + end + end + + def date=( time ) + if time + store 'Date', time2str(time) + else + @header.delete 'date' + end + time + end + + def strftime( fmt, default = nil ) + if t = date + t.strftime(fmt) + else + default + end + end + + + # + # destination + # + + def to_addrs( default = nil ) + if h = @header['to'] + h.addrs + else + default + end + end + + def cc_addrs( default = nil ) + if h = @header['cc'] + h.addrs + else + default + end + end + + def bcc_addrs( default = nil ) + if h = @header['bcc'] + h.addrs + else + default + end + end + + def to_addrs=( arg ) + set_addrfield 'to', arg + end + + def cc_addrs=( arg ) + set_addrfield 'cc', arg + end + + def bcc_addrs=( arg ) + set_addrfield 'bcc', arg + end + + def to( default = nil ) + addrs2specs(to_addrs(nil)) || default + end + + def cc( default = nil ) + addrs2specs(cc_addrs(nil)) || default + end + + def bcc( default = nil ) + addrs2specs(bcc_addrs(nil)) || default + end + + def to=( *strs ) + set_string_array_attr 'To', strs + end + + def cc=( *strs ) + set_string_array_attr 'Cc', strs + end + + def bcc=( *strs ) + set_string_array_attr 'Bcc', strs + end + + + # + # originator + # + + def from_addrs( default = nil ) + if h = @header['from'] + h.addrs + else + default + end + end + + def from_addrs=( arg ) + set_addrfield 'from', arg + end + + def from( default = nil ) + addrs2specs(from_addrs(nil)) || default + end + + def from=( *strs ) + set_string_array_attr 'From', strs + end + + def friendly_from( default = nil ) + h = @header['from'] + a, = h.addrs + return default unless a + return a.phrase if a.phrase + return h.comments.join(' ') unless h.comments.empty? + a.spec + end + + + def reply_to_addrs( default = nil ) + if h = @header['reply-to'] + h.addrs + else + default + end + end + + def reply_to_addrs=( arg ) + set_addrfield 'reply-to', arg + end + + def reply_to( default = nil ) + addrs2specs(reply_to_addrs(nil)) || default + end + + def reply_to=( *strs ) + set_string_array_attr 'Reply-To', strs + end + + + def sender_addr( default = nil ) + f = @header['sender'] or return default + f.addr or return default + end + + def sender_addr=( addr ) + if addr + h = HeaderField.internal_new('sender', @config) + h.addr = addr + @header['sender'] = h + else + @header.delete 'sender' + end + addr + end + + def sender( default ) + f = @header['sender'] or return default + a = f.addr or return default + a.spec + end + + def sender=( str ) + set_string_attr 'Sender', str + end + + + # + # subject + # + + def subject( default = nil ) + if h = @header['subject'] + h.body + else + default + end + end + alias quoted_subject subject + + def subject=( str ) + set_string_attr 'Subject', str + end + + + # + # identity & threading + # + + def message_id( default = nil ) + if h = @header['message-id'] + h.id || default + else + default + end + end + + def message_id=( str ) + set_string_attr 'Message-Id', str + end + + def in_reply_to( default = nil ) + if h = @header['in-reply-to'] + h.ids + else + default + end + end + + def in_reply_to=( *idstrs ) + set_string_array_attr 'In-Reply-To', idstrs + end + + def references( default = nil ) + if h = @header['references'] + h.refs + else + default + end + end + + def references=( *strs ) + set_string_array_attr 'References', strs + end + + + # + # MIME headers + # + + def mime_version( default = nil ) + if h = @header['mime-version'] + h.version || default + else + default + end + end + + def mime_version=( m, opt = nil ) + if opt + if h = @header['mime-version'] + h.major = m + h.minor = opt + else + store 'Mime-Version', "#{m}.#{opt}" + end + else + store 'Mime-Version', m + end + m + end + + + def content_type( default = nil ) + if h = @header['content-type'] + h.content_type || default + else + default + end + end + + def main_type( default = nil ) + if h = @header['content-type'] + h.main_type || default + else + default + end + end + + def sub_type( default = nil ) + if h = @header['content-type'] + h.sub_type || default + else + default + end + end + + def set_content_type( str, sub = nil, param = nil ) + if sub + main, sub = str, sub + else + main, sub = str.split(%r, 2) + raise ArgumentError, "sub type missing: #{str.inspect}" unless sub + end + if h = @header['content-type'] + h.main_type = main + h.sub_type = sub + h.params.clear + else + store 'Content-Type', "#{main}/#{sub}" + end + @header['content-type'].params.replace param if param + + str + end + + alias content_type= set_content_type + + def type_param( name, default = nil ) + if h = @header['content-type'] + h[name] || default + else + default + end + end + + def charset( default = nil ) + if h = @header['content-type'] + h['charset'] or default + else + default + end + end + + def charset=( str ) + if str + if h = @header[ 'content-type' ] + h['charset'] = str + else + store 'Content-Type', "text/plain; charset=#{str}" + end + end + str + end + + + def transfer_encoding( default = nil ) + if h = @header['content-transfer-encoding'] + h.encoding || default + else + default + end + end + + def transfer_encoding=( str ) + set_string_attr 'Content-Transfer-Encoding', str + end + + alias encoding transfer_encoding + alias encoding= transfer_encoding= + alias content_transfer_encoding transfer_encoding + alias content_transfer_encoding= transfer_encoding= + + + def disposition( default = nil ) + if h = @header['content-disposition'] + h.disposition || default + else + default + end + end + + alias content_disposition disposition + + def set_disposition( str, params = nil ) + if h = @header['content-disposition'] + h.disposition = str + h.params.clear + else + store('Content-Disposition', str) + h = @header['content-disposition'] + end + h.params.replace params if params + end + + alias disposition= set_disposition + alias set_content_disposition set_disposition + alias content_disposition= set_disposition + + def disposition_param( name, default = nil ) + if h = @header['content-disposition'] + h[name] || default + else + default + end + end + + ### + ### utils + ### + + def create_reply + mail = TMail::Mail.parse('') + mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '') + mail.to_addrs = reply_addresses([]) + mail.in_reply_to = [message_id(nil)].compact + mail.references = references([]) + [message_id(nil)].compact + mail.mime_version = '1.0' + mail + end + + + def base64_encode + store 'Content-Transfer-Encoding', 'Base64' + self.body = Base64.folding_encode(self.body) + end + + def base64_decode + if /base64/i === self.transfer_encoding('') + store 'Content-Transfer-Encoding', '8bit' + self.body = Base64.decode(self.body, @config.strict_base64decode?) + end + end + + + def destinations( default = nil ) + ret = [] + %w( to cc bcc ).each do |nm| + if h = @header[nm] + h.addrs.each {|i| ret.push i.address } + end + end + ret.empty? ? default : ret + end + + def each_destination( &block ) + destinations([]).each do |i| + if Address === i + yield i + else + i.each(&block) + end + end + end + + alias each_dest each_destination + + + def reply_addresses( default = nil ) + reply_to_addrs(nil) or from_addrs(nil) or default + end + + def error_reply_addresses( default = nil ) + if s = sender(nil) + [s] + else + from_addrs(default) + end + end + + + def multipart? + main_type('').downcase == 'multipart' + end + + end # class Mail + +end # module TMail diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/header.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/header.rb new file mode 100755 index 0000000..be97803 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/header.rb @@ -0,0 +1,914 @@ +# +# header.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/info.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/info.rb new file mode 100755 index 0000000..5c115d5 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/info.rb @@ -0,0 +1,35 @@ +# +# info.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +module TMail + + Version = '0.10.7' + Copyright = 'Copyright (c) 1998-2002 Minero Aoki' + +end diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/loader.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/loader.rb new file mode 100755 index 0000000..7907315 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/loader.rb @@ -0,0 +1 @@ +require 'tmail/mailbox' diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mail.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mail.rb new file mode 100755 index 0000000..e11fa0f --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mail.rb @@ -0,0 +1,447 @@ +# +# mail.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/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', % + 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mailbox.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mailbox.rb new file mode 100755 index 0000000..d85915e --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mailbox.rb @@ -0,0 +1,433 @@ +# +# mailbox.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mbox.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mbox.rb new file mode 100755 index 0000000..7907315 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/mbox.rb @@ -0,0 +1 @@ +require 'tmail/mailbox' diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/net.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/net.rb new file mode 100755 index 0000000..f96cf64 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/net.rb @@ -0,0 +1,280 @@ +# +# net.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require '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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/obsolete.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/obsolete.rb new file mode 100755 index 0000000..f98be74 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/obsolete.rb @@ -0,0 +1,135 @@ +# +# obsolete.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +module TMail + + # 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/parser.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/parser.rb new file mode 100755 index 0000000..825eca9 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/parser.rb @@ -0,0 +1,1522 @@ +# +# 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 +# +# 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 ' ' + 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 +# +# 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/port.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/port.rb new file mode 100755 index 0000000..f973c05 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/port.rb @@ -0,0 +1,377 @@ +# +# port.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/quoting.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/quoting.rb new file mode 100644 index 0000000..a56e226 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/quoting.rb @@ -0,0 +1,125 @@ +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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner.rb new file mode 100755 index 0000000..839dd79 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner.rb @@ -0,0 +1,41 @@ +# +# scanner.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/utils' + +module TMail + 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner_r.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner_r.rb new file mode 100755 index 0000000..ccf576c --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/scanner_r.rb @@ -0,0 +1,263 @@ +# +# scanner_r.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +require 'tmail/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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/stringio.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/stringio.rb new file mode 100755 index 0000000..532be3d --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/stringio.rb @@ -0,0 +1,277 @@ +# +# stringio.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/tmail.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/tmail.rb new file mode 100755 index 0000000..57ed3cc --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/tmail.rb @@ -0,0 +1 @@ +require 'tmail' diff --git a/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/utils.rb b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/utils.rb new file mode 100755 index 0000000..852acd7 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/vendor/tmail/utils.rb @@ -0,0 +1,238 @@ +# +# utils.rb +# +#-- +# Copyright (c) 1998-2003 Minero Aoki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Note: Originally licensed under LGPL v2+. Using MIT license for Rails +# with permission of Minero Aoki. +#++ + +module TMail + + 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 diff --git a/vendor/rails/actionmailer/lib/action_mailer/version.rb b/vendor/rails/actionmailer/lib/action_mailer/version.rb new file mode 100644 index 0000000..ba6a301 --- /dev/null +++ b/vendor/rails/actionmailer/lib/action_mailer/version.rb @@ -0,0 +1,9 @@ +module ActionMailer + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 2 + TINY = 1 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/rails/actionmailer/test/abstract_unit.rb b/vendor/rails/actionmailer/test/abstract_unit.rb new file mode 100644 index 0000000..c705f1c --- /dev/null +++ b/vendor/rails/actionmailer/test/abstract_unit.rb @@ -0,0 +1,7 @@ +require 'test/unit' + +$:.unshift "#{File.dirname(__FILE__)}/../lib" +require 'action_mailer' + +$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers" +ActionMailer::Base.template_root = "#{File.dirname(__FILE__)}/fixtures" diff --git a/vendor/rails/actionmailer/test/fixtures/first_mailer/share.rhtml b/vendor/rails/actionmailer/test/fixtures/first_mailer/share.rhtml new file mode 100644 index 0000000..da43638 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/first_mailer/share.rhtml @@ -0,0 +1 @@ +first mail diff --git a/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_helper.rhtml b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_helper.rhtml new file mode 100644 index 0000000..378777f --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_helper.rhtml @@ -0,0 +1 @@ +Hello, <%= person_name %>. Thanks for registering! diff --git a/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_helper_method.rhtml b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_helper_method.rhtml new file mode 100644 index 0000000..d5b8b28 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_helper_method.rhtml @@ -0,0 +1 @@ +This message brought to you by <%= name_of_the_mailer_class %>. diff --git a/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_mail_helper.rhtml b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_mail_helper.rhtml new file mode 100644 index 0000000..96ec49d --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_mail_helper.rhtml @@ -0,0 +1,5 @@ +From "Romeo and Juliet": + +<%= block_format @text %> + +Good ol' Shakespeare. diff --git a/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_test_helper.rhtml b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_test_helper.rhtml new file mode 100644 index 0000000..52ea9aa --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/helper_mailer/use_test_helper.rhtml @@ -0,0 +1 @@ +So, <%= test_format(@text) %> diff --git a/vendor/rails/actionmailer/test/fixtures/helpers/test_helper.rb b/vendor/rails/actionmailer/test/fixtures/helpers/test_helper.rb new file mode 100644 index 0000000..f479820 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/helpers/test_helper.rb @@ -0,0 +1,5 @@ +module TestHelper + def test_format(text) + "#{text}" + end +end diff --git a/vendor/rails/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.rhtml b/vendor/rails/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.rhtml new file mode 100644 index 0000000..897a506 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/path.with.dots/funky_path_mailer/multipart_with_template_path_with_dots.rhtml @@ -0,0 +1 @@ +Have a lovely picture, from me. Enjoy! \ No newline at end of file diff --git a/vendor/rails/actionmailer/test/fixtures/path.with.dots/multipart_with_template_path_with_dots.rhtml b/vendor/rails/actionmailer/test/fixtures/path.with.dots/multipart_with_template_path_with_dots.rhtml new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email b/vendor/rails/actionmailer/test/fixtures/raw_email new file mode 100644 index 0000000..43f7a59 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email @@ -0,0 +1,14 @@ +From jamis_buck@byu.edu Mon May 2 16:07:05 2005 +Mime-Version: 1.0 (Apple Message framework v622) +Content-Transfer-Encoding: base64 +Message-Id: +Content-Type: text/plain; + charset=EUC-KR; + format=flowed +To: willard15georgina@jamis.backpackit.com +From: Jamis Buck +Subject: =?EUC-KR?Q?NOTE:_=C7=D1=B1=B9=B8=BB=B7=CE_=C7=CF=B4=C2_=B0=CD?= +Date: Mon, 2 May 2005 16:07:05 -0600 + +tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin +wLogSmFtaXPA1LTPtNku diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email10 b/vendor/rails/actionmailer/test/fixtures/raw_email10 new file mode 100644 index 0000000..b1fc2b2 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email10 @@ -0,0 +1,20 @@ +Return-Path: +Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for ; Tue, 10 May 2005 15:27:05 -0500 +Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for ; Tue, 10 May 2005 15:27:04 -0500 +Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for ; Tue, 10 May 2005 15:27:03 -0500 +Date: Tue, 10 May 2005 15:27:03 -0500 +From: xxx@xxxx.xxx +Sender: xxx@xxxx.xxx +To: xxxxxxxxxxx@xxxx.xxxx.xxx +Message-Id: +X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx +Delivered-To: xxx@xxxx.xxx +Importance: normal +Content-Type: text/plain; charset=X-UNKNOWN + +Test test. Hi. Waving. m + +---------------------------------------------------------------- +Sent via Bell Mobility's Text Messaging service. +Envoyé par le service de messagerie texte de Bell Mobilité. +---------------------------------------------------------------- diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email11 b/vendor/rails/actionmailer/test/fixtures/raw_email11 new file mode 100644 index 0000000..8af74b8 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email11 @@ -0,0 +1,34 @@ +From xxx@xxxx.com Wed Apr 27 14:15:31 2005 +Mime-Version: 1.0 (Apple Message framework v619.2) +To: xxxxx@xxxxx +Message-Id: <416eaebec6d333ec6939eaf8a7d80724@xxxxx> +Content-Type: multipart/alternative; + boundary=Apple-Mail-5-1037861608 +From: xxxxx@xxxxx +Subject: worse when you use them. +Date: Wed, 27 Apr 2005 14:15:31 -0700 + + + + +--Apple-Mail-5-1037861608 +Content-Transfer-Encoding: 7bit +Content-Type: text/plain; + charset=US-ASCII; + format=flowed + + +XXXXX Xxxxx + +--Apple-Mail-5-1037861608 +Content-Transfer-Encoding: 7bit +Content-Type: text/enriched; + charset=US-ASCII + + + +XXXXX Xxxxx + + +--Apple-Mail-5-1037861608-- + diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email12 b/vendor/rails/actionmailer/test/fixtures/raw_email12 new file mode 100644 index 0000000..2cd3172 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email12 @@ -0,0 +1,32 @@ +Mime-Version: 1.0 (Apple Message framework v730) +Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151 +Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com> +From: foo@example.com +Subject: testing +Date: Mon, 6 Jun 2005 22:21:22 +0200 +To: blah@example.com + + +--Apple-Mail-13-196941151 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=ISO-8859-1; + delsp=yes; + format=flowed + +This is the first part. + +--Apple-Mail-13-196941151 +Content-Type: image/jpeg +Content-Transfer-Encoding: base64 +Content-Location: Photo25.jpg +Content-ID: +Content-Disposition: inline + +jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw +ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE +QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB +gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5= + +--Apple-Mail-13-196941151-- + diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email13 b/vendor/rails/actionmailer/test/fixtures/raw_email13 new file mode 100644 index 0000000..7d9314e --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email13 @@ -0,0 +1,29 @@ +Mime-Version: 1.0 (Apple Message framework v730) +Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151 +Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com> +From: foo@example.com +Subject: testing +Date: Mon, 6 Jun 2005 22:21:22 +0200 +To: blah@example.com + + +--Apple-Mail-13-196941151 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=ISO-8859-1; + delsp=yes; + format=flowed + +This is the first part. + +--Apple-Mail-13-196941151 +Content-Type: text/x-ruby-script; name="hello.rb" +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment; + filename="api.rb" + +puts "Hello, world!" +gets + +--Apple-Mail-13-196941151-- + diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email2 b/vendor/rails/actionmailer/test/fixtures/raw_email2 new file mode 100644 index 0000000..3999fcc --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email2 @@ -0,0 +1,114 @@ +From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005 +Return-Path: +X-Original-To: xxxxx@xxxxx.xxxxxxxxx.com +Delivered-To: xxxxx@xxxxx.xxxxxxxxx.com +Received: from localhost (localhost [127.0.0.1]) + by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 06C9DA98D + for ; Sun, 8 May 2005 19:09:13 +0000 (GMT) +Received: from xxxxx.xxxxxxxxx.com ([127.0.0.1]) + by localhost (xxxxx.xxxxxxxxx.com [127.0.0.1]) (amavisd-new, port 10024) + with LMTP id 88783-08 for ; + Sun, 8 May 2005 19:09:12 +0000 (GMT) +Received: from xxxxxxx.xxxxxxxxx.com (xxxxxxx.xxxxxxxxx.com [69.36.39.150]) + by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 10D8BA960 + for ; Sun, 8 May 2005 19:09:12 +0000 (GMT) +Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.199]) + by xxxxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 9EBC4148EAB + for ; Sun, 8 May 2005 14:09:11 -0500 (CDT) +Received: by zproxy.gmail.com with SMTP id 13so1233405nzp + for ; Sun, 08 May 2005 12:09:11 -0700 (PDT) +DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; + s=beta; d=gmail.com; + h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:references; + b=cid1mzGEFa3gtRa06oSrrEYfKca2CTKu9sLMkWxjbvCsWMtp9RGEILjUz0L5RySdH5iO661LyNUoHRFQIa57bylAbXM3g2DTEIIKmuASDG3x3rIQ4sHAKpNxP7Pul+mgTaOKBv+spcH7af++QEJ36gHFXD2O/kx9RePs3JNf/K8= +Received: by 10.36.10.16 with SMTP id 16mr1012493nzj; + Sun, 08 May 2005 12:09:11 -0700 (PDT) +Received: by 10.36.5.10 with HTTP; Sun, 8 May 2005 12:09:11 -0700 (PDT) +Message-ID: +Date: Sun, 8 May 2005 14:09:11 -0500 +From: xxxxxxxxx xxxxxxx +Reply-To: xxxxxxxxx xxxxxxx +To: xxxxx xxxx +Subject: Fwd: Signed email causes file attachments +In-Reply-To: +Mime-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_5028_7368284.1115579351471" +References: + +------=_Part_5028_7368284.1115579351471 +Content-Type: text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable +Content-Disposition: inline + +We should not include these files or vcards as attachments. + +---------- Forwarded message ---------- +From: xxxxx xxxxxx +Date: May 8, 2005 1:17 PM +Subject: Signed email causes file attachments +To: xxxxxxx@xxxxxxxxxx.com + + +Hi, + +Just started to use my xxxxxxxx account (to set-up a GTD system, +natch) and noticed that when I send content via email the signature/ +certificate from my email account gets added as a file (e.g. +"smime.p7s"). + +Obviously I can uncheck the signature option in the Mail compose +window but how often will I remember to do that? + +Is there any way these kind of files could be ignored, e.g. via some +sort of exclusions list? + +------=_Part_5028_7368284.1115579351471 +Content-Type: application/pkcs7-signature; name=smime.p7s +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime.p7s" + +MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w +ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh +d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt +YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD +ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL +7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw +o8xS3A0a1LXealcmlEbJibmKkEaoXci3MhryLgpaa+Kk/sH02SNatDO1vS28bPsibZpcc6deFrla +hSYnL+PW54mDTGHIcCN2fbx/Y6qspzqmtKaXrv75NBtuy9cB6KzU4j2xXbTkAwz3pRSghJJaAwdp ++yIivAD3vr0kJE3p+Ez34HMh33EXEpFoWcN+MCEQZD9WnmFViMrvfvMXLGVFQfAAcC060eGFSRJ1 +ZQ9UVQIDAQABoy0wKzAbBgNVHREEFDASgRBzbWhhdW5jaEBtYWMuY29tMAwGA1UdEwEB/wQCMAAw +DQYJKoZIhvcNAQEEBQADgYEAQMrg1n2pXVWteP7BBj+Pk3UfYtbuHb42uHcLJjfjnRlH7AxnSwrd +L3HED205w3Cq8T7tzVxIjRRLO/ljq0GedSCFBky7eYo1PrXhztGHCTSBhsiWdiyLWxKlOxGAwJc/ +lMMnwqLOdrQcoF/YgbjeaUFOQbUh94w9VDNpWZYCZwcwggM/MIICqKADAgECAgENMA0GCSqGSIb3 +DQEBBQUAMIHRMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlD +YXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0 +aW9uIFNlcnZpY2VzIERpdmlzaW9uMSQwIgYDVQQDExtUaGF3dGUgUGVyc29uYWwgRnJlZW1haWwg +Q0ExKzApBgkqhkiG9w0BCQEWHHBlcnNvbmFsLWZyZWVtYWlsQHRoYXd0ZS5jb20wHhcNMDMwNzE3 +MDAwMDAwWhcNMTMwNzE2MjM1OTU5WjBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENv +bnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElz +c3VpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMSmPFVzVftOucqZWh5owHUEcJ3f +6f+jHuy9zfVb8hp2vX8MOmHyv1HOAdTlUAow1wJjWiyJFXCO3cnwK4Vaqj9xVsuvPAsH5/EfkTYk +KhPPK9Xzgnc9A74r/rsYPge/QIACZNenprufZdHFKlSFD0gEf6e20TxhBEAeZBlyYLf7AgMBAAGj +gZQwgZEwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnRo +YXd0ZS5jb20vVGhhd3RlUGVyc29uYWxGcmVlbWFpbENBLmNybDALBgNVHQ8EBAMCAQYwKQYDVR0R +BCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGVMYWJlbDItMTM4MA0GCSqGSIb3DQEBBQUAA4GBAEiM +0VCD6gsuzA2jZqxnD3+vrL7CF6FDlpSdf0whuPg2H6otnzYvwPQcUCCTcDz9reFhYsPZOhl+hLGZ +GwDFGguCdJ4lUJRix9sncVcljd2pnDmOjCBPZV+V2vf3h9bGCE6u9uo05RAaWzVNd+NWIXiC3CEZ +Nd4ksdMdRv9dX2VPMYIC5zCCAuMCAQEwaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3Rl +IENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWls +IElzc3VpbmcgQ0ECAw5c+TAJBgUrDgMCGgUAoIIBUzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB +MBwGCSqGSIb3DQEJBTEPFw0wNTA1MDgxODE3NDZaMCMGCSqGSIb3DQEJBDEWBBQSkG9j6+hB0pKp +fV9tCi/iP59sNTB4BgkrBgEEAYI3EAQxazBpMGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3 +dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h +aWwgSXNzdWluZyBDQQIDDlz5MHoGCyqGSIb3DQEJEAILMWugaTBiMQswCQYDVQQGEwJaQTElMCMG +A1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNv +bmFsIEZyZWVtYWlsIElzc3VpbmcgQ0ECAw5c+TANBgkqhkiG9w0BAQEFAASCAQAm1GeF7dWfMvrW +8yMPjkhE+R8D1DsiCoWSCp+5gAQm7lcK7V3KrZh5howfpI3TmCZUbbaMxOH+7aKRKpFemxoBY5Q8 +rnCkbpg/++/+MI01T69hF/rgMmrGcrv2fIYy8EaARLG0xUVFSZHSP+NQSYz0TTmh4cAESHMzY3JA +nHOoUkuPyl8RXrimY1zn0lceMXlweZRouiPGuPNl1hQKw8P+GhOC5oLlM71UtStnrlk3P9gqX5v7 +Tj7Hx057oVfY8FMevjxGwU3EK5TczHezHbWWgTyum9l2ZQbUQsDJxSniD3BM46C1VcbDLPaotAZ0 +fTYLZizQfm5hcWEbfYVzkSzLAAAAAAAA +------=_Part_5028_7368284.1115579351471-- + diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email3 b/vendor/rails/actionmailer/test/fixtures/raw_email3 new file mode 100644 index 0000000..771a963 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email3 @@ -0,0 +1,70 @@ +From xxxx@xxxx.com Tue May 10 11:28:07 2005 +Return-Path: +X-Original-To: xxxx@xxxx.com +Delivered-To: xxxx@xxxx.com +Received: from localhost (localhost [127.0.0.1]) + by xxx.xxxxx.com (Postfix) with ESMTP id 50FD3A96F + for ; Tue, 10 May 2005 17:26:50 +0000 (GMT) +Received: from xxx.xxxxx.com ([127.0.0.1]) + by localhost (xxx.xxxxx.com [127.0.0.1]) (amavisd-new, port 10024) + with LMTP id 70060-03 for ; + Tue, 10 May 2005 17:26:49 +0000 (GMT) +Received: from xxx.xxxxx.com (xxx.xxxxx.com [69.36.39.150]) + by xxx.xxxxx.com (Postfix) with ESMTP id 8B957A94B + for ; Tue, 10 May 2005 17:26:48 +0000 (GMT) +Received: from xxx.xxxxx.com (xxx.xxxxx.com [64.233.184.203]) + by xxx.xxxxx.com (Postfix) with ESMTP id 9972514824C + for ; Tue, 10 May 2005 12:26:40 -0500 (CDT) +Received: by xxx.xxxxx.com with SMTP id 68so1694448wri + for ; Tue, 10 May 2005 10:26:40 -0700 (PDT) +DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; + s=beta; d=xxxxx.com; + h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type; + b=g8ZO5ttS6GPEMAz9WxrRk9+9IXBUfQIYsZLL6T88+ECbsXqGIgfGtzJJFn6o9CE3/HMrrIGkN5AisxVFTGXWxWci5YA/7PTVWwPOhJff5BRYQDVNgRKqMl/SMttNrrRElsGJjnD1UyQ/5kQmcBxq2PuZI5Zc47u6CILcuoBcM+A= +Received: by 10.54.96.19 with SMTP id t19mr621017wrb; + Tue, 10 May 2005 10:26:39 -0700 (PDT) +Received: by 10.54.110.5 with HTTP; Tue, 10 May 2005 10:26:39 -0700 (PDT) +Message-ID: +Date: Tue, 10 May 2005 11:26:39 -0600 +From: Test Tester +Reply-To: Test Tester +To: xxxx@xxxx.com, xxxx@xxxx.com +Subject: Another PDF +Mime-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_2192_32400445.1115745999735" +X-Virus-Scanned: amavisd-new at textdrive.com + +------=_Part_2192_32400445.1115745999735 +Content-Type: text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable +Content-Disposition: inline + +Just attaching another PDF, here, to see what the message looks like, +and to see if I can figure out what is going wrong here. + +------=_Part_2192_32400445.1115745999735 +Content-Type: application/pdf; name="broken.pdf" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="broken.pdf" + +JVBERi0xLjQNCiXk9tzfDQoxIDAgb2JqDQo8PCAvTGVuZ3RoIDIgMCBSDQogICAvRmlsdGVyIC9G +bGF0ZURlY29kZQ0KPj4NCnN0cmVhbQ0KeJy9Wt2KJbkNvm/od6jrhZxYln9hWEh2p+8HBvICySaE +ycLuTV4/1ifJ9qnq09NpSBimu76yLUuy/qzqcPz7+em3Ixx/CDc6CsXxs3b5+fvfjr/8cPz6/BRu +rbfAx/n3739/fuJylJ5u5fjX81OuDr4deK4Bz3z/aDP+8fz0yw8g0Ofq7ktr1Mn+u28rvhy/jVeD +QSa+9YNKHP/pxjvDNfVAx/m3MFz54FhvTbaseaxiDoN2LeMVMw+yA7RbHSCDzxZuaYB2E1Yay7QU +x89vz0+tyFDKMlAHK5yqLmnjF+c4RjEiQIUeKwblXMe+AsZjN1J5yGQL5DHpDHksurM81rF6PKab +gK6zAarIDzIiUY23rJsN9iorAE816aIu6lsgAdQFsuhhkHOUFgVjp2GjMqSewITXNQ27jrMeamkg +1rPI3iLWG2CIaSBB+V1245YVRICGbbpYKHc2USFDl6M09acQVQYhlwIrkBNLISvXhGlF1wi5FHCw +wxZkoGNJlVeJCEsqKA+3YAV5AMb6KkeaqEJQmFKKQU8T1pRi2ihE1Y4CDrqoYFFXYjJJOatsyzuI +8SIlykuxKTMibWK8H1PgEvqYgs4GmQSrEjJAalgGirIhik+p4ZQN9E3ETFPAHE1b8pp1l/0Rc1gl +fQs0ABWvyoZZzU8VnPXwVVcO9BEsyjEJaO6eBoZRyKGlrKoYoOygA8BGIzgwN3RQ15ouigG5idZQ +fx2U4Db2CqiLO0WHAZoylGiCAqhniNQjFjQPSkmjwfNTgQ6M1Ih+eWo36wFmjIxDJZiGUBiWsAyR +xX3EekGOizkGI96Ol9zVZTAivikURhRsHh2E3JhWMpSTZCnnonrLhMCodgrNcgo4uyJUJc6qnVss +nrGd1Ptr0YwisCOYyIbUwVjV4xBUNLbguSO2YHujonAMJkMdSI7bIw91Akq2AUlMUWGFTMAOamjU +OvZQCxIkY2pCpMFo/IwLdVLHs6nddwTRrgoVbvLU9eB0G4EMndV0TNoxHbt3JBWwK6hhv3iHfDtF +yokB302IpEBTnWICde4uYc/1khDbSIkQopO6lcqamGBu1OSE3N5IPSsZX00CkSHRiiyx6HQIShsS +HSVNswdVsaOUSAWq9aYhDtGDaoG5a3lBGkYt/lFlBFt1UqrYnzVtUpUQnLiZeouKgf1KhRBViRRk +ExepJCzTwEmFDalIRbLEGtw0gfpESOpIAF/NnpPzcVCG86s0g2DuSyd41uhNGbEgaSrWEXORErbw +------=_Part_2192_32400445.1115745999735-- + diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email4 b/vendor/rails/actionmailer/test/fixtures/raw_email4 new file mode 100644 index 0000000..639ad40 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email4 @@ -0,0 +1,59 @@ +Return-Path: +Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id 6AAEE3B4D23 for ; Sun, 8 May 2005 12:30:23 -0500 +Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id j48HUC213279 for ; Sun, 8 May 2005 12:30:13 -0500 +Received: from conversion-xxx.xxxx.xxx.net by xxx.xxxx.xxx id <0IG600901LQ64I@xxx.xxxx.xxx> for ; Sun, 8 May 2005 12:30:12 -0500 +Received: from agw1 by xxx.xxxx.xxx with ESMTP id <0IG600JFYLYCAxxx@xxxx.xxx> for ; Sun, 8 May 2005 12:30:12 -0500 +Date: Sun, 8 May 2005 12:30:08 -0500 +From: xxx@xxxx.xxx +To: xxx@xxxx.xxx +Message-Id: <7864245.1115573412626.JavaMxxx@xxxx.xxx> +Subject: Filth +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary=mimepart_427e4cb4ca329_133ae40413c81ef +X-Mms-Priority: 1 +X-Mms-Transaction-Id: 3198421808-0 +X-Mms-Message-Type: 0 +X-Mms-Sender-Visibility: 1 +X-Mms-Read-Reply: 1 +X-Original-To: xxx@xxxx.xxx +X-Mms-Message-Class: 0 +X-Mms-Delivery-Report: 0 +X-Mms-Mms-Version: 16 +Delivered-To: xxx@xxxx.xxx +X-Nokia-Ag-Version: 2.0 + +This is a multi-part message in MIME format. + +--mimepart_427e4cb4ca329_133ae40413c81ef +Content-Type: multipart/mixed; boundary=mimepart_427e4cb4cbd97_133ae40413c8217 + + + +--mimepart_427e4cb4cbd97_133ae40413c8217 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 7bit +Content-Disposition: inline +Content-Location: text.txt + +Some text + +--mimepart_427e4cb4cbd97_133ae40413c8217-- + +--mimepart_427e4cb4ca329_133ae40413c81ef +Content-Type: text/plain; charset=us-ascii +Content-Transfer-Encoding: 7bit + + +-- +This Orange Multi Media Message was sent wirefree from an Orange +MMS phone. If you would like to reply, please text or phone the +sender directly by using the phone number listed in the sender's +address. To learn more about Orange's Multi Media Messaging +Service, find us on the Web at xxx.xxxx.xxx.uk/mms + + +--mimepart_427e4cb4ca329_133ae40413c81ef + + +--mimepart_427e4cb4ca329_133ae40413c81ef- + diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email5 b/vendor/rails/actionmailer/test/fixtures/raw_email5 new file mode 100644 index 0000000..151c631 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email5 @@ -0,0 +1,19 @@ +Return-Path: +Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for ; Tue, 10 May 2005 15:27:05 -0500 +Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for ; Tue, 10 May 2005 15:27:04 -0500 +Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for ; Tue, 10 May 2005 15:27:03 -0500 +Date: Tue, 10 May 2005 15:27:03 -0500 +From: xxx@xxxx.xxx +Sender: xxx@xxxx.xxx +To: xxxxxxxxxxx@xxxx.xxxx.xxx +Message-Id: +X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx +Delivered-To: xxx@xxxx.xxx +Importance: normal + +Test test. Hi. Waving. m + +---------------------------------------------------------------- +Sent via Bell Mobility's Text Messaging service. +Envoyé par le service de messagerie texte de Bell Mobilité. +---------------------------------------------------------------- diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email6 b/vendor/rails/actionmailer/test/fixtures/raw_email6 new file mode 100644 index 0000000..93289c4 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email6 @@ -0,0 +1,20 @@ +Return-Path: +Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for ; Tue, 10 May 2005 15:27:05 -0500 +Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for ; Tue, 10 May 2005 15:27:04 -0500 +Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for ; Tue, 10 May 2005 15:27:03 -0500 +Date: Tue, 10 May 2005 15:27:03 -0500 +From: xxx@xxxx.xxx +Sender: xxx@xxxx.xxx +To: xxxxxxxxxxx@xxxx.xxxx.xxx +Message-Id: +X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx +Delivered-To: xxx@xxxx.xxx +Importance: normal +Content-Type: text/plain; charset=us-ascii + +Test test. Hi. Waving. m + +---------------------------------------------------------------- +Sent via Bell Mobility's Text Messaging service. +Envoyé par le service de messagerie texte de Bell Mobilité. +---------------------------------------------------------------- diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email7 b/vendor/rails/actionmailer/test/fixtures/raw_email7 new file mode 100644 index 0000000..da64ada --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email7 @@ -0,0 +1,66 @@ +Mime-Version: 1.0 (Apple Message framework v730) +Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151 +Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com> +From: foo@example.com +Subject: testing +Date: Mon, 6 Jun 2005 22:21:22 +0200 +To: blah@example.com + + +--Apple-Mail-13-196941151 +Content-Type: multipart/mixed; + boundary=Apple-Mail-12-196940926 + + +--Apple-Mail-12-196940926 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=ISO-8859-1; + delsp=yes; + format=flowed + +This is the first part. + +--Apple-Mail-12-196940926 +Content-Transfer-Encoding: 7bit +Content-Type: text/x-ruby-script; + x-unix-mode=0666; + name="test.rb" +Content-Disposition: attachment; + filename=test.rb + +puts "testing, testing" + +--Apple-Mail-12-196940926 +Content-Transfer-Encoding: base64 +Content-Type: application/pdf; + x-unix-mode=0666; + name="test.pdf" +Content-Disposition: inline; + filename=test.pdf + +YmxhaCBibGFoIGJsYWg= + +--Apple-Mail-12-196940926 +Content-Transfer-Encoding: 7bit +Content-Type: text/plain; + charset=US-ASCII; + format=flowed + + + +--Apple-Mail-12-196940926-- + +--Apple-Mail-13-196941151 +Content-Transfer-Encoding: base64 +Content-Type: application/pkcs7-signature; + name=smime.p7s +Content-Disposition: attachment; + filename=smime.p7s + +jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw +ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE +QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB +gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5= + +--Apple-Mail-13-196941151-- diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email8 b/vendor/rails/actionmailer/test/fixtures/raw_email8 new file mode 100644 index 0000000..2382dfd --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email8 @@ -0,0 +1,47 @@ +From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005 +Return-Path: +Message-ID: +Date: Sun, 8 May 2005 14:09:11 -0500 +From: xxxxxxxxx xxxxxxx +Reply-To: xxxxxxxxx xxxxxxx +To: xxxxx xxxx +Subject: Fwd: Signed email causes file attachments +In-Reply-To: +Mime-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_5028_7368284.1115579351471" +References: + +------=_Part_5028_7368284.1115579351471 +Content-Type: text/plain; charset=ISO-8859-1 +Content-Transfer-Encoding: quoted-printable +Content-Disposition: inline + +We should not include these files or vcards as attachments. + +---------- Forwarded message ---------- +From: xxxxx xxxxxx +Date: May 8, 2005 1:17 PM +Subject: Signed email causes file attachments +To: xxxxxxx@xxxxxxxxxx.com + + +Hi, + +Test attachments oddly encoded with japanese charset. + + +------=_Part_5028_7368284.1115579351471 +Content-Type: application/octet-stream; name*=iso-2022-jp'ja'01%20Quien%20Te%20Dij%8aat.%20Pitbull.mp3 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment + +MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w +ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh +d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt +YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD +ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL +7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw +------=_Part_5028_7368284.1115579351471-- + diff --git a/vendor/rails/actionmailer/test/fixtures/raw_email9 b/vendor/rails/actionmailer/test/fixtures/raw_email9 new file mode 100644 index 0000000..8b9b1ea --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/raw_email9 @@ -0,0 +1,28 @@ +Received: from xxx.xxx.xxx ([xxx.xxx.xxx.xxx] verified) + by xxx.com (CommuniGate Pro SMTP 4.2.8) + with SMTP id 2532598 for xxx@xxx.com; Wed, 23 Feb 2005 17:51:49 -0500 +Received-SPF: softfail + receiver=xxx.com; client-ip=xxx.xxx.xxx.xxx; envelope-from=xxx@xxx.xxx +quite Delivered-To: xxx@xxx.xxx +Received: by xxx.xxx.xxx (Wostfix, from userid xxx) + id 0F87F333; Wed, 23 Feb 2005 16:16:17 -0600 +Date: Wed, 23 Feb 2005 18:20:17 -0400 +From: "xxx xxx" +Message-ID: <4D6AA7EB.6490534@xxx.xxx> +To: xxx@xxx.com +Subject: Stop adware/spyware once and for all. +X-Scanned-By: MIMEDefang 2.11 (www dot roaringpenguin dot com slash mimedefang) + +You are infected with: +Ad Ware and Spy Ware + +Get your free scan and removal download now, +before it gets any worse. + +http://xxx.xxx.info?aid=3D13&?stat=3D4327kdzt + + + + +no more? (you will still be infected) +http://xxx.xxx.info/discon/?xxx@xxx.com diff --git a/vendor/rails/actionmailer/test/fixtures/second_mailer/share.rhtml b/vendor/rails/actionmailer/test/fixtures/second_mailer/share.rhtml new file mode 100644 index 0000000..9a54010 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/second_mailer/share.rhtml @@ -0,0 +1 @@ +second mail diff --git a/vendor/rails/actionmailer/test/fixtures/templates/signed_up.rhtml b/vendor/rails/actionmailer/test/fixtures/templates/signed_up.rhtml new file mode 100644 index 0000000..a85d5fa --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/templates/signed_up.rhtml @@ -0,0 +1,3 @@ +Hello there, + +Mr. <%= @recipient %> \ No newline at end of file diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.rhtml new file mode 100644 index 0000000..6940419 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.ignored.rhtml @@ -0,0 +1 @@ +Ignored when searching for implicitly multipart parts. diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak new file mode 100644 index 0000000..6940419 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak @@ -0,0 +1 @@ +Ignored when searching for implicitly multipart parts. diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml new file mode 100644 index 0000000..946d99e --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.html.rhtml @@ -0,0 +1,10 @@ + + + HTML formatted message to <%= @recipient %>. + + + + + HTML formatted message to <%= @recipient %>. + + diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml new file mode 100644 index 0000000..a6c8d54 --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.plain.rhtml @@ -0,0 +1,2 @@ +Plain text to <%= @recipient %>. +Plain text to <%= @recipient %>. diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.yaml.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.yaml.rhtml new file mode 100644 index 0000000..c14348c --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.text.yaml.rhtml @@ -0,0 +1 @@ +yaml to: <%= @recipient %> \ No newline at end of file diff --git a/vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up.rhtml b/vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up.rhtml new file mode 100644 index 0000000..a85d5fa --- /dev/null +++ b/vendor/rails/actionmailer/test/fixtures/test_mailer/signed_up.rhtml @@ -0,0 +1,3 @@ +Hello there, + +Mr. <%= @recipient %> \ No newline at end of file diff --git a/vendor/rails/actionmailer/test/mail_helper_test.rb b/vendor/rails/actionmailer/test/mail_helper_test.rb new file mode 100644 index 0000000..19f3707 --- /dev/null +++ b/vendor/rails/actionmailer/test/mail_helper_test.rb @@ -0,0 +1,91 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" + +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 + +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{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 + diff --git a/vendor/rails/actionmailer/test/mail_render_test.rb b/vendor/rails/actionmailer/test/mail_render_test.rb new file mode 100644 index 0000000..42454fe --- /dev/null +++ b/vendor/rails/actionmailer/test/mail_render_test.rb @@ -0,0 +1,79 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" + +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 + +class FirstMailer < ActionMailer::Base + def share(recipient) + recipients recipient + subject "using helpers" + from "tester@example.com" + end +end + +class SecondMailer < ActionMailer::Base + def share(recipient) + recipients recipient + subject "using helpers" + from "tester@example.com" + end +end + +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 + +class FirstSecondHelperTest < Test::Unit::TestCase + def setup + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + + @recipient = 'test@localhost' + end + + def test_ordering + mail = FirstMailer.create_share(@recipient) + assert_equal "first mail", mail.body.strip + mail = SecondMailer.create_share(@recipient) + assert_equal "second mail", mail.body.strip + mail = FirstMailer.create_share(@recipient) + assert_equal "first mail", mail.body.strip + mail = SecondMailer.create_share(@recipient) + assert_equal "second mail", mail.body.strip + end +end diff --git a/vendor/rails/actionmailer/test/mail_service_test.rb b/vendor/rails/actionmailer/test/mail_service_test.rb new file mode 100755 index 0000000..bcce81e --- /dev/null +++ b/vendor/rails/actionmailer/test/mail_service_test.rb @@ -0,0 +1,822 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" + +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 " + attachment :content_type => "image/jpeg", + :body => "not really a jpeg, we're only testing, after all" + 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 " + @sent_on = Time.local 2004, 12, 12 + @cc = "Grytøyr " + @bcc = "Grytøyr " + @body = "Nothing to see here." + @charset = "iso-8859-1" + end + + def utf8_body(recipient) + @recipients = recipient + @subject = "testing utf-8 body" + @from = "Foo áëô îü " + @sent_on = Time.local 2004, 12, 12 + @cc = "Foo áëô îü " + @bcc = "Foo áëô îü " + @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 = "blah" + 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 = "blah" + 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 "Emphasize this" + content_type "text/html" + end + + def html_mail_with_underscores(recipient) + subject "html mail with underscores" + body %{_Google} + 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 => "

    line #1

    \n

    line #2

    \r

    line #3

    \r\n

    line #4

    \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 => "test HTML
    \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' => '' } + 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 " + cc "Three: Four " + bcc "Five: Six " + 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 < 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 "", 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 = <" + + 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 ", "iso-8859-1" + expected.cc = quote_address_if_necessary "Grytøyr ", "iso-8859-1" + expected.bcc = quote_address_if_necessary "Grytøyr ", "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 áëô îü " + 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 áëô îü\" ", "\"Example Recipient\" "] + 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_.*?\?= \r/, created.encoded) + assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= , Example Recipient _Google}, 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 "

    line #1

    \n

    line #2

    \n

    line #3

    \n

    line #4

    \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 diff --git a/vendor/rails/actionmailer/test/quoting_test.rb b/vendor/rails/actionmailer/test/quoting_test.rb new file mode 100644 index 0000000..0b145b1 --- /dev/null +++ b/vendor/rails/actionmailer/test/quoting_test.rb @@ -0,0 +1,45 @@ +require "#{File.dirname(__FILE__)}/abstract_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 diff --git a/vendor/rails/actionmailer/test/tmail_test.rb b/vendor/rails/actionmailer/test/tmail_test.rb new file mode 100644 index 0000000..7d83a68 --- /dev/null +++ b/vendor/rails/actionmailer/test/tmail_test.rb @@ -0,0 +1,13 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" + +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 diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG new file mode 100644 index 0000000..faf5228 --- /dev/null +++ b/vendor/rails/actionpack/CHANGELOG @@ -0,0 +1,2769 @@ +*SVN* + +* Deprecation: check whether instance variables have been monkeyed with before assigning them to deprecation proxies. Raises a RuntimeError if so. [Jeremy Kemper] + +* Add support for the param_name parameter to the auto_complete_field helper. #5026 [david.a.williams@gmail.com] + +* Deprecation! @params, @session, @flash will be removed after 1.2. Use the corresponding instance methods instead. You'll get printed warnings during tests and logged warnings in dev mode when you access either instance variable directly. [Jeremy Kemper] + +* Make Routing noisy when an anchor regexp is assigned to a segment. #5674 [francois.beausoleil@gmail.com] + +* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 [pjhyett@gmail.com] + +* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com] + +* Make controller_path available as an instance method. #5724 [jmckible@gmail.com] + +* Update query parser to support adjacent hashes. [Nicholas Seckar] + +* Make action caching aware of different formats for the same action so that, e.g. foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. [Marcel Molina Jr.] + +* Restrict Request Method hacking with ?_method to POST requests. [Rick Olson] + +* Fix bug when passing multiple options to SimplyRestful, like :new => { :preview => :get, :draft => :get }. [Rick Olson, Josh Susser, Lars Pind] + +* Dup the options passed to map.resources so that multiple resources get the same options. [Rick Olson] + +* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. [Rick Olson] + +* Added map.resources from the Simply Restful plugin [DHH]. Examples (the API has changed to use plurals!): + + map.resources :messages + map.resources :messages, :comments + map.resources :messages, :new => { :preview => :post } + +* Fixed that integration simulation of XHRs should set Accept header as well [Edward Frederick] + +* TestRequest#reset_session should restore a TestSession, not a hash [Koz] + +* Don't search a load-path of '.' for controller files [Jamis Buck] + +* Update integration.rb to require test_process explicitly instead of via Dependencies. [Nicholas Seckar] + +* Fixed that you can still access the flash after the flash has been reset in reset_session. Closes #5584 [lmarlow@yahoo.com] + +* Allow form_for and fields_for to work with indexed form inputs. [Jeremy Kemper, Matt Lyon] + + <% form_for 'post[]', @post do |f| -%> + <% end -%> + +* Remove leak in development mode by replacing define_method with module_eval. [Nicholas Seckar] + +* Provide support for decimal columns to form helpers. Closes #5672. [dave@pragprog.com] + +* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net] + +* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info] + +* Reset @html_document between requests so assert_tag works. #4810 [jarkko@jlaine.net, easleydp@gmail.com] + +* Update render :partial documentation. #5646 [matt@mattmargolis.net] + +* Integration tests behave well with render_component. #4632 [edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com] + +* Added exception handling of missing layouts #5373 [chris@ozmm.org] + +* Fixed that real files and symlinks should be treated the same when compiling templates #5438 [zachary@panandscan.com] + +* Fixed that the flash should be reset when reset_session is called #5584 [shugo@ruby-lang.org] + +* Added special case for "1 Byte" in NumberHelper#number_to_human_size #5593 [murpyh@rubychan.de] + +* Fixed proper form-encoded parameter parsing for requests with "Content-Type: application/x-www-form-urlencoded; charset=utf-8" (note the presence of a charset directive) [DHH] + +* Add route_name_path method to generate only the path for a named routes. For example, map.person will add person_path. [Nicholas Seckar] + +* Avoid naming collision among compiled view methods. [Jeremy Kemper] + +* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [mislav@nippur.irb.hr] + +* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de] + +* Fix that routes with *path segments in the recall can generate URLs. [Rick] + +* Fix strip_links so that it doesn't hang on multiline tags [Jamis Buck] + +* Remove problematic control chars in rescue template. #5316 [Stefan Kaes] + +* Make sure passed routing options are not mutated by routing code. #5314 [Blair Zajac] + +* Make sure changing the controller from foo/bar to bing/bang does not change relative to foo. [Jamis Buck] + +* Escape the path before routing recognition. #3671 + +* Make sure :id and friends are unescaped properly. #5275 [me@julik.nl] + +* Fix documentation for with_routing to reflect new reality. #5281 [rramdas@gmail.com] + +* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 [mklame@atxeu.com, matthew@walker.wattle.id.au] + +* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types [DHH] + +* Added Mime::Type.register(string, symbol, synonyms = []) for adding new custom mime types [DHH]. Example: Mime::Type.register("image/gif", :gif) + +* Added support for Mime objects in render :content_type option [DHH]. Example: render :text => some_atom, :content_type => Mime::ATOM + +* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra ] + +* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck] + + map.connect '/foo/:id', :controller => '...', :action => '...' + map.connect '/foo/:id.:format', :controller => '...', :action => '...' + map.connect '/foo/:id', ..., :conditions => { :method => :get } + +* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper] + +* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com] + +* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output [DHH]. Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find :all + + respond_to do |format| + format.html + format.xml { render :xml => @posts.to_xml } + format.rss { render :action => "feed.rxml" } + end + end + end + + # returns HTML when requested by a browser, since the browser + # has the HTML mimetype at the top of its priority list + Accept: text/html + GET /weblog + + # returns the XML + Accept: application/xml + GET /weblog + + # returns the HTML + Accept: application/xml + GET /weblog.html + + # returns the XML + Accept: text/html + GET /weblog.xml + + All this relies on the fact that you have a route that includes .:format. + +* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [DHH] + +* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [DHH] + +* follow_redirect doesn't complain about being redirected to the same controller. #5153 [dymo@mk.ukrtelecom.ua] + +* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. [Kevin Clark kevin.clark@gmail.com] + +* Fix NoMethodError when parsing params like &&. [Adam Greenfield] + +* Fix flip flopped logic in docs for url_for's :only_path option. Closes #4998. [esad@esse.at] + +* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper] + +* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous] + +* Small fix in routing to allow dynamic routes (broken after [4242]) [Rick] + + map.connect '*path', :controller => 'files', :action => 'show' + +* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.] + +* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.] + +* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac ] + +* Documentation fix: integration test scripts don't require integration_test. Closes #4914. [Frederick Ros ] + +* ActionController::Base Summary documentation rewrite. Closes #4900. [kevin.clark@gmail.com] + +* Fix text_helper.rb documentation rendering. Closes #4725. [Frederick Ros] + +* Fixes bad rendering of JavaScriptMacrosHelper rdoc (closes #4910) [Frederick Ros] + +* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.] + + error_messages_for :account, :user, :subscription, :object_name => :account + +* Enhance documentation for setting headers in integration tests. Skip auto HTTP prepending when its already there. Closes #4079. [Rick Olson] + +* Documentation for AbstractRequest. Closes #4895. [kevin.clark@gmail.com] + +* Refactor various InstanceTag instance method to class methods. Closes #4800. [skaes@web.de] + +* Remove all remaining references to @params in the documentation. [Marcel Molina Jr.] + +* Add documentation for redirect_to :back's RedirectBackError exception. [Marcel Molina Jr.] + +* Update layout and content_for documentation to use yield rather than magic @content_for instance variables. [Marcel Molina Jr.] + +* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick] + + redirect_to :action => 'new' + assert_redirected_to :controller => 'monkeys', :action => 'new' + +* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick] + +* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick] + + assert_recognizes({:controller => 'users', :action => 'index'}, 'users') + assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post}) + +* Diff compared options with #assert_redirected_to [Rick] + +* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action [Jamis Buck] + +* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson] + +* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [DHH] + +* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck] + +* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson] + ex. verify :only => :speak, :method => :post, + :render => { :status => 405, :text => "Must be post" }, + :add_headers => { "Allow" => "POST" } + +* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled #1897 [jeremye@bsa.ca.gov] + + +*1.12.1* (April 6th, 2006) + +* Fixed that template extensions would be cached development mode #4624 [Stefan Kaes] + +* Update to Prototype 1.5.0_rc0 [Sam Stephenson] + +* Honor skipping filters conditionally for only certain actions even when the parent class sets that filter to conditionally be executed only for the same actions. #4522 [Marcel Molina Jr.] + +* Delegate xml_http_request in integration tests to the session instance. [Jamis Buck] + +* Update the diagnostics template skip the useless '' text. [Nicholas Seckar] + +* CHANGED DEFAULT: Don't parse YAML input by default, but keep it available as an easy option [DHH] + +* Add additional autocompleter options [aballai, Thomas Fuchs] + +* Fixed fragment caching of binary data on Windows #4493 [bellis@deepthought.org] + +* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us [Sam Stephenson, Thomas Fuchs] + +* Added automated timestamping to AssetTagHelper methods for stylesheets, javascripts, and images when Action Controller is run under Rails [DHH]. Example: + + image_tag("rails.png") # => 'Rails' + + ...to avoid frequent stats (not a problem for most people), you can set RAILS_ASSET_ID in the ENV to avoid stats: + + ENV["RAILS_ASSET_ID"] = "2345" + image_tag("rails.png") # => 'Rails' + + This can be used by deployment managers to set the asset id by application revision + + +*1.12.0* (March 27th, 2006) + +* Add documentation for respond_to. [Jamis Buck] + +* Fixed require of bluecloth and redcloth when gems haven't been loaded #4446 [murphy@cYcnus.de] + +* Update to Prototype 1.5.0_pre1 [Sam Stephenson] + +* Change #form_for and #fields_for so that the second argument is not required [Dave Thomas] + + <% form_for :post, @post, :url => { :action => 'create' } do |f| -%> + + becomes... + + <% form_for :post, :url => { :action => 'create' } do |f| -%> + +* Update to script.aculo.us 1.6 [Thomas Fuchs] + +* Enable application/x-yaml processing by default [Jamis Buck] + +* Fix double url escaping of remote_function. Add :escape => false option to ActionView's url_for. [Nicholas Seckar] + +* Add :script option to in_place_editor to support evalScripts (closes #4194) [codyfauser@gmail.com] + +* Fix mixed case enumerable methods in the JavaScript Collection Proxy (closes #4314) [codyfauser@gmail.com] + +* Undo accidental escaping for mail_to; add regression test. [Nicholas Seckar] + +* Added nicer message for assert_redirected_to (closes #4294) [court3nay] + + assert_redirected_to :action => 'other_host', :only_path => false + + when it was expecting... + + redirected_to :action => 'other_host', :only_path => true, :host => 'other.test.host' + + gives the error message... + + response is not a redirection to all of the options supplied (redirection is <{:only_path=>false, :host=>"other.test.host", :action=>"other_host"}>), difference: <{:only_path=>"true", :host=>"other.test.host"}> + +* Change url_for to escape the resulting URLs when called from a view. [Nicholas Seckar, coffee2code] + +* Added easy support for testing file uploads with fixture_file_upload #4105 [turnip@turnipspatch.com]. Example: + + # Looks in Test::Unit::TestCase.fixture_path + '/files/spongebob.png' + post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png') + +* Fixed UrlHelper#current_page? to behave even when url-escaped entities are present #3929 [jeremy@planetargon.com] + +* Add ability for relative_url_root to be specified via an environment variable RAILS_RELATIVE_URL_ROOT. [isaac@reuben.com, Nicholas Seckar] + +* Fixed link_to "somewhere", :post => true to produce valid XHTML by using the parentnode instead of document.body for the instant form #3007 [Bob Silva] + +* Added :function option to PrototypeHelper#observe_field/observe_form that allows you to call a function instead of submitting an ajax call as the trigger #4268 [jonathan@daikini.com] + +* Make Mime::Type.parse consider q values (if any) [Jamis Buck] + +* XML-formatted requests are typecast according to "type" attributes for :xml_simple [Jamis Buck] + +* Added protection against proxy setups treating requests as local even when they're not #3898 [stephen_purcell@yahoo.com] + +* Added TestRequest#raw_post that simulate raw_post from CgiRequest #3042 [francois.beausoleil@gmail.com] + +* Underscore dasherized keys in formatted requests [Jamis Buck] + +* Add MimeResponds::Responder#any for managing multiple types with identical responses [Jamis Buck] + +* Make the xml_http_request testing method set the HTTP_ACCEPT header [Jamis Buck] + +* Add Verification to scaffolds. Prevent destructive actions using GET [Michael Koziarski] + +* Avoid hitting the filesystem when using layouts by using a File.directory? cache. [Stefan Kaes, Nicholas Seckar] + +* Simplify ActionController::Base#controller_path [Nicholas Seckar] + +* Added simple alert() notifications for RJS exceptions when config.action_view.debug_rjs = true. [Sam Stephenson] + +* Added :content_type option to render, so you can change the content type on the fly [DHH]. Example: render :action => "atom.rxml", :content_type => "application/atom+xml" + +* CHANGED DEFAULT: The default content type for .rxml is now application/xml instead of type/xml, see http://www.xml.com/pub/a/2004/07/21/dive.html for reason [DHH] + +* Added option to render action/template/file of a specific extension (and here by template type). This means you can have multiple templates with the same name but a different extension [DHH]. Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find :all + + respond_to do |type| + type.html # using defaults, which will render weblog/index.rhtml + type.xml { render :action => "index.rxml" } + type.js { render :action => "index.rjs" } + end + end + end + +* Added better support for using the same actions to output for different sources depending on the Accept header [DHH]. Example: + + class WeblogController < ActionController::Base + def create + @post = Post.create(params[:post]) + + respond_to do |type| + type.js { render } # renders create.rjs + type.html { redirect_to :action => "index" } + type.xml do + headers["Location"] = url_for(:action => "show", :id => @post) + render(:nothing, :status => "201 Created") + end + end + end + end + +* Added Base#render(:xml => xml) that works just like Base#render(:text => text), but sets the content-type to text/xml and the charset to UTF-8 [DHH] + +* Integration test's url_for now runs in the context of the last request (if any) so after post /products/show/1 url_for :action => 'new' will yield /product/new [Tobias Luetke] + +* Re-added mixed-in helper methods for the JavascriptGenerator. Moved JavascriptGenerators methods to a module that is mixed in after the helpers are added. Also fixed that variables set in the enumeration methods like #collect are set correctly. Documentation added for the enumeration methods [Rick Olson]. Examples: + + page.select('#items li').collect('items') do |element| + element.hide + end + # => var items = $$('#items li').collect(function(value, index) { return value.hide(); }); + +* Added plugin support for parameter parsers, which allows for better support for REST web services. By default, posts submitted with the application/xml content type is handled by creating a XmlSimple hash with the same name as the root element of the submitted xml. More handlers can easily be registered like this: + + # Assign a new param parser to a new content type + ActionController::Base.param_parsers['application/atom+xml'] = Proc.new do |data| + node = REXML::Document.new(post) + { node.root.name => node.root } + end + + # Assign the default XmlSimple to a new content type + ActionController::Base.param_parsers['application/backpack+xml'] = :xml_simple + +Default YAML web services were retired, ActionController::Base.param_parsers carries an example which shows how to get this functionality back. As part of this new plugin support, request.[formatted_post?, xml_post?, yaml_post? and post_format] were all deprecated in favor of request.content_type [Tobias Luetke] + +* Fixed Effect.Appear in effects.js to work with floats in Safari #3524, #3813, #3044 [Thomas Fuchs] + +* Fixed that default image extension was not appended when using a full URL with AssetTagHelper#image_tag #4032, #3728 [rubyonrails@beautifulpixel.com] + +* Added that page caching will only happen if the response code is less than 400 #4033 [g.bucher@teti.ch] + +* Add ActionController::IntegrationTest to allow high-level testing of the way the controllers and routes all work together [Jamis Buck] + +* Added support to AssetTagHelper#javascript_include_tag for having :defaults appear anywhere in the list, so you can now make one call ala javascript_include_tag(:defaults, "my_scripts") or javascript_include_tag("my_scripts", :defaults) depending on how you want the load order #3506 [Bob Silva] + +* Added support for visual effects scoped queues to the visual_effect helper #3530 [Abdur-Rahman Advany] + +* Added .rxml (and any non-rhtml template, really) supportfor CaptureHelper#content_for and CaptureHelper#capture #3287 [Brian Takita] + +* Added script.aculo.us drag and drop helpers to RJS [Thomas Fuchs]. Examples: + + page.draggable 'product-1' + page.drop_receiving 'wastebasket', :url => { :action => 'delete' } + page.sortable 'todolist', :url => { action => 'change_order' } + +* Fixed that form elements would strip the trailing [] from the first parameter #3545 [ruby@bobsilva.com] + +* During controller resolution, update the NameError suppression to check for the expected constant. [Nicholas Seckar] + +* Update script.aculo.us to V1.5.3 [Thomas Fuchs] + +* Added various InPlaceEditor options, #3746, #3891, #3896, #3906 [Bill Burcham, ruairi, sl33p3r] + +* Added :count option to pagination that'll make it possible for the ActiveRecord::Base.count call to using something else than * for the count. Especially important for count queries using DISTINCT #3839 [skaes] + +* Update script.aculo.us to V1.5.2 [Thomas Fuchs] + +* Added element and collection proxies to RJS [DHH]. Examples: + + page['blank_slate'] # => $('blank_slate'); + page['blank_slate'].show # => $('blank_slate').show(); + page['blank_slate'].show('first').up # => $('blank_slate').show('first').up(); + + 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(); + +* Add JavaScriptGenerator#replace for replacing an element's "outer HTML". #3246 [tom@craz8.com, Sam Stephenson] + +* Remove over-engineered form_for code for a leaner implementation. [Nicholas Seckar] + +* Document form_for's :html option. [Nicholas Seckar] + +* Major components cleanup and speedup. #3527 [Stefan Kaes] + +* Fix problems with pagination and :include. [Kevin Clark] + +* Add ActiveRecordTestCase for testing AR integration. [Kevin Clark] + +* Add Unit Tests for pagination [Kevin Clark] + +* Add :html option for specifying form tag options in form_for. [Sam Stephenson] + +* Replace dubious controller parent class in filter docs. #3655, #3722 [info@rhalff.com, eigentone@gmail.com] + +* Don't interpret the :value option on text_area as an html attribute. Set the text_area's value. #3752 [gabriel@gironda.org] + +* Fix remote_form_for creates a non-ajax form. [Rick Olson] + +* Don't let arbitrary classes match as controllers -- a potentially dangerous bug. [Nicholas Seckar] + +* Fix Routing tests. Fix routing where failing to match a controller would prevent the rest of routes from being attempted. [Nicholas Seckar] + +* Add :builder => option to form_for and friends. [Nicholas Seckar, Rick Olson] + +* Fix controller resolution to avoid accidentally inheriting a controller from a parent module. [Nicholas Seckar] + +* Set sweeper's @controller to nil after a request so that the controller may be collected between requests. [Nicholas Seckar] + +* Subclasses of ActionController::Caching::Sweeper should be Reloadable. [Rick Olson] + +* Document the :xhr option for verifications. #3666 [leeo] + +* Added :only and :except controls to skip_before/after_filter just like for when you add filters [DHH] + +* Ensure that the instance variables are copied to the template when performing render :update. [Nicholas Seckar] + +* Add the ability to call JavaScriptGenerator methods from helpers called in update blocks. [Sam Stephenson] Example: + module ApplicationHelper + def update_time + page.replace_html 'time', Time.now.to_s(:db) + page.visual_effect :highlight, 'time' + end + end + + class UserController < ApplicationController + def poll + render :update { |page| page.update_time } + end + end + +* Add render(:update) to ActionView::Base. [Sam Stephenson] + +* Fix render(:update) to not render layouts. [Sam Stephenson] + +* Fixed that SSL would not correctly be detected when running lighttpd/fcgi behind lighttpd w/mod_proxy #3548 [stephen_purcell@yahoo.com] + +* Added the possibility to specify atomatic expiration for the memcachd session container #3571 [Stefan Kaes] + +* Change layout discovery to take into account the change in semantics with File.join and nil arguments. [Marcel Molina Jr.] + +* Raise a RedirectBackError if redirect_to :back is called when there's no HTTP_REFERER defined #3049 [kevin.clark@gmail.com] + +* Treat timestamps like datetimes for scaffolding purposes #3388 [Maik Schmidt] + +* Fix IE bug with link_to "something", :post => true #3443 [Justin Palmer] + +* Extract Test::Unit::TestCase test process behavior into an ActionController::TestProcess module. [Sam Stephenson] + +* Pass along blocks from render_to_string to render. [Sam Stephenson] + +* Add render :update for inline RJS. [Sam Stephenson] Example: + class UserController < ApplicationController + def refresh + render :update do |page| + page.replace_html 'user_list', :partial => 'user', :collection => @users + page.visual_effect :highlight, 'user_list' + end + end + end + +* allow nil objects for error_messages_for [Michael Koziarski] + +* Refactor human_size to exclude decimal place if it is zero. [Marcel Molina Jr.] + +* Update to Prototype 1.5.0_pre0 [Sam Stephenson] + +* Automatically discover layouts when a controller is namespaced. #2199, #3424 [me@jonnii.com rails@jeffcole.net Marcel Molina Jr.] + +* Add support for multiple proxy servers to CgiRequest#host [gaetanot@comcast.net] + +* Documentation typo fix. #2367 [Blair Zajac] + +* Remove Upload Progress. #2871 [Sean Treadway] + +* Fix typo in function name mapping in auto_complete_field. #2929 #3446 [doppler@gmail.com phil.ross@gmail.com] + +* Allow auto-discovery of third party template library layouts. [Marcel Molina Jr.] + +* Have the form builder output radio button, not check box, when calling the radio button helper. #3331 [LouisStAmour@gmail.com] + +* Added assignment of the Autocompleter object created by JavaScriptMacroHelper#auto_complete_field to a local javascript variables [DHH] + +* Added :on option for PrototypeHelper#observe_field that allows you to specify a different callback hook to have the observer trigger on [DHH] + +* Added JavaScriptHelper#button_to_function that works just like JavaScriptHelper#link_to_function but uses a button instead of a href [DHH] + +* Added that JavaScriptHelper#link_to_function will honor existing :onclick definitions when adding the function call [DHH] + +* Added :disable_with option to FormTagHelper#submit_tag to allow for easily disabled submit buttons with different text [DHH] + +* Make auto_link handle nil by returning quickly if blank? [Scott Barron] + +* Make auto_link match urls with a port number specified. [Marcel Molina Jr.] + +* Added support for toggling visual effects to ScriptaculousHelper::visual_effect, #3323. [Thomas Fuchs] + +* Update to script.aculo.us to 1.5.0 rev. 3343 [Thomas Fuchs] + +* Added :select option for JavaScriptMacroHelper#auto_complete_field that makes it easier to only use part of the auto-complete suggestion as the value for insertion [Thomas Fuchs] + +* Added delayed execution of Javascript from within RJS #3264 [devslashnull@gmail.com]. Example: + + page.delay(20) do + page.visual_effect :fade, 'notice' + end + +* Add session ID to default logging, but remove the verbose description of every step [DHH] + +* Add the following RJS methods: [Sam Stephenson] + + * alert - Displays an alert() dialog + * redirect_to - Changes window.location.href to simulate a browser redirect + * call - Calls a JavaScript function + * assign - Assigns to a JavaScript variable + * << - Inserts an arbitrary JavaScript string + +* Fix incorrect documentation for form_for [Nicholas Seckar] + +* Don't include a layout when rendering an rjs template using render's :template option. [Marcel Molina Jr.] + +*1.1.2* (December 13th, 2005) + +* Become part of Rails 1.0 + +* Update to script.aculo.us 1.5.0 final (equals 1.5.0_rc6) [Thomas Fuchs] + +* Update to Prototype 1.4.0 final [Sam Stephenson] + +* Added form_remote_for (form_for meets form_remote_tag) [DHH] + +* Update to script.aculo.us 1.5.0_rc6 + +* More robust relative url root discovery for SCGI compatibility. This solves the 'SCGI routes problem' -- you no longer need to prefix all your routes with the name of the SCGI mountpoint. #3070 [Dave Ringoen] + +* Fix docs for text_area_tag. #3083. [Christopher Cotton] + +* Change form_for and fields_for method signatures to take object name and object as separate arguments rather than as a Hash. [DHH] + +* Introduce :selected option to the select helper. Allows you to specify a selection other than the current value of object.method. Specify :selected => nil to leave all options unselected. #2991 [Jonathan Viney ] + +* Initialize @optional in routing code to avoid warnings about uninitialized access to an instance variable. [Nicholas Seckar] + +* Make ActionController's render honor the :locals option when rendering a :file. #1665. [Emanuel Borsboom, Marcel Molina Jr.] + +* Allow assert_tag(:conditions) to match the empty string when a tag has no children. Closes #2959. [Jamis Buck] + +* Update html-scanner to handle CDATA sections better. Closes #2970. [Jamis Buck] + +* Don't put flash in session if sessions are disabled. [Jeremy Kemper] + +* Strip out trailing &_= for raw post bodies. Closes #2868. [Sam Stephenson] + +* Pass multiple arguments to Element.show and Element.hide in JavaScriptGenerator instead of using iterators. [Sam Stephenson] + +* Improve expire_fragment documentation. #2966 [court3nay@gmail.com] + +* Correct docs for automatic layout assignment. #2610. [Charles M. Gerungan] + +* Always create new AR sessions rather than trying too hard to avoid database traffic. #2731 [Jeremy Kemper] + +* Update to Prototype 1.4.0_rc4. Closes #2943 (old Array.prototype.reverse behavior can be obtained by passing false as an argument). [Sam Stephenson] + +* Use Element.update('id', 'html') instead of $('id').innerHTML = 'html' in JavaScriptGenerator#replace_html so that script tags are evaluated. [Sam Stephenson] + +* Make rjs templates always implicitly skip out on layouts. [Marcel Molina Jr.] + +* Correct length for the truncate text helper. #2913 [Stefan Kaes] + +* Update to Prototype 1.4.0_rc3. Closes #1893, #2505, #2550, #2748, #2783. [Sam Stephenson] + +* Add support for new rjs templates which wrap an update_page block. [Marcel Molina Jr.] + +* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.] + +* Correct time_zone_options_for_select docs. #2892 [pudeyo@rpi.com] + +* Remove the unused, slow response_dump and session_dump variables from error pages. #1222 [lmarlow@yahoo.com] + +* Performance tweaks: use Set instead of Array to speed up prototype helper include? calls. Avoid logging code if logger is nil. Inline commonly-called template presence checks. #2880, #2881, #2882, #2883 [Stefan Kaes] + +* MemCache store may be given multiple addresses. #2869 [Ryan Carver ] + +* Handle cookie parsing irregularity for certain Nokia phones. #2530 [zaitzow@gmail.com] + +* Added PrototypeHelper::JavaScriptGenerator and PrototypeHelper#update_page for easily modifying multiple elements in an Ajax response. [Sam Stephenson] Example: + + update_page do |page| + page.insert_html :bottom, 'list', '
  • Last item
  • ' + page.visual_effect :highlight, 'list' + page.hide 'status-indicator', 'cancel-link' + end + + generates the following JavaScript: + + new Insertion.Bottom("list", "
  • Last item
  • "); + new Effect.Highlight("list"); + ["status-indicator", "cancel-link"].each(Element.hide); + +* Refactored JavaScriptHelper into PrototypeHelper and ScriptaculousHelper [Sam Stephenson] + +* Update to latest script.aculo.us version (as of [3031]) + +* Updated docs for in_place_editor, fixes a couple bugs and offers extended support for external controls [Justin Palmer] + +* Update documentation for render :file. #2858 [Tom Werner] + +* Only include builtin filters whose filenames match /^[a-z][a-z_]*_helper.rb$/ to avoid including operating system metadata such as ._foo_helper.rb. #2855 [court3nay@gmail.com] + +* Added FormHelper#form_for and FormHelper#fields_for that makes it easier to work with forms for single objects also if they don't reside in instance variables [DHH]. 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 %> + + <% 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 %> + +* options_for_select allows any objects which respond_to? :first and :last rather than restricting to Array and Range. #2824 [Jacob Robbins , Jeremy Kemper] + +* The auto_link text helper accepts an optional block to format the link text for each url and email address. Example: auto_link(post.body) { |text| truncate(text, 10) } [Jeremy Kemper] + +* assert_tag uses exact matches for string conditions, instead of partial matches. Use regex to do partial matches. #2799 [Jamis Buck] + +* CGI::Session::ActiveRecordStore.data_column_name = 'foobar' to use a different session data column than the 'data' default. [nbpwie102@sneakemail.com] + +* Do not raise an exception when default helper is missing; log a debug message instead. It's nice to delete empty helpers. [Jeremy Kemper] + +* Controllers with acronyms in their names (e.g. PDFController) require the correct default helper (PDFHelper in file pdf_helper.rb). #2262 [jeff@opendbms.com] + + +*1.11.0* (November 7th, 2005) + +* Added request as instance method to views, so you can do <%= request.env["HTTP_REFERER"] %>, just like you can already access response, session, and the likes [DHH] + +* Fix conflict with assert_tag and Glue gem #2255 [david.felstead@gmail.com] + +* Add documentation to assert_tag indicating that it only works with well-formed XHTML #1937, #2570 [Jamis Buck] + +* Added action_pack.rb stub so that ActionPack::Version loads properly [Sam Stephenson] + +* Added short-hand to assert_tag so assert_tag :tag => "span" can be written as assert_tag "span" [DHH] + +* Added skip_before_filter/skip_after_filter for easier control of the filter chain in inheritance hierachies [DHH]. Example: + + class ApplicationController < ActionController::Base + before_filter :authenticate + end + + class WeblogController < ApplicationController + # will run the :authenticate filter + end + + class SignupController < ActionController::Base + # will not run the :authenticate filter + skip_before_filter :authenticate + end + +* Added redirect_to :back as a short-hand for redirect_to(request.env["HTTP_REFERER"]) [DHH] + +* Change javascript_include_tag :defaults to not use script.aculo.us loader, which facilitates the use of plugins for future script.aculo.us and third party javascript extensions, and provide register_javascript_include_default for plugins to specify additional JavaScript files to load. Removed slider.js and builder.js from actionpack. [Thomas Fuchs] + +* Fix problem where redirecting components can cause an infinite loop [Rick Olson] + +* Added support for the queue option on visual_effect [Thomas Fuchs] + +* Update script.aculo.us to V1.5_rc4 [Thomas Fuchs] + +* Fix that render :text didn't interpolate instance variables #2629, #2626 [skaes] + +* Fix line number detection and escape RAILS_ROOT in backtrace Regexp [Nicholas Seckar] + +* Fixed document.getElementsByClassName from Prototype to be speedy again [Sam Stephenson] + +* Recognize ./#{RAILS_ROOT} as RAILS_ROOT in error traces [Nicholas Seckar] + +* Remove ARStore session fingerprinting [Nicholas Seckar] + +* Fix obscure bug in ARStore [Nicholas Seckar] + +* Added TextHelper#strip_tags for removing HTML tags from a string (using HTMLTokenizer) #2229 [marcin@junkheap.net] + +* Added a reader for flash.now, so it's possible to do stuff like flash.now[:alert] ||= 'New if not set' #2422 [Caio Chassot] + + +*1.10.2* (October 26th, 2005) + +* Reset template variables after using render_to_string [skaes@web.de] + +* Expose the session model backing CGI::Session + +* Abbreviate RAILS_ROOT in traces + + +*1.10.1* (October 19th, 2005) + +* Update error trace templates [Nicholas Seckar] + +* Stop showing generated routing code in application traces [Nicholas Seckar] + + +*1.10.0* (October 16th, 2005) + +* Make string-keys locals assigns optional. Add documentation describing depreciated state [skaes@web.de] + +* Improve line number detection for template errors [Nicholas Seckar] + +* Update/clean up documentation (rdoc) + +* Upgrade to Prototype 1.4.0_rc0 [Sam Stephenson] + +* Added assert_vaild. Reports the proper AR error messages as fail message when the passed record is invalid [Tobias Luetke] + +* Add temporary support for passing locals to render using string keys [Nicholas Seckar] + +* Clean up error pages by providing better backtraces [Nicholas Seckar] + +* Raise an exception if an attempt is made to insert more session data into the ActiveRecordStore data column than the column can hold. #2234. [justin@textdrive.com] + +* Removed references to assertions.rb from actionpack assert's backtraces. Makes error reports in functional unit tests much less noisy. [Tobias Luetke] + +* Updated and clarified documentation for JavaScriptHelper to be more concise about the various options for including the JavaScript libs. [Thomas Fuchs] + +* Hide "Retry with Breakpoint" button on error pages until feature is functional. [DHH] + +* Fix Request#host_with_port to use the standard port when Rails is behind a proxy. [Nicholas Seckar] + +* Escape query strings in the href attribute of URLs created by url_helper. #2333 [Michael Schuerig ] + +* Improved line number reporting for template errors [Nicholas Seckar] + +* Added :locals support for render :inline #2463 [mdabney@cavoksolutions.com] + +* Unset the X-Requested-With header when using the xhr wrapper in functional tests so that future requests aren't accidentally xhr'ed #2352 [me@julik.nl, Sam Stephenson] + +* Unescape paths before writing cache to file system. #1877. [Damien Pollet] + +* Wrap javascript_tag contents in a CDATA section and add a cdata_section method to TagHelper #1691 [Michael Schuerig, Sam Stephenson] + +* Misc doc fixes (typos/grammar/etc). #2445. [coffee2code] + +* Speed improvement for session_options. #2287. [skaes@web.de] + +* Make cacheing binary files friendly with Windows. #1975. [Rich Olson] + +* Convert boolean form options form the tag_helper. #809. [Michael Schuerig ] + +* Fixed that an instance variable with the same name as a partial should be implicitly passed as the partial :object #2269 [court3nay] + +* Update Prototype to V1.4.0_pre11, script.aculo.us to [2502] [Thomas Fuchs] + +* Make assert_tag :children count appropriately. Closes #2181. [jamie@bravenet.com] + +* Forced newer versions of RedCloth to use hard breaks [DHH] + +* Added new scriptaculous options for auto_complete_field #2343 [m.stienstra@fngtps.com] + +* Don't prepend the asset host if the string is already a fully-qualified URL + +* Updated to script.aculo.us V1.5.0_rc2 and Prototype to V1.4.0_pre7 [Thomas Fuchs] + +* Undo condition change made in [2345] to prevent normal parameters arriving as StringIO. + +* Tolerate consecutive delimiters in query parameters. #2295 [darashi@gmail.com] + +* Streamline render process, code cleaning. Closes #2294. [skae] + +* Keep flash after components are rendered. #2291 [Rick Olson, Scott] + +* Shorten IE file upload path to filename only to match other browsers. #1507 [court3nay@gmail.com] + +* Fix open/save dialog in IE not opening files send with send_file/send_data, #2279 [Thomas Fuchs] + +* Fixed that auto_discovery_link_tag couldn't take a string as the URL [DHH] + +* Fixed problem with send_file and WEBrick using stdout #1812 [DHH] + +* Optimized tag_options to not sort keys, which is no longer necessary when assert_dom_equal and friend is available #1995 [skae] + +* Added assert_dom_equal and assert_dom_not_equal to compare tags generated by the helpers in an order-indifferent manner #1995 [skae] + +* Fixed that Request#domain caused an exception if the domain header wasn't set in the original http request #1795 [Michael Koziarski] + +* Make the truncate() helper multi-byte safe (assuming $KCODE has been set to something other than "NONE") #2103 + +* Add routing tests from #1945 [ben@groovie.org] + +* Add a routing test case covering #2101 [Nicholas Seckar] + +* Cache relative_url_root for all webservers, not just Apache #2193 [skae] + +* Speed up cookie use by decreasing string copying #2194 [skae] + +* Fixed access to "Host" header with requests made by crappy old HTTP/1.0 clients #2124 [Marcel Molina] + +* Added easy assignment of fragment cache store through use of symbols for included stores (old way still works too) + + Before: + ActionController::Base.fragment_cache_store = + ActionController::Base::Caching::Fragments::FileStore.new("/path/to/cache/directory") + + After: + ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" + +* Added ActionController::Base.session_store=, session_store, and session_options to make it easier to tweak the session options (instead of going straight to ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS) + +* Added TextHelper#cycle to cycle over an array of values on each hit (useful for alternating row colors etc) #2154 [dave-ml@dribin.org] + +* Ensure that request.path never returns nil. Closes #1675 [Nicholas Seckar] + +* Add ability to specify Route Regexps for controllers. Closes #1917. [Sebastian Kanthak] + +* Provide Named Route's hash methods as helper methods. Closes #1744. [Nicholas Seckar, Steve Purcell] + +* Added :multipart option to ActiveRecordHelper#form to make it possible to add file input fields #2034 [jstirk@oobleyboo.com] + +* Moved auto-completion and in-place editing into the Macros module and their helper counterparts into JavaScriptMacrosHelper + +* Added in-place editing support in the spirit of auto complete with ActionController::Base.in_place_edit_for, JavascriptHelper#in_place_editor_field, and Javascript support from script.aculo.us #2038 [Jon Tirsen] + +* Added :disabled option to all data selects that'll make the elements inaccessible for change #2167, #253 [eigentone] + +* Fixed that TextHelper#auto_link_urls would include punctuation in the links #2166, #1671 [eigentone] + +* Fixed that number_to_currency(1000, {:precision => 0})) should return "$1,000", instead of "$1,000." #2122 [sd@notso.net] + +* Allow link_to_remote to use any DOM-element as the parent of the form elements to be submitted #2137 [erik@ruby-lang.nl]. Example: + + + + + <%= link_to_remote 'Save', :update => "row023", + :submit => "row023", :url => {:action => 'save_row'} %> + + +* Fixed that render :partial would fail when :object was a Hash (due to backwards compatibility issues) #2148 [Sam Stephenson] + +* Fixed JavascriptHelper#auto_complete_for to only include unique items #2153 [Thomas Fuchs] + +* Fixed all AssetHelper methods to work with relative paths, such that javascript_include_tag('stdlib/standard') will look in /javascripts/stdlib/standard instead of '/stdlib/standard/' #1963 + +* Avoid extending view instance with helper modules each request. Closes #1979 + +* Performance improvements to CGI methods. Closes #1980 [Skaes] + +* Added :post option to UrlHelper#link_to that makes it possible to do POST requests through normal ahref links using Javascript + +* Fixed overwrite_params + +* Added ActionController::Base.benchmark and ActionController::Base.silence to allow for easy benchmarking and turning off the log + +* Updated vendor copy of html-scanner to support better xml parsing + +* Added :popup option to UrlHelper#link_to #1996 [gabriel.gironda@gmail.com]. Examples: + + link_to "Help", { :action => "help" }, :popup => true + link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600'] + +* Drop trailing \000 if present on RAW_POST_DATA (works around bug in Safari Ajax implementation) #918 + +* Fix observe_field to fall back to event-based observation if frequency <= 0 #1916 [michael@schubert.cx] + +* Allow use of the :with option for submit_to_remote #1936 [jon@instance-design.co.uk] + +* AbstractRequest#domain returns nil when host is an ip address #2012 [kevin.clark@gmail.com] + +* ActionController documentation update #2051 [fbeausoleil@ftml.net] + +* Yield @content_for_ variables to templates #2058 [Sam Stephenson] + +* Make rendering an empty partial collection behave like :nothing => true #2080 [Sam Stephenson] + +* Add option to specify the singular name used by pagination. + +* Use string key to obtain action value. Allows indifferent hashes to be disabled. + +* Added ActionView::Base.cache_template_loading back. + +* Rewrote compiled templates to decrease code complexity. Removed template load caching in favour of compiled caching. Fixed template error messages. [Nicholas Seckar] + +* Fix Routing to handle :some_param => nil better. [Nicholas Seckar, Luminas] + +* Add support for :include with pagination (subject to existing constraints for :include with :limit and :offset) #1478 [michael@schubert.cx] + +* Prevent the benchmark module from blowing up if a non-HTTP/1.1 request is processed + +* Added :use_short_month option to select_month helper to show month names as abbreviations + +* Make link_to escape the javascript in the confirm option #1964 [nicolas.pouillard@gmail.com] + +* Make assert_redirected_to properly check URL's passed as strings #1910 [Scott Barron] + +* Make sure :layout => false is always used when rendering inside a layout + +* Use raise instead of assert_not_nil in Test::Unit::TestCase#process to ensure that the test variables (controller, request, response) have been set + +* Make sure assigns are built for every request when testing #1866 + +* Allow remote_addr to be queried on TestRequest #1668 + +* Fixed bug when a partial render was passing a local with the same name as the partial + +* Improved performance of test app req/sec with ~10% refactoring the render method #1823 [Stefan Kaes] + +* Improved performance of test app req/sec with 5-30% through a series of Action Pack optimizations #1811 [Stefan Kaes] + +* Changed caching/expiration/hit to report using the DEBUG log level and errors to use the ERROR log level instead of both using INFO + +* Added support for per-action session management #1763 + +* Improved rendering speed on complicated templates by up to 100% (the more complex the templates, the higher the speedup) #1234 [Stephan Kaes]. This did necessasitate a change to the internals of ActionView#render_template that now has four parameters. Developers of custom view handlers (like Amrita) need to update for that. + +* Added options hash as third argument to FormHelper#input, so you can do input('person', 'zip', :size=>10) #1719 [jeremye@bsa.ca.gov] + +* Added Base#expires_in(seconds)/Base#expires_now to control HTTP content cache headers #1755 [Thomas Fuchs] + +* Fixed line number reporting for Builder template errors #1753 [piotr] + +* Fixed assert_routing so that testing controllers in modules works as expected [Nicholas Seckar, Rick Olson] + +* Fixed bug with :success/:failure callbacks for the JavaScriptHelper methods #1730 [court3nay/Thomas Fuchs] + +* Added named_route method to RouteSet instances so that RouteSet instance methods do not prevent certain names from being used. [Nicholas Seckar] + +* Fixed routes so that routes which do not specify :action in the path or in the requirements have a default of :action => 'index', In addition, fixed url generation so that :action => 'index' does not need to be provided for such urls. [Nicholas Seckar, Markjuh] + +* Worked around a Safari bug where it wouldn't pass headers through if the response was zero length by having render :nothing return ' ' instead of '' + +* Fixed Request#subdomains to handle "foo.foo.com" correctly + + +*1.9.1* (11 July, 2005) + +* Fixed that auto_complete_for didn't force the input string to lower case even as the db comparison was + +* Fixed that Action View should always use the included Builder, never attempt to require the gem, to ensure compatibility + +* Added that nil options are not included in tags, so tag("p", :ignore => nil) now returns

    not

    but that tag("p", :ignore => "") still includes it #1465 [michael@schuerig.de] + +* Fixed that UrlHelper#link_to_unless/link_to_if used html_escape on the name if no link was to be applied. This is unnecessary and breaks its use with images #1649 [joergd@pobox.com] + +* Improved error message for DoubleRenderError + +* Fixed routing to allow for testing of *path components #1650 [Nicholas Seckar] + +* Added :handle as an option to sortable_element to restrict the drag handle to a given class #1642 [thejohnny] + +* Added a bunch of script.aculo.us features #1644, #1677, #1695 [Thomas Fuchs] + * Effect.ScrollTo, to smoothly scroll the page to an element + * Better Firefox flickering handling on SlideUp/SlideDown + * Removed a possible memory leak in IE with draggables + * Added support for cancelling dragging my hitting ESC + * Added capability to remove draggables/droppables and redeclare sortables in dragdrop.js (this makes it possible to call sortable_element on the same element more than once, e.g. in AJAX returns that modify the sortable element. all current sortable 'stuff' on the element will be discarded and the sortable will be rebuilt) + * Always reset background color on Effect.Highlight; this make change backwards-compatibility, to be sure include style="background-color:(target-color)" on your elements or else elements will fall back to their CSS rules (which is a good thing in most circumstances) + * Removed circular references from element to prevent memory leaks (still not completely gone in IE) + * Changes to class extension in effects.js + * Make Effect.Highlight restore any previously set background color when finishing (makes effect work with CSS classes that set a background color) + * Fixed myriads of memory leaks in IE and Gecko-based browsers [David ZĂĽlke] + * Added incremental and local autocompleting and loads of documentation to controls.js [Ivan Krstic] + * Extended the auto_complete_field helper to accept tokens option + * Changed object extension mechanism to favor Object.extend to make script.aculo.us easily adaptable to support 3rd party libs like IE7.js [David ZĂĽlke] + +* Fixed that named routes didn't use the default values for action and possible other parameters #1534 [Nicholas Seckar] + +* Fixed JavascriptHelper#visual_effect to use camelize such that :blind_up will work #1639 [pelletierm@eastmedia.net] + +* Fixed that a SessionRestoreError was thrown if a model object was placed in the session that wasn't available to all controllers. This means that it's no longer necessary to use the 'model :post' work-around in ApplicationController to have a Post model in your session. + + +*1.9.0* (6 July, 2005) + +* Added logging of the request URI in the benchmark statement (makes it easy to grep for slow actions) + +* Added javascript_include_tag :defaults shortcut that'll include all the default javascripts included with Action Pack (prototype, effects, controls, dragdrop) + +* Cache several controller variables that are expensive to calculate #1229 [skaes@web.de] + +* The session class backing CGI::Session::ActiveRecordStore may be replaced with any class that duck-types with a subset of Active Record. See docs for details #1238 [skaes@web.de] + +* Fixed that hashes was not working properly when passed by GET to lighttpd #849 [Nicholas Seckar] + +* Fixed assert_template nil will be true when no template was rendered #1565 [maceywj@telus.net] + +* Added :prompt option to FormOptions#select (and the users of it, like FormOptions#select_country etc) to create "Please select" style descriptors #1181 [Michael Schuerig] + +* Added JavascriptHelper#update_element_function, which returns a Javascript function (or expression) that'll update a DOM element according to the options passed #933 [mortonda@dgrmm.net]. Examples: + + <%= update_element_function("products", :action => :insert, :position => :bottom, :content => "

    New product!

    ") %> + + <% update_element_function("products", :action => :replace, :binding => binding) do %> +

    Product 1

    +

    Product 2

    + <% end %> + +* Added :field_name option to DateHelper#select_(year|month|day) to deviate from the year/month/day defaults #1266 [Marcel Molina] + +* Added JavascriptHelper#draggable_element and JavascriptHelper#drop_receiving_element to facilitate easy dragging and dropping through the script.aculo.us libraries #1578 [Thomas Fuchs] + +* Added that UrlHelper#mail_to will now also encode the default link title #749 [f.svehla@gmail.com] + +* Removed the default option of wrap=virtual on FormHelper#text_area to ensure XHTML compatibility #1300 [thomas@columbus.rr.com] + +* Adds the ability to include XML CDATA tags using Builder #1563 [Josh Knowles]. Example: + + xml.cdata! "some text" # => + +* Added evaluation of + # + # javascript_include_tag "common.javascript", "/elsewhere/cools" # => + # + # + # + # javascript_include_tag :defaults # => + # + # + # ... + # *see below + # + # If there's an application.js file in your public/javascripts directory, + # javascript_include_tag :defaults will automatically include it. This file + # facilitates the inclusion of small snippets of JavaScript code, along the lines of + # controllers/application.rb and helpers/application_helper.rb. + 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 public/javascripts. + 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" # => + # + # + # stylesheet_link_tag "style", :media => "all" # => + # + # + # stylesheet_link_tag "random.styles", "/css/stylish" # => + # + # + 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: + # + # * :alt - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension) + # * :size - 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb new file mode 100644 index 0000000..1d53be5 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb @@ -0,0 +1,24 @@ +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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb new file mode 100644 index 0000000..de2707a --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb @@ -0,0 +1,10 @@ +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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb new file mode 100644 index 0000000..28b3e29 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb @@ -0,0 +1,128 @@ +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: + # + # + # + # layout with js + # + # + # + # <%= yield %> + # + # + # + # 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 yield + # 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 <%= content_for('footer') %> + # would be avaiable as <%= @content_for_footer %>. The preferred notation now is + # <%= yield :footer %>. + 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb new file mode 100755 index 0000000..7c65b0d --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb @@ -0,0 +1,313 @@ +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: + # + # * :prefix - 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. + # * :include_blank - set to true if it should be possible to set an empty date. + # * :discard_type - set to true if you want to discard the type part of the select name. If set to true, the select_month + # method would use simply "date" (which can be overwritten using :prefix) instead of "date[month]". + module DateHelper + 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, + # distance_of_time_in_words(50) returns "less than a minute". + # + # Set include_seconds 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' + when 2881..43220 then "#{(distance_in_minutes / 1440).round} days" + when 43201..86400 then 'about 1 month' + when 86401..525960 then "#{(distance_in_minutes / 43200).round} months" + when 525961..1051920 then 'about 1 year' + else "over #{(distance_in_minutes / 525600).round} years" + end + end + + # Like distance_of_time_in_words, but where to_time is fixed to Time.now. + 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 :discard_year, :discard_month and :discard_day. Set to true, they'll + # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly + # set the order of the tags using the :order option with an array of symbols :year, :month and :day in + # the desired order. Symbols may be omitted and the respective select is not included. + # + # 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 second can also be substituted for a second number. + # Override the field name using the :field_name 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) ? + %(\n) : + %(\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 minute_step from 0 through 59 with the 00 minute selected + # The minute can also be substituted for a minute number. + # Override the field name using the :field_name 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) ? + %(\n) : + %(\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 hour can also be substituted for a hour number. + # Override the field name using the :field_name 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) ? + %(\n) : + %(\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 date can also be substituted for a hour number. + # Override the field name using the :field_name 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) ? + %(\n) : + %(\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 :use_month_numbers key in +options+ to true for this to happen. If you want both numbers and names, + # set the :add_month_numbers 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 :field_name option, 'month' by default. + # + # If you would prefer to show month names as abbreviations, set the + # :use_short_month 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) ? + %(\n) : + %(\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 :start_year and :end_year keys in the +options+. Both ascending and descending year + # lists are supported by making :start_year less than or greater than :end_year. The date 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 :field_name 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) ? + %(\n) : + %(\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 = %(\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)]") } + value = value(object) + 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)]") } + value = value(object) + 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 << ' — ' + 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb new file mode 100644 index 0000000..8baea6f --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb @@ -0,0 +1,17 @@ +module ActionView + module Helpers + # Provides a set of methods for making it easier to locate problems. + module DebugHelper + # Returns a
    -tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
    +      def debug(object)
    +        begin
    +          Marshal::dump(object)
    +          "
    #{h(object.to_yaml).gsub("  ", "  ")}
    " + rescue Object => e + # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback + "#{h(object.inspect)}" + end + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb new file mode 100644 index 0000000..43185e3 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb @@ -0,0 +1,439 @@ +require '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 @person object was assigned by an action on the controller: + #
    + # 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 %> + # + # + #
    + # + # ...is compiled to: + # + #
    + # Name: + # + # + # Password: + # + # + # Single?: + # + # + # Description: + # + # + # + #
    + # + # If the object name contains square brackets the id for the object will be inserted. Example: + # + # <%= text_field "person[]", "name" %> + # + # ...becomes: + # + # + # + # 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 + # + # + # + # 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 <% %>, + # not <%= %>. 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 text_field :person, :name, + # you get away with f.text_field :name. + # + # 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 text_field :person, :name, :object => person + # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with + # :person, person and all subsequent field calls save :person and :object => person. + # + # 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('', 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) + # + 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) + # + 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") + # + # + # + # Example (call, result). Imagine that @puppy.gooddog returns no: + # check_box("puppy", "gooddog", {}, "yes", "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") + # + # + # + 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!(/\[\]$/,"") + if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:id_before_type_cast) + @auto_index = object.id_before_type_cast + else + raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to id_before_type_cast: #{object.inspect}" + end + 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(object) 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 + if options.has_key?("checked") + cv = options.delete "checked" + checked = cv == true || cv == "checked" + else + checked = self.class.radio_button_checked?(value(object), tag_value) + end + options["checked"] = "checked" if checked + pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase + options["id"] = defined?(@auto_index) ? + "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" : + "#{@object_name}_#{@method_name}_#{pretty_tag_value}" + 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) + + if size = options.delete("size") + options["cols"], options["rows"] = size.split("x") + end + + content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), 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 + if options.has_key?("checked") + cv = options.delete "checked" + checked = cv == true || cv == "checked" + else + checked = self.class.check_box_checked?(value(object), checked_value) + end + options["checked"] = "checked" if checked + 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(object) || Date.today + options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") } + html_day_select(date, options.call(3)) + + html_month_select(date, options.call(2)) + + html_year_select(date, options.call(1)) + end + + def to_boolean_select_tag(options = {}) + options = options.stringify_keys + add_default_name_and_id(options) + value = value(object) + tag_text = "" + end + + def to_content_tag(tag_name, options = {}) + content_tag(tag_name, value(object), options) + end + + def object + @object || @template_object.instance_variable_get("@#{@object_name}") + end + + def value(object) + self.class.value(object, @method_name) + end + + def value_before_type_cast(object) + self.class.value_before_type_cast(object, @method_name) + end + + class << self + def value(object, method_name) + object.send method_name unless object.nil? + end + + def value_before_type_cast(object, method_name) + unless object.nil? + object.respond_to?(method_name + "_before_type_cast") ? + object.send(method_name + "_before_type_cast") : + object.send(method_name) + end + end + + def check_box_checked?(value, checked_value) + case value + when TrueClass, FalseClass + value + when NilClass + false + when Integer + value != 0 + when String + value == checked_value + else + value.to_i != 0 + end + end + + def radio_button_checked?(value, checked_value) + value.to_s == checked_value.to_s + 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 defined?(@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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb new file mode 100644 index 0000000..12a5f7f --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -0,0 +1,363 @@ +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 collection_select, country_select, select, + # and time_zone_select methods take an options parameter, + # a hash. + # + # * :include_blank - 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: + # + # + # + # * :prompt - 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 belongs_to-associated object. For example, + # + # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }) + # + # could become: + # + # + 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: + # + # + # + # 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 :include_blank option documented above, + # this method also supports a :model option, which defaults + # to TimeZone. This may be used by users to specify a different time + # zone model object. (See #time_zone_options_for_select for more + # 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"]]) + # \n + # + # options_for_select([ "VISA", "MasterCard" ], "MasterCard") + # \n + # + # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40") + # \n + # + # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) + # \n\n + # + # 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.is_a?(String) ? selected.include?(element.last) : element.last == selected) ) + if is_selected + options << "" + else + options << "" + end + else + is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element) : element == selected) ) + options << ((is_selected) ? "" : "") + 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 @project.people to generate an input tag: + # options_from_collection_for_select(@project.people, "id", "name") + # + # + # 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 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: + # + # + # + # ... + # + # + # + # + # + # ... + # + # + # 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 += "" + options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) + options_for_select += '' + 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 += "\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 += "\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) + value = value(object) + 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) + value = value(object) + 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) + value = value(object) + 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) + value = value(object) + 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 = "\n" + option_tags if options[:include_blank] + + if value.blank? && options[:prompt] + ("\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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb new file mode 100644 index 0000000..816f106 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -0,0 +1,153 @@ +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 :disabled => true + # will give disabled="disabled". + module FormTagHelper + # Starts a form tag that points the action to an url configured with url_for_options just like + # ActionController::Base#url_for. The method for the form defaults to POST. + # + # Options: + # * :multipart - If set to true, the enctype is set to "multipart/form-data". + # * :method - The method to use when submitting the form, usually either "get" or "post". + # If "put", "delete", or another verb is used, a hidden input with name _method + # is added to simulate the verb over post. + def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc) + html_options = 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) + + method_tag = "" + + case method = html_options.delete("method") + when /^get$/i # must be case-insentive, but can't use downcase as might be nil + html_options["method"] = "get" + when /^post$/i, nil + html_options["method"] = "post" + else + html_options["method"] = "post" + method_tag = tag(:input, :type => "hidden", :name => "_method", :value => method) + end + + tag(:form, html_options, true) + method_tag + end + + alias_method :start_form_tag, :form_tag + + # Outputs "" + def end_form_tag + "" + end + + # Creates a dropdown selection box, or if the :multiple 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. + # + # option_tags is a string containing the option tags for the select box: + # # Outputs + # select_tag "people", "" + # + # Options: + # * :multiple - 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: + # * :disabled - If set to true, the user will not be able to use this input. + # * :size - The number of visible characters that will fit in the input. + # * :maxlength - 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 } %> + # <%= 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: + # * :size - A string specifying the dimensions of the textarea. + # # Outputs + # <%= 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 value 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. + # + # source 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/java_script_macros_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/java_script_macros_helper.rb new file mode 100644 index 0000000..f7523b8 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/java_script_macros_helper.rb @@ -0,0 +1,219 @@ +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: + #
    + # + # + # cancel + #
    + # + # 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: + # :url:: Specifies the url where the updated value should + # be sent after the user presses "ok". + # + # Addtional +options+ are: + # :rows:: Number of rows (more than 1 will use a TEXTAREA) + # :cols:: Number of characters the text input should span (works for both INPUT and TEXTAREA) + # :size:: Synonym for :cols when using a single line text input. + # :cancel_text:: The text on the cancel link. (default: "cancel") + # :save_text:: The text on the save link. (default: "ok") + # :loading_text:: The text to display when submitting to the server (default: "Saving...") + # :external_control:: The id of an external control used to enter edit mode. + # :load_text_url:: URL where initial value of editor (content) is retrieved. + # :options:: Pass through options to the AJAX call (see prototype's Ajax.Updater) + # :with:: JavaScript snippet that should return what is to be sent + # in the AJAX call, +form+ is an implicit parameter + # :script:: 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 an HTML
      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 an autocomplete="off" attribute with your text + # input field. + # + # The autocompleter object is assigned to a Javascript variable named field_id_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 activate method on that object). + # + # Required +options+ are: + # :url:: URL to call for autocompletion results + # in url_for format. + # + # Addtional +options+ are: + # :update:: 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' + # :with:: A JavaScript expression specifying the + # parameters for the XMLHttpRequest. This defaults + # to 'fieldname=value'. + # :frequency:: Determines the time to wait after the last keystroke + # for the AJAX request to be initiated. + # :indicator:: Specifies the DOM ID of an element which will be + # displayed while autocomplete is running. + # :tokens:: A string or an array of strings containing + # separator tokens for tokenized incremental + # autocompletion. Example: :tokens => ',' would + # allow multiple autocompletion entries, separated + # by commas. + # :min_chars:: The minimum number of characters that should be + # in the input field before an Ajax call is made + # to the server. + # :on_hide:: 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. + # :on_show:: Like on_hide, only now the expression is called + # then the div is shown. + # :after_update_element:: 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. + # :select:: 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[:paramName] = "'#{options[:param_name]}'" if options[:param_name] + 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, :type => 'text/css') + 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascript_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/javascript_helper.rb new file mode 100644 index 0000000..86829c2 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -0,0 +1,152 @@ +require File.dirname(__FILE__) + '/tag_helper' +require File.dirname(__FILE__) + '/prototype_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 <%= javascript_include_tag :defaults %> in the HEAD + # section of your page (recommended): This function will return + # references to the JavaScript files created by the +rails+ command in + # your public/javascripts directory. Using it is recommended as + # the browser can then cache the libraries instead of fetching all the + # functions anew on every request. + # * Use <%= javascript_include_tag 'prototype' %>: 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 <%= define_javascript_functions %>: 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 + + include PrototypeHelper + + # Returns a link that'll trigger a JavaScript +function+ using the + # onclick handler and return false after the fact. + # + # The +function+ argument can be omitted in favor of an +update_page+ + # block, which evaluates to a string when the template is rendered + # (instead of making an Ajax request first). + # + # Examples: + # link_to_function "Greeting", "alert('Hello world!')" + # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()") + # link_to_function("Show me more", nil, :id => "more_link") do |page| + # page[:details].visual_effect :toggle_blind + # page[:more_link].replace_html "Show me less" + # end + def link_to_function(name, function = '', html_options = {}, &block) + html_options.symbolize_keys! + function = update_page(&block) if block_given? + content_tag( + "a", name, + html_options.merge({ + :href => html_options[:href] || "#", + :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;" + }) + ) + end + + # Returns a button that'll trigger a JavaScript +function+ using the + # onclick handler. + # + # The +function+ argument can be omitted in favor of an +update_page+ + # block, which evaluates to a string when the template is rendered + # (instead of making an Ajax request first). + # + # Examples: + # button_to_function "Greeting", "alert('Hello world!')" + # button_to_function "Delete", "if (confirm('Really?')) do_delete()" + # button_to_function "Details" do |page| + # page[:details].visual_effect :toggle_slide + # end + def button_to_function(name, function = '', html_options = {}, &block) + html_options.symbolize_keys! + function = update_page(&block) if block_given? + 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 ' + 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')" # => + 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js new file mode 100644 index 0000000..de0261e --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -0,0 +1,815 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// See scriptaculous.js for full license. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
    • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
    • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
    • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
    • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
        " + ret.join('') + "
      "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
      /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
      /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

      /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js new file mode 100644 index 0000000..a01b7be --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -0,0 +1,913 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// See scriptaculous.js for full license. + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable.prototype = { + initialize: function(element) { + var options = Object.extend({ + handle: false, + starteffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); + }, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur}); + }, + endeffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } + }, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) { + var h = Element.childrenWithClassName(this.element, options.handle, true); + if(h.length>0) this.handle = h[0]; + } + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) + options.scroll = $(options.scroll); + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + if(this.element._revert) { + this.element._revert.cancel(); + this.element._revert = null; + } + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft; + p[1] += this.options.scroll.scrollTop; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1]); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: /^[^_]*_(.*)$/, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + //greedy: !options.dropOnEmpty + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.childrenWithClassName(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.addClassName(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.left = offsets[0] + 'px'; + Sortable._marker.style.top = offsets[1] + 'px'; + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; + else + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + + Element.show(Sortable._marker); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: new Array, + position: parent.children.length, + container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + /* Finds the first element of the given tag type within a parent element. + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ + _findChildrenElement: function (element, containerTag) { + if (element && element.hasChildNodes) + for (var i = 0; i < element.childNodes.length; ++i) + if (element.childNodes[i].tagName == containerTag) + return element.childNodes[i]; + + return null; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: new Array, + container: element, + position: 0 + } + + return Sortable._tree (element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +/* Returns true if child is contained within element */ +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + + if (child.parentNode == element) return true; + + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + if (type == 'vertical' || type == 'height') + return element.offsetHeight; + else + return element.offsetWidth; +} \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js new file mode 100644 index 0000000..9274005 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -0,0 +1,958 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : null }); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className, findFirst) { + var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); + var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { + return (c.className && c.className.match(classNameRegExp)); + }); + if(!results) results = []; + return results; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: this.options.x * position + this.originalLeft + 'px', + top: this.options.y * position + this.originalTop + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = width + 'px'; + if(this.options.scaleY) d.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide(); + effect.element.setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from); + effect.element.show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + effect.effects[0].element.setStyle({position: 'absolute'}); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned(); + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.undoPositioned(); + effect.element.setStyle({opacity: oldOpacity}); + } + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned(); + effect.element.setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + element.cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + // IE will crash if child is undoPositioned first + if(/MSIE/.test(navigator.userAgent)){ + effect.element.undoPositioned(); + effect.element.firstChild.undoPositioned(); + }else{ + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + } + effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + element.cleanWhitespace(); + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + effect.element.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + effect.element.hide(effect.element); + effect.element.undoClipping(effect.element); } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide(); + effect.element.makeClipping(); + effect.element.makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}); + effect.effects[0].element.show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned(); + effect.effects[0].element.makeClipping(); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_view/helpers/javascripts/prototype.js b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/prototype.js new file mode 100644 index 0000000..5ba3a30 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -0,0 +1,2012 @@ +/* Prototype JavaScript framework, version 1.5.0_rc0 + * (c) 2005 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0_rc0', + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += (replacement(match) || '').toString(); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + (object[match[3]] || '').toString(); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + /* Simulate other verbs over post */ + if (this.options.method != 'get' && this.options.method != 'post') { + parameters += (parameters.length > 0 ? '&' : '') + '_method=' + this.options.method + this.options.method = 'post' + } + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version, + 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', this.options.contentType); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval('(' + this.header('X-JSON') + ')'); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $() { + var results = [], element; + for (var i = 0; i < arguments.length; i++) { + element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + results.push(Element.extend(element)); + } + return results.length < 2 ? results[0] : results; +} + +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(Element.extend(child)); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element) return; + if (_nativeExtensions) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Element.Methods, cache = Element.extend.cache; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +} + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + replace: function(element, html) { + element = $(element); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + childOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +} + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + var HTMLElement = {} + HTMLElement.prototype = document.createElement('div').__proto__; +} + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + if(typeof HTMLElement != 'undefined') { + var methods = Element.Methods, cache = Element.extend.cache; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + HTMLElement.prototype[property] = cache.findOrStore(value); + } + _nativeExtensions = true; + } +} + +Element.addMethods(); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toLowerCase(); + if (tagName == 'tbody' || tagName == 'tr') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
      '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.id == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0; i < clause.length; i++) + conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push(value + ' != null'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0; i < scope.length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +function $$() { + return $A(arguments).map(function(expression) { + return expression.strip().split(/\s+/).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.map(selector.findElements.bind(selector)).flatten(); + }); + }).flatten(); +} +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (var tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value || opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = []; + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) + value.push(opt.value || opt.text); + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} \ No newline at end of file diff --git a/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb new file mode 100644 index 0000000..cb31232 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/number_helper.rb @@ -0,0 +1,109 @@ +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 => "£", :separator => ",", :delimiter => ""}) => £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 : '1 Byte' + 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/pagination_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/pagination_helper.rb new file mode 100644 index 0000000..6123b73 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/pagination_helper.rb @@ -0,0 +1,86 @@ +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: + # :name:: the routing name for this paginator + # (defaults to +page+) + # :window_size:: the number of pages to show around + # the current page (defaults to +2+) + # :always_show_anchors:: whether or not the first and last + # pages should always be shown + # (defaults to +true+) + # :link_to_current_page:: whether or not the current page + # should be linked to (defaults to + # +false+) + # :params:: 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 diff --git a/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb new file mode 100644 index 0000000..531ff0b --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -0,0 +1,905 @@ +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 options[:url] + # (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 options[:update]. + # Usually, the result would be a partial prepared by the controller with + # render :partial. + # + # 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 options[:update] 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 options[:position] parameter to + # influence how the target DOM element is updated. It must be one of + # :before, :top, :bottom, or :after. + # + # The method used is by default POST. You can also specify GET or you + # can simulate PUT or DELETE over POST. All specified with options[:method] + # + # Example: + # link_to_remote "Destroy", person_url(:id => person), :method => :delete + # + # 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 + # request object, which holds the underlying XMLHttpRequest. + # + # To access the server response, use request.responseText, to + # find out the HTTP status, use request.status. + # + # Example: + # link_to_remote word, + # :url => { :action => "undo", :n => word_counter }, + # :complete => "undoRequestCompleted(request)" + # + # The callbacks that may be specified are (in order): + # + # :loading:: Called when the remote document is being + # loaded with data by the browser. + # :loaded:: Called when the browser has finished loading + # the remote document. + # :interactive:: Called when the user can interact with the + # remote document, even though it has not + # finished loading. + # :success:: Called when the XMLHttpRequest is completed, + # and the HTTP status code is in the 2XX range. + # :failure:: Called when the XMLHttpRequest is completed, + # and the HTTP status code is not in the 2XX + # range. + # :complete:: Called when the XMLHttpRequest is complete + # (fires after success/failure if they are + # present). + # + # You can further refine :success and :failure 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 + # options[:type] = :synchronous. + # + # 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: + # + # :confirm:: Adds confirmation dialog. + # :condition:: Perform remote request conditionally + # by this expression. Use this to + # describe browser-side conditions when + # request should not be initiated. + # :before:: Called before request is initiated. + # :after:: Called immediately after request was + # initiated and before :loading. + # :submit:: 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 (options[:url]) every + # options[:frequency] seconds (default is 10). Usually used to + # update a specified div (options[:update]) with the results + # of the remote call. The options for specifying the target with :url + # 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 params). 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;" + + form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html]) + 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('', 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. + # options argument is the same as in form_remote_tag. + 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. + # + # * :content: The content to use for updating. Can be left out + # if using block, see example. + # * :action: Valid options are :update (assumed by default), + # :empty, :remove + # * :position 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 => "

      New product!

      ")) %> + # + # <% replacement_function = update_element_function("products") do %> + #

      Product 1

      + #

      Product 2

      + # <% 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 => "

      New Product: #{@product.name}

      ")) %> + # <% 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: + # + 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: + # :url:: +url_for+-style options for the action to call + # when the field has changed. + # :function:: Instead of making a remote call to a URL, you + # can specify a function to be called instead. + # + # Additional options are: + # :frequency:: 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. + # :update:: Specifies the DOM ID of the element whose + # innerHTML should be updated with the + # XMLHttpRequest response text. + # :with:: 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. + # :on:: 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 :with 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 + # + # mail_to "me@domain.com", "My email", :encode => "hex" # => + # My email + # + # 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+ html_options keys. Each of these options are URI escaped and then appended to + # the email_address before being output. Be aware that javascript keywords will not be escaped and may break this feature + # when encoding with javascript. + # 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." # => + # My email + 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 + "" + 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 = html_options.delete("confirm"), html_options.delete("popup") + + # post is deprecated, but if its specified and method is not, assume that method = :post + method, post = html_options.delete("method"), html_options.delete("post") + method = :post if !method && post + + html_options["onclick"] = case + when popup && method + 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 && method + "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;" + when confirm + "return #{confirm_javascript_function(confirm)};" + when method + "#{method_javascript_function(method)}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 method_javascript_function(method) + submit_function = + "var f = document.createElement('form'); f.style.display = 'none'; " + + "this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;" + + unless method == :post + submit_function << "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); " + submit_function << "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); f.appendChild(m);" + end + + submit_function << "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 diff --git a/vendor/rails/actionpack/lib/action_view/partials.rb b/vendor/rails/actionpack/lib/action_view/partials.rb new file mode 100644 index 0000000..063ff56 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/partials.rb @@ -0,0 +1,128 @@ +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 diff --git a/vendor/rails/actionpack/lib/action_view/template_error.rb b/vendor/rails/actionpack/lib/action_view/template_error.rb new file mode 100644 index 0000000..e21f751 --- /dev/null +++ b/vendor/rails/actionpack/lib/action_view/template_error.rb @@ -0,0 +1,86 @@ +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) diff --git a/vendor/rails/actionpack/test/abstract_unit.rb b/vendor/rails/actionpack/test/abstract_unit.rb new file mode 100644 index 0000000..94fcae4 --- /dev/null +++ b/vendor/rails/actionpack/test/abstract_unit.rb @@ -0,0 +1,14 @@ +$:.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 nil \ No newline at end of file diff --git a/vendor/rails/actionpack/test/active_record_unit.rb b/vendor/rails/actionpack/test/active_record_unit.rb new file mode 100644 index 0000000..016f331 --- /dev/null +++ b/vendor/rails/actionpack/test/active_record_unit.rb @@ -0,0 +1,88 @@ +require 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.setup \ No newline at end of file diff --git a/vendor/rails/actionpack/test/activerecord/active_record_assertions_test.rb b/vendor/rails/actionpack/test/activerecord/active_record_assertions_test.rb new file mode 100644 index 0000000..f5661d1 --- /dev/null +++ b/vendor/rails/actionpack/test/activerecord/active_record_assertions_test.rb @@ -0,0 +1,84 @@ +require "#{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 \ No newline at end of file diff --git a/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb b/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb new file mode 100644 index 0000000..96c147c --- /dev/null +++ b/vendor/rails/actionpack/test/activerecord/active_record_store_test.rb @@ -0,0 +1,174 @@ +# 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 diff --git a/vendor/rails/actionpack/test/activerecord/pagination_test.rb b/vendor/rails/actionpack/test/activerecord/pagination_test.rb new file mode 100644 index 0000000..386300c --- /dev/null +++ b/vendor/rails/actionpack/test/activerecord/pagination_test.rb @@ -0,0 +1,161 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/action_caching_test.rb b/vendor/rails/actionpack/test/controller/action_caching_test.rb new file mode 100644 index 0000000..84b0cbd --- /dev/null +++ b/vendor/rails/actionpack/test/controller/action_caching_test.rb @@ -0,0 +1,137 @@ +require 'fileutils' +require File.dirname(__FILE__) + '/../abstract_unit' + +CACHE_DIR = 'test_cache' +# Don't change '/../temp/' cavalierly or you might hoze something you don't want hozed +FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) +ActionController::Base.perform_caching = true +ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH + +class ActionCachingTestController < ActionController::Base + caches_action :index + + def index + @cache_this = Time.now.to_f.to_s + render :text => @cache_this + end + + def expire + expire_action :controller => 'action_caching_test', :action => 'index' + render :nothing => true + end + +end + +class ActionCachingMockController + attr_accessor :mock_url_for + attr_accessor :mock_path + + def initialize + yield self if block_given? + end + + def url_for(*args) + @mock_url_for + end + + def request + mocked_path = @mock_path + Object.new.instance_eval(<<-EVAL) + def path; '#{@mock_path}' end + self + EVAL + end +end + +class ActionCacheTest < Test::Unit::TestCase + def setup + reset! + FileUtils.mkdir_p(FILE_STORE_PATH) + @path_class = ActionController::Caching::Actions::ActionCachePath + @mock_controller = ActionCachingMockController.new + end + + def teardown + FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) + end + + def test_simple_action_cache + get :index + cached_time = content_to_cache + assert_equal cached_time, @response.body + reset! + + get :index + assert_equal cached_time, @response.body + end + + def test_cache_expiration + get :index + cached_time = content_to_cache + reset! + + get :index + assert_equal cached_time, @response.body + reset! + + get :expire + reset! + + get :index + new_cached_time = content_to_cache + assert_not_equal cached_time, @response.body + reset! + + get :index + assert_response :success + assert_equal new_cached_time, @response.body + end + + def test_cache_is_scoped_by_subdomain + @request.host = 'jamis.hostname.com' + get :index + jamis_cache = content_to_cache + + @request.host = 'david.hostname.com' + get :index + david_cache = content_to_cache + assert_not_equal jamis_cache, @response.body + + @request.host = 'jamis.hostname.com' + get :index + assert_equal jamis_cache, @response.body + + @request.host = 'david.hostname.com' + get :index + assert_equal david_cache, @response.body + end + + def test_xml_version_of_resource_is_treated_as_different_cache + @mock_controller.mock_url_for = 'http://example.org/posts/' + @mock_controller.mock_path = '/posts/index.xml' + path_object = @path_class.new(@mock_controller) + assert_equal 'xml', path_object.extension + assert_equal 'example.org/posts/index.xml', path_object.path + end + + def test_empty_path_is_normalized + @mock_controller.mock_url_for = 'http://example.org/' + @mock_controller.mock_path = '/' + + assert_equal 'example.org/index', @path_class.path_for(@mock_controller) + end + + private + + def content_to_cache + assigns(:cache_this) + end + + def reset! + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller = ActionCachingTestController.new + @request.host = 'hostname.com' + end + +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb b/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb new file mode 100644 index 0000000..56b1752 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/action_pack_assertions_test.rb @@ -0,0 +1,545 @@ +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 "
      #{url_for(:action => 'flash_me', :only_path => true)}
      " + end + + def render_text_with_custom_content_type + render :text => "Hello!", :content_type => Mime::RSS + 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 + with_routing do |set| + set.draw do |map| + map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing' + map.connect ':controller/:action/:id' + end + set.named_routes.install + + process :redirect_to_named_route + assert_redirected_to 'http://test.host/route_one' + assert_redirected_to route_one_url + assert_redirected_to :route_one_url + end + end + + def test_assert_redirect_to_named_route_failure + with_routing do |set| + set.draw do |map| + map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one' + map.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' + map.connect ':controller/:action/:id' + end + process :redirect_to_named_route + assert_raise(Test::Unit::AssertionFailedError) do + assert_redirected_to 'http://test.host/route_two' + end + assert_raise(Test::Unit::AssertionFailedError) do + assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' + end + assert_raise(Test::Unit::AssertionFailedError) do + assert_redirected_to route_two_url + end + assert_raise(Test::Unit::AssertionFailedError) do + assert_redirected_to :route_two_url + end + 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_assert_redirection_fails_with_incorrect_controller + process :redirect_to_controller + assert_raise(Test::Unit::AssertionFailedError) do + assert_redirected_to :controller => "action_pack_assertions", :action => "flash_me" + end + end + + def test_assert_redirection_with_extra_controller_option + get :redirect_to_action + assert_redirected_to :controller => 'action_pack_assertions', :action => "flash_me", :id => 1, :params => { :panda => 'fun' } + 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 + + + def test_render_text_with_custom_content_type + get :render_text_with_custom_content_type + assert_equal 'application/rss+xml', @response.headers['Content-Type'] + end +end diff --git a/vendor/rails/actionpack/test/controller/addresses_render_test.rb b/vendor/rails/actionpack/test/controller/addresses_render_test.rb new file mode 100644 index 0000000..d85b6f5 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/addresses_render_test.rb @@ -0,0 +1,49 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/base_test.rb b/vendor/rails/actionpack/test/controller/base_test.rb new file mode 100644 index 0000000..588b9f6 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/base_test.rb @@ -0,0 +1,67 @@ +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 +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 EmptyController.controller_path, EmptyController.new.controller_path + assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path + assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.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.controller_path} should be empty!" + end + @non_empty_controllers.each do |c| + assert_equal Set.new('public_action'), c.send(:action_methods), "#{c.controller_path} should not be empty!" + end + end +end diff --git a/vendor/rails/actionpack/test/controller/benchmark_test.rb b/vendor/rails/actionpack/test/controller/benchmark_test.rb new file mode 100644 index 0000000..f346e57 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/benchmark_test.rb @@ -0,0 +1,33 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/caching_filestore.rb b/vendor/rails/actionpack/test/controller/caching_filestore.rb new file mode 100644 index 0000000..389ebe0 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/caching_filestore.rb @@ -0,0 +1,74 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/capture_test.rb b/vendor/rails/actionpack/test/controller/capture_test.rb new file mode 100644 index 0000000..e4f76a4 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/capture_test.rb @@ -0,0 +1,80 @@ +require 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( + "" + + "\n\n$('status').innerHTML = '\\n You bought something!\\n';", + @response.body.strip + ) + end + + private + def expected_content_for_output + "Putting stuff in the title!\n\nGreat stuff!" + end +end diff --git a/vendor/rails/actionpack/test/controller/cgi_test.rb b/vendor/rails/actionpack/test/controller/cgi_test.rb new file mode 100755 index 0000000..45d4cf6 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/cgi_test.rb @@ -0,0 +1,397 @@ +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_deep_query_string_with_array_of_hashes_with_one_pair + assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, CGIMethods.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')) + end + + def test_deep_query_string_with_array_of_hashes_with_multiple_pairs + assert_equal( + {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, + CGIMethods.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b') + ) + 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 + + def test_parse_params_with_nil_key + input = { nil => nil, "test2" => %w(value1) } + expected = { "test2" => "value1" } + 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 + + # Rewind readable cgi params so others may reread them (such as CGI::Session + # when passing the session id in a multipart form). + def test_multipart_param_rewound + params = process('text_file') + assert_equal 'bar', @cgi.params['foo'][0].read + 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 + @cgi = CGI.new + CGIMethods.parse_request_parameters @cgi.params + end + ensure + $stdin = old_stdin + end +end + +# Ensures that PUT works with multipart as well as POST. +class PutMultipartCGITest < MultipartCGITest + def setup + super + ENV['REQUEST_METHOD'] = 'PUT' + 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 diff --git a/vendor/rails/actionpack/test/controller/components_test.rb b/vendor/rails/actionpack/test/controller/components_test.rb new file mode 100644 index 0000000..57e3b21 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/components_test.rb @@ -0,0 +1,148 @@ +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 +end + +module A + module B + module C + class NestedController < ActionController::Base + # Stub for uses_component_template_root + def self.caller + ['./test/fixtures/a/b/c/nested_controller.rb'] + end + end + end + end +end + +class UsesComponentTemplateRootTest < Test::Unit::TestCase + def test_uses_component_template_root + assert_equal './test/fixtures/', A::B::C::NestedController.uses_component_template_root + end +end diff --git a/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb b/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb b/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb b/vendor/rails/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/actionpack/test/controller/cookie_test.rb b/vendor/rails/actionpack/test/controller/cookie_test.rb new file mode 100644 index 0000000..f0189eb --- /dev/null +++ b/vendor/rails/actionpack/test/controller/cookie_test.rb @@ -0,0 +1,80 @@ +require 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 diff --git a/vendor/rails/actionpack/test/controller/custom_handler_test.rb b/vendor/rails/actionpack/test/controller/custom_handler_test.rb new file mode 100644 index 0000000..2747a0f --- /dev/null +++ b/vendor/rails/actionpack/test/controller/custom_handler_test.rb @@ -0,0 +1,41 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/deprecated_instance_variables_test.rb b/vendor/rails/actionpack/test/controller/deprecated_instance_variables_test.rb new file mode 100644 index 0000000..edfd8b7 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/deprecated_instance_variables_test.rb @@ -0,0 +1,48 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class DeprecatedInstanceVariablesTest < Test::Unit::TestCase + class Target < ActionController::Base + def initialize(run = nil) + instance_eval(run) if run + super() + end + + def noop + render :nothing => true + end + + ActionController::Base::DEPRECATED_INSTANCE_VARIABLES.each do |var| + class_eval "def old_#{var}; render :text => @#{var}.inspect end" + class_eval "def new_#{var}; render :text => #{var}.inspect end" + class_eval "def internal_#{var}; render :text => @_#{var}.inspect end" + end + + def rescue_action(e) raise e end + end + + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller = Target.new + end + + ActionController::Base::DEPRECATED_INSTANCE_VARIABLES.each do |var| + class_eval <<-end_eval, __FILE__, __LINE__ + def test_old_#{var}_is_deprecated + assert_deprecated('@#{var}') { get :old_#{var} } + end + def test_new_#{var}_isnt_deprecated + assert_not_deprecated { get :new_#{var} } + end + def test_internal_#{var}_isnt_deprecated + assert_not_deprecated { get :internal_#{var} } + end + def test_#{var}_raises_if_already_set + assert_raise(RuntimeError) do + @controller = Target.new '@#{var} = Object.new' + get :noop + end + end + end_eval + end +end diff --git a/vendor/rails/actionpack/test/controller/fake_controllers.rb b/vendor/rails/actionpack/test/controller/fake_controllers.rb new file mode 100644 index 0000000..5f958b2 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/fake_controllers.rb @@ -0,0 +1,16 @@ +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 + 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 diff --git a/vendor/rails/actionpack/test/controller/filter_params_test.rb b/vendor/rails/actionpack/test/controller/filter_params_test.rb new file mode 100644 index 0000000..5ad0d7f --- /dev/null +++ b/vendor/rails/actionpack/test/controller/filter_params_test.rb @@ -0,0 +1,42 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/filters_test.rb b/vendor/rails/actionpack/test/controller/filters_test.rb new file mode 100644 index 0000000..d92143e --- /dev/null +++ b/vendor/rails/actionpack/test/controller/filters_test.rb @@ -0,0 +1,410 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/flash_test.rb b/vendor/rails/actionpack/test/controller/flash_test.rb new file mode 100644 index 0000000..a6ce3d1 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/flash_test.rb @@ -0,0 +1,102 @@ +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 use_flash_after_reset_session + flash["that"] = "hello" + @flashy_that = flash["that"] + reset_session + @flashy_that_reset = flash["that"] + flash["this"] = "good-bye" + @flashy_this = flash["this"] + 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 + + def test_flash_after_reset_session + get :use_flash_after_reset_session + assert_equal "hello", @response.template.assigns["flashy_that"] + assert_equal "good-bye", @response.template.assigns["flashy_this"] + assert_nil @response.template.assigns["flashy_that_reset"] + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/fragment_store_setting_test.rb b/vendor/rails/actionpack/test/controller/fragment_store_setting_test.rb new file mode 100644 index 0000000..cb872f6 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/fragment_store_setting_test.rb @@ -0,0 +1,45 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +MemCache = Struct.new(:MemCache, :address) unless Object.const_defined?(:MemCache) + +class FragmentCacheStoreSettingTest < Test::Unit::TestCase + def teardown + ActionController::Base.fragment_cache_store = ActionController::Caching::Fragments::MemoryStore.new + end + + def test_file_fragment_cache_store + ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" + assert_kind_of( + ActionController::Caching::Fragments::FileStore, + ActionController::Base.fragment_cache_store + ) + assert_equal "/path/to/cache/directory", ActionController::Base.fragment_cache_store.cache_path + end + + def test_drb_fragment_cache_store + ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" + assert_kind_of( + ActionController::Caching::Fragments::DRbStore, + ActionController::Base.fragment_cache_store + ) + assert_equal "druby://localhost:9192", ActionController::Base.fragment_cache_store.address + end + + 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 diff --git a/vendor/rails/actionpack/test/controller/helper_test.rb b/vendor/rails/actionpack/test/controller/helper_test.rb new file mode 100644 index 0000000..98c8f7e --- /dev/null +++ b/vendor/rails/actionpack/test/controller/helper_test.rb @@ -0,0 +1,187 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/layout_test.rb b/vendor/rails/actionpack/test/controller/layout_test.rb new file mode 100644 index 0000000..bbb13d0 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/layout_test.rb @@ -0,0 +1,143 @@ +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 +end + + +class DefaultLayoutController < LayoutTest +end + +class HasOwnLayoutController < LayoutTest + layout 'item' +end + +class SetsLayoutInRenderController < LayoutTest + def hello + render :layout => 'third_party_template_library' + end +end + +class RendersNoLayoutController < LayoutTest + def hello + render :layout => false + end +end + +class LayoutSetInResponseTest < Test::Unit::TestCase + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_layout_set_when_using_default_layout + @controller = DefaultLayoutController.new + get :hello + assert_equal 'layouts/layout_test', @response.layout + end + + def test_layout_set_when_set_in_controller + @controller = HasOwnLayoutController.new + get :hello + assert_equal 'layouts/item', @response.layout + end + + def test_layout_set_when_using_render + @controller = SetsLayoutInRenderController.new + get :hello + assert_equal 'layouts/third_party_template_library', @response.layout + end + + def test_layout_is_not_set_when_none_rendered + @controller = RendersNoLayoutController.new + get :hello + assert_nil @response.layout + end +end + + +class SetsNonExistentLayoutFile < LayoutTest + layout "nofile.rhtml" +end + +class LayoutExceptionRaised < Test::Unit::TestCase + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_exception_raised_when_layout_file_not_found + @controller = SetsNonExistentLayoutFile.new + get :hello + @response.template.class.module_eval { attr_accessor :exception } + assert_equal ActionController::MissingTemplate, @response.template.exception.class + end +end diff --git a/vendor/rails/actionpack/test/controller/mime_responds_test.rb b/vendor/rails/actionpack/test/controller/mime_responds_test.rb new file mode 100644 index 0000000..d18d467 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/mime_responds_test.rb @@ -0,0 +1,287 @@ +require 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 custom_constant_handling + Mime::Type.register("text/x-mobile", :mobile) + + respond_to do |type| + type.html { render :text => "HTML" } + type.mobile { render :text => "Mobile" } + end + + Mime.send :remove_const, :MOBILE + 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 "

      Hello world!

      \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 "

      Hello world!

      \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 for all_types_with_layout', @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 + + def test_custom_constant + get :custom_constant_handling, :format => "mobile" + assert_equal "Mobile", @response.body + end + + def test_forced_format + get :html_xml_or_rss + assert_equal "HTML", @response.body + + get :html_xml_or_rss, :format => "html" + assert_equal "HTML", @response.body + + get :html_xml_or_rss, :format => "xml" + assert_equal "XML", @response.body + + get :html_xml_or_rss, :format => "rss" + assert_equal "RSS", @response.body + end +end diff --git a/vendor/rails/actionpack/test/controller/mime_type_test.rb b/vendor/rails/actionpack/test/controller/mime_type_test.rb new file mode 100644 index 0000000..65acbbf --- /dev/null +++ b/vendor/rails/actionpack/test/controller/mime_type_test.rb @@ -0,0 +1,33 @@ +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 + + def test_custom_type + Mime::Type.register("image/gif", :gif) + assert_nothing_raised do + Mime::GIF + assert_equal Mime::GIF, Mime::SET.last + end + Mime.send :remove_const, :GIF + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/new_render_test.rb b/vendor/rails/actionpack/test/controller/new_render_test.rb new file mode 100644 index 0000000..0722bb9 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/new_render_test.rb @@ -0,0 +1,600 @@ +require 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 "Hello world!", @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 "hello world, I'm here!", @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 "\n

      Hello David

      \n

      This is grand!

      \n\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 "

      This is grand!

      \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 "Hello world!", @response.body + end + + def test_layout_test_with_different_layout + get :layout_test_with_different_layout + assert_equal "Hello world!", @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{}, @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\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 "Putting stuff in the 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 "\n

      Hello

      \n\n", @response.body + + get :hello_world_from_rxml_using_action + assert_equal "\n

      Hello

      \n\n", @response.body + end +end diff --git a/vendor/rails/actionpack/test/controller/raw_post_test.rb b/vendor/rails/actionpack/test/controller/raw_post_test.rb new file mode 100644 index 0000000..c981648 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/raw_post_test.rb @@ -0,0 +1,68 @@ +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.delete('RAW_POST_DATA') + @request_body = 'a=1' + end + + def test_post_with_urlencoded_body + ENV['REQUEST_METHOD'] = 'POST' + ENV['CONTENT_TYPE'] = ' apPlication/x-Www-form-urlEncoded; charset=utf-8' + assert_equal ['1'], cgi_params['a'] + assert_has_raw_post_data + end + + def test_post_with_empty_content_type_treated_as_urlencoded + ENV['REQUEST_METHOD'] = 'POST' + ENV['CONTENT_TYPE'] = '' + assert_equal ['1'], cgi_params['a'] + assert_has_raw_post_data + end + + def test_post_with_unrecognized_content_type_reads_body_but_doesnt_parse_params + ENV['REQUEST_METHOD'] = 'POST' + ENV['CONTENT_TYPE'] = 'foo/bar' + assert cgi_params.empty? + assert_has_raw_post_data + end + + def test_put_with_urlencoded_body + ENV['REQUEST_METHOD'] = 'PUT' + ENV['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' + assert_equal ['1'], cgi_params['a'] + assert_has_raw_post_data + end + + def test_put_with_empty_content_type_ignores_body + ENV['REQUEST_METHOD'] = 'PUT' + ENV['CONTENT_TYPE'] = '' + assert cgi_params.empty? + assert_has_raw_post_data + end + + def test_put_with_unrecognized_content_type_ignores_body + ENV['REQUEST_METHOD'] = 'PUT' + ENV['CONTENT_TYPE'] = 'foo/bar' + assert cgi_params.empty? + assert_has_raw_post_data + end + + private + def cgi_params + old_stdin, $stdin = $stdin, StringIO.new(@request_body.dup) + ENV['CONTENT_LENGTH'] = $stdin.size.to_s + CGI.new.params + ensure + $stdin = old_stdin + end + + def assert_has_raw_post_data(expected_body = @request_body) + assert_not_nil ENV['RAW_POST_DATA'] + assert ENV['RAW_POST_DATA'].frozen? + assert_equal expected_body, ENV['RAW_POST_DATA'] + end +end diff --git a/vendor/rails/actionpack/test/controller/redirect_test.rb b/vendor/rails/actionpack/test/controller/redirect_test.rb new file mode 100755 index 0000000..84f0319 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/redirect_test.rb @@ -0,0 +1,143 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/render_test.rb b/vendor/rails/actionpack/test/controller/render_test.rb new file mode 100644 index 0000000..1ee77a5 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/render_test.rb @@ -0,0 +1,246 @@ +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 "\n

      Hello David

      \n

      This is grand!

      \n\n", @response.body + end + + def test_render_xml_with_default + get :greeting + assert_equal "

      This is grand!

      \n", @response.body + end + + def test_layout_rendering + get :layout_test + assert_equal "Hello world!", @response.body + end + + def test_render_xml_with_layouts + get :builder_layout_test + assert_equal "\n\n

      Hello

      \n

      This is grand!

      \n\n
      \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 diff --git a/vendor/rails/actionpack/test/controller/request_test.rb b/vendor/rails/actionpack/test/controller/request_test.rb new file mode 100644 index 0000000..9f79e7d --- /dev/null +++ b/vendor/rails/actionpack/test/controller/request_test.rb @@ -0,0 +1,294 @@ +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 + + def test_symbolized_request_methods + [:head, :get, :post, :put, :delete].each do |method| + set_request_method_to method + assert_equal method, @request.method + end + end + + def test_allow_method_hacking_on_post + set_request_method_to :post + [:head, :get, :put, :delete].each do |method| + @request.instance_eval { @parameters = { :_method => method } ; @request_method = nil } + assert_equal method, @request.method + end + end + + def test_restrict_method_hacking + @request.instance_eval { @parameters = { :_method => 'put' } } + [:head, :get, :put, :delete].each do |method| + set_request_method_to method + assert_equal method, @request.method + end + end + + protected + def set_request_method_to(method) + @request.env['REQUEST_METHOD'] = method.to_s.upcase + @request.instance_eval { @request_method = nil } + end +end diff --git a/vendor/rails/actionpack/test/controller/resources_test.rb b/vendor/rails/actionpack/test/controller/resources_test.rb new file mode 100644 index 0000000..7e0299e --- /dev/null +++ b/vendor/rails/actionpack/test/controller/resources_test.rb @@ -0,0 +1,237 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class ResourcesController < ActionController::Base + def index() render :nothing => true end + def rescue_action(e) raise e end +end + +class ThreadsController < ResourcesController; end +class MessagesController < ResourcesController; end +class CommentsController < ResourcesController; end + + +class ResourcesTest < Test::Unit::TestCase + def test_should_arrange_actions + resource = ActionController::Resources::Resource.new(:messages, + :collection => { :rss => :get, :reorder => :post, :csv => :post }, + :member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post }, + :new => { :preview => :get, :draft => :get }) + + assert_resource_methods [:rss], resource, :collection, :get + assert_resource_methods [:create, :csv, :reorder], resource, :collection, :post + assert_resource_methods [:edit, :rss, :atom], resource, :member, :get + assert_resource_methods [:upload, :fix], resource, :member, :post + assert_resource_methods [:update], resource, :member, :put + assert_resource_methods [:new, :preview, :draft], resource, :new, :get + end + + def test_default_restful_routes + with_restful_routing :messages do + assert_simply_restful_for :messages + end + end + + def test_multiple_default_restful_routes + with_restful_routing :messages, :comments do + assert_simply_restful_for :messages + assert_simply_restful_for :comments + end + end + + def test_with_path_prefix + with_restful_routing :messages, :path_prefix => '/thread/:thread_id' do + assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' } + end + end + + def test_multile_with_path_prefix + with_restful_routing :messages, :comments, :path_prefix => '/thread/:thread_id' do + assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' } + assert_simply_restful_for :comments, :path_prefix => 'thread/5/', :options => { :thread_id => '5' } + end + end + + def test_with_collection_action + with_restful_routing :messages, :collection => { :rss => :get } do + rss_options = {:action => 'rss'} + rss_path = "/messages;rss" + assert_restful_routes_for :messages do |options| + assert_routing rss_path, options.merge(rss_options) + end + + assert_restful_named_routes_for :messages do |options| + assert_named_route rss_path, :rss_messages_path, rss_options + end + end + end + + def test_with_member_action + [:put, :post].each do |method| + with_restful_routing :messages, :member => { :mark => method } do + mark_options = {:action => 'mark', :id => '1'} + mark_path = "/messages/1;mark" + assert_restful_routes_for :messages do |options| + assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method) + end + + assert_restful_named_routes_for :messages do |options| + assert_named_route mark_path, :mark_message_path, mark_options + end + end + end + end + + def test_with_two_member_actions_with_same_method + [:put, :post].each do |method| + with_restful_routing :messages, :member => { :mark => method, :unmark => method } do + %w(mark unmark).each do |action| + action_options = {:action => action, :id => '1'} + action_path = "/messages/1;#{action}" + assert_restful_routes_for :messages do |options| + assert_recognizes(options.merge(action_options), :path => action_path, :method => method) + end + + assert_restful_named_routes_for :messages do |options| + assert_named_route action_path, "#{action}_message_path".to_sym, action_options + end + end + end + end + end + + + def test_with_new_action + with_restful_routing :messages, :new => { :preview => :post } do + preview_options = {:action => 'preview'} + preview_path = "/messages/new;preview" + assert_restful_routes_for :messages do |options| + assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post) + end + + assert_restful_named_routes_for :messages do |options| + assert_named_route preview_path, :preview_new_message_path, preview_options + end + end + end + + def test_nested_restful_routes + with_routing do |set| + set.draw do |map| + map.resources :threads do |map| + map.resources :messages do |map| + map.resources :comments + end + end + end + + assert_simply_restful_for :threads + assert_simply_restful_for :messages, + :path_prefix => 'threads/1/', + :options => { :thread_id => '1' } + assert_simply_restful_for :comments, + :path_prefix => 'threads/1/messages/2/', + :options => { :thread_id => '1', :message_id => '2' } + end + end + + def test_restful_routes_dont_generate_duplicates + with_restful_routing :messages do + routes = ActionController::Routing::Routes.routes + routes.each do |route| + routes.each do |r| + next if route === r # skip the comparison instance + assert distinct_routes?(route, r), "Duplicate Route: #{route}" + end + end + end + end + + protected + def with_restful_routing(*args) + with_routing do |set| + set.draw { |map| map.resources(*args) } + yield + end + end + + # runs assert_restful_routes_for and assert_restful_named_routes for on the controller_name and options, without passing a block. + def assert_simply_restful_for(controller_name, options = {}) + assert_restful_routes_for controller_name, options + assert_restful_named_routes_for controller_name, options + end + + def assert_restful_routes_for(controller_name, options = {}) + (options[:options] ||= {})[:controller] = controller_name.to_s + + with_options(options[:options]) do |controller| + controller.assert_routing "/#{options[:path_prefix]}#{controller_name}", :action => 'index' + controller.assert_routing "/#{options[:path_prefix]}#{controller_name}.xml" , :action => 'index', :format => 'xml' + controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/new", :action => 'new' + controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/1", :action => 'show', :id => '1' + controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/1;edit", :action => 'edit', :id => '1' + controller.assert_routing "/#{options[:path_prefix]}#{controller_name}/1.xml", :action => 'show', :id => '1', :format => 'xml' + end + + assert_recognizes( + options[:options].merge(:action => 'create'), + {:path => "/#{options[:path_prefix]}#{controller_name}", :method => :post}) + + assert_recognizes( + options[:options].merge(:action => 'update', :id => '1'), + {:path => "/#{options[:path_prefix]}#{controller_name}/1", :method => :put}) + + assert_recognizes( + options[:options].merge(:action => 'destroy', :id => '1'), + {:path => "/#{options[:path_prefix]}#{controller_name}/1", :method => :delete}) + + yield options[:options] if block_given? + end + + # test named routes like foo_path and foos_path map to the correct options. + def assert_restful_named_routes_for(controller_name, singular_name = nil, options = {}) + if singular_name.is_a?(Hash) + options = singular_name + singular_name = nil + end + singular_name ||= controller_name.to_s.singularize + (options[:options] ||= {})[:controller] = controller_name.to_s + @controller = "#{controller_name.to_s.camelize}Controller".constantize.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + get :index, options[:options] + options[:options].delete :action + + full_prefix = "/#{options[:path_prefix]}#{controller_name}" + + assert_named_route "#{full_prefix}", "#{controller_name}_path", options[:options] + assert_named_route "#{full_prefix}.xml", "formatted_#{controller_name}_path", options[:options].merge(:format => 'xml') + assert_named_route "#{full_prefix}/new", "new_#{singular_name}_path", options[:options] + assert_named_route "#{full_prefix}/1", "#{singular_name}_path", options[:options].merge(:id => '1') + assert_named_route "#{full_prefix}/1;edit", "edit_#{singular_name}_path", options[:options].merge(:id => '1') + assert_named_route "#{full_prefix}/1.xml", "formatted_#{singular_name}_path", options[:options].merge(:format => 'xml', :id => '1') + yield options[:options] if block_given? + end + + def assert_named_route(expected, route, options) + actual = @controller.send(route, options) rescue $!.class.name + assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})" + end + + def assert_resource_methods(expected, resource, action_method, method) + assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}" + expected.each do |action| + assert resource.send("#{action_method}_methods")[method].include?(action), + "#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}" + end + end + + def distinct_routes? (r1, r2) + if r1.conditions == r2.conditions and r1.requirements == r2.requirements then + if r1.segments.collect(&:to_s) == r2.segments.collect(&:to_s) then + return false + end + end + true + end + +end diff --git a/vendor/rails/actionpack/test/controller/routing_test.rb b/vendor/rails/actionpack/test/controller/routing_test.rb new file mode 100644 index 0000000..8def555 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/routing_test.rb @@ -0,0 +1,1542 @@ +require File.dirname(__FILE__) + '/../abstract_unit' +require 'test/unit' +require File.dirname(__FILE__) + '/fake_controllers' +require 'action_controller/routing' + +RunTimeTests = ARGV.include? 'time' +ROUTING = ActionController::Routing + +class ROUTING::RouteBuilder + attr_reader :warn_output + + def warn(msg) + (@warn_output ||= []) << msg + end +end + +class LegacyRouteSetTests < Test::Unit::TestCase + attr_reader :rs + def setup + @rs = ::ActionController::Routing::RouteSet.new + ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) + @rs.draw {|m| m.connect ':controller/:action/:id' } + end + + def test_default_setup + assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) + assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) + assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) + + assert_equal({:controller => "admin/user", :action => 'show', :id => '10'}, rs.recognize_path("/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("content") + rs.recognize_path("content/list") + rs.recognize_path("content/show/10") + rs.recognize_path("admin/user") + rs.recognize_path("admin/user/list") + rs.recognize_path("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_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/user", :admintoken => "foo", :action => "index"}, + rs.recognize_path("/admin/user/foo")) + assert_equal({:controller => "content", :action => "foo"}, rs.recognize_path("/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.add_named_route :home, '', :controller => 'content', :action => 'list' + x = setup_for_named_route.new + assert_equal({:controller => 'content', :action => 'list', :use_route => :home}, + x.send(:home_url)) + end + + def test_named_route_with_option + rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page' + x = setup_for_named_route.new + assert_equal({:controller => 'content', :action => 'show_page', :title => 'new stuff', :use_route => :page}, + x.send(:page_url, :title => 'new stuff')) + end + + def test_named_route_with_default + rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage' + x = setup_for_named_route.new + assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutPage', :use_route => :page}, + x.send(:page_url)) + assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutRails', :use_route => :page}, + x.send(:page_url, :title => "AboutRails")) + + end + + def setup_for_named_route + x = Class.new + x.send(:define_method, :url_for) {|x| x} + rs.named_routes.install(x) + x + end + + def test_named_route_without_hash + rs.draw do |map| + map.normal ':controller/:action/:id' + end + end + + def test_named_route_with_regexps + rs.draw do |map| + map.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show', + :year => /\d+/, :month => /\d+/, :day => /\d+/ + map.connect ':controller/:action/:id' + end + x = setup_for_named_route.new + assert_equal( + {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article}, + x.send(:article_url, :title => 'hi') + ) + assert_equal( + {:controller => 'page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6, :use_route => :article}, + x.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| + map.path 'file/*path', :controller => 'content', :action => 'show_file' + map.connect ':controller/:action/:id' + end + results = rs.recognize_path "/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 "/file" + assert results, "Recognition should have succeeded" + assert_equal [], results[:path] + end + + def test_non_controllers_cannot_be_matched + rs.draw do |map| + map.connect ':controller/:action/:id' + end + assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } + end + + def test_paths_do_not_accept_defaults + assert_raises(ActionController::RoutingError) do + rs.draw do |map| + map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) + map.connect ':controller/:action/:id' + end + end + + rs.draw do |map| + map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => [] + map.connect ':controller/:action/:id' + end + end + + def test_dynamic_path_allowed + rs.draw do |map| + map.connect '*path', :controller => 'content', :action => 'show_file' + end + + assert_equal '/pages/boo', rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo)) + end + + def test_dynamic_recall_paths_allowed + rs.draw do |map| + map.connect '*path', :controller => 'content', :action => 'show_file' + end + + recall_path = ActionController::Routing::PathSegment::Result.new(%w(pages boo)) + assert_equal '/pages/boo', rs.generate({}, :controller => 'content', :action => 'show_file', :path => recall_path) + end + + def test_backwards + rs.draw do |map| + map.connect 'page/:id/:action', :controller => 'pages', :action => 'show' + map.connect ':controller/:action/:id' + end + + assert_equal '/page/20', rs.generate({:id => 20}, {:controller => 'pages', :action => 'show'}) + 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| + map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 + map.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) + + assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page")) + assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page/1")) + assert_equal({:controller => "content", :action => 'show_page', :id => '10'}, rs.recognize_path("/page/10")) + end + + # For newer revision + def test_route_with_text_default + rs.draw do |map| + map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 + map.connect ':controller/:action/:id' + end + + assert_equal '/page/foo', rs.generate(:controller => 'content', :action => 'show_page', :id => 'foo') + assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo")) + + token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian + escaped_token = CGI::escape(token) + + assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token) + assert_equal({:controller => "content", :action => 'show_page', :id => token}, rs.recognize_path("/page/#{escaped_token}")) + 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 => "content", :action => 'index'}, rs.recognize_path("/Content")) + assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list")) + assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10")) + + # these used to work, before the routes rewrite, but support for this was pulled in the new version... + #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed")) + #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed")) + end + + def test_both_requirement_and_optional + rs.draw do |map| + map.blog('test/:year', :controller => 'post', :action => 'show', + :defaults => { :year => nil }, + :requirements => { :year => /\d{4}/ } + ) + map.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.new + assert_equal({:controller => 'post', :action => 'show', :use_route => :blog}, + x.send(:blog_url)) + end + + def test_set_to_nil_forgets + rs.draw do |map| + map.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil + map.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 |map| + map.connect '', :controller => 'content' + map.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 |map| + map.root '', :controller => 'content' + map.connect ':controller/:action/:id' + end + + assert_equal '/', rs.generate(:controller => 'content', :action => 'index') + assert_equal '/', rs.generate(:controller => 'content') + + x = setup_for_named_route.new + assert_equal({:controller => 'content', :action => 'index', :use_route => :root}, + x.send(:root_url)) + end + + def test_url_generated_when_forgetting_action + [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash| + rs.draw do |map| + map.root '', hash + map.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 |map| + map.categories 'categories', :controller => 'content', :action => 'categories' + map.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_routes_array + test_named_route_method + assert_equal [:categories], rs.named_routes.names + end + + def test_nil_defaults + rs.draw do |map| + map.connect 'journal', + :controller => 'content', + :action => 'list_journal', + :date => nil, :user_id => nil + map.connect ':controller/:action/:id' + end + + assert_equal '/journal', rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil) + end + + def setup_request_method_routes_for(method) + @request = ActionController::TestRequest.new + @request.env["REQUEST_METHOD"] = method + @request.request_uri = "/match" + + rs.draw do |r| + r.connect '/match', :controller => 'books', :action => 'get', :conditions => { :method => :get } + r.connect '/match', :controller => 'books', :action => 'post', :conditions => { :method => :post } + r.connect '/match', :controller => 'books', :action => 'put', :conditions => { :method => :put } + r.connect '/match', :controller => 'books', :action => 'delete', :conditions => { :method => :delete } + end + end + + %w(GET POST PUT DELETE).each do |request_method| + define_method("test_request_method_recognized_with_#{request_method}") do + begin + Object.const_set(:BooksController, Class.new(ActionController::Base)) + + setup_request_method_routes_for(request_method) + + assert_nothing_raised { rs.recognize(@request) } + assert_equal request_method.downcase, @request.path_parameters[:action] + ensure + Object.send(:remove_const, :BooksController) rescue nil + end + end + end + + def test_subpath_recognized + Object.const_set(:SubpathBooksController, Class.new(ActionController::Base)) + + rs.draw do |r| + r.connect '/books/:id;edit', :controller => 'subpath_books', :action => 'edit' + r.connect '/items/:id;:action', :controller => 'subpath_books' + r.connect '/posts/new;:action', :controller => 'subpath_books' + r.connect '/posts/:id', :controller => 'subpath_books', :action => "show" + end + + hash = rs.recognize_path "/books/17;edit" + assert_not_nil hash + assert_equal %w(subpath_books 17 edit), [hash[:controller], hash[:id], hash[:action]] + + hash = rs.recognize_path "/items/3;complete" + assert_not_nil hash + assert_equal %w(subpath_books 3 complete), [hash[:controller], hash[:id], hash[:action]] + + hash = rs.recognize_path "/posts/new;preview" + assert_not_nil hash + assert_equal %w(subpath_books preview), [hash[:controller], hash[:action]] + + hash = rs.recognize_path "/posts/7" + assert_not_nil hash + assert_equal %w(subpath_books show 7), [hash[:controller], hash[:action], hash[:id]] + ensure + Object.send(:remove_const, :SubpathBooksController) rescue nil + end + + def test_subpath_generated + Object.const_set(:SubpathBooksController, Class.new(ActionController::Base)) + + rs.draw do |r| + r.connect '/books/:id;edit', :controller => 'subpath_books', :action => 'edit' + r.connect '/items/:id;:action', :controller => 'subpath_books' + r.connect '/posts/new;:action', :controller => 'subpath_books' + end + + assert_equal "/books/7;edit", rs.generate(:controller => "subpath_books", :id => 7, :action => "edit") + assert_equal "/items/15;complete", rs.generate(:controller => "subpath_books", :id => 15, :action => "complete") + assert_equal "/posts/new;preview", rs.generate(:controller => "subpath_books", :action => "preview") + ensure + Object.send(:remove_const, :SubpathBooksController) rescue nil + end +end + +class SegmentTest < Test::Unit::TestCase + + def test_first_segment_should_interpolate_for_structure + s = ROUTING::Segment.new + def s.interpolation_statement(array) 'hello' end + assert_equal 'hello', s.continue_string_structure([]) + end + + def test_interpolation_statement + s = ROUTING::StaticSegment.new + s.value = "Hello" + assert_equal "Hello", eval(s.interpolation_statement([])) + assert_equal "HelloHello", eval(s.interpolation_statement([s])) + + s2 = ROUTING::StaticSegment.new + s2.value = "-" + assert_equal "Hello-Hello", eval(s.interpolation_statement([s, s2])) + + s3 = ROUTING::StaticSegment.new + s3.value = "World" + assert_equal "Hello-World", eval(s3.interpolation_statement([s, s2])) + end + +end + +class StaticSegmentTest < Test::Unit::TestCase + + def test_interpolation_chunk_should_respect_raw + s = ROUTING::StaticSegment.new + s.value = 'Hello/World' + assert ! s.raw? + assert_equal 'Hello/World', CGI.unescape(s.interpolation_chunk) + + s.raw = true + assert s.raw? + assert_equal 'Hello/World', s.interpolation_chunk + end + + def test_regexp_chunk_should_escape_specials + s = ROUTING::StaticSegment.new + + s.value = 'Hello*World' + assert_equal 'Hello\*World', s.regexp_chunk + + s.value = 'HelloWorld' + assert_equal 'HelloWorld', s.regexp_chunk + end + + def test_regexp_chunk_should_add_question_mark_for_optionals + s = ROUTING::StaticSegment.new + s.value = "/" + s.is_optional = true + assert_equal "/?", s.regexp_chunk + + s.value = "hello" + assert_equal "(?:hello)?", s.regexp_chunk + end + +end + +class DynamicSegmentTest < Test::Unit::TestCase + + def segment + unless @segment + @segment = ROUTING::DynamicSegment.new + @segment.key = :a + end + @segment + end + + def test_extract_value + s = ROUTING::DynamicSegment.new + s.key = :a + + hash = {:a => '10', :b => '20'} + assert_equal '10', eval(s.extract_value) + + hash = {:b => '20'} + assert_equal nil, eval(s.extract_value) + + s.default = '20' + assert_equal '20', eval(s.extract_value) + end + + def test_default_local_name + assert_equal 'a_value', segment.local_name, + "Unexpected name -- all value_check tests will fail!" + end + + def test_presence_value_check + a_value = 10 + assert eval(segment.value_check) + end + + def test_regexp_value_check_rejects_nil + segment.regexp = /\d+/ + a_value = nil + assert ! eval(segment.value_check) + end + + def test_optional_regexp_value_check_should_accept_nil + segment.regexp = /\d+/ + segment.is_optional = true + a_value = nil + assert eval(segment.value_check) + end + + def test_regexp_value_check_rejects_no_match + segment.regexp = /\d+/ + + a_value = "Hello20World" + assert ! eval(segment.value_check) + + a_value = "20Hi" + assert ! eval(segment.value_check) + end + + def test_regexp_value_check_accepts_match + segment.regexp = /\d+/ + + a_value = "30" + assert eval(segment.value_check) + end + + def test_value_check_fails_on_nil + a_value = nil + assert ! eval(segment.value_check) + end + + def test_optional_value_needs_no_check + segment.is_optional = true + a_value = nil + assert_equal nil, segment.value_check + end + + def test_regexp_value_check_should_accept_match_with_default + segment.regexp = /\d+/ + segment.default = '200' + + a_value = '100' + assert eval(segment.value_check) + end + + def test_expiry_should_not_trigger_once_expired + not_expired = false + hash = merged = {:a => 2, :b => 3} + options = {:b => 3} + expire_on = Hash.new { raise 'No!!!' } + + eval(segment.expiry_statement) + rescue RuntimeError + flunk "Expiry check should not have occured!" + end + + def test_expiry_should_occur_according_to_expire_on + not_expired = true + hash = merged = {:a => 2, :b => 3} + options = {:b => 3} + + expire_on = {:b => true, :a => false} + eval(segment.expiry_statement) + assert not_expired + assert_equal({:a => 2, :b => 3}, hash) + + expire_on = {:b => true, :a => true} + eval(segment.expiry_statement) + assert ! not_expired + assert_equal({:b => 3}, hash) + end + + def test_extraction_code_should_return_on_nil + hash = merged = {:b => 3} + options = {:b => 3} + a_value = nil + + # Local jump because of return inside eval. + assert_raises(LocalJumpError) { eval(segment.extraction_code) } + end + + def test_extraction_code_should_return_on_mismatch + segment.regexp = /\d+/ + hash = merged = {:a => 'Hi', :b => '3'} + options = {:b => '3'} + a_value = nil + + # Local jump because of return inside eval. + assert_raises(LocalJumpError) { eval(segment.extraction_code) } + end + + def test_extraction_code_should_accept_value_and_set_local + hash = merged = {:a => 'Hi', :b => '3'} + options = {:b => '3'} + a_value = nil + + eval(segment.extraction_code) + assert_equal 'Hi', a_value + end + + def test_extraction_should_work_without_value_check + segment.default = 'hi' + hash = merged = {:b => '3'} + options = {:b => '3'} + a_value = nil + + eval(segment.extraction_code) + assert_equal 'hi', a_value + end + + def test_extraction_code_should_perform_expiry + not_expired = true + hash = merged = {:a => 'Hi', :b => '3'} + options = {:b => '3'} + expire_on = {:a => true} + a_value = nil + + eval(segment.extraction_code) + assert_equal 'Hi', a_value + assert ! not_expired + assert_equal options, hash + end + + def test_interpolation_chunk_should_replace_value + a_value = 'Hi' + assert_equal a_value, eval(%("#{segment.interpolation_chunk}")) + end + + def test_value_regexp_should_be_nil_without_regexp + assert_equal nil, segment.value_regexp + end + + def test_value_regexp_should_match_exacly + segment.regexp = /\d+/ + assert_no_match segment.value_regexp, "Hello 10 World" + assert_no_match segment.value_regexp, "Hello 10" + assert_no_match segment.value_regexp, "10 World" + assert_match segment.value_regexp, "10" + end + + def test_regexp_chunk_should_return_string + segment.regexp = /\d+/ + assert_kind_of String, segment.regexp_chunk + end + +end + +class ControllerSegmentTest < Test::Unit::TestCase + + def test_regexp_should_only_match_possible_controllers + ActionController::Routing.with_controllers %w(admin/accounts admin/users account pages) do + cs = ROUTING::ControllerSegment.new :controller + regexp = %r{\A#{cs.regexp_chunk}\Z} + + ActionController::Routing.possible_controllers.each do |name| + assert_match regexp, name + assert_no_match regexp, "#{name}_fake" + + match = regexp.match name + assert_equal name, match[1] + end + end + end + +end + +class RouteTest < Test::Unit::TestCase + + def setup + @route = ROUTING::Route.new + end + + def slash_segment(is_optional = false) + returning ROUTING::DividerSegment.new('/') do |s| + s.is_optional = is_optional + end + end + + def default_route + unless @default_route + @default_route = ROUTING::Route.new + + @default_route.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + + @default_route.segments << (s = ROUTING::DynamicSegment.new) + s.key = :controller + + @default_route.segments << slash_segment(:optional) + @default_route.segments << (s = ROUTING::DynamicSegment.new) + s.key = :action + s.default = 'index' + s.is_optional = true + + @default_route.segments << slash_segment(:optional) + @default_route.segments << (s = ROUTING::DynamicSegment.new) + s.key = :id + s.is_optional = true + + @default_route.segments << slash_segment(:optional) + end + @default_route + end + + def test_default_route_recognition + expected = {:controller => 'accounts', :action => 'show', :id => '10'} + assert_equal expected, default_route.recognize('/accounts/show/10') + assert_equal expected, default_route.recognize('/accounts/show/10/') + + expected[:id] = 'jamis' + assert_equal expected, default_route.recognize('/accounts/show/jamis/') + + expected.delete :id + assert_equal expected, default_route.recognize('/accounts/show') + assert_equal expected, default_route.recognize('/accounts/show/') + + expected[:action] = 'index' + assert_equal expected, default_route.recognize('/accounts/') + assert_equal expected, default_route.recognize('/accounts') + + assert_equal nil, default_route.recognize('/') + assert_equal nil, default_route.recognize('/accounts/how/goood/it/is/to/be/free') + end + + def test_default_route_should_omit_default_action + o = {:controller => 'accounts', :action => 'index'} + assert_equal '/accounts', default_route.generate(o, o, {}) + end + + def test_default_route_should_include_default_action_when_id_present + o = {:controller => 'accounts', :action => 'index', :id => '20'} + assert_equal '/accounts/index/20', default_route.generate(o, o, {}) + end + + def test_default_route_should_work_with_action_but_no_id + o = {:controller => 'accounts', :action => 'list_all'} + assert_equal '/accounts/list_all', default_route.generate(o, o, {}) + end + + def test_parameter_shell + page_url = ROUTING::Route.new + page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/} + assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell) + end + + def test_defaults + route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html" + assert_equal( + { :controller => "users", :action => "show", :format => "html" }, + route.defaults) + end + + def test_significant_keys_for_default_route + keys = default_route.significant_keys.sort_by {|k| k.to_s } + assert_equal [:action, :controller, :id], keys + end + + def test_significant_keys + user_url = ROUTING::Route.new + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = 'user' + + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + s.is_optional = true + + user_url.segments << (s = ROUTING::DynamicSegment.new) + s.key = :user + + user_url.segments << (s = ROUTING::StaticSegment.new) + s.value = '/' + s.raw = true + s.is_optional = true + + user_url.requirements = {:controller => 'users', :action => 'show'} + + keys = user_url.significant_keys.sort_by { |k| k.to_s } + assert_equal [:action, :controller, :user], keys + end + + def test_build_empty_query_string + assert_equal '', @route.build_query_string({}) + end + + def test_build_query_string_with_nil_value + assert_equal '', @route.build_query_string({:x => nil}) + end + + def test_simple_build_query_string + assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => '1', :y => '2')) + end + + def test_convert_ints_build_query_string + assert_equal '?x=1&y=2', order_query_string(@route.build_query_string(:x => 1, :y => 2)) + end + + def test_escape_spaces_build_query_string + assert_equal '?x=hello+world&y=goodbye+world', order_query_string(@route.build_query_string(:x => 'hello world', :y => 'goodbye world')) + end + + def test_expand_array_build_query_string + assert_equal '?x[]=1&x[]=2', order_query_string(@route.build_query_string(:x => [1, 2])) + end + + def test_escape_spaces_build_query_string_selected_keys + assert_equal '?x=hello+world', order_query_string(@route.build_query_string({:x => 'hello world', :y => 'goodbye world'}, [:x])) + end + + private + def order_query_string(qs) + '?' + qs[1..-1].split('&').sort.join('&') + end +end + +class RouteBuilderTest < Test::Unit::TestCase + + def builder + @builder ||= ROUTING::RouteBuilder.new + end + + def build(path, options) + builder.build(path, options) + end + + def test_options_should_not_be_modified + requirements1 = { :id => /\w+/, :controller => /(?:[a-z](?:-?[a-z]+)*)/ } + requirements2 = requirements1.dup + + assert_equal requirements1, requirements2 + + with_options(:controller => 'folder', + :requirements => requirements2) do |m| + m.build 'folders/new', :action => 'new' + end + + assert_equal requirements1, requirements2 + end + + def test_segment_for_static + segment, rest = builder.segment_for 'ulysses' + assert_equal '', rest + assert_kind_of ROUTING::StaticSegment, segment + assert_equal 'ulysses', segment.value + end + + def test_segment_for_action + segment, rest = builder.segment_for ':action' + assert_equal '', rest + assert_kind_of ROUTING::DynamicSegment, segment + assert_equal :action, segment.key + assert_equal 'index', segment.default + end + + def test_segment_for_dynamic + segment, rest = builder.segment_for ':login' + assert_equal '', rest + assert_kind_of ROUTING::DynamicSegment, segment + assert_equal :login, segment.key + assert_equal nil, segment.default + assert ! segment.optional? + end + + def test_segment_for_with_rest + segment, rest = builder.segment_for ':login/:action' + assert_equal :login, segment.key + assert_equal '/:action', rest + segment, rest = builder.segment_for rest + assert_equal '/', segment.value + assert_equal ':action', rest + segment, rest = builder.segment_for rest + assert_equal :action, segment.key + assert_equal '', rest + end + + def test_segments_for + segments = builder.segments_for_route_path '/:controller/:action/:id' + + assert_kind_of ROUTING::DividerSegment, segments[0] + assert_equal '/', segments[2].value + + assert_kind_of ROUTING::DynamicSegment, segments[1] + assert_equal :controller, segments[1].key + + assert_kind_of ROUTING::DividerSegment, segments[2] + assert_equal '/', segments[2].value + + assert_kind_of ROUTING::DynamicSegment, segments[3] + assert_equal :action, segments[3].key + + assert_kind_of ROUTING::DividerSegment, segments[4] + assert_equal '/', segments[4].value + + assert_kind_of ROUTING::DynamicSegment, segments[5] + assert_equal :id, segments[5].key + end + + def test_segment_for_action + s, r = builder.segment_for(':action/something/else') + assert_equal '/something/else', r + assert_equal 'index', s.default + assert_equal :action, s.key + end + + def test_action_default_should_not_trigger_on_prefix + s, r = builder.segment_for ':action_name/something/else' + assert_equal '/something/else', r + assert_equal :action_name, s.key + assert_equal nil, s.default + end + + def test_divide_route_options + segments = builder.segments_for_route_path '/cars/:action/:person/:car/' + defaults, requirements = builder.divide_route_options(segments, + :action => 'buy', :person => /\w+/, :car => /\w+/, + :defaults => {:person => nil, :car => nil} + ) + + assert_equal({:action => 'buy', :person => nil, :car => nil}, defaults) + assert_equal({:person => /\w+/, :car => /\w+/}, requirements) + end + + def test_assign_route_options + segments = builder.segments_for_route_path '/cars/:action/:person/:car/' + defaults = {:action => 'buy', :person => nil, :car => nil} + requirements = {:person => /\w+/, :car => /\w+/} + + route_requirements = builder.assign_route_options(segments, defaults, requirements) + assert_equal({}, route_requirements) + + assert_equal :action, segments[3].key + assert_equal 'buy', segments[3].default + + assert_equal :person, segments[5].key + assert_equal %r/\w+/, segments[5].regexp + assert segments[5].optional? + + assert_equal :car, segments[7].key + assert_equal %r/\w+/, segments[7].regexp + assert segments[7].optional? + end + + def test_optional_segments_preceding_required_segments + segments = builder.segments_for_route_path '/cars/:action/:person/:car/' + defaults = {:action => 'buy', :person => nil, :car => "model-t"} + assert builder.assign_route_options(segments, defaults, {}).empty? + + 0.upto(1) { |i| assert !segments[i].optional?, "segment #{i} is optional and it shouldn't be" } + assert segments[2].optional? + + assert_equal nil, builder.warn_output # should only warn on the :person segment + end + + def test_segmentation_of_semicolon_path + segments = builder.segments_for_route_path '/books/:id;:action' + defaults = { :action => 'show' } + assert builder.assign_route_options(segments, defaults, {}).empty? + segments.each do |segment| + assert ! segment.optional? || segment.key == :action + end + end + + def test_segmentation_of_dot_path + segments = builder.segments_for_route_path '/books/:action.rss' + assert builder.assign_route_options(segments, {}, {}).empty? + assert_equal 6, segments.length # "/", "books", "/", ":action", ".", "rss" + assert !segments.any? { |seg| seg.optional? } + end + + def test_segmentation_of_dynamic_dot_path + segments = builder.segments_for_route_path '/books/:action.:format' + assert builder.assign_route_options(segments, {}, {}).empty? + assert_equal 6, segments.length # "/", "books", "/", ":action", ".", ":format" + assert !segments.any? { |seg| seg.optional? } + assert_kind_of ROUTING::DynamicSegment, segments.last + end + + def test_assignment_of_is_optional_when_default + segments = builder.segments_for_route_path '/books/:action.rss' + assert_equal segments[3].key, :action + segments[3].default = 'changes' + builder.ensure_required_segments(segments) + assert ! segments[3].optional? + end + + def test_is_optional_is_assigned_to_default_segments + segments = builder.segments_for_route_path '/books/:action' + builder.assign_route_options(segments, {:action => 'index'}, {}) + + assert_equal segments[3].key, :action + assert segments[3].optional? + assert_kind_of ROUTING::DividerSegment, segments[2] + assert segments[2].optional? + end + + # XXX is optional not being set right? + # /blah/:defaulted_segment <-- is the second slash optional? it should be. + + def test_route_build + ActionController::Routing.with_controllers %w(users pages) do + r = builder.build '/:controller/:action/:id/', :action => nil + + [0, 2, 4].each do |i| + assert_kind_of ROUTING::DividerSegment, r.segments[i] + assert_equal '/', r.segments[i].value + assert r.segments[i].optional? if i > 1 + end + + assert_kind_of ROUTING::DynamicSegment, r.segments[1] + assert_equal :controller, r.segments[1].key + assert_equal nil, r.segments[1].default + + assert_kind_of ROUTING::DynamicSegment, r.segments[3] + assert_equal :action, r.segments[3].key + assert_equal 'index', r.segments[3].default + + assert_kind_of ROUTING::DynamicSegment, r.segments[5] + assert_equal :id, r.segments[5].key + assert r.segments[5].optional? + end + end + + def test_slashes_are_implied + routes = [ + builder.build('/:controller/:action/:id/', :action => nil), + builder.build('/:controller/:action/:id', :action => nil), + builder.build(':controller/:action/:id', :action => nil), + builder.build('/:controller/:action/:id/', :action => nil) + ] + expected = routes.first.segments.length + routes.each_with_index do |route, i| + found = route.segments.length + assert_equal expected, found, "Route #{i + 1} has #{found} segments, expected #{expected}" + end + end + +end + +class RouteSetTest < Test::Unit::TestCase + class MockController + attr_accessor :routes + + def initialize(routes) + self.routes = routes + end + + def url_for(options) + only_path = options.delete(:only_path) + path = routes.generate(options) + only_path ? path : "http://named.route.test#{path}" + end + end + + class MockRequest + attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method + + def initialize(values={}) + values.each { |key, value| send("#{key}=", value) } + if values[:host] + subdomain, self.domain = values[:host].split(/\./, 2) + self.subdomains = [subdomain] + end + end + end + + def set + @set ||= ROUTING::RouteSet.new + end + + def request + @request ||= MockRequest.new(:host => "named.routes.test", :method => :get) + end + + def test_generate_extras + set.draw { |m| m.connect ':controller/:action/:id' } + path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + assert_equal "/foo/bar/15", path + assert_equal %w(that this), extras.map(&:to_s).sort + end + + def test_extra_keys + set.draw { |m| m.connect ':controller/:action/:id' } + extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") + assert_equal %w(that this), extras.map(&:to_s).sort + end + + def test_draw + assert_equal 0, set.routes.size + set.draw do |map| + map.connect '/hello/world', :controller => 'a', :action => 'b' + end + assert_equal 1, set.routes.size + end + + def test_named_draw + assert_equal 0, set.routes.size + set.draw do |map| + map.hello '/hello/world', :controller => 'a', :action => 'b' + end + assert_equal 1, set.routes.size + assert_equal set.routes.first, set.named_routes[:hello] + end + + def test_later_named_routes_take_precedence + set.draw do |map| + map.hello '/hello/world', :controller => 'a', :action => 'b' + map.hello '/hello', :controller => 'a', :action => 'b' + end + assert_equal set.routes.last, set.named_routes[:hello] + end + + def setup_named_route_test + set.draw do |map| + map.show '/people/:id', :controller => 'people', :action => 'show' + map.index '/people', :controller => 'people', :action => 'index' + map.multi '/people/go/:foo/:bar/joe/:id', :controller => 'people', :action => 'multi' + end + + klass = Class.new(MockController) + set.named_routes.install(klass) + klass.new(set) + end + + def test_named_route_hash_access_method + controller = setup_named_route_test + + assert_equal( + { :controller => 'people', :action => 'show', :id => 5, :use_route => :show }, + controller.send(:hash_for_show_url, :id => 5)) + + assert_equal( + { :controller => 'people', :action => 'index', :use_route => :index }, + controller.send(:hash_for_index_url)) + + assert_equal( + { :controller => 'people', :action => 'show', :id => 5, :use_route => :show, :only_path => true }, + controller.send(:hash_for_show_path, :id => 5) + ) + end + + def test_named_route_url_method + controller = setup_named_route_test + + assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5) + assert_equal "/people/5", controller.send(:show_path, :id => 5) + + assert_equal "http://named.route.test/people", controller.send(:index_url) + assert_equal "/people", controller.send(:index_path) + end + + def test_namd_route_url_method_with_ordered_parameters + controller = setup_named_route_test + assert_equal "http://named.route.test/people/go/7/hello/joe/5", + controller.send(:multi_url, 7, "hello", 5) + end + + def test_draw_default_route + ActionController::Routing.with_controllers(['users']) do + set.draw do |map| + map.connect '/:controller/:action/:id' + end + + assert_equal 1, set.routes.size + route = set.routes.first + + assert route.segments.last.optional? + + assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) + assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) + + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) + end + end + + def test_route_with_parameter_shell + ActionController::Routing.with_controllers(['users', 'pages']) do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ + map.connect '/:controller/:action/:id' + end + + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) + assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) + + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) + end + end + + def test_route_requirements_with_anchor_chars_are_invalid + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /^\d+/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\A\d+/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+$/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\Z/ + end + end + assert_raises ArgumentError do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/ + end + end + assert_nothing_raised do + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/ + end + assert_raises ActionController::RoutingError do + set.generate :controller => 'pages', :action => 'show', :id => 10 + end + end + end + + def test_non_path_route_requirements_match_all + set.draw do |map| + map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ + end + assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis') + assert_raises ActionController::RoutingError do + set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis') + end + assert_raises ActionController::RoutingError do + set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david') + end + end + + def test_recognize_with_encoded_id_and_regex + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9 ]+/ + end + + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) + assert_equal({:controller => 'pages', :action => 'show', :id => 'hello world'}, set.recognize_path('/page/hello+world')) + end + + def test_recognize_with_conditions + Object.const_set(:PeopleController, Class.new) + + set.draw do |map| + map.with_options(:controller => "people") do |people| + people.people "/people", :action => "index", :conditions => { :method => :get } + people.connect "/people", :action => "create", :conditions => { :method => :post } + people.person "/people/:id", :action => "show", :conditions => { :method => :get } + people.connect "/people/:id", :action => "update", :conditions => { :method => :put } + people.connect "/people/:id", :action => "destroy", :conditions => { :method => :delete } + end + end + + request.path = "/people" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("index", request.path_parameters[:action]) + + request.method = :post + assert_nothing_raised { set.recognize(request) } + assert_equal("create", request.path_parameters[:action]) + + request.path = "/people/5" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("show", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + + request.method = :put + assert_nothing_raised { set.recognize(request) } + assert_equal("update", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + + request.method = :delete + assert_nothing_raised { set.recognize(request) } + assert_equal("destroy", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + ensure + Object.send(:remove_const, :PeopleController) + end + + def test_typo_recognition + Object.const_set(:ArticlesController, Class.new) + + set.draw do |map| + map.connect 'articles/:year/:month/:day/:title', + :controller => 'articles', :action => 'permalink', + :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/ + end + + request.path = "/articles/2005/11/05/a-very-interesting-article" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("permalink", request.path_parameters[:action]) + assert_equal("2005", request.path_parameters[:year]) + assert_equal("11", request.path_parameters[:month]) + assert_equal("05", request.path_parameters[:day]) + assert_equal("a-very-interesting-article", request.path_parameters[:title]) + + ensure + Object.send(:remove_const, :ArticlesController) + end + + def test_routing_traversal_does_not_load_extra_classes + assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" + set.draw do |map| + map.connect '/profile', :controller => 'profile' + end + + request.path = '/profile' + + set.recognize(request) rescue nil + + assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded" + end + + def test_recognize_with_conditions_and_format + Object.const_set(:PeopleController, Class.new) + + set.draw do |map| + map.with_options(:controller => "people") do |people| + people.person "/people/:id", :action => "show", :conditions => { :method => :get } + people.connect "/people/:id", :action => "update", :conditions => { :method => :put } + people.connect "/people/:id.:_format", :action => "show", :conditions => { :method => :get } + end + end + + request.path = "/people/5" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("show", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + + request.method = :put + assert_nothing_raised { set.recognize(request) } + assert_equal("update", request.path_parameters[:action]) + + request.path = "/people/5.png" + request.method = :get + assert_nothing_raised { set.recognize(request) } + assert_equal("show", request.path_parameters[:action]) + assert_equal("5", request.path_parameters[:id]) + assert_equal("png", request.path_parameters[:_format]) + ensure + Object.send(:remove_const, :PeopleController) + end + + def test_generate_with_default_action + set.draw do |map| + map.connect "/people", :controller => "people" + map.connect "/people/list", :controller => "people", :action => "list" + end + + url = set.generate(:controller => "people", :action => "list") + assert_equal "/people/list", url + end + + def test_generate_finds_best_fit + set.draw do |map| + map.connect "/people", :controller => "people", :action => "index" + map.connect "/ws/people", :controller => "people", :action => "index", :ws => true + end + + url = set.generate(:controller => "people", :action => "index", :ws => true) + assert_equal "/ws/people", url + end + + def test_generate_changes_controller_module + set.draw { |map| map.connect ':controller/:action/:id' } + current = { :controller => "bling/bloop", :action => "bap", :id => 9 } + url = set.generate({:controller => "foo/bar", :action => "baz", :id => 7}, current) + assert_equal "/foo/bar/baz/7", url + end + + def test_id_is_not_impossibly_sticky + set.draw do |map| + map.connect 'foo/:number', :controller => "people", :action => "index" + map.connect ':controller/:action/:id' + end + + url = set.generate({:controller => "people", :action => "index", :number => 3}, + {:controller => "people", :action => "index", :id => "21"}) + assert_equal "/foo/3", url + end + + def test_id_is_sticky_when_it_ought_to_be + set.draw do |map| + map.connect ':controller/:id/:action' + end + + url = set.generate({:action => "destroy"}, {:controller => "people", :action => "show", :id => "7"}) + assert_equal "/people/7/destroy", url + end + + def test_use_static_path_when_possible + set.draw do |map| + map.connect 'about', :controller => "welcome", :action => "about" + map.connect ':controller/:action/:id' + end + + url = set.generate({:controller => "welcome", :action => "about"}, + {:controller => "welcome", :action => "get", :id => "7"}) + assert_equal "/about", url + end + + def test_generate_extras + set.draw { |map| map.connect ':controller/:action/:id' } + + args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + assert_equal "/foo/bar/7?x=y", set.generate(args) + assert_equal ["/foo/bar/7", [:x]], set.generate_extras(args) + assert_equal [:x], set.extra_keys(args) + end + + def test_named_routes_are_never_relative_to_modules + set.draw do |map| + map.connect "/connection/manage/:action", :controller => 'connection/manage' + map.connect "/connection/connection", :controller => "connection/connection" + map.family_connection "/connection", :controller => "connection" + end + + url = set.generate({:controller => "connection"}, {:controller => 'connection/manage'}) + assert_equal "/connection/connection", url + + url = set.generate({:use_route => :family_connection, :controller => "connection"}, {:controller => 'connection/manage'}) + assert_equal "/connection", url + end +end + +class RoutingTest < Test::Unit::TestCase + + def test_possible_controllers + true_load_paths = $LOAD_PATH.dup + + ActionController::Routing.use_controllers! nil + Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures') + + $LOAD_PATH.clear + $LOAD_PATH.concat [ + RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib' + ] + + assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort + ensure + if true_load_paths + $LOAD_PATH.clear + $LOAD_PATH.concat true_load_paths + end + Object.send(:remove_const, :RAILS_ROOT) rescue nil + end + + def test_with_controllers + c = %w(admin/accounts admin/users account pages) + ActionController::Routing.with_controllers c do + assert_equal c, ActionController::Routing.possible_controllers + end + end + + def test_normalize_unix_paths + load_paths = %w(. config/../app/controllers config/../app//helpers script/../config/../vendor/rails/actionpack/lib vendor/rails/railties/builtin/rails_info app/models lib script/../config/../foo/bar/../../app/models) + paths = ActionController::Routing.normalize_paths(load_paths) + assert_equal %w(vendor/rails/railties/builtin/rails_info vendor/rails/actionpack/lib app/controllers app/helpers app/models lib .), paths + end + + def test_normalize_windows_paths + load_paths = %w(. config\\..\\app\\controllers config\\..\\app\\\\helpers script\\..\\config\\..\\vendor\\rails\\actionpack\\lib vendor\\rails\\railties\\builtin\\rails_info app\\models lib script\\..\\config\\..\\foo\\bar\\..\\..\\app\\models) + paths = ActionController::Routing.normalize_paths(load_paths) + assert_equal %w(vendor\\rails\\railties\\builtin\\rails_info vendor\\rails\\actionpack\\lib app\\controllers app\\helpers app\\models lib .), paths + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/controller/send_file_test.rb b/vendor/rails/actionpack/test/controller/send_file_test.rb new file mode 100644 index 0000000..4c97e2d --- /dev/null +++ b/vendor/rails/actionpack/test/controller/send_file_test.rb @@ -0,0 +1,109 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/session_management_test.rb b/vendor/rails/actionpack/test/controller/session_management_test.rb new file mode 100644 index 0000000..c611cb8 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/session_management_test.rb @@ -0,0 +1,94 @@ +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 diff --git a/vendor/rails/actionpack/test/controller/test_test.rb b/vendor/rails/actionpack/test/controller/test_test.rb new file mode 100644 index 0000000..58ed819 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/test_test.rb @@ -0,0 +1,451 @@ +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 => < + + +
      +
        +
      • hello
      • +
      • goodbye
      • +
      +
      +
      +
      + Name: +
      +
      + + +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 + + def redirect_to_same_controller + redirect_to :controller => 'test', :action => 'test_uri', :id => 5 + end + + def redirect_to_different_controller + redirect_to :controller => 'fail', :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 + ActionController::Routing.use_controllers! %w(content admin/user) + 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 |map| + map.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params' + map.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 + with_foo_routing do |set| + get :redirect_to_symbol + assert_response :redirect + assert_redirected_to :generate_url + end + end + + def test_assert_follow_redirect_to_same_controller + with_foo_routing do |set| + get :redirect_to_same_controller + assert_response :redirect + assert_redirected_to :controller => 'test_test/test', :action => 'test_uri', :id => 5 + assert_nothing_raised { follow_redirect } + end + end + + def test_assert_follow_redirect_to_different_controller + with_foo_routing do |set| + get :redirect_to_different_controller + assert_response :redirect + assert_redirected_to :controller => 'fail', :id => 5 + assert_raise(RuntimeError) { follow_redirect } + end + end + + protected + def with_foo_routing + with_routing do |set| + set.draw do |map| + map.generate_url 'foo', :controller => 'test' + map.connect ':controller/:action/:id' + end + yield set + end + end +end diff --git a/vendor/rails/actionpack/test/controller/url_rewriter_test.rb b/vendor/rails/actionpack/test/controller/url_rewriter_test.rb new file mode 100644 index 0000000..57a38f2 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/url_rewriter_test.rb @@ -0,0 +1,29 @@ +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_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 diff --git a/vendor/rails/actionpack/test/controller/verification_test.rb b/vendor/rails/actionpack/test/controller/verification_test.rb new file mode 100644 index 0000000..a3a913d --- /dev/null +++ b/vendor/rails/actionpack/test/controller/verification_test.rb @@ -0,0 +1,225 @@ +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 => 405, :text => "Must be post" }, :add_headers => { "Allow" => "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_and_sets_allow_header + get :must_be_post + assert_response 405 + assert_equal "Must be post", @response.body + assert_equal "POST", @response.headers["Allow"] + end + + def test_second_redirect + assert_nothing_raised { get :two_redirects } + end +end diff --git a/vendor/rails/actionpack/test/controller/webservice_test.rb b/vendor/rails/actionpack/test/controller/webservice_test.rb new file mode 100644 index 0000000..e60e3a8 --- /dev/null +++ b/vendor/rails/actionpack/test/controller/webservice_test.rb @@ -0,0 +1,248 @@ +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', 'content...') + + 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', 'content...') + + 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', 'content...SimpleXml' ) + 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', "\n...\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_xml + ActionController::Base.param_parsers[Mime::XML] = :xml_simple + process('POST', 'application/xml', <<-XML) + + 15 + false + true + 2005-03-17 + 2005-03-17T21:41:07Z + unparsed + 1 + hello + 1974-07-25 + + 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) + <foo "bar's" & friends> + XML + assert_equal %(), @controller.params[:data] + 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(%{ + + + With O'Reilly and Adaptive Path + + + Staying at the Savoy + + + + + + + + + } + ) + 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('hi') + assert_equal 'hi', node.node_value + end + +end diff --git a/vendor/rails/actionpack/test/fixtures/addresses/list.rhtml b/vendor/rails/actionpack/test/fixtures/addresses/list.rhtml new file mode 100644 index 0000000..c75e01e --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/addresses/list.rhtml @@ -0,0 +1 @@ +We only need to get this far! diff --git a/vendor/rails/actionpack/test/fixtures/companies.yml b/vendor/rails/actionpack/test/fixtures/companies.yml new file mode 100644 index 0000000..707f72a --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/companies.yml @@ -0,0 +1,24 @@ +thirty_seven_signals: + id: 1 + name: 37Signals + rating: 4 + +TextDrive: + id: 2 + name: TextDrive + rating: 4 + +PlanetArgon: + id: 3 + name: Planet Argon + rating: 4 + +Google: + id: 4 + name: Google + rating: 4 + +Ionist: + id: 5 + name: Ioni.st + rating: 4 \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/company.rb b/vendor/rails/actionpack/test/fixtures/company.rb new file mode 100644 index 0000000..0d1c29b --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/company.rb @@ -0,0 +1,9 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/db_definitions/sqlite.sql b/vendor/rails/actionpack/test/fixtures/db_definitions/sqlite.sql new file mode 100644 index 0000000..b4e7539 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/db_definitions/sqlite.sql @@ -0,0 +1,42 @@ +CREATE TABLE 'companies' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' TEXT DEFAULT NULL, + 'rating' INTEGER DEFAULT 1 +); + +CREATE TABLE 'replies' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'content' text, + 'created_at' datetime, + 'updated_at' datetime, + 'topic_id' integer +); + +CREATE TABLE 'topics' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'title' varchar(255), + 'subtitle' varchar(255), + 'content' text, + 'created_at' datetime, + 'updated_at' datetime +); + +CREATE TABLE 'developers' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' TEXT DEFAULT NULL, + 'salary' INTEGER DEFAULT 70000, + 'created_at' DATETIME DEFAULT NULL, + 'updated_at' DATETIME DEFAULT NULL +); + +CREATE TABLE 'projects' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' TEXT DEFAULT NULL +); + +CREATE TABLE 'developers_projects' ( + 'developer_id' INTEGER NOT NULL, + 'project_id' INTEGER NOT NULL, + 'joined_on' DATE DEFAULT NULL, + 'access_level' INTEGER DEFAULT 1 +); diff --git a/vendor/rails/actionpack/test/fixtures/developer.rb b/vendor/rails/actionpack/test/fixtures/developer.rb new file mode 100644 index 0000000..f5e5b90 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/developer.rb @@ -0,0 +1,7 @@ +class Developer < ActiveRecord::Base + has_and_belongs_to_many :projects +end + +class DeVeLoPeR < ActiveRecord::Base + set_table_name "developers" +end diff --git a/vendor/rails/actionpack/test/fixtures/developers.yml b/vendor/rails/actionpack/test/fixtures/developers.yml new file mode 100644 index 0000000..308bf75 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/developers.yml @@ -0,0 +1,21 @@ +david: + id: 1 + name: David + salary: 80000 + +jamis: + id: 2 + name: Jamis + salary: 150000 + +<% for digit in 3..10 %> +dev_<%= digit %>: + id: <%= digit %> + name: fixture_<%= digit %> + salary: 100000 +<% end %> + +poor_jamis: + id: 11 + name: Jamis + salary: 9000 \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/developers_projects.yml b/vendor/rails/actionpack/test/fixtures/developers_projects.yml new file mode 100644 index 0000000..cee359c --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/developers_projects.yml @@ -0,0 +1,13 @@ +david_action_controller: + developer_id: 1 + project_id: 2 + joined_on: 2004-10-10 + +david_active_record: + developer_id: 1 + project_id: 1 + joined_on: 2004-10-10 + +jamis_active_record: + developer_id: 2 + project_id: 1 \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/fun/games/hello_world.rhtml b/vendor/rails/actionpack/test/fixtures/fun/games/hello_world.rhtml new file mode 100644 index 0000000..1ebfbe2 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/fun/games/hello_world.rhtml @@ -0,0 +1 @@ +Living in a nested world \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/helpers/abc_helper.rb b/vendor/rails/actionpack/test/fixtures/helpers/abc_helper.rb new file mode 100644 index 0000000..7104ff3 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/helpers/abc_helper.rb @@ -0,0 +1,5 @@ +module AbcHelper + def bare_a() end + def bare_b() end + def bare_c() end +end diff --git a/vendor/rails/actionpack/test/fixtures/helpers/fun/games_helper.rb b/vendor/rails/actionpack/test/fixtures/helpers/fun/games_helper.rb new file mode 100644 index 0000000..bf60d9d --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/helpers/fun/games_helper.rb @@ -0,0 +1,3 @@ +module Fun::GamesHelper + def stratego() "Iz guuut!" end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/helpers/fun/pdf_helper.rb b/vendor/rails/actionpack/test/fixtures/helpers/fun/pdf_helper.rb new file mode 100644 index 0000000..1890f6c --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/helpers/fun/pdf_helper.rb @@ -0,0 +1,3 @@ +module Fun::PDFHelper + def foobar() 'baz' end +end diff --git a/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml new file mode 100644 index 0000000..5f86a7d --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.rhtml @@ -0,0 +1 @@ +controller_name_space/nested.rhtml <%= yield %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/item.rhtml b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/item.rhtml new file mode 100644 index 0000000..1bc7cbd --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/item.rhtml @@ -0,0 +1 @@ +item.rhtml <%= yield %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/layout_test.rhtml b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/layout_test.rhtml new file mode 100644 index 0000000..c0f2642 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/layout_test.rhtml @@ -0,0 +1 @@ +layout_test.rhtml <%= yield %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab new file mode 100644 index 0000000..018abfb --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab @@ -0,0 +1 @@ +Mab \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layout_tests/views/hello.rhtml b/vendor/rails/actionpack/test/fixtures/layout_tests/views/hello.rhtml new file mode 100644 index 0000000..bbccf09 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layout_tests/views/hello.rhtml @@ -0,0 +1 @@ +hello.rhtml \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layouts/builder.rxml b/vendor/rails/actionpack/test/fixtures/layouts/builder.rxml new file mode 100644 index 0000000..729af4b --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layouts/builder.rxml @@ -0,0 +1,3 @@ +xml.wrapper do + xml << @content_for_layout +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layouts/standard.rhtml b/vendor/rails/actionpack/test/fixtures/layouts/standard.rhtml new file mode 100644 index 0000000..368764e --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layouts/standard.rhtml @@ -0,0 +1 @@ +<%= @content_for_layout %><%= @variable_for_layout %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layouts/talk_from_action.rhtml b/vendor/rails/actionpack/test/fixtures/layouts/talk_from_action.rhtml new file mode 100644 index 0000000..187aab0 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layouts/talk_from_action.rhtml @@ -0,0 +1,2 @@ +<%= @title || @content_for_title %> +<%= @content_for_layout -%> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/layouts/yield.rhtml b/vendor/rails/actionpack/test/fixtures/layouts/yield.rhtml new file mode 100644 index 0000000..482dc90 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/layouts/yield.rhtml @@ -0,0 +1,2 @@ +<%= yield :title %> +<%= yield %> diff --git a/vendor/rails/actionpack/test/fixtures/multipart/binary_file b/vendor/rails/actionpack/test/fixtures/multipart/binary_file new file mode 100644 index 0000000..1bcf97f Binary files /dev/null and b/vendor/rails/actionpack/test/fixtures/multipart/binary_file differ diff --git a/vendor/rails/actionpack/test/fixtures/multipart/large_text_file b/vendor/rails/actionpack/test/fixtures/multipart/large_text_file new file mode 100644 index 0000000..7f97fb1 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/multipart/large_text_file @@ -0,0 +1,10 @@ +--AaB03x +Content-Disposition: form-data; name="foo" + +bar +--AaB03x +Content-Disposition: form-data; name="file"; filename="file.txt" +Content-Type: text/plain + +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +--AaB03x-- diff --git a/vendor/rails/actionpack/test/fixtures/multipart/mixed_files b/vendor/rails/actionpack/test/fixtures/multipart/mixed_files new file mode 100644 index 0000000..5eba7a6 Binary files /dev/null and b/vendor/rails/actionpack/test/fixtures/multipart/mixed_files differ diff --git a/vendor/rails/actionpack/test/fixtures/multipart/mona_lisa.jpg b/vendor/rails/actionpack/test/fixtures/multipart/mona_lisa.jpg new file mode 100644 index 0000000..5cf3bef Binary files /dev/null and b/vendor/rails/actionpack/test/fixtures/multipart/mona_lisa.jpg differ diff --git a/vendor/rails/actionpack/test/fixtures/multipart/single_parameter b/vendor/rails/actionpack/test/fixtures/multipart/single_parameter new file mode 100644 index 0000000..8962c35 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/multipart/single_parameter @@ -0,0 +1,5 @@ +--AaB03x +Content-Disposition: form-data; name="foo" + +bar +--AaB03x-- diff --git a/vendor/rails/actionpack/test/fixtures/multipart/text_file b/vendor/rails/actionpack/test/fixtures/multipart/text_file new file mode 100644 index 0000000..e0367d6 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/multipart/text_file @@ -0,0 +1,10 @@ +--AaB03x +Content-Disposition: form-data; name="foo" + +bar +--AaB03x +Content-Disposition: form-data; name="file"; filename="file.txt" +Content-Type: text/plain + +contents +--AaB03x-- diff --git a/vendor/rails/actionpack/test/fixtures/project.rb b/vendor/rails/actionpack/test/fixtures/project.rb new file mode 100644 index 0000000..2b53d39 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/project.rb @@ -0,0 +1,3 @@ +class Project < ActiveRecord::Base + has_and_belongs_to_many :developers, :uniq => true +end diff --git a/vendor/rails/actionpack/test/fixtures/projects.yml b/vendor/rails/actionpack/test/fixtures/projects.yml new file mode 100644 index 0000000..02800c7 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/projects.yml @@ -0,0 +1,7 @@ +action_controller: + id: 2 + name: Active Controller + +active_record: + id: 1 + name: Active Record diff --git a/vendor/rails/actionpack/test/fixtures/public/images/rails.png b/vendor/rails/actionpack/test/fixtures/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/vendor/rails/actionpack/test/fixtures/public/images/rails.png differ diff --git a/vendor/rails/actionpack/test/fixtures/replies.yml b/vendor/rails/actionpack/test/fixtures/replies.yml new file mode 100644 index 0000000..284c9c0 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/replies.yml @@ -0,0 +1,13 @@ +witty_retort: + id: 1 + topic_id: 1 + content: Birdman is better! + created_at: <%= 6.hours.ago.to_s(:db) %> + updated_at: nil + +another: + id: 2 + topic_id: 2 + content: Nuh uh! + created_at: <%= 1.hour.ago.to_s(:db) %> + updated_at: nil \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/reply.rb b/vendor/rails/actionpack/test/fixtures/reply.rb new file mode 100644 index 0000000..ea84042 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/reply.rb @@ -0,0 +1,5 @@ +class Reply < ActiveRecord::Base + belongs_to :topic, :include => [:replies] + + validates_presence_of :content +end diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/all_types_with_layout.rhtml b/vendor/rails/actionpack/test/fixtures/respond_to/all_types_with_layout.rhtml new file mode 100644 index 0000000..84a8404 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/all_types_with_layout.rhtml @@ -0,0 +1 @@ +HTML for all_types_with_layout \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/all_types_with_layout.rjs b/vendor/rails/actionpack/test/fixtures/respond_to/all_types_with_layout.rjs new file mode 100644 index 0000000..b7aec7c --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/all_types_with_layout.rjs @@ -0,0 +1 @@ +page << "RJS for all_types_with_layout" \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/layouts/standard.rhtml b/vendor/rails/actionpack/test/fixtures/respond_to/layouts/standard.rhtml new file mode 100644 index 0000000..fcb28ec --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/layouts/standard.rhtml @@ -0,0 +1 @@ +<%= @content_for_layout %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rhtml b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rhtml new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rhtml @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rjs b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rjs new file mode 100644 index 0000000..469fcd8 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rjs @@ -0,0 +1 @@ +page[:body].visual_effect :highlight \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rxml b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rxml new file mode 100644 index 0000000..598d62e --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults.rxml @@ -0,0 +1 @@ +xml.p "Hello world!" \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rhtml b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rhtml new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rhtml @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rjs b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rjs new file mode 100644 index 0000000..469fcd8 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rjs @@ -0,0 +1 @@ +page[:body].visual_effect :highlight \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rxml b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rxml new file mode 100644 index 0000000..598d62e --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.rxml @@ -0,0 +1 @@ +xml.p "Hello world!" \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/scope/test/modgreet.rhtml b/vendor/rails/actionpack/test/fixtures/scope/test/modgreet.rhtml new file mode 100644 index 0000000..8947726 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/scope/test/modgreet.rhtml @@ -0,0 +1 @@ +

      Beautiful modules!

      \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/_customer.rhtml b/vendor/rails/actionpack/test/fixtures/test/_customer.rhtml new file mode 100644 index 0000000..872d8c4 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/_customer.rhtml @@ -0,0 +1 @@ +Hello: <%= customer.name %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/_customer_greeting.rhtml b/vendor/rails/actionpack/test/fixtures/test/_customer_greeting.rhtml new file mode 100644 index 0000000..6acbcb2 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/_customer_greeting.rhtml @@ -0,0 +1 @@ +<%= greeting %>: <%= customer_greeting.name %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/_hash_object.rhtml b/vendor/rails/actionpack/test/fixtures/test/_hash_object.rhtml new file mode 100644 index 0000000..037a736 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/_hash_object.rhtml @@ -0,0 +1 @@ +<%= hash_object[:first_name] %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/_partial_only.rhtml b/vendor/rails/actionpack/test/fixtures/test/_partial_only.rhtml new file mode 100644 index 0000000..a44b3ee --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/_partial_only.rhtml @@ -0,0 +1 @@ +only partial \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/_person.rhtml b/vendor/rails/actionpack/test/fixtures/test/_person.rhtml new file mode 100644 index 0000000..b2e5688 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/_person.rhtml @@ -0,0 +1,2 @@ +Second: <%= name %> +Third: <%= @name %> diff --git a/vendor/rails/actionpack/test/fixtures/test/action_talk_to_layout.rhtml b/vendor/rails/actionpack/test/fixtures/test/action_talk_to_layout.rhtml new file mode 100644 index 0000000..36e896d --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/action_talk_to_layout.rhtml @@ -0,0 +1,2 @@ +<% @title = "Talking to the layout" -%> +Action was here! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/block_content_for.rhtml b/vendor/rails/actionpack/test/fixtures/test/block_content_for.rhtml new file mode 100644 index 0000000..9510337 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/block_content_for.rhtml @@ -0,0 +1,2 @@ +<% block_content_for :title do 'Putting stuff in the title!' end %> +Great stuff! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/capturing.rhtml b/vendor/rails/actionpack/test/fixtures/test/capturing.rhtml new file mode 100644 index 0000000..1addaa4 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/capturing.rhtml @@ -0,0 +1,4 @@ +<% days = capture do %> + Dreamy days +<% end %> +<%= days %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/content_for.rhtml b/vendor/rails/actionpack/test/fixtures/test/content_for.rhtml new file mode 100644 index 0000000..0e47ca8 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/content_for.rhtml @@ -0,0 +1,2 @@ +<% content_for :title do %>Putting stuff in the title!<% end %> +Great stuff! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/delete_with_js.rjs b/vendor/rails/actionpack/test/fixtures/test/delete_with_js.rjs new file mode 100644 index 0000000..4b75a95 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/delete_with_js.rjs @@ -0,0 +1,2 @@ +page.remove 'person' +page.visual_effect :highlight, "project-#{@project_id}" diff --git a/vendor/rails/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml b/vendor/rails/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml new file mode 100644 index 0000000..8b8a449 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.rhtml @@ -0,0 +1 @@ +The secret is <%= @secret %> diff --git a/vendor/rails/actionpack/test/fixtures/test/enum_rjs_test.rjs b/vendor/rails/actionpack/test/fixtures/test/enum_rjs_test.rjs new file mode 100644 index 0000000..e300407 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/enum_rjs_test.rjs @@ -0,0 +1,6 @@ +page.select('.product').each do |value| + page.visual_effect :highlight + page.visual_effect :highlight, value + page.sortable(value, :url => { :action => "order" }) + page.draggable(value) +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/erb_content_for.rhtml b/vendor/rails/actionpack/test/fixtures/test/erb_content_for.rhtml new file mode 100644 index 0000000..c3bdd13 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/erb_content_for.rhtml @@ -0,0 +1,2 @@ +<% erb_content_for :title do %>Putting stuff in the title!<% end %> +Great stuff! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/greeting.rhtml b/vendor/rails/actionpack/test/fixtures/test/greeting.rhtml new file mode 100644 index 0000000..62fb029 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/greeting.rhtml @@ -0,0 +1 @@ +

      This is grand!

      diff --git a/vendor/rails/actionpack/test/fixtures/test/hello.rxml b/vendor/rails/actionpack/test/fixtures/test/hello.rxml new file mode 100644 index 0000000..82a4a31 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/hello.rxml @@ -0,0 +1,4 @@ +xml.html do + xml.p "Hello #{@name}" + xml << render_file("test/greeting") +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/hello_world.rhtml b/vendor/rails/actionpack/test/fixtures/test/hello_world.rhtml new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/hello_world.rhtml @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/hello_world.rxml b/vendor/rails/actionpack/test/fixtures/test/hello_world.rxml new file mode 100644 index 0000000..bffd219 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/hello_world.rxml @@ -0,0 +1,3 @@ +xml.html do + xml.p "Hello" +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/hello_world_with_layout_false.rhtml b/vendor/rails/actionpack/test/fixtures/test/hello_world_with_layout_false.rhtml new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/hello_world_with_layout_false.rhtml @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/hello_xml_world.rxml b/vendor/rails/actionpack/test/fixtures/test/hello_xml_world.rxml new file mode 100644 index 0000000..02b14fe --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/hello_xml_world.rxml @@ -0,0 +1,11 @@ +xml.html do + xml.head do + xml.title "Hello World" + end + + xml.body do + xml.p "abes" + xml.p "monks" + xml.p "wiseguys" + end +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/list.rhtml b/vendor/rails/actionpack/test/fixtures/test/list.rhtml new file mode 100644 index 0000000..cd0ab45 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/list.rhtml @@ -0,0 +1 @@ +<%= @test_unchanged = 'goodbye' %><%= render_collection_of_partials "customer", @customers %><%= @test_unchanged %> diff --git a/vendor/rails/actionpack/test/fixtures/test/non_erb_block_content_for.rxml b/vendor/rails/actionpack/test/fixtures/test/non_erb_block_content_for.rxml new file mode 100644 index 0000000..6ff6db0 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/non_erb_block_content_for.rxml @@ -0,0 +1,4 @@ +content_for :title do + 'Putting stuff in the title!' +end +xml << "\nGreat stuff!" \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/potential_conflicts.rhtml b/vendor/rails/actionpack/test/fixtures/test/potential_conflicts.rhtml new file mode 100644 index 0000000..a5e964e --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/potential_conflicts.rhtml @@ -0,0 +1,4 @@ +First: <%= @name %> +<%= render :partial => "person", :locals => { :name => "Stephan" } -%> +Fourth: <%= @name %> +Fifth: <%= name %> \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/test/render_file_with_ivar.rhtml b/vendor/rails/actionpack/test/fixtures/test/render_file_with_ivar.rhtml new file mode 100644 index 0000000..8b8a449 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/render_file_with_ivar.rhtml @@ -0,0 +1 @@ +The secret is <%= @secret %> diff --git a/vendor/rails/actionpack/test/fixtures/test/render_file_with_locals.rhtml b/vendor/rails/actionpack/test/fixtures/test/render_file_with_locals.rhtml new file mode 100644 index 0000000..ebe09fa --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/render_file_with_locals.rhtml @@ -0,0 +1 @@ +The secret is <%= secret %> diff --git a/vendor/rails/actionpack/test/fixtures/test/render_to_string_test.rhtml b/vendor/rails/actionpack/test/fixtures/test/render_to_string_test.rhtml new file mode 100644 index 0000000..6e267e8 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/render_to_string_test.rhtml @@ -0,0 +1 @@ +The value of foo is: ::<%= @foo %>:: diff --git a/vendor/rails/actionpack/test/fixtures/test/update_element_with_capture.rhtml b/vendor/rails/actionpack/test/fixtures/test/update_element_with_capture.rhtml new file mode 100644 index 0000000..fa3ef20 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/test/update_element_with_capture.rhtml @@ -0,0 +1,9 @@ +<% replacement_function = update_element_function("products", :action => :update) do %> +

      Product 1

      +

      Product 2

      +<% end %> +<%= javascript_tag(replacement_function) %> + +<% update_element_function("status", :action => :update, :binding => binding) do %> + You bought something! +<% end %> diff --git a/vendor/rails/actionpack/test/fixtures/topic.rb b/vendor/rails/actionpack/test/fixtures/topic.rb new file mode 100644 index 0000000..0929de7 --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/topic.rb @@ -0,0 +1,3 @@ +class Topic < ActiveRecord::Base + has_many :replies, :include => [:user], :dependent => true +end \ No newline at end of file diff --git a/vendor/rails/actionpack/test/fixtures/topics.yml b/vendor/rails/actionpack/test/fixtures/topics.yml new file mode 100644 index 0000000..61ea02d --- /dev/null +++ b/vendor/rails/actionpack/test/fixtures/topics.yml @@ -0,0 +1,22 @@ +futurama: + id: 1 + title: Isnt futurama awesome? + subtitle: It really is, isnt it. + content: I like futurama + created_at: <%= 1.day.ago.to_s(:db) %> + updated_at: + +harvey_birdman: + id: 2 + title: Harvey Birdman is the king of all men + subtitle: yup + content: It really is + created_at: <%= 2.hours.ago.to_s(:db) %> + updated_at: + +rails: + id: 3 + title: Rails is nice + subtitle: It makes me happy + content: except when I have to hack internals to fix pagination. even then really. + created_at: <%= 20.minutes.ago.to_s(:db) %> diff --git a/vendor/rails/actionpack/test/template/active_record_helper_test.rb b/vendor/rails/actionpack/test/template/active_record_helper_test.rb new file mode 100644 index 0000000..6f7f013 --- /dev/null +++ b/vendor/rails/actionpack/test/template/active_record_helper_test.rb @@ -0,0 +1,196 @@ +require '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 + + User = Struct.new("User", :email) + User.class_eval do + alias_method :email_before_type_cast, :email unless respond_to?(:email_before_type_cast) + end + + Column = Struct.new("Column", :type, :name, :human_name) + end + + def setup_post + @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 + + silence_warnings do + def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end + 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) + end + + def setup_user + @user = User.new + def @user.errors + Class.new { + def on(field) field == "email" end + def empty?() false end + def count() 1 end + def full_messages() [ "User email can't be empty" ] end + }.new + end + + def @user.new_record?() true end + def @user.to_param() nil end + + def @user.column_for_attribute(attr_name) + User.content_columns.select { |column| column.name == attr_name }.first + end + + silence_warnings do + def User.content_columns() [ Column.new(:string, "email", "Email") ] end + end + + @user.email = "" + end + + def setup + setup_post + setup_user + + @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("post", "title") + ) + end + + def test_text_area_with_errors + assert_dom_equal( + %(
      ), + text_area("post", "body") + ) + end + + def test_text_field_with_errors + assert_dom_equal( + %(
      ), + text_field("post", "author_name") + ) + end + + def test_form_with_string + assert_dom_equal( + %(


      \n


      ), + form("post") + ) + + silence_warnings do + class << @post + def new_record?() false end + def to_param() id end + def id() 1 end + end + end + + assert_dom_equal( + %(


      \n


      ), + form("post") + ) + end + + def test_form_with_date + silence_warnings do + def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end + end + + assert_dom_equal( + %(


      \n\n\n

      ), + form("post") + ) + end + + def test_form_with_datetime + silence_warnings do + def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end + end + @post.written_on = Time.gm(2004, 6, 15, 16, 30) + + assert_dom_equal( + %(


      \n\n\n — \n : \n

      ), + form("post") + ) + end + + def test_error_for_block + assert_dom_equal %(

      1 error prohibited this post from being saved

      There were problems with the following fields:

      • Author name can't be empty
      ), error_messages_for("post") + assert_equal %(

      1 error prohibited this post from being saved

      There were problems with the following fields:

      • Author name can't be empty
      ), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1") + assert_equal %(

      1 error prohibited this post from being saved

      There were problems with the following fields:

      • Author name can't be empty
      ), error_messages_for("post", :class => nil, :id => "errorDeathById", :header_tag => "h1") + assert_equal %(

      1 error prohibited this post from being saved

      There were problems with the following fields:

      • Author name can't be empty
      ), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1") + end + + def test_error_messages_for_handles_nil + assert_equal "", error_messages_for("notthere") + end + + def test_error_messages_for_many_objects + assert_dom_equal %(

      2 errors prohibited this post from being saved

      There were problems with the following fields:

      • Author name can't be empty
      • User email can't be empty
      ), error_messages_for("post", "user") + + # reverse the order, error order changes and so does the title + assert_dom_equal %(

      2 errors prohibited this user from being saved

      There were problems with the following fields:

      • User email can't be empty
      • Author name can't be empty
      ), error_messages_for("user", "post") + + # add the default to put post back in the title + assert_dom_equal %(

      2 errors prohibited this post from being saved

      There were problems with the following fields:

      • User email can't be empty
      • Author name can't be empty
      ), error_messages_for("user", "post", :object_name => "post") + + # symbols work as well + assert_dom_equal %(

      2 errors prohibited this post from being saved

      There were problems with the following fields:

      • User email can't be empty
      • Author name can't be empty
      ), error_messages_for(:user, :post, :object_name => :post) + + # any default works too + assert_dom_equal %(

      2 errors prohibited this monkey from being saved

      There were problems with the following fields:

      • User email can't be empty
      • Author name can't be empty
      ), error_messages_for(:user, :post, :object_name => "monkey") + end + + def test_form_with_string_multipart + assert_dom_equal( + %(


      \n


      ), + form("post", :multipart => true) + ) + end +end diff --git a/vendor/rails/actionpack/test/template/asset_tag_helper_test.rb b/vendor/rails/actionpack/test/template/asset_tag_helper_test.rb new file mode 100644 index 0000000..d5a8cef --- /dev/null +++ b/vendor/rails/actionpack/test/template/asset_tag_helper_test.rb @@ -0,0 +1,252 @@ +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) => %(), + %(auto_discovery_link_tag(:atom)) => %(), + %(auto_discovery_link_tag(:rss, :action => "feed")) => %(), + %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(), + %(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(), + %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(), + %(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(), + %(auto_discovery_link_tag(nil, {}, {:title => "No stream.. really", :type => "text/html"})) => %(), + %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS", :type => "text/html"})) => %(), + %(auto_discovery_link_tag(:atom, {}, {:rel => "Not so alternate"})) => %(), + } + + JavascriptPathToTag = { + %(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js), + %(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js) + } + + JavascriptIncludeToTag = { + %(javascript_include_tag("xmlhr")) => %(), + %(javascript_include_tag("xmlhr", :lang => "vbscript")) => %(), + %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(\n), + %(javascript_include_tag(:defaults)) => %(\n\n\n), + %(javascript_include_tag(:defaults, "test")) => %(\n\n\n\n), + %(javascript_include_tag("test", :defaults)) => %(\n\n\n\n) + } + + 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")) => %(), + %(stylesheet_link_tag("/dir/file")) => %(), + %(stylesheet_link_tag("dir/file")) => %(), + %(stylesheet_link_tag("style", :media => "all")) => %(), + %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(\n) + } + + ImagePathToTag = { + %(image_path("xml")) => %(/images/xml.png), + } + + ImageLinkToTag = { + %(image_tag("xml")) => %(Xml), + %(image_tag("rss", :alt => "rss syndication")) => %(rss syndication), + %(image_tag("gold", :size => "45x70")) => %(Gold), + %(image_tag("symbolize", "size" => "45x70")) => %(Symbolize), + %(image_tag("http://www.rubyonrails.com/images/rails")) => %(Rails) + } + + 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 %(\n\n\n\n), javascript_include_tag(:defaults) + ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2' + assert_dom_equal %(\n\n\n\n\n\n), 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 %(Rails), 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 %(Rails), 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 %(Rails), 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")) => %(), + %(auto_discovery_link_tag(:atom)) => %(), + %(auto_discovery_link_tag) => %(), + } + + JavascriptPathToTag = { + %(javascript_path("xmlhr")) => %(/calloboration/hieraki/javascripts/xmlhr.js), + } + + JavascriptIncludeToTag = { + %(javascript_include_tag("xmlhr")) => %(), + %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(\n), + %(javascript_include_tag(:defaults)) => %(\n\n\n) + } + + StylePathToTag = { + %(stylesheet_path("style")) => %(/calloboration/hieraki/stylesheets/style.css), + } + + StyleLinkToTag = { + %(stylesheet_link_tag("style")) => %(), + %(stylesheet_link_tag("random.styles", "/css/stylish")) => %(\n) + } + + ImagePathToTag = { + %(image_path("xml")) => %(/calloboration/hieraki/images/xml.png), + } + + ImageLinkToTag = { + %(image_tag("xml")) => %(Xml), + %(image_tag("rss", :alt => "rss syndication")) => %(rss syndication), + %(image_tag("gold", :size => "45x70")) => %(Gold), + %(image_tag("http://www.example.com/images/icon.gif")) => %(Icon), + %(image_tag("symbolize", "size" => "45x70")) => %(Symbolize) + } + + 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 %(\n\n\n\n), javascript_include_tag(:defaults) + ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'lib1', '/elsewhere/blub/lib2' + assert_dom_equal %(\n\n\n\n\n\n), 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( + %(), + result) + ensure + ActionController::Base.asset_host = "" + end + +end diff --git a/vendor/rails/actionpack/test/template/benchmark_helper_test.rb b/vendor/rails/actionpack/test/template/benchmark_helper_test.rb new file mode 100644 index 0000000..3c7d9b5 --- /dev/null +++ b/vendor/rails/actionpack/test/template/benchmark_helper_test.rb @@ -0,0 +1,72 @@ +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 diff --git a/vendor/rails/actionpack/test/template/compiled_templates_test.rb b/vendor/rails/actionpack/test/template/compiled_templates_test.rb new file mode 100644 index 0000000..3bb1e58 --- /dev/null +++ b/vendor/rails/actionpack/test/template/compiled_templates_test.rb @@ -0,0 +1,134 @@ +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 diff --git a/vendor/rails/actionpack/test/template/date_helper_test.rb b/vendor/rails/actionpack/test/template/date_helper_test.rb new file mode 100755 index 0000000..a3010fa --- /dev/null +++ b/vendor/rails/actionpack/test/template/date_helper_test.rb @@ -0,0 +1,640 @@ +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)) + + # test greater date separation + assert_equal "29 days", distance_of_time_in_words(from, Time.mktime(2004, 4, 5, 21, 41, 18)) + assert_equal "about 1 month", distance_of_time_in_words(from, Time.mktime(2004, 4, 6, 21, 41, 18)) + assert_equal "about 1 month", distance_of_time_in_words(from, Time.mktime(2004, 4, 7, 21, 41, 18)) + assert_equal "2 months", distance_of_time_in_words(from, Time.mktime(2004, 5, 6, 21, 41, 18)) + assert_equal "11 months", distance_of_time_in_words(from, Time.mktime(2005, 2, 6, 21, 41, 18)) + assert_equal "about 1 year", distance_of_time_in_words(from, Time.mktime(2005, 4, 6, 21, 41, 18)) + assert_equal "about 1 year", distance_of_time_in_words(from, Time.mktime(2005, 4, 12, 21, 41, 18)) + assert_equal "over 2 years", distance_of_time_in_words(from, Time.mktime(2006, 4, 6, 21, 41, 18)) + + # 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 = %(\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 = %(\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 = %(\n" + + assert_equal expected, select_day(nil, :include_blank => true) + end + + def test_select_month + expected = %(\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 = %(\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 = %(\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 = %(\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 = %(\n" + + assert_equal expected, select_month(nil, :include_blank => true) + end + + def test_select_month_with_numbers + expected = %(\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 = %(\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 = %(\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 = %(\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 = %(\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 = %(\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 = %(\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 = %(\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 = %(\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 = %(\n" + + assert_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18)) + end + + def test_select_hour_with_disabled + expected = %(\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 = %(\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 = %(\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 = %(\n" + + assert_equal expected, select_hour(nil, :include_blank => true) + end + + def test_select_minute + expected = %(\n" + + assert_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)) + end + + def test_select_minute_with_disabled + expected = %(\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 = %(\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 = %(\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 = %(\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 = %(\n" + + assert_equal expected, select_minute(nil, :include_blank => true) + end + + def test_select_minute_nil_with_blank_and_step + expected = %(\n" + + assert_equal expected, select_minute(nil, { :include_blank => true , :minute_step => 15 }) + end + + def test_select_second + expected = %(\n" + + assert_equal expected, select_second(Time.mktime(2003, 8, 16, 8, 4, 18)) + end + + def test_select_second_with_disabled + expected = %(\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 = %(\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 = %(\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 = %(\n" + + assert_equal expected, select_second(nil, :include_blank => true) + end + + def test_select_date + expected = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\n" + + assert_equal expected, select_date( + Time.mktime(Date.today.year, 8, 16), :prefix => "date[first]" + ) + end + + def test_select_time_with_seconds + expected = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\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 = "\n" + + "\n" + + "\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 = "\n\n\n — \n : \n" + + assert_dom_equal(expected, _erbout) + end + + def test_date_select_with_zero_value_and_no_start_year + expected = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\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 = %(\n" + + expected << %(\n" + + expected << %(\n" + + expected << %(\n" + + expected << %(\n" + + assert_equal expected, select_datetime(nil, :prefix => "date[first]") + end + +end diff --git a/vendor/rails/actionpack/test/template/deprecated_instance_variables_test.rb b/vendor/rails/actionpack/test/template/deprecated_instance_variables_test.rb new file mode 100644 index 0000000..86fc7d6 --- /dev/null +++ b/vendor/rails/actionpack/test/template/deprecated_instance_variables_test.rb @@ -0,0 +1,31 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class DeprecatedInstanceVariablesTest < Test::Unit::TestCase + class Target < ActionController::Base + ActionController::Base::DEPRECATED_INSTANCE_VARIABLES.each do |var| + class_eval <<-end_eval + def old_#{var}; render :inline => '<%= @#{var}.inspect %>' end + def new_#{var}; render :inline => '<%= #{var}.inspect %>' end + end_eval + end + + def rescue_action(e) raise e end + end + + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller = Target.new + end + + ActionController::Base::DEPRECATED_INSTANCE_VARIABLES.each do |var| + class_eval <<-end_eval, __FILE__, __LINE__ + def test_old_#{var}_is_deprecated + assert_deprecated('@#{var}') { get :old_#{var} } + end + def test_new_#{var}_isnt_deprecated + assert_not_deprecated { get :new_#{var} } + end + end_eval + end +end diff --git a/vendor/rails/actionpack/test/template/form_helper_test.rb b/vendor/rails/actionpack/test/template/form_helper_test.rb new file mode 100644 index 0000000..95d52a3 --- /dev/null +++ b/vendor/rails/actionpack/test/template/form_helper_test.rb @@ -0,0 +1,469 @@ +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( + '', text_field("post", "title") + ) + assert_dom_equal( + '', password_field("post", "title") + ) + assert_dom_equal( + '', password_field("person", "name") + ) + end + + def test_text_field_with_escapes + @post.title = "Hello World" + assert_dom_equal( + '', text_field("post", "title") + ) + end + + def test_text_field_with_options + expected = '' + 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 = '' + 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 = '' + assert_equal expected, text_field(object_name, "title") + assert_equal object_name, "post[]" + end + + def test_check_box + assert_dom_equal( + '', + check_box("post", "secret") + ) + @post.secret = 0 + assert_dom_equal( + '', + check_box("post", "secret") + ) + assert_dom_equal( + '', + check_box("post", "secret" ,{"checked"=>"checked"}) + ) + @post.secret = true + assert_dom_equal( + '', + check_box("post", "secret") + ) + end + + def test_check_box_with_explicit_checked_and_unchecked_values + @post.secret = "on" + assert_dom_equal( + '', + check_box("post", "secret", {}, "on", "off") + ) + end + + def test_radio_button + assert_dom_equal('', + radio_button("post", "title", "Hello World") + ) + assert_dom_equal('', + radio_button("post", "title", "Goodbye World") + ) + end + + def test_radio_button_is_checked_with_integers + assert_dom_equal('', + radio_button("post", "secret", "1") + ) + end + + def test_text_area + assert_dom_equal( + '', + text_area("post", "body") + ) + end + + def test_text_area_with_escapes + @post.body = "Back to the hill and over it again!" + assert_dom_equal( + '', + text_area("post", "body") + ) + end + + def test_text_area_with_alternate_value + assert_dom_equal( + '', + text_area("post", "body", :value => 'Testing alternate values.') + ) + end + + def test_text_area_with_size_option + assert_dom_equal( + '', + text_area("post", "body", :size => "183x820") + ) + end + + def test_date_selects + assert_dom_equal( + '', + text_area("post", "body") + ) + end + + def test_explicit_name + assert_dom_equal( + '', text_field("post", "title", "name" => "dont guess") + ) + assert_dom_equal( + '', + text_area("post", "body", "name" => "really!") + ) + assert_dom_equal( + '', + 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( + '', text_field("post", "title", "id" => "dont guess") + ) + assert_dom_equal( + '', + text_area("post", "body", "id" => "really!") + ) + assert_dom_equal( + '', + 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( + "", text_field("post[]","title") + ) + assert_dom_equal( + "", + text_area("post[]", "body") + ) + assert_dom_equal( + "", + check_box("post[]", "secret") + ) + assert_dom_equal( +"", + radio_button("post[]", "title", "Hello World") + ) + assert_dom_equal("", + 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 = + "
      " + + "" + + "" + + "" + + "" + + "
      " + + assert_dom_equal expected, _erbout + end + + def test_form_for_with_method + _erbout = '' + + form_for(:post, @post, :html => { :id => 'create-post', :method => :put }) do |f| + _erbout.concat f.text_field(:title) + _erbout.concat f.text_area(:body) + _erbout.concat f.check_box(:secret) + end + + expected = + "
      " + + "" + + "" + + "" + + "" + + "" + + "
      " + + 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 = + "
      " + + "" + + "" + + "" + + "" + + "
      " + + assert_dom_equal expected, _erbout + end + + def test_form_for_with_index + _erbout = '' + + form_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 = + "
      " + + "" + + "" + + "" + + "" + + "
      " + 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 = + "" + + "" + + "" + + "" + + 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 = + "" + + "" + + "" + + "" + + 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 = + "
      " + + "" + + "" + + "" + + "" + + "
      " + + 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) + " " + super + "
      " + 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 = + "
      " + + "
      " + + "
      " + + " " + + "
      " + + "
      " + + 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 = + %(
      ) + + "
      " + + "
      " + + " " + + "
      " + + "
      " + + 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 = + "
      " + + "
      " + + " " + + "
      " + + 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 = "
      " + + 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 = "
      " + + assert_dom_equal expected, _erbout + end +end diff --git a/vendor/rails/actionpack/test/template/form_options_helper_test.rb b/vendor/rails/actionpack/test/template/form_options_helper_test.rb new file mode 100644 index 0000000..ad29341 --- /dev/null +++ b/vendor/rails/actionpack/test/template/form_options_helper_test.rb @@ -0,0 +1,470 @@ +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(" went home", "", "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( + "\n\n", + options_from_collection_for_select(@posts, "author_name", "title") + ) + end + + + def test_collection_options_with_preselected_value + @posts = [ + Post.new(" went home", "", "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( + "\n\n", + options_from_collection_for_select(@posts, "author_name", "title", "Babe") + ) + end + + def test_collection_options_with_preselected_value_array + @posts = [ + Post.new(" went home", "", "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( + "\n\n", + options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ]) + ) + end + + def test_array_options_for_select + assert_dom_equal( + "\n\n", + options_for_select([ "", "USA", "Sweden" ]) + ) + end + + def test_array_options_for_select_with_selection + assert_dom_equal( + "\n\n", + options_for_select([ "Denmark", "", "Sweden" ], "") + ) + end + + def test_array_options_for_select_with_selection_array + assert_dom_equal( + "\n\n", + options_for_select([ "Denmark", "", "Sweden" ], [ "", "Sweden" ]) + ) + end + + def test_array_options_for_string_include_in_other_string_bug_fix + assert_dom_equal( + "\n", + options_for_select([ "ruby", "rubyonrails" ], "rubyonrails") + ) + assert_dom_equal( + "\n", + options_for_select([ "ruby", "rubyonrails" ], "ruby") + ) + assert_dom_equal( + %(\n\n), + options_for_select([ "ruby", "rubyonrails", nil ], "ruby") + ) + end + + def test_hash_options_for_select + assert_dom_equal( + "\n", + options_for_select({ "$" => "Dollar", "" => "" }) + ) + assert_dom_equal( + "\n", + options_for_select({ "$" => "Dollar", "" => "" }, "Dollar") + ) + assert_dom_equal( + "\n", + options_for_select({ "$" => "Dollar", "" => "" }, [ "Dollar", "" ]) + ) + end + + def test_ducktyped_options_for_select + quack = Struct.new(:first, :last) + assert_dom_equal( + "\n", + options_for_select([quack.new("", ""), quack.new("$", "Dollar")]) + ) + assert_dom_equal( + "\n", + options_for_select([quack.new("", ""), quack.new("$", "Dollar")], "Dollar") + ) + assert_dom_equal( + "\n", + options_for_select([quack.new("", ""), quack.new("$", "Dollar")], ["Dollar", ""]) + ) + end + + def test_html_option_groups_from_collection + @continents = [ + Continent.new("", [Country.new("", ""), Country.new("so", "Somalia")] ), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) + ] + + assert_dom_equal( + "\n\n", + 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 "\n" + + "\n" + + "\n" + + "\n" + + "", + opts + end + + def test_time_zone_options_with_selected + opts = time_zone_options_for_select( "D" ) + assert_dom_equal "\n" + + "\n" + + "\n" + + "\n" + + "", + opts + end + + def test_time_zone_options_with_unknown_selected + opts = time_zone_options_for_select( "K" ) + assert_dom_equal "\n" + + "\n" + + "\n" + + "\n" + + "", + 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 "\n" + + "" + + "\n" + + "\n" + + "\n" + + "", + 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 "\n" + + "" + + "\n" + + "\n" + + "\n" + + "", + 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 "\n" + + "" + + "\n" + + "\n" + + "\n" + + "", + opts + end + + def test_select + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest)) + ) + end + + def test_select_under_fields_for + @post = Post.new + @post.category = "" + + _erbout = '' + + fields_for :post, @post do |f| + _erbout.concat f.select(:category, %w( abe hest)) + end + + assert_dom_equal( + "", + _erbout + ) + end + + def test_select_with_blank + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest), :include_blank => true) + ) + end + + def test_select_with_default_prompt + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest), :prompt => true) + ) + end + + def test_select_no_prompt_when_select_has_value + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest), :prompt => true) + ) + end + + def test_select_with_given_prompt + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest), :prompt => 'The prompt') + ) + end + + def test_select_with_prompt_and_blank + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest), :prompt => true, :include_blank => true) + ) + end + + def test_select_with_selected_value + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest ), :selected => 'abe') + ) + end + + def test_select_with_selected_nil + @post = Post.new + @post.category = "" + assert_dom_equal( + "", + select("post", "category", %w( abe hest ), :selected => nil) + ) + end + + def test_collection_select + @posts = [ + Post.new(" went home", "", "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( + "", + collection_select("post", "author_name", @posts, "author_name", "author_name") + ) + end + + def test_collection_select_under_fields_for + @posts = [ + Post.new(" went home", "", "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( + "", + _erbout + ) + end + + def test_collection_select_with_blank_and_style + @posts = [ + Post.new(" went home", "", "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( + "", + 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( + "", + country_select("post", "origin") + ) + end + + def test_time_zone_select + @firm = Firm.new("D") + html = time_zone_select( "firm", "time_zone" ) + assert_dom_equal "", + 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( + "", + _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 "", + 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 "", + 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 "", + 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 "", + html + end +end diff --git a/vendor/rails/actionpack/test/template/form_tag_helper_test.rb b/vendor/rails/actionpack/test/template/form_tag_helper_test.rb new file mode 100644 index 0000000..0e4ac5f --- /dev/null +++ b/vendor/rails/actionpack/test/template/form_tag_helper_test.rb @@ -0,0 +1,114 @@ +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 = %() + assert_dom_equal expected, actual + end + + def test_form_tag + actual = form_tag + expected = %(
      ) + assert_dom_equal expected, actual + end + + def test_form_tag_multipart + actual = form_tag({}, { 'multipart' => true }) + expected = %() + assert_dom_equal expected, actual + end + + def test_form_tag_with_method + actual = form_tag({}, { :method => :put }) + expected = %() + assert_dom_equal expected, actual + end + + def test_hidden_field_tag + actual = hidden_field_tag "id", 3 + expected = %() + assert_dom_equal expected, actual + end + + def test_password_field_tag + actual = password_field_tag + expected = %() + assert_dom_equal expected, actual + end + + def test_radio_button_tag + actual = radio_button_tag "people", "david" + expected = %() + assert_dom_equal expected, actual + end + + def test_select_tag + actual = select_tag "people", "" + expected = %() + assert_dom_equal expected, actual + end + + def test_text_area_tag_size_string + actual = text_area_tag "body", "hello world", "size" => "20x40" + expected = %() + assert_dom_equal expected, actual + end + + def test_text_area_tag_size_symbol + actual = text_area_tag "body", "hello world", :size => "20x40" + expected = %() + assert_dom_equal expected, actual + end + + def test_text_field_tag + actual = text_field_tag "title", "Hello!" + expected = %() + assert_dom_equal expected, actual + end + + def test_text_field_tag_class_string + actual = text_field_tag "title", "Hello!", "class" => "admin" + expected = %() + assert_dom_equal expected, actual + end + + def test_boolean_optios + assert_dom_equal %(), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") + assert_dom_equal %(), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) + assert_dom_equal %(), select_tag("people", "", :multiple => true) + assert_dom_equal %(), select_tag("people", "", :multiple => nil) + end + + def test_stringify_symbol_keys + actual = text_field_tag "title", "Hello!", :id => "admin" + expected = %() + assert_dom_equal expected, actual + end + + def test_submit_tag + assert_dom_equal( + %(), + submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')") + ) + end + + def test_pass + assert_equal 1, 1 + end +end + diff --git a/vendor/rails/actionpack/test/template/java_script_macros_helper_test.rb b/vendor/rails/actionpack/test/template/java_script_macros_helper_test.rb new file mode 100644 index 0000000..7d2d8eb --- /dev/null +++ b/vendor/rails/actionpack/test/template/java_script_macros_helper_test.rb @@ -0,0 +1,108 @@ +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 %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ','); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :min_chars => 3); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :on_hide => "function(element, update){alert('me');}"); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :frequency => 2); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, + :after_update_element => "function(element,value){alert('You have chosen: '+value)}"); + assert_dom_equal %(), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :param_name => 'huidriwusch'); + end + + def test_auto_complete_result + result = [ { :title => 'test1' }, { :title => 'test2' } ] + assert_equal %(
      • test1
      • test2
      ), + auto_complete_result(result, :title) + assert_equal %(
      • test1
      • test2
      ), + auto_complete_result(result, :title, "est") + + resultuniq = [ { :title => 'test1' }, { :title => 'test1' } ] + assert_equal %(
      • test1
      ), + auto_complete_result(resultuniq, :title, "est") + end + + def test_text_field_with_auto_complete + assert_match %( + + + +<%= @content_for_layout %> + + + diff --git a/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml b/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml new file mode 100644 index 0000000..60dfe23 --- /dev/null +++ b/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/methods.rhtml @@ -0,0 +1,6 @@ +<% @scaffold_container.services.each do |service| %> + +

      API Methods for <%= service %>

      + <%= service_method_list(service) %> + +<% end %> diff --git a/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml b/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml new file mode 100644 index 0000000..ce755f7 --- /dev/null +++ b/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml @@ -0,0 +1,29 @@ +

      Method Invocation Details for <%= @scaffold_service %>#<%= @scaffold_method.public_name %>

      + +<%= form_tag :action => @scaffold_action_name + '_submit' %> +<%= hidden_field_tag "service", @scaffold_service.name %> +<%= hidden_field_tag "method", @scaffold_method.public_name %> + +

      +
      +<%= select_tag 'protocol', options_for_select([['SOAP', 'soap'], ['XML-RPC', 'xmlrpc']], @params['protocol']) %> +

      + +<% if @scaffold_method.expects %> + +Method Parameters:
      +<% @scaffold_method.expects.each_with_index do |type, i| %> +

      +
      + <%= method_parameter_input_fields(@scaffold_method, type, "method_params", i) %> +

      +<% end %> + +<% end %> + +<%= submit_tag "Invoke" %> +<%= end_form_tag %> + +

      +<%= link_to "Back", :action => @scaffold_action_name %> +

      diff --git a/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml b/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml new file mode 100644 index 0000000..5317688 --- /dev/null +++ b/vendor/rails/actionwebservice/lib/action_web_service/templates/scaffolds/result.rhtml @@ -0,0 +1,30 @@ +

      Method Invocation Result for <%= @scaffold_service %>#<%= @scaffold_method.public_name %>

      + +

      +Invocation took <%= '%f' % @method_elapsed %> seconds +

      + +

      +Return Value:
      +

      +<%= h @method_return_value.inspect %>
      +
      +

      + +

      +Request XML:
      +

      +<%= h @method_request_xml %>
      +
      +

      + +

      +Response XML:
      +

      +<%= h @method_response_xml %>
      +
      +

      + +

      +<%= link_to "Back", :action => @scaffold_action_name + '_method_params', :method => @scaffold_method.public_name, :service => @scaffold_service.name %> +

      diff --git a/vendor/rails/actionwebservice/lib/action_web_service/test_invoke.rb b/vendor/rails/actionwebservice/lib/action_web_service/test_invoke.rb new file mode 100644 index 0000000..7e714c9 --- /dev/null +++ b/vendor/rails/actionwebservice/lib/action_web_service/test_invoke.rb @@ -0,0 +1,110 @@ +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})" if method && method.expects && 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 diff --git a/vendor/rails/actionwebservice/lib/action_web_service/version.rb b/vendor/rails/actionwebservice/lib/action_web_service/version.rb new file mode 100644 index 0000000..aa2a458 --- /dev/null +++ b/vendor/rails/actionwebservice/lib/action_web_service/version.rb @@ -0,0 +1,9 @@ +module ActionWebService + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 1 + TINY = 2 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/rails/actionwebservice/setup.rb b/vendor/rails/actionwebservice/setup.rb new file mode 100644 index 0000000..9ab880d --- /dev/null +++ b/vendor/rails/actionwebservice/setup.rb @@ -0,0 +1,1379 @@ +# +# 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} " + out.puts " ruby #{File.basename $0} [] []" + + 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 diff --git a/vendor/rails/actionwebservice/test/abstract_client.rb b/vendor/rails/actionwebservice/test/abstract_client.rb new file mode 100644 index 0000000..5207d8e --- /dev/null +++ b/vendor/rails/actionwebservice/test/abstract_client.rb @@ -0,0 +1,184 @@ +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 diff --git a/vendor/rails/actionwebservice/test/abstract_dispatcher.rb b/vendor/rails/actionwebservice/test/abstract_dispatcher.rb new file mode 100644 index 0000000..b857e4a --- /dev/null +++ b/vendor/rails/actionwebservice/test/abstract_dispatcher.rb @@ -0,0 +1,500 @@ +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 diff --git a/vendor/rails/actionwebservice/test/abstract_unit.rb b/vendor/rails/actionwebservice/test/abstract_unit.rb new file mode 100644 index 0000000..392951e --- /dev/null +++ b/vendor/rails/actionwebservice/test/abstract_unit.rb @@ -0,0 +1,38 @@ +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 => "actionwebservice_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 +end diff --git a/vendor/rails/actionwebservice/test/api_test.rb b/vendor/rails/actionwebservice/test/api_test.rb new file mode 100644 index 0000000..0e58d84 --- /dev/null +++ b/vendor/rails/actionwebservice/test/api_test.rb @@ -0,0 +1,102 @@ +require 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 diff --git a/vendor/rails/actionwebservice/test/apis/auto_load_api.rb b/vendor/rails/actionwebservice/test/apis/auto_load_api.rb new file mode 100644 index 0000000..a35bbe3 --- /dev/null +++ b/vendor/rails/actionwebservice/test/apis/auto_load_api.rb @@ -0,0 +1,3 @@ +class AutoLoadAPI < ActionWebService::API::Base + api_method :void +end diff --git a/vendor/rails/actionwebservice/test/apis/broken_auto_load_api.rb b/vendor/rails/actionwebservice/test/apis/broken_auto_load_api.rb new file mode 100644 index 0000000..139597f --- /dev/null +++ b/vendor/rails/actionwebservice/test/apis/broken_auto_load_api.rb @@ -0,0 +1,2 @@ + + diff --git a/vendor/rails/actionwebservice/test/base_test.rb b/vendor/rails/actionwebservice/test/base_test.rb new file mode 100644 index 0000000..55a112a --- /dev/null +++ b/vendor/rails/actionwebservice/test/base_test.rb @@ -0,0 +1,42 @@ +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 diff --git a/vendor/rails/actionwebservice/test/casting_test.rb b/vendor/rails/actionwebservice/test/casting_test.rb new file mode 100644 index 0000000..34bad07 --- /dev/null +++ b/vendor/rails/actionwebservice/test/casting_test.rb @@ -0,0 +1,86 @@ +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 diff --git a/vendor/rails/actionwebservice/test/client_soap_test.rb b/vendor/rails/actionwebservice/test/client_soap_test.rb new file mode 100644 index 0000000..c03c241 --- /dev/null +++ b/vendor/rails/actionwebservice/test/client_soap_test.rb @@ -0,0 +1,152 @@ +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 diff --git a/vendor/rails/actionwebservice/test/client_xmlrpc_test.rb b/vendor/rails/actionwebservice/test/client_xmlrpc_test.rb new file mode 100644 index 0000000..0abd589 --- /dev/null +++ b/vendor/rails/actionwebservice/test/client_xmlrpc_test.rb @@ -0,0 +1,151 @@ +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 diff --git a/vendor/rails/actionwebservice/test/container_test.rb b/vendor/rails/actionwebservice/test/container_test.rb new file mode 100644 index 0000000..325d420 --- /dev/null +++ b/vendor/rails/actionwebservice/test/container_test.rb @@ -0,0 +1,73 @@ +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 diff --git a/vendor/rails/actionwebservice/test/dispatcher_action_controller_soap_test.rb b/vendor/rails/actionwebservice/test/dispatcher_action_controller_soap_test.rb new file mode 100644 index 0000000..681c7c5 --- /dev/null +++ b/vendor/rails/actionwebservice/test/dispatcher_action_controller_soap_test.rb @@ -0,0 +1,139 @@ +$:.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 diff --git a/vendor/rails/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb b/vendor/rails/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb new file mode 100644 index 0000000..95c9333 --- /dev/null +++ b/vendor/rails/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb @@ -0,0 +1,57 @@ +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 diff --git a/vendor/rails/actionwebservice/test/fixtures/db_definitions/mysql.sql b/vendor/rails/actionwebservice/test/fixtures/db_definitions/mysql.sql new file mode 100644 index 0000000..026f2a2 --- /dev/null +++ b/vendor/rails/actionwebservice/test/fixtures/db_definitions/mysql.sql @@ -0,0 +1,7 @@ +CREATE TABLE `users` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(30) default NULL, + `active` tinyint(4) default NULL, + `created_on` date default NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; diff --git a/vendor/rails/actionwebservice/test/fixtures/users.yml b/vendor/rails/actionwebservice/test/fixtures/users.yml new file mode 100644 index 0000000..a97d8c8 --- /dev/null +++ b/vendor/rails/actionwebservice/test/fixtures/users.yml @@ -0,0 +1,10 @@ +user1: + id: 1 + name: Kent + active: 1 + created_on: <%= Date.today %> +user2: + id: 2 + name: David + active: 1 + created_on: <%= Date.today %> diff --git a/vendor/rails/actionwebservice/test/gencov b/vendor/rails/actionwebservice/test/gencov new file mode 100755 index 0000000..1faab34 --- /dev/null +++ b/vendor/rails/actionwebservice/test/gencov @@ -0,0 +1,3 @@ +#!/bin/sh + +rcov -x '.*_test\.rb,rubygems,abstract_,/run,/apis' ./run diff --git a/vendor/rails/actionwebservice/test/invocation_test.rb b/vendor/rails/actionwebservice/test/invocation_test.rb new file mode 100644 index 0000000..3ef22fa --- /dev/null +++ b/vendor/rails/actionwebservice/test/invocation_test.rb @@ -0,0 +1,185 @@ +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 diff --git a/vendor/rails/actionwebservice/test/run b/vendor/rails/actionwebservice/test/run new file mode 100755 index 0000000..c8c0372 --- /dev/null +++ b/vendor/rails/actionwebservice/test/run @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +require 'test/unit' +$:.unshift(File.dirname(__FILE__) + '/../lib') +args = Dir[File.join(File.dirname(__FILE__), '*_test.rb')] + Dir[File.join(File.dirname(__FILE__), 'ws/*_test.rb')] +(r = Test::Unit::AutoRunner.new(true)).process_args(args) +exit r.run diff --git a/vendor/rails/actionwebservice/test/scaffolded_controller_test.rb b/vendor/rails/actionwebservice/test/scaffolded_controller_test.rb new file mode 100644 index 0000000..123a704 --- /dev/null +++ b/vendor/rails/actionwebservice/test/scaffolded_controller_test.rb @@ -0,0 +1,145 @@ +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 diff --git a/vendor/rails/actionwebservice/test/struct_test.rb b/vendor/rails/actionwebservice/test/struct_test.rb new file mode 100644 index 0000000..f689746 --- /dev/null +++ b/vendor/rails/actionwebservice/test/struct_test.rb @@ -0,0 +1,52 @@ +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 diff --git a/vendor/rails/actionwebservice/test/test_invoke_test.rb b/vendor/rails/actionwebservice/test/test_invoke_test.rb new file mode 100644 index 0000000..72ebc71 --- /dev/null +++ b/vendor/rails/actionwebservice/test/test_invoke_test.rb @@ -0,0 +1,112 @@ +require File.dirname(__FILE__) + '/abstract_unit' +require 'action_web_service/test_invoke' + +class TestInvokeAPI < ActionWebService::API::Base + api_method :null + 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 + + def null + 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 + + def null + 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 + + def test_with_no_parameters_declared + @controller = TestInvokeDirectController.new + assert_nil invoke(:null) + end + +end diff --git a/vendor/rails/activerecord/CHANGELOG b/vendor/rails/activerecord/CHANGELOG new file mode 100644 index 0000000..a700efb --- /dev/null +++ b/vendor/rails/activerecord/CHANGELOG @@ -0,0 +1,2826 @@ +*SVN* + +* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. [Michael A. Schoen] + +* Add documentation for how to disable timestamps on a per model basis. Closes #5684. [matt@mattmargolis.net Marcel Molina Jr.] + +* Don't save has_one associations unnecessarily. #5735 [Jonathan Viney] + +* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. [Rick Olson] + +* Formally deprecate the deprecated finders. [Koz] + +* Formally deprecate rich associations. [Koz] + +* Fixed that default timezones for new / initialize should uphold utc setting #5709 [daniluk@yahoo.com] + +* Fix announcement of very long migration names. #5722 [blake@near-time.com] + +* The exists? class method should treat a string argument as an id rather than as conditions. #5698 [jeremy@planetargon.com] + +* Fixed to_xml with :include misbehaviors when invoked on array of model instances #5690 [alexkwolfe@gmail.com] + +* Added support for conditions on Base.exists? #5689 [josh@joshpeek.com]. Examples: + + assert (Topic.exists?(:author_name => "David")) + assert (Topic.exists?(:author_name => "Mary", :approved => true)) + assert (Topic.exists?(["parent_id = ?", 1])) + +* Schema dumper quotes date :default values. [Dave Thomas] + +* Calculate sum with SQL, not Enumerable on HasManyThrough Associations. [Dan Peterson] + +* Factor the attribute#{suffix} methods out of method_missing for easier extension. [Jeremy Kemper] + +* Patch sql injection vulnerability when using integer or float columns. [Jamis Buck] + +* Allow #count through a has_many association to accept :include. [Dan Peterson] + +* create_table rdoc: suggest :id => false for habtm join tables. [Zed Shaw] + +* PostgreSQL: return array fields as strings. #4664 [Robby Russell] + +* SQLServer: added tests to ensure all database statements are closed, refactored identity_insert management code to use blocks, removed update/delete rowcount code out of execute and into update/delete, changed insert to go through execute method, removed unused quoting methods, disabled pessimistic locking tests as feature is currently unsupported, fixed RakeFile to load sqlserver specific tests whether running in ado or odbc mode, fixed support for recently added decimal types, added support for limits on integer types. #5670 [Tom Ward] + +* SQLServer: fix db:schema:dump case-sensitivity. #4684 [Will Rogers] + +* Oracle: BigDecimal support. #5667 [schoenm@earthlink.net] + +* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 [robbat2@gentoo.org, work@ashleymoran.me.uk] + +* Firebird migrations support. #5337 [Ken Kunz ] + +* PostgreSQL: create/drop as postgres user. #4790 [mail@matthewpainter.co.uk, mlaster@metavillage.com] + +* Update callbacks documentation. #3970 [Robby Russell ] + +* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 [tietew@tietew.net] + +* PostgreSQL: correctly quote microseconds in timestamps. #5641 [rick@rickbradley.com] + +* Clearer has_one/belongs_to model names (account has_one :user). #5632 [matt@mattmargolis.net] + +* Oracle: use nonblocking queries if allow_concurrency is set, fix pessimistic locking, don't guess date vs. time by default (set OracleAdapter.emulate_dates = true for the old behavior), adapter cleanup. #5635 [schoenm@earthlink.net] + +* Fixed a few Oracle issues: Allows Oracle's odd date handling to still work consistently within #to_xml, Passes test that hardcode insert statement by dropping the :id column, Updated RUNNING_UNIT_TESTS with Oracle instructions, Corrects method signature for #exec #5294 [schoenm@earthlink.net] + +* Added :group to available options for finds done on associations #5516 [mike@michaeldewey.org] + +* Minor tweak to improve performance of ActiveRecord::Base#to_param. + +* Observers also watch subclasses created after they are declared. #5535 [daniels@pronto.com.au] + +* Removed deprecated timestamps_gmt class methods. [Jeremy Kemper] + +* rake build_mysql_database grants permissions to rails@localhost. #5501 [brianegge@yahoo.com] + +* PostgreSQL: support microsecond time resolution. #5492 [alex@msgpad.com] + +* Add AssociationCollection#sum since the method_missing invokation has been shadowed by Enumerable#sum. + +* Added find_or_initialize_by_X which works like find_or_create_by_X but doesn't save the newly instantiated record. [Sam Stephenson] + +* Row locking. Provide a locking clause with the :lock finder option or true for the default "FOR UPDATE". Use the #lock! method to obtain a row lock on a single record (reloads the record with :lock => true). [Shugo Maeda] + # Obtain an exclusive lock on person 1 so we can safely increment visits. + Person.transaction do + # select * from people where id=1 for update + person = Person.find(1, :lock => true) + person.visits += 1 + person.save! + end + +* PostgreSQL: introduce allow_concurrency option which determines whether to use blocking or asynchronous #execute. Adapters with blocking #execute will deadlock Ruby threads. The default value is ActiveRecord::Base.allow_concurrency. [Jeremy Kemper] + +* Use a per-thread (rather than global) transaction mutex so you may execute concurrent transactions on separate connections. [Jeremy Kemper] + +* Change AR::Base#to_param to return a String instead of a Fixnum. Closes #5320. [Nicholas Seckar] + +* Use explicit delegation instead of method aliasing for AR::Base.to_param -> AR::Base.id. #5299 (skaes@web.de) + +* Refactored ActiveRecord::Base.to_xml to become a delegate for XmlSerializer, which restores sanity to the mega method. This refactoring also reinstates the opinions that type="string" is redundant and ugly and nil-differentiation is not a concern of serialization [DHH] + +* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [hcatlin@gmail.com]. Example: + + Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2) + +...is the same as: + + Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2) + + This makes it easier to pass in the options from a form or otherwise outside. + + +* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 [kennethkunz@gmail.com] + +* Fixed usage of :limit and with_scope when the association in scope is a 1:m #5208 [alex@purefiction.net] + +* Fixed migration trouble with SQLite when NOT NULL is used in the new definition #5215 [greg@lapcominc.com] + +* Fixed problems with eager loading and counting on SQL Server #5212 [kajism@yahoo.com] + +* Fixed that count distinct should use the selected column even when using :include #5251 [anna@wota.jp] + +* Fixed that :includes merged from with_scope won't cause the same association to be loaded more than once if repetition occurs in the clauses #5253 [alex@purefiction.net] + +* Allow models to override to_xml. #4989 [Blair Zajac ] + +* PostgreSQL: don't ignore port when host is nil since it's often used to label the domain socket. #5247 [shimbo@is.naist.jp] + +* Records and arrays of records are bound as quoted ids. [Jeremy Kemper] + Foo.find(:all, :conditions => ['bar_id IN (?)', bars]) + Foo.find(:first, :conditions => ['bar_id = ?', bar]) + +* Fixed that Base.find :all, :conditions => [ "id IN (?)", collection ] would fail if collection was empty [DHH] + +* Add a list of regexes assert_queries skips in the ActiveRecord test suite. [Rick] + +* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [josh@hasmanythrough.com] + +* Provide Association Extensions access to the instance that the association is being accessed from. + Closes #4433 [josh@hasmanythrough.com] + +* Update OpenBase adaterp's maintainer's email address. Closes #5176. [Derrick Spell] + +* Add a quick note about :select and eagerly included associations. [Rick] + +* Add docs for the :as option in has_one associations. Closes #5144 [cdcarter@gmail.com] + +* Fixed that has_many collections shouldn't load the entire association to do build or create [DHH] + +* Added :allow_nil option for aggregations #5091 [ian.w.white@gmail.com] + +* Fix Oracle boolean support and tests. Closes #5139. [schoenm@earthlink.net] + +* create! no longer blows up when no attributes are passed and a :create scope is in effect (e.g. foo.bars.create! failed whereas foo.bars.create!({}) didn't.) [Jeremy Kemper] + +* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 [info@loobmedia.com] + +* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk] + +* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 [keegan@thebasement.org] + +* Dates and times interpret empty strings as nil rather than 2000-01-01. #4830 [kajism@yahoo.com] + +* Allow :uniq => true with has_many :through associations. [Jeremy Kemper] + +* Ensure that StringIO is always available for the Schema dumper. [Marcel Molina Jr.] + +* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com] + +* Replace superfluous name_to_class_name variant with camelize. [Marcel Molina Jr.] + +* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.] + +* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.] + +* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac ] + +* Update FrontBase adapter to check binding version. Closes #4920. [mlaster@metavillage.com] + +* New Frontbase connections don't start in auto-commit mode. Closes #4922. [mlaster@metavillage.com] + +* When grouping, use the appropriate option key. [Marcel Molina Jr.] + +* Only modify the sequence name in the FrontBase adapter if the FrontBase adapter is actually being used. [Marcel Molina Jr.] + +* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com] + +* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil ] + +* Fix syntax error in documentation. Closes #4679. [mislav@nippur.irb.hr] + +* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu] + +* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. [kajism@yahoo.com] + +* Add support for :order option to with_scope. Closes #3887. [eric.daspet@survol.net] + +* Prettify output of schema_dumper by making things line up. Closes #4241 [Caio Chassot ] + +* Make build_postgresql_databases task make databases owned by the postgres user. Closes #4790. [mlaster@metavillage.com] + +* Sybase Adapter type conversion cleanup. Closes #4736. [dev@metacasa.net] + +* Fix bug where calculations with long alias names return null. [Rick] + +* Raise error when trying to add to a has_many :through association. Use the Join Model instead. [Rick] + + @post.tags << @tag # BAD + @post.taggings.create(:tag => @tag) # GOOD + +* Allow all calculations to take the :include option, not just COUNT (closes #4840) [Rick] + +* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com] + +* Add ActiveRecord::Errors#to_xml [Jamis Buck] + +* Properly quote index names in migrations (closes #4764) [John Long] + +* Fix the HasManyAssociation#count method so it uses the new ActiveRecord::Base#count syntax, while maintaining backwards compatibility. [Rick] + +* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick] + +* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick] + +* DRY up association collection reader method generation. [Marcel Molina Jr.] + +* DRY up and tweak style of the validation error object. [Marcel Molina Jr.] + +* Add :case_sensitive option to validates_uniqueness_of (closes #3090) [Rick] + + class Account < ActiveRecord::Base + validates_uniqueness_of :email, :case_sensitive => false + end + +* Allow multiple association extensions with :extend option (closes #4666) [Josh Susser] + + class Account < ActiveRecord::Base + has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension] + end + + +*1.14.2* (April 9th, 2005) + +* Fixed calculations for the Oracle Adapter (closes #4626) [Michael Schoen] + + +*1.14.1* (April 6th, 2006) + +* Fix type_name_with_module to handle type names that begin with '::'. Closes #4614. [Nicholas Seckar] + +* Fixed that that multiparameter assignment doesn't work with aggregations (closes #4620) [Lars Pind] + +* Enable Limit/Offset in Calculations (closes #4558) [lmarlow@yahoo.com] + +* Fixed that loading including associations returns all results if Load IDs For Limited Eager Loading returns none (closes #4528) [Rick] + +* Fixed HasManyAssociation#find bugs when :finder_sql is set #4600 [lagroue@free.fr] + +* Allow AR::Base#respond_to? to behave when @attributes is nil [zenspider] + +* Support eager includes when going through a polymorphic has_many association. [Rick] + +* Added support for eagerly including polymorphic has_one associations. (closes #4525) [Rick] + + class Post < ActiveRecord::Base + has_one :tagging, :as => :taggable + end + + Post.find :all, :include => :tagging + +* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many [Rick] + +* Added support for going through a polymorphic has_many association: (closes #4401) [Rick] + + class PhotoCollection < ActiveRecord::Base + has_many :photos, :as => :photographic + belongs_to :firm + end + + class Firm < ActiveRecord::Base + has_many :photo_collections + has_many :photos, :through => :photo_collections + end + +* Multiple fixes and optimizations in PostgreSQL adapter, allowing ruby-postgres gem to work properly. [ruben.nine@gmail.com] + +* Fixed that AssociationCollection#delete_all should work even if the records of the association are not loaded yet. [Florian Weber] + +* Changed those private ActiveRecord methods to take optional third argument :auto instead of nil for performance optimizations. (closes #4456) [Stefan] + +* Private ActiveRecord methods add_limit!, add_joins!, and add_conditions! take an OPTIONAL third argument 'scope' (closes #4456) [Rick] + +* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [DHH] + +* Fixed that records returned from has_and_belongs_to_many associations with additional attributes should be marked as read only (fixes #4512) [DHH] + +* Do not implicitly mark recordss of has_many :through as readonly but do mark habtm records as readonly (eventually only on join tables without rich attributes). [Marcel Mollina Jr.] + +* Fixed broken OCIAdapter #4457 [schoenm@earthlink.net] + + +*1.14.0* (March 27th, 2006) + +* Replace 'rescue Object' with a finer grained rescue. Closes #4431. [Nicholas Seckar] + +* Fixed eager loading so that an aliased table cannot clash with a has_and_belongs_to_many join table [Rick] + +* Add support for :include to with_scope [andrew@redlinesoftware.com] + +* Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 [schoenm@earthlink.net] + +* Change periods (.) in table aliases to _'s. Closes #4251 [jeff@ministrycentered.com] + +* Changed has_and_belongs_to_many join to INNER JOIN for Mysql 3.23.x. Closes #4348 [Rick] + +* Fixed issue that kept :select options from being scoped [Rick] + +* Fixed db_schema_import when binary types are present #3101 [DHH] + +* Fixed that MySQL enums should always be returned as strings #3501 [DHH] + +* Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. [Rick Olson] + + class Connection < ActiveRecord::Base + belongs_to :user + belongs_to :channel + end + + class Channel < ActiveRecord::Base + has_many :connections + has_many :contacts, :through => :connections, :class_name => 'User' # OLD + has_many :contacts, :through => :connections, :source => :user # NEW + end + +* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 [contact@maik-schmidt.de] + +* Allow overriding of find parameters in scoped has_many :through calls [Rick Olson] + + In this example, :include => false disables the default eager association from loading. :select changes the standard + select clause. :joins specifies a join that is added to the end of the has_many :through query. + + class Post < ActiveRecord::Base + 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 + end + +* Fixed that schema changes while the database was open would break any connections to a SQLite database (now we reconnect if that error is throw) [DHH] + +* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) [jonathan@bluewire.net.nz] + +* Quit ignoring default :include options in has_many :through calls [Mark James] + +* Allow has_many :through associations to find the source association by setting a custom class (closes #4307) [jonathan@bluewire.net.nz] + +* Eager Loading support added for has_many :through => :has_many associations (see below). [Rick Olson] + +* Allow has_many :through to work on has_many associations (closes #3864) [sco@scottraymond.net] Example: + + 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 + +* Raise error when trying to select many polymorphic objects with has_many :through or :include (closes #4226) [josh@hasmanythrough.com] + +* Fixed has_many :through to include :conditions set on the :through association. closes #4020 [jonathan@bluewire.net.nz] + +* Fix that has_many :through honors the foreign key set by the belongs_to association in the join model (closes #4259) [andylien@gmail.com / Rick] + +* SQL Server adapter gets some love #4298 [rtomayko@gmail.com] + +* Added OpenBase database adapter that builds on top of the http://www.spice-of-life.net/ruby-openbase/ driver. All functionality except LIMIT/OFFSET is supported #3528 [derrickspell@cdmplus.com] + +* Rework table aliasing to account for truncated table aliases. Add smarter table aliasing when doing eager loading of STI associations. This allows you to use the association name in the order/where clause. [Jonathan Viney / Rick Olson] #4108 Example (SpecialComment is using STI): + + Author.find(:all, :include => { :posts => :special_comments }, :order => 'special_comments.body') + +* Add AbstractAdapter#table_alias_for to create table aliases according to the rules of the current adapter. [Rick] + +* Provide access to the underlying database connection through Adapter#raw_connection. Enables the use of db-specific methods without complicating the adapters. #2090 [Koz] + +* Remove broken attempts at handling columns with a default of 'now()' in the postgresql adapter. #2257 [Koz] + +* Added connection#current_database that'll return of the current database (only works in MySQL, SQL Server, and Oracle so far -- please help implement for the rest of the adapters) #3663 [Tom ward] + +* Fixed that Migration#execute would have the table name prefix appended to its query #4110 [mark.imbriaco@pobox.com] + +* Make all tinyint(1) variants act like boolean in mysql (tinyint(1) unsigned, etc.) [Jamis Buck] + +* Use association's :conditions when eager loading. [jeremyevans0@gmail.com] #4144 + +* Alias the has_and_belongs_to_many join table on eager includes. #4106 [jeremyevans0@gmail.com] + + This statement would normally error because the projects_developers table is joined twice, and therefore joined_on would be ambiguous. + + Developer.find(:all, :include => {:projects => :developers}, :conditions => 'join_project_developers.joined_on IS NOT NULL') + +* Oracle adapter gets some love #4230 [schoenm@earthlink.net] + + * Changes :text to CLOB rather than BLOB [Moses Hohman] + * Fixes an issue with nil numeric length/scales (several) + * Implements support for XMLTYPE columns [wilig / Kubo Takehiro] + * Tweaks a unit test to get it all green again + * Adds support for #current_database + +* Added Base.abstract_class? that marks which classes are not part of the Active Record hierarchy #3704 [Rick Olson] + + class CachedModel < ActiveRecord::Base + self.abstract_class = true + end + + class Post < CachedModel + end + + CachedModel.abstract_class? + => true + + Post.abstract_class? + => false + + Post.base_class + => Post + + Post.table_name + => 'posts' + +* Allow :dependent options to be used with polymorphic joins. #3820 [Rick Olson] + + class Foo < ActiveRecord::Base + has_many :attachments, :as => :attachable, :dependent => :delete_all + end + +* Nicer error message on has_many :through when :through reflection can not be found. #4042 [court3nay@gmail.com] + +* Upgrade to Transaction::Simple 1.3 [Jamis Buck] + +* Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model [Rick Olson] + +* Allow ordering of calculated results and/or grouped fields in calculations [solo@gatelys.com] + +* Make ActiveRecord::Base#save! return true instead of nil on success. #4173 [johan@johansorensen.com] + +* Dynamically set allow_concurrency. #4044 [Stefan Kaes] + +* Added Base#to_xml that'll turn the current record into a XML representation [DHH]. Example: + + topic.to_xml + + ...returns: + + + + The First Topic + David + 1 + false + 0 + 2000-01-01 08:28:00 + 2003-07-16 09:28:00 + Have a nice day + david@loudthinking.com + + 2004-04-15 + + + ...and you can configure with: + + topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ]) + + ...that'll return: + + + The First Topic + David + false + Have a nice day + david@loudthinking.com + + 2004-04-15 + + + You can even do load first-level associations as part of the document: + + firm.to_xml :include => [ :account, :clients ] + + ...that'll return something like: + + + + 1 + 1 + 37signals + + + 1 + Summit + + + 1 + Microsoft + + + + 1 + 50 + + + +* Allow :counter_cache to take a column name for custom counter cache columns [Jamis Buck] + +* Documentation fixes for :dependent [robby@planetargon.com] + +* Stop the MySQL adapter crashing when views are present. #3782 [Jonathan Viney] + +* Don't classify the belongs_to class, it is already singular #4117 [keithm@infused.org] + +* Allow set_fixture_class to take Classes instead of strings for a class in a module. Raise FixtureClassNotFound if a fixture can't load. [Rick Olson] + +* Fix quoting of inheritance column for STI eager loading #4098 [Jonathan Viney ] + +* Added smarter table aliasing for eager associations for multiple self joins #3580 [Rick Olson] + + * The first time a table is referenced in a join, no alias is used. + * After that, the parent class name and the reflection name are used. + + Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ... + + * Any additional join references get a numerical suffix like '_2', '_3', etc. + +* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to. + +* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples: + + # cascaded in two levels + >> Author.find(:all, :include=>{:posts=>:comments}) + => authors + +- posts + +- comments + + # cascaded in two levels and normal association + >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations]) + => authors + +- posts + +- comments + +- categorizations + + # cascaded in two levels with two has_many associations + >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}) + => authors + +- posts + +- comments + +- categorizations + + # cascaded in three levels + >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}}) + => companies + +- groups + +- members + +- favorites + +* Make counter cache work when replacing an association #3245 [eugenol@gmail.com] + +* Make migrations verbose [Jamis Buck] + +* Make counter_cache work with polymorphic belongs_to [Jamis Buck] + +* Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 [anna@wota.jp] + +* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats: + + * Does not support DATE SQL column types; use DATETIME instead. + * Date columns on HABTM join tables are returned as String, not Time. + * Insertions are potentially broken for :polymorphic join tables + * BLOB column access not yet fully supported + +* Clear stale, cached connections left behind by defunct threads. [Jeremy Kemper] + +* CHANGED DEFAULT: set ActiveRecord::Base.allow_concurrency to false. Most AR usage is in single-threaded applications. [Jeremy Kemper] + +* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 [schoenm@earthlink.net] + +* Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 [DHH] + +* Speed up class -> connection caching and stale connection verification. #3979 [Stefan Kaes] + +* Add set_fixture_class to allow the use of table name accessors with models which use set_table_name. [Kevin Clark] + +* Added that fixtures to placed in subdirectories of the main fixture files are also loaded #3937 [dblack@wobblini.net] + +* Define attribute query methods to avoid method_missing calls. #3677 [jonathan@bluewire.net.nz] + +* ActiveRecord::Base.remove_connection explicitly closes database connections and doesn't corrupt the connection cache. Introducing the disconnect! instance method for the PostgreSQL, MySQL, and SQL Server adapters; implementations for the others are welcome. #3591 [Simon Stapleton, Tom Ward] + +* Added support for nested scopes #3407 [anna@wota.jp]. Examples: + + Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do + Developer.find(:all) # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10 + + # inner rule is used. (all previous parameters are ignored) + Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do + Developer.find(:all) # => SELECT * FROM developers WHERE (name = 'Jamis') + end + + # parameters are merged + Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do + Developer.find(:all) # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10 + end + end + +* Fixed db2 connection with empty user_name and auth options #3622 [phurley@gmail.com] + +* Fixed validates_length_of to work on UTF-8 strings by using characters instead of bytes #3699 [Masao Mutoh] + +* Fixed that reflections would bleed across class boundaries in single-table inheritance setups #3796 [lars@pind.com] + +* Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Base.maxmium, and the generic Base.calculate. All can be used with :group and :having. Calculations and statitics need no longer require custom SQL. #3958 [Rick Olson]. Examples: + + Person.average :age + Person.minimum :age + Person.maximum :age + Person.sum :salary, :group => :last_name + +* Renamed Errors#count to Errors#size but kept an alias for the old name (and included an alias for length too) #3920 [contact@lukeredpath.co.uk] + +* Reflections don't attempt to resolve module nesting of association classes. Simplify type computation. [Jeremy Kemper] + +* Improved the Oracle OCI Adapter with better performance for column reflection (from #3210), fixes to migrations (from #3476 and #3742), tweaks to unit tests (from #3610), and improved documentation (from #2446) #3879 [Aggregated by schoenm@earthlink.net] + +* Fixed that the schema_info table used by ActiveRecord::Schema.define should respect table pre- and suffixes #3834 [rubyonrails@atyp.de] + +* Added :select option to Base.count that'll allow you to select something else than * to be counted on. Especially important for count queries using DISTINCT #3839 [skaes] + +* Correct syntax error in mysql DDL, and make AAACreateTablesTest run first [Bob Silva] + +* Allow :include to be used with has_many :through associations #3611 [Michael Schoen] + +* PostgreSQL: smarter schema dumps using pk_and_sequence_for(table). #2920 [Blair Zajac] + +* SQLServer: more compatible limit/offset emulation. #3779 [Tom Ward] + +* Polymorphic join support for has_one associations (has_one :foo, :as => :bar) #3785 [Rick Olson] + +* PostgreSQL: correctly parse negative integer column defaults. #3776 [bellis@deepthought.org] + +* Fix problems with count when used with :include [Jeremy Hopple and Kevin Clark] + +* ActiveRecord::RecordInvalid now states which validations failed in its default error message [Tobias Luetke] + +* Using AssociationCollection#build with arrays of hashes should call build, not create [DHH] + +* Remove definition of reloadable? from ActiveRecord::Base to make way for new Reloadable code. [Nicholas Seckar] + +* Fixed schema handling for DB2 adapter that didn't work: an initial schema could be set, but it wasn't used when getting tables and indexes #3678 [Maik Schmidt] + +* Support the :column option for remove_index with the PostgreSQL adapter. #3661 [shugo@ruby-lang.org] + +* Add documentation for add_index and remove_index. #3600 [Manfred Stienstra ] + +* If the OCI library is not available, raise an exception indicating as much. #3593 [schoenm@earthlink.net] + +* Add explicit :order in finder tests as postgresql orders results differently by default. #3577. [Rick Olson] + +* Make dynamic finders honor additional passed in :conditions. #3569 [Oleg Pudeyev , Marcel Molina Jr.] + +* Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies. [Nicholas Seckar] + +* Make .count work for has_many associations with multi line finder sql [schoenm@earthlink.net] + +* Add AR::Base.base_class for querying the ancestor AR::Base subclass [Jamis Buck] + +* Allow configuration of the column used for optimistic locking [wilsonb@gmail.com] + +* Don't hardcode 'id' in acts as list. [ror@philippeapril.com] + +* Fix date errors for SQLServer in association tests. #3406 [kevin.clark@gmal.com] + +* Escape database name in MySQL adapter when creating and dropping databases. #3409 [anna@wota.jp] + +* Disambiguate table names for columns in validates_uniquness_of's WHERE clause. #3423 [alex.borovsky@gmail.com] + +* .with_scope imposed create parameters now bypass attr_protected [Tobias Luetke] + +* Don't raise an exception when there are more keys than there are named bind variables when sanitizing conditions. [Marcel Molina Jr.] + +* Multiple enhancements and adjustments to DB2 adaptor. #3377 [contact@maik-schmidt.de] + +* Sanitize scoped conditions. [Marcel Molina Jr.] + +* Added option to Base.reflection_of_all_associations to specify a specific association to scope the call. For example Base.reflection_of_all_associations(:has_many) [DHH] + +* Added ActiveRecord::SchemaDumper.ignore_tables which tells SchemaDumper which tables to ignore. Useful for tables with funky column like the ones required for tsearch2. [TobiasLuetke] + +* SchemaDumper now doesn't fail anymore when there are unknown column types in the schema. Instead the table is ignored and a Comment is left in the schema.rb. [TobiasLuetke] + +* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 [yanowitz-rubyonrails@quantumfoam.org, Florian Weber] + +* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron] + +* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Luetke] + +* made method missing delegation to class methods on relation target work on :through associations. [Tobias Luetke] + +* made .find() work on :through relations. [Tobias Luetke] + +* Fix typo in association docs. #3296. [Blair Zajac] + +* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Luetke] + +*1.13.2* (December 13th, 2005) + +* Become part of Rails 1.0 + +* MySQL: allow encoding option for mysql.rb driver. [Jeremy Kemper] + +* Added option inheritance for find calls on has_and_belongs_to_many and has_many assosociations [DHH]. Example: + + class Post + has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author + end + + post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors + post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors + post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors + +* Added option to specify :group, :limit, :offset, and :select options from find on has_and_belongs_to_many and has_many assosociations [DHH] + +* MySQL: fixes for the bundled mysql.rb driver. #3160 [Justin Forder] + +* SQLServer: fix obscure optimistic locking bug. #3068 [kajism@yahoo.com] + +* SQLServer: support uniqueidentifier columns. #2930 [keithm@infused.org] + +* SQLServer: cope with tables names qualified by owner. #3067 [jeff@ministrycentered.com] + +* SQLServer: cope with columns with "desc" in the name. #1950 [Ron Lusk, Ryan Tomayko] + +* SQLServer: cope with primary keys with "select" in the name. #3057 [rdifrango@captechventures.com] + +* Oracle: active? performs a select instead of a commit. #3133 [Michael Schoen] + +* MySQL: more robust test for nullified result hashes. #3124 [Stefan Kaes] + +* Reloading an instance refreshes its aggregations as well as its associations. #3024 [François Beausolei] + +* Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 [Paul Hammmond] + +* PostgreSQL: more robust sequence name discovery. #3087 [Rick Olson] + +* Oracle: use syntax compatible with Oracle 8. #3131 [Michael Schoen] + +* MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. [Jeremy Kemper] + +* Added preliminary support for polymorphic associations [DHH] + +* Added preliminary support for join models [DHH] + +* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. [jeremy@jthopple.com, Marcel Molina Jr.] + +* Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz ] + +* Firebird: updated for FireRuby 0.4.0. #3009 [Ken Kunz ] + +* MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 [Jeremy Kemper] + +* Oracle: active? check pings the database rather than testing the last command status. #428 [Michael Schoen] + +* SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 [kajism@yahoo.com] + +* Reloading a model doesn't lose track of its connection. #2996 [junk@miriamtech.com, Jeremy Kemper] + +* Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 [colman@rominato.com, Florian Weber, Michael Schoen] + +* MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. #2978 [Shugo Maeda] + +* has_and_belongs_to_many: use JOIN instead of LEFT JOIN. [Jeremy Kemper] + +* MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 [Shugo Maeda] + +* Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. [Marcel Molina Jr.] + +* Correct boolean handling in generated reader methods. #2945 [don.park@gmail.com, Stefan Kaes] + +* Don't generate read methods for columns whose names are not valid ruby method names. #2946 [Stefan Kaes] + +* Document :force option to create_table. #2921 [Blair Zajac ] + +* Don't add the same conditions twice in has_one finder sql. #2916 [Jeremy Evans] + +* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.] + +* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 [Ken Kunz ] + +* SQLServer: active? and reconnect! methods for handling stale connections. #428 [kajism@yahoo.com, Tom Ward ] + +* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 [MarkusQ@reality.com] + +* SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY. #2866 [kajism@yahoo.com, Tom Ward ] + +* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 [Michael Schoen ] + +* Correct documentation for Base.delete_all. #1568 [Newhydra] + +* Oracle: test case for column default parsing. #2788 [Michael Schoen ] + +* Update documentation for Migrations. #2861 [Tom Werner ] + +* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 [Jeremy Kemper] + +* Oracle: Much faster column reflection. #2848 [Michael Schoen ] + +* Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing). Base.define_attr_method allows nil values. [Jeremy Kemper] + +* PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence. [Jeremy Kemper] + +* PostgreSQL: correctly discover custom primary key sequences. #2594 [Blair Zajac , meadow.nnick@gmail.com, Jeremy Kemper] + +* SQLServer: don't report limits for unsupported field types. #2835 [Ryan Tomayko] + +* Include the Enumerable module in ActiveRecord::Errors. [Rick Bradley ] + +* Add :group option, correspond to GROUP BY, to the find method and to the has_many association. #2818 [rubyonrails@atyp.de] + +* Don't cast nil or empty strings to a dummy date. #2789 [Rick Bradley ] + +* acts_as_list plays nicely with inheritance by remembering the class which declared it. #2811 [rephorm@rephorm.com] + +* Fix sqlite adaptor's detection of missing dbfile or database declaration. [Nicholas Seckar] + +* Fixed acts_as_list for definitions without an explicit :order #2803 [jonathan@bluewire.net.nz] + +* Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql 0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now a part of the main driver; reapplied local change [2182]. Removed GC.start from Result.free. [tommy@tmtm.org, akuroda@gmail.com, Doug Fales , Jeremy Kemper] + +* Correct handling of complex order clauses with SQL Server limit emulation. #2770 [Tom Ward , Matt B.] + +* Correct whitespace problem in Oracle default column value parsing. #2788 [rick@rickbradley.com] + +* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 [larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper] + +* Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. [Jeremy Kemper] + +* More compatible Oracle column reflection. #2771 [Ryan Davis , Michael Schoen ] + + +*1.13.0* (November 7th, 2005) + +* Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 [Ryan Tomayko] + +* Added :include as an option for association declarations [DHH]. Example: + + has_many :posts, :include => [ :author, :comments ] + +* Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example: + + Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do + # Find where name = ? and active=true + Comment.find :all, :conditions => ['name = ?', name] + # Create comment associated with :post_id + Comment.create :body => "Hello world" + end + +* Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 [Ryan Tomayko] + +* Added constrain scoping for creates using a hash of attributes bound to the :creation key [DHH]. Example: + + Comment.constrain(:creation => { :post_id => 5 }) do + # Associated with :post_id + Comment.create :body => "Hello world" + end + + This is rarely used directly, but allows for find_or_create on associations. So you can do: + + # If the tag doesn't exist, a new one is created that's associated with the person + person.tags.find_or_create_by_name("Summer") + +* Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [DHH]. 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") + +* Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]. Example: + + class Account < ActiveRecord::Base + has_many :people do + def find_or_create_by_name(name) + first_name, *last_name = name.split + last_name = last_name.join " " + + 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" + + Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation). + +* Omit internal dtproperties table from SQLServer table list. #2729 [rtomayko@gmail.com] + +* Quote column names in generated SQL. #2728 [rtomayko@gmail.com] + +* Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 [Jeremy Kemper] + +* Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). [Jeremy Kemper] + +* Correct fixture behavior when table name pluralization is off. #2719 [Rick Bradley ] + +* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 [Dan Peterson] + +* Added migration support for Oracle #2647 [Michael Schoen] + +* Worked around that connection can't be reset if allow_concurrency is off. #2648 [Michael Schoen ] + +* Fixed SQL Server adapter to pass even more tests and do even better #2634 [rtomayko@gmail.com] + +* Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 [Tom Ward] + +* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 [Tom Ward] + +* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 [dansketcher@gmail.com] + +* Constraints are cloned so they can't be inadvertently modified while they're +in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper ] + +* Added :offset and :limit to the kinds of options that Base.constrain can use #2466 [duane.johnson@gmail.com] + +* Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 [schoenm@earthlink.net] + +* Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 [maik schmidt] + + +*1.12.2* (October 26th, 2005) + +* Allow symbols to rename columns when using SQLite adapter. #2531 [kevin.clark@gmail.com] + +* Map Active Record time to SQL TIME. #2575, #2576 [Robby Russell ] + +* Clarify semantics of ActiveRecord::Base#respond_to? #2560 [skaes@web.de] + +* Fixed Association#clear for associations which have not yet been accessed. #2524 [Patrick Lenz ] + +* HABTM finders shouldn't return readonly records. #2525 [Patrick Lenz ] + +* Make all tests runnable on their own. #2521. [Blair Zajac ] + + +*1.12.1* (October 19th, 2005) + +* Always parenthesize :conditions options so they may be safely combined with STI and constraints. + +* Correct PostgreSQL primary key sequence detection. #2507 [tmornini@infomania.com] + +* Added support for using limits in eager loads that involve has_many and has_and_belongs_to_many associations + + +*1.12.0* (October 16th, 2005) + +* Update/clean up documentation (rdoc) + +* PostgreSQL sequence support. Use set_sequence_name in your model class to specify its primary key sequence. #2292 [Rick Olson , Robby Russell ] + +* Change default logging colors to work on both white and black backgrounds. [Sam Stephenson] + +* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 [purestorm@ggnore.net] + +* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell ] + +* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around. + +* Avoid memleak in dev mode when using fcgi + +* Simplified .clear on active record associations by using the existing delete_records method. #1906 [Caleb ] + +* Delegate access to a customized primary key to the conventional id method. #2444. [Blair Zajac ] + +* Fix errors caused by assigning a has-one or belongs-to property to itself + +* Add ActiveRecord::Base.schema_format setting which specifies how databases should be dumped [Sam Stephenson] + +* Update DB2 adapter. #2206. [contact@maik-schmidt.de] + +* Corrections to SQLServer native data types. #2267. [rails.20.clarry@spamgourmet.com] + +* Deprecated ActiveRecord::Base.threaded_connection in favor of ActiveRecord::Base.allow_concurrency. + +* Protect id attribute from mass assigment even when the primary key is set to something else. #2438. [Blair Zajac ] + +* Misc doc fixes (typos/grammar/etc.). #2430. [coffee2code] + +* Add test coverage for content_columns. #2432. [coffee2code] + +* Speed up for unthreaded environments. #2431. [skaes@web.de] + +* Optimization for Mysql selects using mysql-ruby extension greater than 2.6.3. #2426. [skaes@web.de] + +* Speed up the setting of table_name. #2428. [skaes@web.de] + +* Optimize instantiation of STI subclass records. In partial fullfilment of #1236. [skaes@web.de] + +* Fix typo of 'constrains' to 'contraints'. #2069. [Michael Schuerig ] + +* Optimization refactoring for add_limit_offset!. In partial fullfilment of #1236. [skaes@web.de] + +* Add ability to get all siblings, including the current child, with acts_as_tree. Recloses #2140. [Michael Schuerig ] + +* Add geometric type for postgresql adapter. #2233 [akaspick@gmail.com] + +* Add option (true by default) to generate reader methods for each attribute of a record to avoid the overhead of calling method missing. In partial fullfilment of #1236. [skaes@web.de] + +* Add convenience predicate methods on Column class. In partial fullfilment of #1236. [skaes@web.de] + +* Raise errors when invalid hash keys are passed to ActiveRecord::Base.find. #2363 [Chad Fowler , Nicholas Seckar] + +* Added :force option to create_table that'll try to drop the table if it already exists before creating + +* Fix transactions so that calling return while inside a transaction will not leave an open transaction on the connection. [Nicholas Seckar] + +* Use foreign_key inflection uniformly. #2156 [Blair Zajac ] + +* model.association.clear should destroy associated objects if :dependent => true instead of nullifying their foreign keys. #2221 [joergd@pobox.com, ObieFernandez ] + +* Returning false from before_destroy should cancel the action. #1829 [Jeremy Huffman] + +* Recognize PostgreSQL NOW() default as equivalent to CURRENT_TIMESTAMP or CURRENT_DATE, depending on the column's type. #2256 [mat ] + +* Extensive documentation for the abstract database adapter. #2250 [François Beausoleil ] + +* Clean up Fixtures.reset_sequences for PostgreSQL. Handle tables with no rows and models with custom primary keys. #2174, #2183 [jay@jay.fm, Blair Zajac ] + +* Improve error message when nil is assigned to an attr which validates_size_of within a range. #2022 [Manuel Holtgrewe ] + +* Make update_attribute use the same writer method that update_attributes uses. + #2237 [trevor@protocool.com] + +* Make migrations honor table name prefixes and suffixes. #2298 [Jakob S, Marcel Molina] + +* Correct and optimize PostgreSQL bytea escaping. #1745, #1837 [dave@cherryville.org, ken@miriamtech.com, bellis@deepthought.org] + +* Fixtures should only reset a PostgreSQL sequence if it corresponds to an integer primary key named id. #1749 [chris@chrisbrinker.com] + +* Standardize the interpretation of boolean columns in the Mysql and Sqlite adapters. (Use MysqlAdapter.emulate_booleans = false to disable this behavior) + +* Added new symbol-driven approach to activating observers with Base#observers= [DHH]. Example: + + ActiveRecord::Base.observers = :cacher, :garbage_collector + +* Added AbstractAdapter#select_value and AbstractAdapter#select_values as convenience methods for selecting single values, instead of hashes, of the first column in a SELECT #2283 [solo@gatelys.com] + +* Wrap :conditions in parentheses to prevent problems with OR's #1871 [Jamis Buck] + +* Allow the postgresql adapter to work with the SchemaDumper. [Jamis Buck] + +* Add ActiveRecord::SchemaDumper for dumping a DB schema to a pure-ruby file, making it easier to consolidate large migration lists and port database schemas between databases. [Jamis Buck] + +* Fixed migrations for Windows when using more than 10 [David Naseby] + +* Fixed that the create_x method from belongs_to wouldn't save the association properly #2042 [Florian Weber] + +* Fixed saving a record with two unsaved belongs_to associations pointing to the same object #2023 [Tobias Luetke] + +* Improved migrations' behavior when the schema_info table is empty. [Nicholas Seckar] + +* Fixed that Observers didn't observe sub-classes #627 [Florian Weber] + +* Fix eager loading error messages, allow :include to specify tables using strings or symbols. Closes #2222 [Marcel Molina] + +* Added check for RAILS_CONNECTION_ADAPTERS on startup and only load the connection adapters specified within if its present (available in Rails through config.connection_adapters using the new config) #1958 [skae] + +* Fixed various problems with has_and_belongs_to_many when using customer finder_sql #2094 [Florian Weber] + +* Added better exception error when unknown column types are used with migrations #1814 [fbeausoleil@ftml.net] + +* Fixed "connection lost" issue with the bundled Ruby/MySQL driver (would kill the app after 8 hours of inactivity) #2163, #428 [kajism@yahoo.com] + +* Fixed comparison of Active Record objects so two new objects are not equal #2099 [deberg] + +* Fixed that the SQL Server adapter would sometimes return DBI::Timestamp objects instead of Time #2127 [Tom Ward] + +* Added the instance methods #root and #ancestors on acts_as_tree and fixed siblings to not include the current node #2142, #2140 [coffee2code] + +* Fixed that Active Record would call SHOW FIELDS twice (or more) for the same model when the cached results were available #1947 [sd@notso.net] + +* Added log_level and use_silence parameter to ActiveRecord::Base.benchmark. The first controls at what level the benchmark statement will be logged (now as debug, instead of info) and the second that can be passed false to include all logging statements during the benchmark block/ + +* Make sure the schema_info table is created before querying the current version #1903 + +* Fixtures ignore table name prefix and suffix #1987 [Jakob S] + +* Add documentation for index_type argument to add_index method for migrations #2005 [blaine@odeo.com] + +* Modify read_attribute to allow a symbol argument #2024 [Ken Kunz] + +* Make destroy return self #1913 [sebastian.kanthak@muehlheim.de] + +* Fix typo in validations documentation #1938 [court3nay] + +* Make acts_as_list work for insert_at(1) #1966 [hensleyl@papermountain.org] + +* Fix typo in count_by_sql documentation #1969 [Alexey Verkhovsky] + +* Allow add_column and create_table to specify NOT NULL #1712 [emptysands@gmail.com] + +* Fix create_table so that id column is implicitly added [Rick Olson] + +* Default sequence names for Oracle changed to #{table_name}_seq, which is the most commonly used standard. In addition, a new method ActiveRecord::Base#set_sequence_name allows the developer to set the sequence name per model. This is a non-backwards-compatible change -- anyone using the old-style "rails_sequence" will need to either create new sequences, or set: ActiveRecord::Base.set_sequence_name = "rails_sequence" #1798 + +* OCIAdapter now properly handles synonyms, which are commonly used to separate out the schema owner from the application user #1798 + +* Fixed the handling of camelCase columns names in Oracle #1798 + +* Implemented for OCI the Rakefile tasks of :clone_structure_to_test, :db_structure_dump, and :purge_test_database, which enable Oracle folks to enjoy all the agile goodness of Rails for testing. Note that the current implementation is fairly limited -- only tables and sequences are cloned, not constraints or indexes. A full clone in Oracle generally requires some manual effort, and is version-specific. Post 9i, Oracle recommends the use of the DBMS_METADATA package, though that approach requires editing of the physical characteristics generated #1798 + +* Fixed the handling of multiple blob columns in Oracle if one or more of them are null #1798 + +* Added support for calling constrained class methods on has_many and has_and_belongs_to_many collections #1764 [Tobias Luetke] + + class Comment < AR:B + def self.search(q) + find(:all, :conditions => ["body = ?", q]) + end + end + + class Post < AR:B + has_many :comments + end + + Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi' + + NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as + by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on + details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself + noticed :) + +* Added migration support for SQLite (using temporary tables to simulate ALTER TABLE) #1771 [Sam Stephenson] + +* Remove extra definition of supports_migrations? from abstract_adaptor.rb [Nicholas Seckar] + +* Fix acts_as_list so that moving next-to-last item to the bottom does not result in duplicate item positions + +* Fixed incompatibility in DB2 adapter with the new limit/offset approach #1718 [Maik Schmidt] + +* Added :select option to find which can specify a different value than the default *, like find(:all, :select => "first_name, last_name"), if you either only want to select part of the columns or exclude columns otherwise included from a join #1338 [Stefan Kaes] + + +*1.11.1* (11 July, 2005) + +* Added support for limit and offset with eager loading of has_one and belongs_to associations. Using the options with has_many and has_and_belongs_to_many associations will now raise an ActiveRecord::ConfigurationError #1692 [Rick Olsen] + +* Fixed that assume_bottom_position (in acts_as_list) could be called on items already last in the list and they would move one position away from the list #1648 [tyler@kianta.com] + +* Added ActiveRecord::Base.threaded_connections flag to turn off 1-connection per thread (required for thread safety). By default it's on, but WEBrick in Rails need it off #1685 [Sam Stephenson] + +* Correct reflected table name for singular associations. #1688 [court3nay@gmail.com] + +* Fixed optimistic locking with SQL Server #1660 [tom@popdog.net] + +* Added ActiveRecord::Migrator.migrate that can figure out whether to go up or down based on the target version and the current + +* Added better error message for "packets out of order" #1630 [courtenay] + +* Fixed first run of "rake migrate" on PostgreSQL by not expecting a return value on the id #1640 + + +*1.11.0* (6 July, 2005) + +* Fixed that Yaml error message in fixtures hid the real error #1623 [Nicholas Seckar] + +* Changed logging of SQL statements to use the DEBUG level instead of INFO + +* Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Luetke] See documentation under ActiveRecord::Migration and the additional support in the Rails rakefile/generator. + +* Added callback hooks to association collections #1549 [Florian Weber]. Example: + + class Project + has_and_belongs_to_many :developers, :before_add => :evaluate_velocity + + def evaluate_velocity(developer) + ... + end + end + + ..raising an exception will cause the object not to be added (or removed, with before_remove). + + +* Fixed Base.content_columns call for SQL Server adapter #1450 [DeLynn Berry] + +* Fixed Base#write_attribute to work with both symbols and strings #1190 [Paul Legato] + +* Fixed that has_and_belongs_to_many didn't respect single table inheritance types #1081 [Florian Weber] + +* Speed up ActiveRecord#method_missing for the common case (read_attribute). + +* Only notify observers on after_find and after_initialize if these methods are defined on the model. #1235 [skaes@web.de] + +* Fixed that single-table inheritance sub-classes couldn't be used to limit the result set with eager loading #1215 [Chris McGrath] + +* Fixed validates_numericality_of to work with overrided getter-method when :allow_nil is on #1316 [raidel@onemail.at] + +* Added roots, root, and siblings to the batch of methods added by acts_as_tree #1541 [michael@schuerig.de] + +* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 [DeLynn Berry] + +* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 [Mark Imbriaco/DeLynn Berry] + +* Fixed that multiparameter posts ignored attr_protected #1532 [alec+rails@veryclever.net] + +* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl] + +* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be: + + Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1' + + ...should instead be: + + Developer.find( + :all, + :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id', + :conditions => 'project_id=1' + ) + +* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 [Marcel Molina] + +* Fixed that clear_association_cache doesn't delete new associations on new records (so you can safely place new records in the session with Action Pack without having new associations wiped) #1494 [cluon] + +* Fixed that calling Model.find([]) returns [] and doesn't throw an exception #1379 + +* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 [Alisdair McDiarmid] + +* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice + +* Allow any Enumerable, not just Array, to work as bind variables #1344 [Jeremy Kemper] + +* Added actual database-changing behavior to collection assigment for has_many and has_and_belongs_to_many #1425 [Sebastian Kanthak]. + Example: + + david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")] + david.save + + If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new + project is saved when david.save is called. + + Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do: + + david.project_ids = [1, 5, 7] + +* Corrected typo in find SQL for has_and_belongs_to_many. #1312 [ben@bensinclair.com] + +* Fixed sanitized conditions for has_many finder method. #1281 [jackc@hylesanderson.com, pragdave, Tobias Luetke] + +* Comprehensive PostgreSQL schema support. Use the optional schema_search_path directive in database.yml to give a comma-separated list of schemas to search for your tables. This allows you, for example, to have tables in a shared schema without having to use a custom table name. See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html to learn more. #827 [dave@cherryville.org] + +* Corrected @@configurations typo #1410 [david@ruppconsulting.com] + +* Return PostgreSQL columns in the order they were declared #1374 [perlguy@gmail.com] + +* Allow before/after update hooks to work on models using optimistic locking + +* Eager loading of dependent has_one associations won't delete the association #1212 + +* Added a second parameter to the build and create method for has_one that controls whether the existing association should be replaced (which means nullifying its foreign key as well). By default this is true, but false can be passed to prevent it. + +* Using transactional fixtures now causes the data to be loaded only once. + +* Added fixture accessor methods that can be used when instantiated fixtures are disabled. + + fixtures :web_sites + + def test_something + assert_equal "Ruby on Rails", web_sites(:rubyonrails).name + end + +* Added DoubleRenderError exception that'll be raised if render* is called twice #518 [Nicholas Seckar] + +* Fixed exceptions occuring after render has been called #1096 [Nicholas Seckar] + +* CHANGED: validates_presence_of now uses Errors#add_on_blank, which will make " " fail the validation where it didn't before #1309 + +* Added Errors#add_on_blank which works like Errors#add_on_empty, but uses Object#blank? instead + +* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples: + + Conditional validations such as the following are made possible: + validates_numericality_of :income, :if => :employed? + + Conditional validations can also solve the salted login generator problem: + validates_confirmation_of :password, :if => :new_password? + + Using blocks: + validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 } + +* Fixed use of construct_finder_sql when using :join #1288 [dwlt@dwlt.net] + +* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 [Rick Olson] + +* Fixed that clone would break when an aggregate had the same name as one of its attributes #1307 [Jeremy Kemper] + +* Changed that destroying an object will only freeze the attributes hash, which keeps the object from having attributes changed (as that wouldn't make sense), but allows for the querying of associations after it has been destroyed. + +* Changed the callbacks such that observers are notified before the in-object callbacks are triggered. Without this change, it wasn't possible to act on the whole object in something like a before_destroy observer without having the objects own callbacks (like deleting associations) called first. + +* Added option for passing an array to the find_all version of the dynamic finders and have it evaluated as an IN fragment. Example: + + # SELECT * FROM topics WHERE title IN ('First', 'Second') + Topic.find_all_by_title(["First", "Second"]) + +* Added compatibility with camelCase column names for dynamic finders #533 [Dee.Zsombor] + +* Fixed extraneous comma in count() function that made it not work with joins #1156 [jarkko/Dee.Zsombor] + +* Fixed incompatibility with Base#find with an array of ids that would fail when using eager loading #1186 [Alisdair McDiarmid] + +* Fixed that validate_length_of lost :on option when :within was specified #1195 [jhosteny@mac.com] + +* Added encoding and min_messages options for PostgreSQL #1205 [shugo]. Configuration example: + + development: + adapter: postgresql + database: rails_development + host: localhost + username: postgres + password: + encoding: UTF8 + min_messages: ERROR + +* Fixed acts_as_list where deleting an item that was removed from the list would ruin the positioning of other list items #1197 [Jamis Buck] + +* Added validates_exclusion_of as a negative of validates_inclusion_of + +* Optimized counting of has_many associations by setting the association to empty if the count is 0 so repeated calls doesn't trigger database calls + + +*1.10.1* (20th April, 2005) + +* Fixed frivilous database queries being triggered with eager loading on empty associations and other things + +* Fixed order of loading in eager associations + +* Fixed stray comma when using eager loading and ordering together from has_many associations #1143 + + +*1.10.0* (19th April, 2005) + +* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example: + + for post in Post.find(:all, :limit => 100) + puts "Post: " + post.title + puts "Written by: " + post.author.name + puts "Last comment on: " + post.comments.first.created_on + end + + This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as: + + for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ]) + + ...and the number of database queries needed is now 1. + +* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples: + + Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC") + Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC") + Person.find(:first, :order => "created_on DESC", :offset => 5) + Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50) + Person.find(:all, :offset => 10, :limit => 10) + +* Added acts_as_nested_set #1000 [wschenk]. Introduction: + + 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 it's descendants 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. + +* Added Base.save! that 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 [After much pestering from Dave Thomas] + +* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 [gnuman1@gmail.com] + +* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 [andrew.john.peters@gmail.com] + +* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 [stephenh@chase3000.com] + +* Fixed page caching for non-vhost applications living underneath the root #1004 [Ben Schumacher] + +* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 [adelle] + +* Added the option to specify the acceptance string in validates_acceptance_of #1106 [caleb@aei-tech.com] + +* Added insert_at(position) to acts_as_list #1083 [DeLynnB] + +* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order) + +* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 [yon@milliped.com] + +* Fixed boolean saving on Oracle #1093 [mparrish@pearware.org] + +* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864 + +* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 [Leon Bredt] + +* Added quoting of column names for fixtures #997 [jcfischer@gmail.com] + +* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 [Caleb Tennis] + +* Fixed that benchmarking times for rendering included db runtimes #987 [skaes@web.de] + +* Fixed boolean queries for t/f fields in PostgreSQL #995 [dave@cherryville.org] + +* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 [Jeremy Kemper] + +* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 [dave@cherryville.org] + +* Fixed Base.silence/benchmark to only log if a logger has been configured #986 [skaes@web.de] + +* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 [skaes@web.de] + +* Fixed bug in Base#hash method that would treat records with the same string-based id as different [Dave Thomas] + +* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias) + + +*1.9.1* (27th March, 2005) + +* Fixed that Active Record objects with float attribute could not be cloned #808 + +* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 [Nicholas Seckar] + +* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count + +* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 [Scott Barron] + +* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 [delynnb] + + +*1.9.0* (22th March, 2005) + +* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example: + + Developer.find_all nil, 'id ASC', 5 # return the first five developers + Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward + + This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged. + +* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 [Nicholas Seckar/Sam Stephenson] + +* Improved the performance of the OCI8 adapter for Oracle #723 [pilx/gjenkins] + +* Added type conversion before saving a record, so string-based values like "10.0" aren't left for the database to convert #820 [dave@cherryville.org] + +* Added with additional settings for working with transactional fixtures and pre-loaded test databases #865 [mindel] + +* Fixed acts_as_list to trigger remove_from_list on destroy after the fact, not before, so a unique position can be maintained #871 [Alisdair McDiarmid] + +* Added the possibility of specifying fixtures in multiple calls #816 [kim@tinker.com] + +* Added Base.exists?(id) that'll return true if an object of the class with the given id exists #854 [stian@grytoyr.net] + +* Added optionally allow for nil or empty strings with validates_numericality_of #801 [Sebastian Kanthak] + +* Fixed problem with using slashes in validates_format_of regular expressions #801 [Sebastian Kanthak] + +* Fixed that SQLite3 exceptions are caught and reported properly #823 [yerejm] + +* Added that all types of after_find/after_initialized callbacks are triggered if the explicit implementation is present, not only the explicit implementation itself + +* Fixed that symbols can be used on attribute assignment, like page.emails.create(:subject => data.subject, :body => data.body) + + +*1.8.0* (7th March, 2005) + +* Added ActiveRecord::Base.colorize_logging to control whether to use colors in logs or not (on by default) + +* Added support for timestamp with time zone in PostgreSQL #560 [Scott Barron] + +* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation: + + * +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. + +* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 [rodrigo k] + +* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [Jeremy Kemper] + +* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example: + + class Account < ActiveRecord::Base + has_one :credit_card, :dependent => true + end + class CreditCard < ActiveRecord::Base + belongs_to :account + end + + account.credit_card # => returns existing credit card, lets say id = 12 + account.credit_card = CreditCard.create("number" => "123") + account.save # => CC with id = 12 is destroyed + + +* Added validates_numericality_of #716 [skanthak/c.r.mcgrath]. Docuemntation: + + Validates whether the value of the specified attribute is numeric by trying to convert it to + a float with Kernel.Float (if integer is false) or applying it to the regular expression + /^[\+\-]?\d+$/ (if integer is set to true). + + class Person < ActiveRecord::Base + validates_numericality_of :value, :on => :create + end + + Configuration options: + * message - A custom error message (default is: "is not a number") + * on Specifies when this validation is active (default is :save, other options :create, :update) + * only_integer Specifies whether the value has to be an integer, e.g. an integral value (default is false) + + +* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 [Scott Barron] + +* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed) + +* Added MacroReflection#macro which will return a symbol describing the macro used (like :composed_of or :has_many) #718, #248 [james@slashetc.com] + + +*1.7.0* (24th February, 2005) + +* Changed the auto-timestamping feature to use ActiveRecord::Base.default_timezone instead of entertaining the parallel ActiveRecord::Base.timestamps_gmt method. The latter is now deprecated and will throw a warning on use (but still work) #710 [Jamis Buck] + +* Added a OCI8-based Oracle adapter that has been verified to work with Oracle 8 and 9 #629 [Graham Jenkins]. Usage notes: + + 1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple + and safe way of passing table-specific sequence information to the adapter.) + 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to + resort to some hacks to get data converted to Date or Time in Ruby. + If the column_name ends in _at (like created_at, updated_at) 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 nasty - but if you use Duck Typing you'll probably not care very much. + In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is + valid - too many databases use DATE for both. + Timezones and sub-second precision on timestamps are not supported. + 3. Default values that are functions (such as "SYSDATE") are not supported. This is a + restriction of the way active record supports default values. + 4. Referential integrity constraints are not fully supported. Under at least + some circumstances, active record appears to delete parent and child records out of + sequence and out of transaction scope. (Or this may just be a problem of test setup.) + + The OCI8 driver can be retrieved from http://rubyforge.org/projects/ruby-oci8/ + +* Added option :schema_order to the PostgreSQL adapter to support the use of multiple schemas per database #697 [YuriSchimke] + +* Optimized the SQL used to generate has_and_belongs_to_many queries by listing the join table first #693 [yerejm] + +* Fixed that when using validation macros with a custom message, if you happened to use single quotes in the message string you would get a parsing error #657 [tonka] + +* Fixed that Active Record would throw Broken Pipe errors with FCGI when the MySQL connection timed out instead of reconnecting #428 [Nicholas Seckar] + +* Added options to specify an SSL connection for MySQL. Define the following attributes in the connection config (config/database.yml in Rails) to use it: sslkey, sslcert, sslca, sslcapath, sslcipher. To use SSL with no client certs, just set :sslca = '/dev/null'. http://dev.mysql.com/doc/mysql/en/secure-connections.html #604 [daniel@nightrunner.com] + +* Added automatic dropping/creating of test tables for running the unit tests on all databases #587 [adelle@bullet.net.au] + +* Fixed that find_by_* would fail when column names had numbers #670 [demetrius] + +* Fixed the SQL Server adapter on a bunch of issues #667 [DeLynn] + + 1. Created a new columns method that is much cleaner. + 2. Corrected a problem with the select and select_all methods + that didn't account for the LIMIT clause being passed into raw SQL statements. + 3. Implemented the string_to_time method in order to create proper instances of the time class. + 4. Added logic to the simplified_type method that allows the database to specify the scale of float data. + 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string. + +* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 [lmarlow@yahoo.com] + +* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example: + + class Person < ActiveRecord::Base + validates_each :first_name, :last_name do |record, attr| + record.errors.add attr, 'starts with z.' if attr[0] == ?z + end + end + +* Added :allow_nil as an explicit option for validates_length_of, so unless that's set to true having the attribute as nil will also return an error if a range is specified as :within #610 [Jeremy Kemper] + +* Added that validates_* now accept blocks to perform validations #618 [Tim Bates]. Example: + + class Person < ActiveRecord::Base + validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID } + end + +* Addded validation for validate all the associated objects before declaring failure with validates_associated #618 [Tim Bates] + +* Added keyword-style approach to defining the custom relational bindings #545 [Jamis Buck]. Example: + + class Project < ActiveRecord::Base + primary_key "sysid" + table_name "XYZ_PROJECT" + inheritance_column { original_inheritance_column + "_id" } + end + +* Fixed Base#clone for use with PostgreSQL #565 [hanson@surgery.wisc.edu] + + +*1.6.0* (January 25th, 2005) + +* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once. + +* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336 + +* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once. + +* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example + + people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} } + Person.update(people.keys, people.values) + +* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 [Scott Baron] + +* Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 [notahat] + +* Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 [Demetrius] + +* Added bind-named arrays for interpolating a group of ids or strings in conditions #528 [Jeremy Kemper] + +* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson] + +* Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 [Eric Anderson] + +* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not + +* Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription + +* Fixed that column aliases didn't work as expected with the new MySql411 driver #507 [Demetrius] + +* Fixed that find_all would produce invalid sql when called sequentialy #490 [Scott Baron] + + +*1.5.1* (January 18th, 2005) + +* Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 [Tim Bates] + +* Fixed that the Ruby/MySQL adapter wouldn't connect if the password was empty #503 [Pelle] + + +*1.5.0* (January 17th, 2005) + +* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 [Eric Hodel] + +* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example: + + class Book < ActiveRecord::Base + has_many :pages + belongs_to :library + + validates_associated :pages, :library + end + +* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition: + + == 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. + +* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 [Tim Bates] + +* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 [Tim Bates] + +* Added that 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. #402 [Tim Bates] + +* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 [Tim Bates] + +* Fixed binary support for PostgreSQL #444 [alex@byzantine.no] + +* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size 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. + +* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de] + +* Fixed that foreign keys named the same as the association would cause stack overflow #437 [Eric Anderson] + +* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 [Alexey] + +* Added Base#reload that reloads the attributes of an object from the database #422 [Andreas Schwarz] + +* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [Jeremy Kemper] + +* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower] + +* Added that Observers can use the observes class method instead of overwriting self.observed_class(). + + Before: + class ListSweeper < ActiveRecord::Base + def self.observed_class() [ List, Item ] + end + + After: + class ListSweeper < ActiveRecord::Base + observes List, Item + end + +* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is + +* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name + +* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag. + + Before: topic.update_attribute(:approved, !approved?) + After : topic.toggle!(:approved) + +* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example: + + page.views # => 1 + page.increment!(:views) # executes an UPDATE statement + page.views # => 2 + + page.increment(:views).increment!(:views) + page.views # => 4 + +* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns. + + +*1.4.0* (January 4th, 2005) + +* Added automated optimistic locking if the field lock_version 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. + + #384 [Michael Koziarski] + +* Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL. + They work by appending the name of an attribute to find_by_, so you get finders like Person.find_by_user_name, + Payment.find_by_transaction_id. So instead of writing Person.find_first(["user_name = ?", user_name]), you just do + Person.find_by_user_name(user_name). + + It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like + Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing + Person.find_first(["user_name = ? AND password = ?", user_name, password]), you just do + Person.find_by_user_name_and_password(user_name, password). + + While primarily a construct for easier find_firsts, it can also be used as a construct for find_all by using calls like + Payment.find_all_by_amount(50) that is turned into Payment.find_all(["amount = ?", 50]). This is something not as equally useful, + though, as it's not possible to specify the order in which the objects are returned. + +* Added block-style for callbacks #332 [Jeremy Kemper]. + + Before: + before_destroy(Proc.new{ |record| Person.destroy_all "firm_id = #{record.id}" }) + + After: + before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" } + +* Added :counter_cache option to acts_as_tree that works just like the one you can define on belongs_to #371 [Josh] + +* Added Base.default_timezone accessor that 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. + +* Added the possibility for adapters to overwrite add_limit! to implement a different limiting scheme than "LIMIT X" used by MySQL, PostgreSQL, and SQLite. + +* Added the possibility of having objects with acts_as_list created before their scope is available or... + +* Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 [Maik Schmidt] + +* Added the final touches to the Microsoft SQL Server adapter by Joey Gibson that makes it suitable for actual use #394 [DeLynn Barry] + +* Added that Base#find takes an optional options hash, including :conditions. Base#find_on_conditions deprecated in favor of #find with :conditions #407 [Jeremy Kemper] + +* Added HasManyAssociation#count that works like Base#count #413 [intinig] + +* Fixed handling of binary content in blobs and similar fields for Ruby/MySQL and SQLite #409 [xal] + +* Fixed a bug in the Ruby/MySQL that caused binary content to be escaped badly and come back mangled #405 [Tobias Luetke] + +* Fixed that the const_missing autoload assumes the requested constant is set by require_association and calls const_get to retrieve it. + If require_association did not set the constant then const_get will call const_missing, resulting in an infinite loop #380 [Jeremy Kemper] + +* Fixed broken transactions that were actually only running object-level and not db level transactions [andreas] + +* Fixed that validates_uniqueness_of used 'id' instead of defined primary key #406 + +* Fixed that the overwritten respond_to? method didn't take two parameters like the original #391 + +* Fixed quoting in validates_format_of that would allow some rules to pass regardless of input #390 [Dmitry V. Sabanin] + + +*1.3.0* (December 23, 2004) + +* Added a require_association hook on const_missing that makes it possible to use any model class without requiring it first. This makes STI look like: + + before: + require_association 'person' + class Employee < Person + end + + after: + class Employee < Person + end + + This also reduces the usefulness of Controller.model in Action Pack to currently only being for documentation purposes. + +* Added that Base.update_all and Base.delete_all return an integer of the number of affected rows #341 + +* Added scope option to validation_uniqueness #349 [Kent Sibilev] + +* Added respondence to *_before_type_cast for all attributes to return their string-state before they were type casted by the column type. + This is helpful for getting "100,000" back on a integer-based validation where the value would normally be "100". + +* Added allow_nil options to validates_inclusion_of so that validation is only triggered if the attribute is not nil [what-a-day] + +* Added work-around for PostgreSQL and the problem of getting fixtures to be created from id 1 on each test case. + This only works for auto-incrementing primary keys called "id" for now #359 [Scott Baron] + +* Added Base#clear_association_cache to empty all the cached associations #347 [Tobias Luetke] + +* Added more informative exceptions in establish_connection #356 [Jeremy Kemper] + +* Added Base#update_attributes that'll accept a hash of attributes and save the record (returning true if it passed validation, false otherwise). + + Before: + person.attributes = @params["person"] + person.save + + Now: + person.update_attributes(@params["person"]) + +* Added Base.destroy and Base.delete to remove records without holding a reference to them first. + +* Added that query benchmarking will only happen if its going to be logged anyway #344 + +* Added higher_item and lower_item as public methods for acts_as_list #342 [Tobias Luetke] + +* Fixed that options[:counter_sql] was overwritten with interpolated sql rather than original sql #355 [Jeremy Kemper] + +* Fixed that overriding an attribute's accessor would be disregarded by add_on_empty and add_on_boundary_breaking because they simply used + the attributes[] hash instead of checking for @base.respond_to?(attr.to_s). [Marten] + +* Fixed that Base.table_name would expect a parameter when used in has_and_belongs_to_many joins [Anna Lissa Cruz] + +* Fixed that nested transactions now work by letting the outer most transaction have the responsibilty of starting and rolling back the transaction. + If any of the inner transactions swallow the exception raised, though, the transaction will not be rolled back. So always let the transaction + bubble up even when you've dealt with local issues. Closes #231 and #340. + +* Fixed validates_{confirmation,acceptance}_of to only happen when the virtual attributes are not nil #348 [dpiddy@gmail.com] + +* Changed the interface on AbstractAdapter to require that adapters return the number of affected rows on delete and update operations. + +* Fixed the automated timestamping feature when running under Rails' development environment that resets the inheritable attributes on each request. + + + +*1.2.0* + +* Added Base.validates_inclusion_of that validates whether the value of the specified attribute is available in a particular enumerable + object. [what-a-day] + + 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 + +* Added acts_as_list that can decorates an existing class with methods like move_higher/lower, move_to_top/bottom. [Tobias Luetke] Example: + + class TodoItem < ActiveRecord::Base + acts_as_list :scope => :todo_list_id + belongs_to :todo_list + end + +* Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in + categories and the likes. [Tobias Luetke] + +* Added that 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. [Tobias Luetke] + +* Added Base.default_error_messages as a hash of all the error messages used in the validates_*_of so they can be changed in one place [Tobias Luetke] + +* Added automatic transaction block around AssociationCollection.<<, AssociationCollection.delete, and AssociationCollection.destroy_all + +* Fixed that Base#find will return an array if given an array -- regardless of the number of elements #270 [Marten] + +* Fixed that has_and_belongs_to_many would generate bad sql when naming conventions differed from using vanilla "id" everywhere [RedTerror] + +* Added a better exception for when a type column is used in a table without the intention of triggering single-table inheritance. Example: + + ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'bad_class!'. + This error is raised because the column 'type' 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 Company.inheritance_column to use another column for that information. + +* Added that single-table inheritance will only kick in if the inheritance_column (by default "type") is present. Otherwise, inheritance won't + have any magic side effects. + +* Added the possibility of marking fields as being in error without adding a message (using nil) to it that'll get displayed wth full_messages #208 [mjobin] + +* Fixed Base.errors to be indifferent as to whether strings or symbols are used. Examples: + + Before: + errors.add(:name, "must be shorter") if name.size > 10 + errors.on(:name) # => "must be shorter" + errors.on("name") # => nil + + After: + errors.add(:name, "must be shorter") if name.size > 10 + errors.on(:name) # => "must be shorter" + errors.on("name") # => "must be shorter" + +* Added Base.validates_format_of that Validates whether the value of the specified attribute is of the correct form by matching + it against the regular expression provided. [Marcel] + + class Person < ActiveRecord::Base + validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :on => :create + end + +* Added Base.validates_length_of that delegates to add_on_boundary_breaking #312 [Tobias Luetke]. Example: + + Validates that the specified attribute matches the length restrictions supplied in either: + + - configuration[:minimum] + - configuration[:maximum] + - configuration[:is] + - configuration[:within] (aka. configuration[:in]) + + 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 :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 + +* Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself. + +* Added Base.validates_uniqueness_of that alidates 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 + end + + When the record is created, a check is performed to make sure that no record exist 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. + + +* Added Base.validates_confirmation_of that 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 :password + 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 both on create and update. + + +* Added Base.validates_acceptance_of that 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 + end + + The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update. + + NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox. + + +* Added validation macros to make the stackable just like the lifecycle callbacks. Examples: + + class Person < ActiveRecord::Base + validate { |record| record.errors.add("name", "too short") unless name.size > 10 } + validate { |record| record.errors.add("name", "too long") unless name.size < 20 } + validate_on_create :validate_password + + private + def validate_password + errors.add("password", "too short") unless password.size > 6 + end + end + +* Added the option for sanitizing find_by_sql and the offset parts in regular finds [Sam Stephenson]. Examples: + + Project.find_all ["category = ?", category_name], "created ASC", ["? OFFSET ?", 15, 20] + Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date] + +* Fixed value quoting in all generated SQL statements, so that integers are not surrounded in quotes and that all sanitation are happening + through the database's own quoting routine. This should hopefully make it lots easier for new adapters that doesn't accept '1' for integer + columns. + +* Fixed has_and_belongs_to_many guessing of foreign key so that keys are generated correctly for models like SomeVerySpecialClient + [Florian Weber] + +* Added counter_sql option for has_many associations [Jeremy Kemper]. Documentation: + + :counter_sql - 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. + +* Fixed that methods wrapped in callbacks still return their original result #260 [Jeremy Kemper] + +* Fixed the Inflector to handle the movie/movies pair correctly #261 [Scott Baron] + +* Added named bind-style variable interpolation #281 [Michael Koziarski]. Example: + + Person.find(["id = :id and first_name = :first_name", { :id => 5, :first_name = "bob' or 1=1" }]) + +* Added bind-style variable interpolation for the condition arrays that uses the adapter's quote method [Michael Koziarski] + + Before: + find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])] + find_first([ "firm_id = %s", firm_id ])] # unsafe! + + After: + find_first([ "user_name = ? AND password = ?", user_name, password ])] + find_first([ "firm_id = ?", firm_id ])] + +* Added CSV format for fixtures #272 [what-a-day]. (See the new and expanded documentation on fixtures for more information) + +* Fixed fixtures using primary key fields called something else than "id" [dave] + +* Added proper handling of time fields that are turned into Time objects with the dummy date of 2000/1/1 [HariSeldon] + +* Added reverse order of deleting fixtures, so referential keys can be maintained #247 [Tim Bates] + +* Added relative path search for sqlite dbfiles in database.yml (if RAILS_ROOT is defined) #233 [Jeremy Kemper] + +* Added option to establish_connection where you'll be able to leave out the parameter to have it use the RAILS_ENV environment variable + +* Fixed problems with primary keys and postgresql sequences (#230) [Tim Bates] + +* Added reloading for associations under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development. + This is turned on by default, but can be turned off with ActiveRecord::Base.reload_dependencies = false in production environments. + + NOTE: This will only have an effect if you let the associations manage the requiring of model classes. All libraries loaded through + require will be "forever" cached. You can, however, use ActiveRecord::Base.load_or_require("library") to get this behavior outside of the + auto-loading associations. + +* Added ERB capabilities to the fixture files for dynamic fixture generation. You don't need to do anything, just include ERB blocks like: + + david: + id: 1 + name: David + + jamis: + id: 2 + name: Jamis + + <% for digit in 3..10 %> + dev_<%= digit %>: + id: <%= digit %> + name: fixture_<%= digit %> + <% end %> + +* Changed the yaml fixture searcher to look in the root of the fixtures directory, so when you before could have something like: + + fixtures/developers/fixtures.yaml + fixtures/accounts/fixtures.yaml + + ...you now need to do: + + fixtures/developers.yaml + fixtures/accounts.yaml + +* Changed the fixture format from: + + name: david + data: + id: 1 + name: David Heinemeier Hansson + birthday: 1979-10-15 + profession: Systems development + --- + name: steve + data: + id: 2 + name: Steve Ross Kellock + birthday: 1974-09-27 + profession: guy with keyboard + + ...to: + + david: + id: 1 + name: David Heinemeier Hansson + birthday: 1979-10-15 + profession: Systems development + + steve: + id: 2 + name: Steve Ross Kellock + birthday: 1974-09-27 + profession: guy with keyboard + + The change is NOT backwards compatible. Fixtures written in the old YAML style needs to be rewritten! + +* All associations will now attempt to require the classes that they associate to. Relieving the need for most explicit 'require' statements. + + +*1.1.0* (34) + +* Added automatic fixture setup and instance variable availability. Fixtures can also be automatically + instantiated in instance variables relating to their names using the following style: + + class FixturesTest < Test::Unit::TestCase + fixtures :developers # you can add more with comma separation + + def test_developers + assert_equal 3, @developers.size # the container for all the fixtures is automatically set + assert_kind_of Developer, @david # works like @developers["david"].find + assert_equal "David Heinemeier Hansson", @david.name + end + end + +* Added HasAndBelongsToManyAssociation#push_with_attributes(object, join_attributes) that can create associations in the join table with additional + attributes. This is really useful when you have information that's only relevant to the join itself, such as a "added_on" column for an association + between post and category. The added attributes will automatically be injected into objects retrieved through the association similar to the piggy-back + approach: + + post.categories.push_with_attributes(category, :added_on => Date.today) + post.categories.first.added_on # => Date.today + + NOTE: The categories table doesn't have a added_on column, it's the categories_post join table that does! + +* Fixed that :exclusively_dependent and :dependent can't be activated at the same time on has_many associations [Jeremy Kemper] + +* Fixed that database passwords couldn't be all numeric [Jeremy Kemper] + +* Fixed that calling id would create the instance variable for new_records preventing them from being saved correctly [Jeremy Kemper] + +* Added sanitization feature to HasManyAssociation#find_all so it works just like Base.find_all [Sam Stephenson/bitsweat] + +* Added that you can pass overlapping ids to find without getting duplicated records back [Jeremy Kemper] + +* Added that Base.benchmark returns the result of the block [Jeremy Kemper] + +* Fixed problem with unit tests on Windows with SQLite [paterno] + +* Fixed that quotes would break regular non-yaml fixtures [Dmitry Sabanin/daft] + +* Fixed fixtures on windows with line endings cause problems under unix / mac [Tobias Luetke] + +* Added HasAndBelongsToManyAssociation#find(id) that'll search inside the collection and find the object or record with that id + +* Added :conditions option to has_and_belongs_to_many that works just like the one on all the other associations + +* Added AssociationCollection#clear to remove all associations from has_many and has_and_belongs_to_many associations without destroying the records [geech] + +* Added type-checking and remove in 1-instead-of-N sql statements to AssociationCollection#delete [geech] + +* Added a return of self to AssociationCollection#<< so appending can be chained, like project << Milestone.create << Milestone.create [geech] + +* Added Base#hash and Base#eql? which means that all of the equality using features of array and other containers now works: + + [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] + +* Added :uniq as an option to has_and_belongs_to_many which will automatically ensure that AssociateCollection#uniq is called + before pulling records out of the association. This is especially useful for three-way (and above) has_and_belongs_to_many associations. + +* Added AssociateCollection#uniq which is especially useful for has_and_belongs_to_many associations that can include duplicates, + which is common on associations that also use metadata. Usage: post.categories.uniq + +* Fixed respond_to? to use a subclass specific hash instead of an Active Record-wide one + +* Fixed has_and_belongs_to_many to treat associations between classes in modules properly [Florian Weber] + +* Added a NoMethod exception to be raised when query and writer methods are called for attributes that doesn't exist [geech] + +* Added a more robust version of Fixtures that throws meaningful errors when on formatting issues [geech] + +* Added Base#transaction as a compliment to Base.transaction for prettier use in instance methods [geech] + +* Improved the speed of respond_to? by placing the dynamic methods lookup table in a hash [geech] + +* Added that any additional fields added to the join table in a has_and_belongs_to_many association + will be placed as attributes when pulling records out through has_and_belongs_to_many associations. + This is helpful when have information about the association itself that you want available on retrival. + +* Added better loading exception catching and RubyGems retries to the database adapters [alexeyv] + +* Fixed bug with per-model transactions [daniel] + +* Fixed Base#transaction so that it returns the result of the last expression in the transaction block [alexeyv] + +* Added Fixture#find to find the record corresponding to the fixture id. The record + class name is guessed by using Inflector#classify (also new) on the fixture directory name. + + Before: Document.find(@documents["first"]["id"]) + After : @documents["first"].find + +* Fixed that the table name part of column names ("TABLE.COLUMN") wasn't removed properly [Andreas Schwarz] + +* Fixed a bug with Base#size when a finder_sql was used that didn't capitalize SELECT and FROM [geech] + +* Fixed quoting problems on SQLite by adding quote_string to the AbstractAdapter that can be overwritten by the concrete + adapters for a call to the dbm. [Andreas Schwarz] + +* Removed RubyGems backup strategy for requiring SQLite-adapter -- if people want to use gems, they're already doing it with AR. + + +*1.0.0 (35)* + +* Added OO-style associations methods [Florian Weber]. Examples: + + Project#milestones_count => Project#milestones.size + Project#build_to_milestones => Project#milestones.build + Project#create_for_milestones => Project#milestones.create + Project#find_in_milestones => Project#milestones.find + Project#find_all_in_milestones => Project#milestones.find_all + +* Added serialize as a new class method to control when text attributes should be YAMLized or not. This means that automated + serialization of hashes, arrays, and so on WILL NO LONGER HAPPEN (#10). You need to do something like this: + + class User < ActiveRecord::Base + serialize :settings + end + + This will assume that settings is a text column and will now YAMLize any object put in that attribute. You can also specify + an optional :class_name option 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 :settings, :class_name => "Hash" + end + + user = User.create("settings" => %w( one two three )) + User.find(user.id).settings # => raises SerializationTypeMismatch + +* Added the option to connect to a different database for one model at a time. Just call establish_connection on the class + you want to have connected to another database than Base. This will automatically also connect decendents of that class + to the different database [Renald Buter]. + +* Added transactional protection for Base#save. Validations can now check for values knowing that it happens in a transaction and callbacks + can raise exceptions knowing that the save will be rolled back. [Suggested by Alexey Verkhovsky] + +* Added column name quoting so reserved words, such as "references", can be used as column names [Ryan Platte] + +* Added the possibility to chain the return of what happened inside a logged block [geech]: + + This now works: + log { ... }.map { ... } + + Instead of doing: + result = [] + log { result = ... } + result.map { ... } + +* Added "socket" option for the MySQL adapter, so you can change it to something else than "/tmp/mysql.sock" [Anna Lissa Cruz] + +* Added respond_to? answers for all the attribute methods. So if Person has a name attribute retrieved from the table schema, + person.respond_to? "name" will return true. + +* Added Base.benchmark which can be used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block. + Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all): + + Project.benchmark("Creating project") do + project = Project.create("name" => "stuff") + project.create_manager("name" => "David") + project.milestones << Milestone.find_all + end + +* Added logging of invalid SQL statements [Suggested by Daniel Von Fange] + +* Added alias Errors#[] for Errors#on, so you can now say person.errors["name"] to retrieve the errors for name [Andreas Schwarz] + +* Added RubyGems require attempt if sqlite-ruby is not available through regular methods. + +* Added compatibility with 2.x series of sqlite-ruby drivers. [Jamis Buck] + +* Added type safety for association assignments, so a ActiveRecord::AssociationTypeMismatch will be raised if you attempt to + assign an object that's not of the associated class. This cures the problem with nil giving id = 4 and fixnums giving id = 1 on + mistaken association assignments. [Reported by Andreas Schwarz] + +* Added the option to keep many fixtures in one single YAML document [what-a-day] + +* Added the class method "inheritance_column" that can be overwritten to return the name of an alternative column than "type" for storing + the type for inheritance hierarchies. [Dave Steinberg] + +* Added [] and []= as an alternative way to access attributes when the regular methods have been overwritten [Dave Steinberg] + +* Added the option to observer more than one class at the time by specifying observed_class as an array + +* Added auto-id propagation support for tables with arbitrary primary keys that have autogenerated sequences associated with them + on PostgreSQL. [Dave Steinberg] + +* Changed that integer and floats set to "" through attributes= remain as NULL. This was especially a problem for scaffolding and postgresql. (#49) + +* Changed the MySQL Adapter to rely on MySQL for its defaults for socket, host, and port [Andreas Schwarz] + +* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue. + +* Changed class inheritable attributes to not use eval [Caio Chassot] + +* Changed Errors#add to now use "invalid" as the default message instead of true, which means full_messages work with those [Marcel Molina Jr] + +* Fixed spelling on Base#add_on_boundry_breaking to Base#add_on_boundary_breaking (old naming still works) [Marcel Molina Jr.] + +* Fixed that entries in the has_and_belongs_to_many join table didn't get removed when an associated object was destroyed. + +* Fixed unnecessary calls to SET AUTOCOMMIT=0/1 for MySQL adapter [Andreas Schwarz] + +* Fixed PostgreSQL defaults are now handled gracefully [Dave Steinberg] + +* Fixed increment/decrement_counter are now atomic updates [Andreas Schwarz] + +* Fixed the problems the Inflector had turning Attachment into attuchments and Cases into Casis [radsaq/Florian Gross] + +* Fixed that cloned records would point attribute references on the parent object [Andreas Schwarz] + +* Fixed SQL for type call on inheritance hierarchies [Caio Chassot] + +* Fixed bug with typed inheritance [Florian Weber] + +* Fixed a bug where has_many collection_count wouldn't use the conditions specified for that association + + +*0.9.5* + +* Expanded the table_name guessing rules immensely [Florian Green]. Documentation: + + 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 guessing rules are as follows: + * Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table. + * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies", + so a Category class becomes a categories table. + * Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table. + * Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table. + * Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table. + * Class name ends in "man": The "man" is replaced with "men", so a Spokesman class becomes a spokesmen table. + * Class name ends in "sis": The "i" is replaced with an "e", so a Basis class becomes a bases table. + * Class name ends in "tum" or "ium": The "um" is replaced with an "a", so a Datum class becomes a data table. + * Class name ends in "child": The "child" is replaced with "children", so a NodeChild class becomes a node_children table. + * Class name ends in an "s": No additional characters are added or removed. + * Class name doesn't end in "s": An "s" is appended, so a Comment class becomes a comments table. + * Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table. + 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 + def self.table_name() "mice" end + end + + This conversion is now done through an external class called Inflector residing in lib/active_record/support/inflector.rb. + +* Added find_all_in_collection to has_many defined collections. Works like this: + + class Firm < ActiveRecord::Base + has_many :clients + end + + firm.id # => 1 + firm.find_all_in_clients "revenue > 1000" # SELECT * FROM clients WHERE firm_id = 1 AND revenue > 1000 + + [Requested by Dave Thomas] + +* Fixed finders for inheritance hierarchies deeper than one level [Florian Weber] + +* Added add_on_boundry_breaking to errors to accompany add_on_empty as a default validation method. It's used like this: + + class Person < ActiveRecord::Base + protected + def validation + errors.add_on_boundry_breaking "password", 3..20 + end + end + + This will add an error to the tune of "is too short (minimum is 3 characters)" or "is too long (minimum is 20 characters)" if + the password is outside the boundry. The messages can be changed by passing a third and forth parameter as message strings. + +* Implemented a clone method that works properly with AR. It returns a clone of the record that + hasn't been assigned an id yet and is treated as a new record. + +* Allow for domain sockets in PostgreSQL by not assuming localhost when no host is specified [Scott Barron] + +* Fixed that bignums are saved properly instead of attempted to be YAMLized [Andreas Schwartz] + +* Fixed a bug in the GEM where the rdoc options weren't being passed according to spec [Chad Fowler] + +* Fixed a bug with the exclusively_dependent option for has_many + + +*0.9.4* + +* Correctly guesses the primary key when the class is inside a module [Dave Steinberg]. + +* Added [] and []= as alternatives to read_attribute and write_attribute [Dave Steinberg] + +* has_and_belongs_to_many now accepts an :order key to determine in which order the collection is returned [radsaq]. + +* The ids passed to find and find_on_conditions are now automatically sanitized. + +* Added escaping of plings in YAML content. + +* Multi-parameter assigns where all the parameters are empty will now be set to nil instead of a new instance of their class. + +* Proper type within an inheritance hierarchy is now ensured already at object initialization (instead of first at create) + + +*0.9.3* + +* Fixed bug with using a different primary key name together with has_and_belongs_to_many [Investigation by Scott] + +* Added :exclusively_dependent option to the has_many association macro. The doc reads: + + 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. + +* Added :port key to connection options, so the PostgreSQL and MySQL adapters can connect to a database server + running on another port than the default. + +* Converted the new natural singleton methods that prevented AR objects from being saved by PStore + (and hence be placed in a Rails session) to a module. [Florian Weber] + +* Fixed the use of floats (was broken since 0.9.0+) + +* Fixed PostgreSQL adapter so default values are displayed properly when used in conjunction with + Action Pack scaffolding. + +* Fixed booleans support for PostgreSQL (use real true/false on boolean fields instead of 0/1 on tinyints) [radsaq] + + +*0.9.2* + +* Added static method for instantly updating a record + +* Treat decimal and numeric as Ruby floats [Andreas Schwartz] + +* Treat chars as Ruby strings (fixes problem for Action Pack form helpers too) + +* Removed debugging output accidently left in (which would screw web applications) + + +*0.9.1* + +* Added MIT license + +* Added natural object-style assignment for has_and_belongs_to_many associations. Consider the following model: + + class Event < ActiveRecord::Base + has_one_and_belongs_to_many :sponsors + end + + class Sponsor < ActiveRecord::Base + has_one_and_belongs_to_many :sponsors + end + + Earlier, you'd have to use synthetic methods for creating associations between two objects of the above class: + + roskilde_festival.add_to_sponsors(carlsberg) + roskilde_festival.remove_from_sponsors(carlsberg) + + nike.add_to_events(world_cup) + nike.remove_from_events(world_cup) + + Now you can use regular array-styled methods: + + roskilde_festival.sponsors << carlsberg + roskilde_festival.sponsors.delete(carlsberg) + + nike.events << world_cup + nike.events.delete(world_cup) + +* Added delete method for has_many associations. Using this will nullify an association between the has_many and the belonging + object by setting the foreign key to null. Consider this model: + + class Post < ActiveRecord::Base + has_many :comments + end + + class Comment < ActiveRecord::Base + belongs_to :post + end + + You could do something like: + + funny_comment.has_post? # => true + announcement.comments.delete(funny_comment) + funny_comment.has_post? # => false + + +*0.9.0* + +* Active Record is now thread safe! (So you can use it with Cerise and WEBrick applications) + [Implementation idea by Michael Neumann, debugging assistance by Jamis Buck] + +* Improved performance by roughly 400% on a basic test case of pulling 100 records and querying one attribute. + This brings the tax for using Active Record instead of "riding on the metal" (using MySQL-ruby C-driver directly) down to ~50%. + Done by doing lazy type conversions and caching column information on the class-level. + +* Added callback objects and procs as options for implementing the target for callback macros. + +* Added "counter_cache" option to belongs_to that automates the usage of increment_counter and decrement_counter. Consider: + + class Post < ActiveRecord::Base + has_many :comments + end + + class Comment < ActiveRecord::Base + belongs_to :post + end + + Iterating over 100 posts like this: + + <% for post in @posts %> + <%= post.title %> has <%= post.comments_count %> comments + <% end %> + + Will generate 100 SQL count queries -- one for each call to post.comments_count. If you instead add a "comments_count" int column + to the posts table and rewrite the comments association macro with: + + class Comment < ActiveRecord::Base + belongs_to :post, :counter_cache => true + end + + Those 100 SQL count queries will be reduced to zero. Beware that counter caching is only appropriate for objects that begin life + with the object it's specified to belong with and is destroyed like that as well. Typically objects where you would also specify + :dependent => true. If your objects switch from one belonging to another (like a post that can be move from one category to another), + you'll have to manage the counter yourself. + +* Added natural object-style assignment for has_one and belongs_to associations. Consider the following model: + + class Project < ActiveRecord::Base + has_one :manager + end + + class Manager < ActiveRecord::Base + belongs_to :project + end + + Earlier, assignments would work like following regardless of which way the assignment told the best story: + + active_record.manager_id = david.id + + Now you can do it either from the belonging side: + + david.project = active_record + + ...or from the having side: + + active_record.manager = david + + If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the + project_id attribute on david would be set to the id of active_record, then david would be saved. + +* Added natural object-style assignment for has_many associations [Florian Weber]. Consider the following model: + + class Project < ActiveRecord::Base + has_many :milestones + end + + class Milestone < ActiveRecord::Base + belongs_to :project + end + + Earlier, assignments would work like following regardless of which way the assignment told the best story: + + deadline.project_id = active_record.id + + Now you can do it either from the belonging side: + + deadline.project = active_record + + ...or from the having side: + + active_record.milestones << deadline + + The milestone is automatically saved with the new foreign key. + +* API CHANGE: Attributes for text (or blob or similar) columns will now have unknown classes stored using YAML instead of using + to_s. (Known classes that won't be yamelized are: String, NilClass, TrueClass, FalseClass, Fixnum, Date, and Time). + Likewise, data pulled out of text-based attributes will be attempted converged using Yaml if they have the "--- " header. + This was primarily done to be enable the storage of hashes and arrays without wrapping them in aggregations, so now you can do: + + user = User.find(1) + user.preferences = { "background" => "black", "display" => large } + user.save + + User.find(1).preferences # => { "background" => "black", "display" => large } + + Please note that this method should only be used when you don't care about representing the object in proper columns in + the database. A money object consisting of an amount and a currency is still a much better fit for a value object done through + aggregations than this new option. + +* POSSIBLE CODE BREAKAGE: As a consequence of the lazy type conversions, it's a bad idea to reference the @attributes hash + directly (it always was, but now it's paramount that you don't). If you do, you won't get the type conversion. So to implement + new accessors for existing attributes, use read_attribute(attr_name) and write_attribute(attr_name, value) instead. Like this: + + 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 + + The clever kid will notice that this opens a door to sidestep the automated type conversion by using @attributes directly. + This is not recommended as read/write_attribute may be granted additional responsibilities in the future, but if you think + you know what you're doing and aren't afraid of future consequences, this is an option. + +* Applied a few minor bug fixes reported by Daniel Von Fange. + + +*0.8.4* + +_Reflection_ + +* Added ActiveRecord::Reflection with a bunch of methods and classes for reflecting in aggregations and associations. + +* Added Base.columns and Base.content_columns which returns arrays of column description (type, default, etc) objects. + +* Added Base#attribute_names which returns an array of names for the attributes available on the object. + +* Added Base#column_for_attribute(name) which returns the column description object for the named attribute. + + +_Misc_ + +* Added multi-parameter assignment: + + # Instantiate 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 + # parenteses 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. + + This is incredibly useful for assigning dates from HTML drop-downs of month, year, and day. + +* Fixed bug with custom primary key column name and Base.find on multiple parameters. + +* Fixed bug with dependent option on has_one associations if there was no associated object. + + +*0.8.3* + +_Transactions_ + +* Added transactional protection for destroy (important for the new :dependent option) [Suggested by Carl Youngblood] + +* Fixed so transactions are ignored on MyISAM tables for MySQL (use InnoDB to get transactions) + +* Changed transactions so only exceptions will cause a rollback, not returned false. + + +_Mapping_ + +* Added support for non-integer primary keys [Aredridel/earlier work by Michael Neumann] + + User.find "jdoe" + Product.find "PDKEY-INT-12" + +* Added option to specify naming method for primary key column. ActiveRecord::Base.primary_key_prefix_type can either + be set to nil, :table_name, or :table_name_with_underscore. :table_name will assume that Product class has a primary key + of "productid" and :table_name_with_underscore will assume "product_id". The default nil will just give "id". + +* Added an overwriteable primary_key method that'll instruct AR to the name of the + id column [Aredridele/earlier work by Guan Yang] + + class Project < ActiveRecord::Base + def self.primary_key() "project_id" end + end + +* Fixed that Active Records can safely associate inside and out of modules. + + class MyApplication::Account < ActiveRecord::Base + has_many :clients # will look for MyApplication::Client + has_many :interests, :class_name => "Business::Interest" # will look for Business::Interest + end + +* Fixed that Active Records can safely live inside modules [Aredridel] + + class MyApplication::Account < ActiveRecord::Base + end + + +_Misc_ + +* Added freeze call to value object assignments to ensure they remain immutable [Spotted by Gavin Sinclair] + +* Changed interface for specifying observed class in observers. Was OBSERVED_CLASS constant, now is + observed_class() class method. This is more consistant with things like self.table_name(). Works like this: + + class AuditObserver < ActiveRecord::Observer + def self.observed_class() Account end + def after_update(account) + AuditTrail.new(account, "UPDATED") + end + end + + [Suggested by Gavin Sinclair] + +* Create new Active Record objects by setting the attributes through a block. Like this: + + person = Person.new do |p| + p.name = 'Freddy' + p.age = 19 + end + + [Suggested by Gavin Sinclair] + + +*0.8.2* + +* Added inheritable callback queues that can ensure that certain callback methods or inline fragments are + run throughout the entire inheritance hierarchy. Regardless of whether a descendent overwrites the callback + method: + + class Topic < ActiveRecord::Base + before_destroy :destroy_author, 'puts "I'm an inline fragment"' + end + + Learn more in link:classes/ActiveRecord/Callbacks.html + +* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when + the holder is destroyed: + + class Album < ActiveRecord::Base + has_many :tracks, :dependent => true + end + + All the associated tracks are destroyed when the album is. + +* Added Base.create as a factory that'll create, save, and return a new object in one step. + +* Automatically convert strings in config hashes to symbols for the _connection methods. This allows you + to pass the argument hashes directly from yaml. (Luke) + +* Fixed the install.rb to include simple.rb [Spotted by Kevin Bullock] + +* Modified block syntax to better follow our code standards outlined in + http://www.rubyonrails.org/CodingStandards + + +*0.8.1* + +* Added object-level transactions [Thanks to Austin Ziegler for Transaction::Simple] + +* Changed adapter-specific connection methods to use centralized ActiveRecord::Base.establish_connection, + which is parametized through a config hash with symbol keys instead of a regular parameter list. + This will allow for database connections to be opened in a more generic fashion. (Luke) + + NOTE: This requires all *_connections to be updated! Read more in: + http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000081 + +* Fixed SQLite adapter so objects fetched from has_and_belongs_to_many have proper attributes + (t.name is now name). [Spotted by Garrett Rooney] + +* Fixed SQLite adapter so dates are returned as Date objects, not Time objects [Spotted by Gavin Sinclair] + +* Fixed requirement of date class, so date conversions are succesful regardless of whether you + manually require date or not. + + +*0.8.0* + +* Added transactions + +* Changed Base.find to also accept either a list (1, 5, 6) or an array of ids ([5, 7]) + as parameter and then return an array of objects instead of just an object + +* Fixed method has_collection? for has_and_belongs_to_many macro to behave as a + collection, not an association + +* Fixed SQLite adapter so empty or nil values in columns of datetime, date, or time type + aren't treated as current time [Spotted by Gavin Sinclair] + + +*0.7.6* + +* Fixed the install.rb to create the lib/active_record/support directory [Spotted by Gavin Sinclair] +* Fixed that has_association? would always return true [Spotted by Daniel Von Fange] diff --git a/vendor/rails/activerecord/MIT-LICENSE b/vendor/rails/activerecord/MIT-LICENSE new file mode 100644 index 0000000..5919c28 --- /dev/null +++ b/vendor/rails/activerecord/MIT-LICENSE @@ -0,0 +1,20 @@ +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. \ No newline at end of file diff --git a/vendor/rails/activerecord/README b/vendor/rails/activerecord/README new file mode 100755 index 0000000..1be4df6 --- /dev/null +++ b/vendor/rails/activerecord/README @@ -0,0 +1,360 @@ += Active Record -- Object-relation mapping put on rails + +Active Record connects business objects and database tables to create a persistable +domain model where logic and data are presented in one wrapping. It's an implementation +of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] +by the same name as described by Martin Fowler: + + "An object that wraps a row in a database table or view, encapsulates + the database access, and adds domain logic on that data." + +Active Record's main contribution to the pattern is to relieve the original of two stunting problems: +lack of associations and inheritance. By adding a simple domain language-like set of macros to describe +the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the +gap of functionality between the data mapper and active record approach. + +A short rundown of the major features: + +* Automated mapping between classes and tables, attributes and columns. + + class Product < ActiveRecord::Base; end + + ...is automatically mapped to the table named "products", such as: + + CREATE TABLE products ( + id int(11) NOT NULL auto_increment, + name varchar(255), + PRIMARY KEY (id) + ); + + ...which again gives Product#name and Product#name=(new_name) + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Associations between objects controlled by simple meta-programming macros. + + class Firm < ActiveRecord::Base + has_many :clients + has_one :account + belongs_to :conglomorate + end + + {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] + + +* Aggregations of value objects controlled by simple meta-programming macros. + + class Account < 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 + + {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] + + +* Validation rules that can differ for new or existing objects. + + class Account < ActiveRecord::Base + validates_presence_of :subdomain, :name, :email_address, :password + validates_uniqueness_of :subdomain + validates_acceptance_of :terms_of_service, :on => :create + validates_confirmation_of :password, :email_address, :on => :create + end + + {Learn more}[link:classes/ActiveRecord/Validations.html] + + +* Acts that can make records work as lists or trees: + + class Item < ActiveRecord::Base + belongs_to :list + acts_as_list :scope => :list + end + + item.move_higher + item.move_to_bottom + + Learn about {acts_as_list}[link:classes/ActiveRecord/Acts/List/ClassMethods.html], {the instance methods acts_as_list provides}[link:classes/ActiveRecord/Acts/List/InstanceMethods.html], and + {acts_as_tree}[link:classes/ActiveRecord/Acts/Tree/ClassMethods.html] + +* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc). + + class Person < ActiveRecord::Base + def before_destroy # is called just before Person#destroy + CreditCard.find(credit_card_id).destroy + end + end + + class Account < ActiveRecord::Base + after_find :eager_load, 'self.class.announce(#{id})' + end + + {Learn more}[link:classes/ActiveRecord/Callbacks.html] + + +* Observers for the entire lifecycle + + class CommentObserver < ActiveRecord::Observer + def after_create(comment) # is called just after Comment#save + Notifications.deliver_new_comment("david@loudthinking.com", comment) + end + end + + {Learn more}[link:classes/ActiveRecord/Observer.html] + + +* Inheritance hierarchies + + class Company < ActiveRecord::Base; end + class Firm < Company; end + class Client < Company; end + class PriorityClient < Client; end + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Transaction support on both a database and object level. The latter is implemented + by using Transaction::Simple[http://www.halostatue.ca/ruby/Transaction__Simple.html] + + # Just database transaction + Account.transaction do + david.withdrawal(100) + mary.deposit(100) + end + + # Database and object transaction + Account.transaction(david, mary) do + david.withdrawal(100) + mary.deposit(100) + end + + {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] + + +* Reflections on columns, associations, and aggregations + + reflection = Firm.reflect_on_association(:clients) + reflection.klass # => Client (class) + Firm.columns # Returns an array of column descriptors for the firms table + + {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] + + +* Direct manipulation (instead of service invocation) + + So instead of (Hibernate[http://www.hibernate.org/] example): + + long pkId = 1234; + DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) ); + // something interesting involving a cat... + sess.save(cat); + sess.flush(); // force the SQL INSERT + + Active Record lets you: + + pkId = 1234 + cat = Cat.find(pkId) + # something even more interesting involving the same cat... + cat.save + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Database abstraction through simple adapters (~100 lines) with a shared connector + + ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile") + + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "me", + :password => "secret", + :database => "activerecord" + ) + + {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for + MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OCIAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html]. + + +* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc] + + ActiveRecord::Base.logger = Logger.new(STDOUT) + ActiveRecord::Base.logger = Log4r::Logger.new("Application Log") + + +== Simple example (1/2): Defining tables and classes (using MySQL) + +Data definitions are specified only in the database. Active Record queries the database for +the column names (that then serves to determine which attributes are valid) on regular +object instantiation through the new constructor and relies on the column names in the rows +with the finders. + + # CREATE TABLE companies ( + # id int(11) unsigned NOT NULL auto_increment, + # client_of int(11), + # name varchar(255), + # type varchar(100), + # PRIMARY KEY (id) + # ) + +Active Record automatically links the "Company" object to the "companies" table + + class Company < ActiveRecord::Base + has_many :people, :class_name => "Person" + end + + class Firm < Company + has_many :clients + + def people_with_all_clients + clients.inject([]) { |people, client| people + client.people } + end + end + +The foreign_key is only necessary because we didn't use "firm_id" in the data definition + + class Client < Company + belongs_to :firm, :foreign_key => "client_of" + end + + # CREATE TABLE people ( + # id int(11) unsigned NOT NULL auto_increment, + # name text, + # company_id text, + # PRIMARY KEY (id) + # ) + +Active Record will also automatically link the "Person" object to the "people" table + + class Person < ActiveRecord::Base + belongs_to :company + end + +== Simple example (2/2): Using the domain + +Picking a database connection for all the Active Records + + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "localhost", + :username => "me", + :password => "secret", + :database => "activerecord" + ) + +Create some fixtures + + firm = Firm.new("name" => "Next Angle") + # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm") + firm.save + + client = Client.new("name" => "37signals", "client_of" => firm.id) + # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm") + client.save + +Lots of different finders + + # SQL: SELECT * FROM companies WHERE id = 1 + next_angle = Company.find(1) + + # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm' + next_angle = Firm.find(1) + + # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle' + next_angle = Company.find_first "name = 'Next Angle'" + + next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first + +The supertype, Company, will return subtype instances + + Firm === next_angle + +All the dynamic methods added by the has_many macro + + next_angle.clients.empty? # true + next_angle.clients.size # total number of clients + all_clients = next_angle.clients + +Constrained finds makes access security easier when ID comes from a web-app + + # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2 + thirty_seven_signals = next_angle.clients.find(2) + +Bi-directional associations thanks to the "belongs_to" macro + + thirty_seven_signals.firm.nil? # true + + +== Examples + +Active Record ships with a couple of examples that should give you a good feel for +operating usage. Be sure to edit the examples/shared_setup.rb file for your +own database before running the examples. Possibly also the table definition SQL in +the examples themselves. + +It's also highly recommended to have a look at the unit tests. Read more in link:files/RUNNING_UNIT_TESTS.html + + +== Philosophy + +Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is +object-relational mapping. The prime directive for this mapping has been to minimize +the amount of code needed to build a real-world domain model. This is made possible +by relying on a number of conventions that make it easy for Active Record to infer +complex relations and structures from a minimal amount of explicit direction. + +Convention over Configuration: +* No XML-files! +* Lots of reflection and run-time extension +* Magic is not inherently a bad word + +Admit the Database: +* Lets you drop down to SQL for odd cases and performance +* Doesn't attempt to duplicate or replace data definitions + + +== Download + +The latest version of Active Record can be found at + +* http://rubyforge.org/project/showfiles.php?group_id=182 + +Documentation can be found at + +* http://ar.rubyonrails.com + + +== Installation + +The prefered method of installing Active Record is through its GEM file. You'll need to have +RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have, +then use: + + % [sudo] gem install activerecord-1.10.0.gem + +You can also install Active Record the old-fashion way with the following command: + + % [sudo] ruby install.rb + +from its distribution directory. + + +== License + +Active Record is released under the MIT license. + + +== Support + +The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record +RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. + +For other information, feel free to ask on the ruby-talk mailing list +(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com. diff --git a/vendor/rails/activerecord/RUNNING_UNIT_TESTS b/vendor/rails/activerecord/RUNNING_UNIT_TESTS new file mode 100644 index 0000000..17d9b1a --- /dev/null +++ b/vendor/rails/activerecord/RUNNING_UNIT_TESTS @@ -0,0 +1,63 @@ +== Creating the test database + +The default names for the test databases are "activerecord_unittest" and +"activerecord_unittest2". If you want to use another database name then be sure +to update the connection adapter setups you want to test with in +test/connections//connection.rb. +When you have the database online, you can import the fixture tables with +the test/fixtures/db_definitions/*.sql files. + +Make sure that you create database objects with the same user that you specified in i +connection.rb otherwise (on Postgres, at least) tests for default values will fail. + +== Running with Rake + +The easiest way to run the unit tests is through Rake. The default task runs +the entire test suite for all the adapters. You can also run the suite on just +one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite, +or test_postgresql. For more information, checkout the full array of rake tasks with "rake -T" + +Rake can be found at http://rake.rubyforge.org + +== Running by hand + +Unit tests are located in test directory. If you only want to run a single test suite, +or don't want to bother with Rake, you can do so with something like: + + cd test; ruby -I "connections/native_mysql" base_test.rb + +That'll run the base suite using the MySQL-Ruby adapter. Change the adapter +and test suite name as needed. + +You can also run all the suites on a specific adapter with: + + cd test; all.sh "connections/native_mysql" + +== Faster tests + +If you are using a database that supports transactions, you can set the +"AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures. +This gives a very large speed boost. With rake: + + rake AR_TX_FIXTURES=yes + +Or, by hand: + + AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb + +== Testing with Oracle + +In order to allow for testing against Oracle using an "arunit" schema within an existing +Oracle database, the database name and tns connection string must be set in environment +variables prior to running the unit tests. + + $ export ARUNIT_DB_NAME=MYDB + $ export ARUNIT_DB=MYDB + +The ARUNIT_DB_NAME variable should be set to the name by which the database knows +itself, ie., what will be returned by the query: + + select sys_context('userenv','db_name') db from dual + +And the ARUNIT_DB variable should be set to the tns connection string. + diff --git a/vendor/rails/activerecord/Rakefile b/vendor/rails/activerecord/Rakefile new file mode 100755 index 0000000..c54731e --- /dev/null +++ b/vendor/rails/activerecord/Rakefile @@ -0,0 +1,226 @@ +require 'rubygems' +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/contrib/rubyforgepublisher' +require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version') + +PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' +PKG_NAME = 'activerecord' +PKG_VERSION = ActiveRecord::VERSION::STRING + PKG_BUILD +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" + +RELEASE_NAME = "REL #{PKG_VERSION}" + +RUBY_FORGE_PROJECT = "activerecord" +RUBY_FORGE_USER = "webster132" + +PKG_FILES = FileList[ + "lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile" +].exclude(/\bCVS\b|~$/) + + +desc "Default Task" +task :default => [ :test_mysql, :test_sqlite, :test_postgresql ] + +# Run the unit tests + +for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase frontbase ) + Rake::TestTask.new("test_#{adapter}") { |t| + t.libs << "test" << "test/connections/native_#{adapter}" + if adapter =~ /^sqlserver/ + t.pattern = "test/*_test{,_sqlserver}.rb" + else + t.pattern = "test/*_test{,_#{adapter}}.rb" + end + t.verbose = true + } +end + +SCHEMA_PATH = File.join(File.dirname(__FILE__), *%w(test fixtures db_definitions)) + +desc 'Build the MySQL test databases' +task :build_mysql_databases do + %x( mysqladmin create activerecord_unittest ) + %x( mysqladmin create activerecord_unittest2 ) + %x( mysql -e "grant all on activerecord_unittest.* to rails@localhost" ) + %x( mysql -e "grant all on activerecord_unittest2.* to rails@localhost" ) + %x( mysql activerecord_unittest < #{File.join(SCHEMA_PATH, 'mysql.sql')} ) + %x( mysql activerecord_unittest < #{File.join(SCHEMA_PATH, 'mysql2.sql')} ) +end + +desc 'Drop the MySQL test databases' +task :drop_mysql_databases do + %x( mysqladmin -f drop activerecord_unittest ) + %x( mysqladmin -f drop activerecord_unittest2 ) +end + +desc 'Rebuild the MySQL test databases' +task :rebuild_mysql_databases => [:drop_mysql_databases, :build_mysql_databases] + +desc 'Build the PostgreSQL test databases' +task :build_postgresql_databases do + %x( createdb -U postgres activerecord_unittest ) + %x( createdb -U postgres activerecord_unittest2 ) + %x( psql activerecord_unittest -f #{File.join(SCHEMA_PATH, 'postgresql.sql')} postgres ) + %x( psql activerecord_unittest2 -f #{File.join(SCHEMA_PATH, 'postgresql2.sql')} postgres ) +end + +desc 'Drop the PostgreSQL test databases' +task :drop_postgresql_databases do + %x( dropdb -U postgres activerecord_unittest ) + %x( dropdb -U postgres activerecord_unittest2 ) +end + +desc 'Rebuild the PostgreSQL test databases' +task :rebuild_postgresql_databases => [:drop_postgresql_databases, :build_postgresql_databases] + +desc 'Build the FrontBase test databases' +task :build_frontbase_databases => :rebuild_frontbase_databases + +desc 'Rebuild the FrontBase test databases' +task :rebuild_frontbase_databases do + build_frontbase_database = Proc.new do |db_name, sql_definition_file| + %( + STOP DATABASE #{db_name}; + DELETE DATABASE #{db_name}; + CREATE DATABASE #{db_name}; + + CONNECT TO #{db_name} AS SESSION_NAME USER _SYSTEM; + SET COMMIT FALSE; + + CREATE USER RAILS; + CREATE SCHEMA RAILS AUTHORIZATION RAILS; + COMMIT; + + SET SESSION AUTHORIZATION RAILS; + SCRIPT '#{sql_definition_file}'; + + COMMIT; + + DISCONNECT ALL; + ) + end + create_activerecord_unittest = build_frontbase_database['activerecord_unittest', File.join(SCHEMA_PATH, 'frontbase.sql')] + create_activerecord_unittest2 = build_frontbase_database['activerecord_unittest2', File.join(SCHEMA_PATH, 'frontbase2.sql')] + execute_frontbase_sql = Proc.new do |sql| + system(<<-SHELL) + /Library/FrontBase/bin/sql92 <<-SQL + #{sql} + SQL + SHELL + end + execute_frontbase_sql[create_activerecord_unittest] + execute_frontbase_sql[create_activerecord_unittest2] +end + +# Generate the RDoc documentation + +Rake::RDocTask.new { |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "Active Record -- Object-relation mapping put on rails" + rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG') + rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.rdoc_files.exclude('lib/active_record/vendor/*') + rdoc.rdoc_files.include('dev-utils/*.rb') +} + +# Enhance rdoc task to copy referenced images also +task :rdoc do + FileUtils.mkdir_p "doc/files/examples/" + FileUtils.copy "examples/associations.png", "doc/files/examples/associations.png" +end + + +# Create compressed packages + +dist_dirs = [ "lib", "test", "examples", "dev-utils" ] + +spec = Gem::Specification.new do |s| + s.name = PKG_NAME + s.version = PKG_VERSION + s.summary = "Implements the ActiveRecord pattern for ORM." + s.description = %q{Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.} + + s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG" ] + dist_dirs.each do |dir| + s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } + end + + s.add_dependency('activesupport', '= 1.3.1' + PKG_BUILD) + + s.files.delete "test/fixtures/fixture_database.sqlite" + s.files.delete "test/fixtures/fixture_database_2.sqlite" + s.files.delete "test/fixtures/fixture_database.sqlite3" + s.files.delete "test/fixtures/fixture_database_2.sqlite3" + s.require_path = 'lib' + s.autorequire = 'active_record' + + s.has_rdoc = true + s.extra_rdoc_files = %w( README ) + s.rdoc_options.concat ['--main', 'README'] + + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://www.rubyonrails.org" + s.rubyforge_project = "activerecord" +end + +Rake::GemPackageTask.new(spec) do |p| + p.gem_spec = spec + p.need_tar = true + p.need_zip = true +end + +task :lines do + lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 + + for file_name in FileList["lib/active_record/**/*.rb"] + next if file_name =~ /vendor/ + f = File.open(file_name) + + while line = f.gets + lines += 1 + next if line =~ /^\s*$/ + next if line =~ /^\s*#/ + codelines += 1 + end + puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" + + total_lines += lines + total_codelines += codelines + + lines, codelines = 0, 0 + end + + puts "Total: Lines #{total_lines}, LOC #{total_codelines}" +end + + +# Publishing ------------------------------------------------------ + +desc "Publish the beta gem" +task :pgem => [:package] do + Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` +end + +desc "Publish the API documentation" +task :pdoc => [:rdoc] do + Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload +end + +desc "Publish the release files to RubyForge." +task :release => [ :package ] do + `rubyforge login` + + for ext in %w( gem tgz zip ) + release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" + puts release_command + system(release_command) + end +end diff --git a/vendor/rails/activerecord/benchmarks/benchmark.rb b/vendor/rails/activerecord/benchmarks/benchmark.rb new file mode 100644 index 0000000..241d915 --- /dev/null +++ b/vendor/rails/activerecord/benchmarks/benchmark.rb @@ -0,0 +1,26 @@ +$:.unshift(File.dirname(__FILE__) + '/../lib') +if ARGV[2] + require 'rubygems' + require_gem 'activerecord', ARGV[2] +else + require 'active_record' +end + +ActiveRecord::Base.establish_connection(:adapter => "mysql", :database => "basecamp") + +class Post < ActiveRecord::Base; end + +require 'benchmark' + +RUNS = ARGV[0].to_i +if ARGV[1] == "profile" then require 'profile' end + +runtime = Benchmark::measure { + RUNS.times { + Post.find_all(nil,nil,100).each { |p| p.title } + } +} + +puts "Runs: #{RUNS}" +puts "Avg. runtime: #{runtime.real / RUNS}" +puts "Requests/second: #{RUNS / runtime.real}" diff --git a/vendor/rails/activerecord/benchmarks/mysql_benchmark.rb b/vendor/rails/activerecord/benchmarks/mysql_benchmark.rb new file mode 100644 index 0000000..2f9e0e6 --- /dev/null +++ b/vendor/rails/activerecord/benchmarks/mysql_benchmark.rb @@ -0,0 +1,19 @@ +require 'mysql' + +conn = Mysql::real_connect("localhost", "root", "", "basecamp") + +require 'benchmark' + +require 'profile' if ARGV[1] == "profile" +RUNS = ARGV[0].to_i + +runtime = Benchmark::measure { + RUNS.times { + result = conn.query("SELECT * FROM posts LIMIT 100") + result.each_hash { |p| p["title"] } + } +} + +puts "Runs: #{RUNS}" +puts "Avg. runtime: #{runtime.real / RUNS}" +puts "Requests/second: #{RUNS / runtime.real}" \ No newline at end of file diff --git a/vendor/rails/activerecord/examples/associations.png b/vendor/rails/activerecord/examples/associations.png new file mode 100644 index 0000000..661c7a8 Binary files /dev/null and b/vendor/rails/activerecord/examples/associations.png differ diff --git a/vendor/rails/activerecord/examples/associations.rb b/vendor/rails/activerecord/examples/associations.rb new file mode 100644 index 0000000..b0df367 --- /dev/null +++ b/vendor/rails/activerecord/examples/associations.rb @@ -0,0 +1,87 @@ +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) \ No newline at end of file diff --git a/vendor/rails/activerecord/examples/shared_setup.rb b/vendor/rails/activerecord/examples/shared_setup.rb new file mode 100644 index 0000000..6ede4b1 --- /dev/null +++ b/vendor/rails/activerecord/examples/shared_setup.rb @@ -0,0 +1,15 @@ +# 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" +) diff --git a/vendor/rails/activerecord/examples/validation.rb b/vendor/rails/activerecord/examples/validation.rb new file mode 100644 index 0000000..e6a448a --- /dev/null +++ b/vendor/rails/activerecord/examples/validation.rb @@ -0,0 +1,85 @@ +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") +end \ No newline at end of file diff --git a/vendor/rails/activerecord/install.rb b/vendor/rails/activerecord/install.rb new file mode 100644 index 0000000..592c4b9 --- /dev/null +++ b/vendor/rails/activerecord/install.rb @@ -0,0 +1,30 @@ +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("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 +} diff --git a/vendor/rails/activerecord/lib/active_record.rb b/vendor/rails/activerecord/lib/active_record.rb new file mode 100755 index 0000000..34ab69f --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record.rb @@ -0,0 +1,85 @@ +#-- +# 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/optimistic' +require 'active_record/locking/pessimistic' +require 'active_record/migration' +require 'active_record/schema' +require 'active_record/calculations' +require 'active_record/xml_serialization' +require 'active_record/attribute_methods' + +ActiveRecord::Base.class_eval do + include ActiveRecord::Validations + include ActiveRecord::Locking::Optimistic + include ActiveRecord::Locking::Pessimistic + 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 + include ActiveRecord::XmlSerialization + include ActiveRecord::AttributeMethods +end + +unless defined?(RAILS_CONNECTION_ADAPTERS) + RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase frontbase ) +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' diff --git a/vendor/rails/activerecord/lib/active_record/acts/list.rb b/vendor/rails/activerecord/lib/active_record/acts/list.rb new file mode 100644 index 0000000..87bb128 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/acts/list.rb @@ -0,0 +1,244 @@ +module ActiveRecord + module Acts #:nodoc: + module List #:nodoc: + def self.included(base) + 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: acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0' + 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 acts_as_list specified. Each method works + # by assuming the object to be the item in the list, so chapter.move_lower would move that chapter + # lower in the list of all chapters. Likewise, chapter.first? 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 + + # Swap positions with the next lower item, if one exists. + def move_lower + return unless lower_item + + acts_as_list_class.transaction do + lower_item.decrement_position + increment_position + end + end + + # Swap positions with the next higher item, if one exists. + def move_higher + return unless higher_item + + acts_as_list_class.transaction do + higher_item.increment_position + decrement_position + end + end + + # Move to the bottom of the list. If the item is already in the list, the items below it have their + # position adjusted accordingly. + def move_to_bottom + return unless in_list? + acts_as_list_class.transaction do + decrement_positions_on_lower_items + assume_bottom_position + end + end + + # Move to the top of the list. If the item is already in the list, the items above it have their + # position adjusted accordingly. + 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 + + # Increase the position of this item without adjusting the rest of the list. + def increment_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i + 1 + end + + # Decrease the position of this item without adjusting the rest of the list. + def decrement_position + return unless in_list? + update_attribute position_column, self.send(position_column).to_i - 1 + end + + # Return true if this object is the first in the list. + def first? + return false unless in_list? + self.send(position_column) == 1 + end + + # Return true if this object is the last in the list. + def last? + return false unless in_list? + self.send(position_column) == bottom_position_in_list + end + + # Return the next higher item in the list. + 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 + + # Return the next lower item in the list. + 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 diff --git a/vendor/rails/activerecord/lib/active_record/acts/nested_set.rb b/vendor/rails/activerecord/lib/active_record/acts/nested_set.rb new file mode 100644 index 0000000..9d5bcf4 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/acts/nested_set.rb @@ -0,0 +1,211 @@ +module ActiveRecord + module Acts #:nodoc: + module NestedSet #:nodoc: + def self.included(base) + 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: acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0' + 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 diff --git a/vendor/rails/activerecord/lib/active_record/acts/tree.rb b/vendor/rails/activerecord/lib/active_record/acts/tree.rb new file mode 100644 index 0000000..b8654ce --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/acts/tree.rb @@ -0,0 +1,89 @@ +module ActiveRecord + module Acts #:nodoc: + module Tree #:nodoc: + def self.included(base) + 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: + # + # * foreign_key - specifies the column name to use for tracking of the tree (default: parent_id) + # * order - makes it possible to sort the children according to this SQL snippet. + # * counter_cache - 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 diff --git a/vendor/rails/activerecord/lib/active_record/aggregations.rb b/vendor/rails/activerecord/lib/active_record/aggregations.rb new file mode 100644 index 0000000..a137a11 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/aggregations.rb @@ -0,0 +1,191 @@ +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: + # * Customer#balance, Customer#balance=(money) + # * Customer#address, Customer#address=(address) + # + # 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 reader and writer methods for manipulating a value object: + # composed_of :address adds address and address=(new_address) methods. + # + # Options are: + # * :class_name - specify the class name of the association. Use it only if that name can't be inferred + # from the part id. So composed_of :address 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. + # * :mapping - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name + # to a constructor parameter on the value class. + # * :allow_nil - specifies that the aggregate object will not be instantiated when all mapped + # attributes are nil. Setting the aggregate class to nil has the effect of writing nil to all mapped attributes. + # This defaults to false. + # + # 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 + # composed_of :gps_location, :allow_nil => true + # + def composed_of(part_id, options = {}) + options.assert_valid_keys(:class_name, :mapping, :allow_nil) + + name = part_id.id2name + class_name = options[:class_name] || name.camelize + mapping = options[:mapping] || [ name, name ] + allow_nil = options[:allow_nil] || false + + reader_method(name, class_name, mapping, allow_nil) + writer_method(name, class_name, mapping, allow_nil) + + create_reflection(:composed_of, part_id, options, self) + end + + private + def reader_method(name, class_name, mapping, allow_nil) + mapping = (Array === mapping.first ? mapping : [ mapping ]) + + allow_nil_condition = if allow_nil + mapping.collect { |pair| "!read_attribute(\"#{pair.first}\").nil?"}.join(" && ") + else + "true" + end + + module_eval <<-end_eval + def #{name}(force_reload = false) + if (@#{name}.nil? || force_reload) && #{allow_nil_condition} + @#{name} = #{class_name}.new(#{mapping.collect { |pair| "read_attribute(\"#{pair.first}\")"}.join(", ")}) + end + return @#{name} + end + end_eval + end + + def writer_method(name, class_name, mapping, allow_nil) + mapping = (Array === mapping.first ? mapping : [ mapping ]) + + if allow_nil + module_eval <<-end_eval + def #{name}=(part) + if part.nil? + #{mapping.collect { |pair| "@attributes[\"#{pair.first}\"] = nil" }.join("\n")} + else + @#{name} = part.freeze + #{mapping.collect { |pair| "@attributes[\"#{pair.first}\"] = part.#{pair.last}" }.join("\n")} + end + end + end_eval + else + module_eval <<-end_eval + def #{name}=(part) + @#{name} = part.freeze + #{mapping.collect{ |pair| "@attributes[\"#{pair.first}\"] = part.#{pair.last}" }.join("\n")} + end + end_eval + end + end + end + end +end diff --git a/vendor/rails/activerecord/lib/active_record/associations.rb b/vendor/rails/activerecord/lib/active_record/associations.rb new file mode 100755 index 0000000..c803560 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations.rb @@ -0,0 +1,1536 @@ +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(owner_class_name, reflection) + super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}") + end + end + + class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc: + def initialize(owner_class_name, reflection, source_reflection) + super("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) + 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 } + super("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 => '. Is it one of #{source_associations.to_sentence :connector => 'or'}?") + end + end + + class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc + def initialize(reflection) + through_reflection = reflection.through_reflection + source_reflection = reflection.source_reflection + super("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) + super("Can not eagerly load the polymorphic association #{reflection.name.inspect}") + end + end + + class ReadOnlyAssociation < ActiveRecordError #:nodoc: + def initialize(reflection) + super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.") + end + end + + module Associations # :nodoc: + def self.included(base) + 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: + # * Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil? + # * Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?, + # * Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone), + # Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find_all(conditions), + # Project#milestones.build, Project#milestones.create + # * Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1), + # Project#categories.delete(category1) + # + # == 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 User < ActiveRecord::Base + # # I reference an account. + # belongs_to :account + # end + # + # class Account < ActiveRecord::Base + # # One user references me. + # has_one :user + # end + # + # The tables for these classes could look something like: + # + # CREATE TABLE users ( + # id int(11) NOT NULL auto_increment, + # account_id int(11) default NULL, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # CREATE TABLE accounts ( + # id int(11) NOT NULL auto_increment, + # 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 + # + # If you need to use multiple named extension modules, you can specify an array of modules with the :extend option. + # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede + # those earlier in the array. Example: + # + # class Account < ActiveRecord::Base + # has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension] + # end + # + # Some extensions can only be made to work with knowledge of the association proxy's internals. + # Extensions can access relevant state using accessors on the association proxy: + # + # * +proxy_owner+ - Returns the object the association is part of. + # * +proxy_reflection+ - Returns the reflection object that describes the association. + # * +proxy_target+ - Returns the associated object for belongs_to and has_one, or the collection of associated objects for has_many and has_and_belongs_to_many. + # + # === 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 has_and_belongs_to_many 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 :as 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. Because eager loading generates the SELECT statement too, + # the :select option is ignored. + # + # 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 MyApplication::Business::Company.find(firm.id). 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 :class_name, 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 + # has_many :clients would add among others clients.empty?. + # * collection(force_reload = false) - returns an array of all the associated objects. + # An empty array is returned if none are found. + # * collection<<(object, ...) - adds one or more objects to the collection by setting their foreign keys to the collection's primary key. + # * collection.delete(object, ...) - 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. + # * collection=objects - replaces the collections content by deleting and adding objects as appropriate. + # * collection_singular_ids=ids - replace the collection by the objects identified by the primary keys in +ids+ + # * collection.clear - removes every object from the collection. This destroys the associated objects if they + # are :dependent, deletes them directly from the database if they are :dependent => :delete_all, + # and sets their foreign keys to NULL otherwise. + # * collection.empty? - returns true if there are no associated objects. + # * collection.size - returns the number of associated objects. + # * collection.find - finds an associated object according to the same rules as Base.find. + # * collection.build(attributes = {}) - 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! + # * collection.create(attributes = {}) - 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 has_many :clients, which will add: + # * Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}") + # * Firm#clients<< + # * Firm#clients.delete + # * Firm#clients= + # * Firm#client_ids= + # * Firm#clients.clear + # * Firm#clients.empty? (similar to firm.clients.size == 0) + # * Firm#clients.size (similar to Client.count "firm_id = #{id}") + # * Firm#clients.find (similar to Client.find(id, :conditions => "firm_id = #{id}")) + # * Firm#clients.build (similar to Client.new("firm_id" => id)) + # * Firm#clients.create (similar to c = Client.new("firm_id" => id); c.save; c) + # The declaration can also include an options hash to specialize the behavior of the association. + # + # Options are: + # * :class_name - specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_many :products 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. + # * :conditions - 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%'". + # * :order - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, + # such as "last_name, first_name DESC" + # * :group - specify the attribute by which the associated objects are returned as a "GROUP BY" sql fragment, + # such as "category" + # * :foreign_key - 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. + # * :dependent - 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. + # * :exclusively_dependent - 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. + # * :finder_sql - 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. + # * :counter_sql - 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. + # * :extend - specify a named module for extending the proxy, see "Association extensions". + # * :include - specify second-order associations that should be eager loaded when the collection is loaded. + # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :limit: An integer determining the limit on the number of rows that should be returned. + # * :offset: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # * :select: 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. + # * :as: Specifies a polymorphic interface (See #belongs_to). + # * :through: Specifies a Join Model to perform the query through. Options for :class_name and :foreign_key + # are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to + # or has_many association. + # * :source: Specifies the source association name used by has_many :through queries. Only use it if the name cannot be + # inferred from the association. has_many :subscribers, :through => :subscriptions will look for either +:subscribers+ or + # +:subscriber+ on +Subscription+, unless a +:source+ is given. + # * :uniq - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through. + # + # 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 + # has_one :manager would add among others manager.nil?. + # * association(force_reload = false) - returns the associated object. Nil is returned if none is found. + # * association=(associate) - assigns the associate object, extracts the primary key, sets it as the foreign key, + # and saves the associate object. + # * association.nil? - returns true if there is no associated object. + # * build_association(attributes = {}) - 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. + # * create_association(attributes = {}) - 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 has_one :beneficiary, which will add: + # * Account#beneficiary (similar to Beneficiary.find(:first, :conditions => "account_id = #{id}")) + # * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save) + # * Account#beneficiary.nil? + # * Account#build_beneficiary (similar to Beneficiary.new("account_id" => id)) + # * Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b) + # + # The declaration can also include an options hash to specialize the behavior of the association. + # + # Options are: + # * :class_name - specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_one :manager 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. + # * :conditions - specify the conditions that the associated object must meet in order to be included as a "WHERE" + # sql fragment, such as "rank = 5". + # * :order - 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" + # * :dependent - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also, + # association is assigned. + # * :foreign_key - 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. + # * :include - specify second-order associations that should be eager loaded when this object is loaded. + # * :as: Specifies a polymorphic interface (See #belongs_to). + # + # 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'" + # has_one :attachment, :as => :attachable + 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}") + if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id) + 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 + # belongs_to :author would add among others author.nil?. + # * association(force_reload = false) - returns the associated object. Nil is returned if none is found. + # * association=(associate) - assigns the associate object, extracts the primary key, and sets it as the foreign key. + # * association.nil? - returns true if there is no associated object. + # * build_association(attributes = {}) - 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. + # * create_association(attributes = {}) - 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 belongs_to :author, which will add: + # * Post#author (similar to Author.find(author_id)) + # * Post#author=(author) (similar to post.author_id = author.id) + # * Post#author? (similar to post.author == some_author) + # * Post#author.nil? + # * Post#build_author (similar to post.author = Author.new) + # * Post#create_author (similar to post.author = Author.new; post.author.save; post.author) + # The declaration can also include an options hash to specialize the behavior of the association. + # + # Options are: + # * :class_name - specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_one :author 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. + # * :conditions - specify the conditions that the associated object must meet in order to be included as a "WHERE" + # sql fragment, such as "authorized = 1". + # * :order - 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" + # * :foreign_key - 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. + # * :counter_cache - 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., :counter_cache => :my_custom_counter.) + # * :include - specify second-order associations that should be eager loaded when this object is loaded. + # * :polymorphic - 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 && association.target + 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 + # has_and_belongs_to_many :categories would add among others categories.empty?. + # * collection(force_reload = false) - returns an array of all the associated objects. + # An empty array is returned if none is found. + # * collection<<(object, ...) - 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). + # * collection.push_with_attributes(object, join_attributes) - adds one to the collection by creating an association in the join table that + # also holds the attributes from join_attributes (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. + # * collection.delete(object, ...) - removes one or more objects from the collection by removing their associations from the join table. + # This does not destroy the objects. + # * collection=objects - replaces the collections content by deleting and adding objects as appropriate. + # * collection_singular_ids=ids - replace the collection by the objects identified by the primary keys in +ids+ + # * collection.clear - removes every object from the collection. This does not destroy the objects. + # * collection.empty? - returns true if there are no associated objects. + # * collection.size - returns the number of associated objects. + # * collection.find(id) - finds an associated object responding to the +id+ and that + # meets the condition that it has to be associated with this object. + # * collection.build(attributes = {}) - returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object through the join table but has not yet been saved. + # * collection.create(attributes = {}) - returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object through the join table and that has already been saved (if it passed the validation). + # + # Example: An Developer class declares has_and_belongs_to_many :projects, which will add: + # * Developer#projects + # * Developer#projects<< + # * Developer#projects.push_with_attributes + # * Developer#projects.delete + # * Developer#projects= + # * Developer#project_ids= + # * Developer#projects.clear + # * Developer#projects.empty? + # * Developer#projects.size + # * Developer#projects.find(id) + # * Developer#projects.build (similar to Project.new("project_id" => id)) + # * Developer#projects.create (similar to c = Project.new("project_id" => id); c.save; c) + # The declaration may include an options hash to specialize the behavior of the association. + # + # Options are: + # * :class_name - specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_and_belongs_to_many :projects 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. + # * :join_table - 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. + # * :foreign_key - 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. + # * :association_foreign_key - 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. + # * :conditions - specify the conditions that the associated object must meet in order to be included as a "WHERE" + # sql fragment, such as "authorized = 1". + # * :order - specify the order in which the associated objects are returned as a "ORDER BY" sql fragment, such as "last_name, first_name DESC" + # * :uniq - if set to true, duplicate associated objects will be ignored by accessors and query methods + # * :finder_sql - overwrite the default generated SQL used to fetch the association with a manual one + # * :delete_sql - overwrite the default generated SQL used to remove links between the associated + # classes with a manual one + # * :insert_sql - overwrite the default generated SQL used to add links between the associated classes + # with a manual one + # * :extend - anonymous module for extending the proxy, see "Association extensions". + # * :include - specify second-order associations that should be eager loaded when the collection is loaded. + # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :limit: An integer determining the limit on the number of rows that should be returned. + # * :offset: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # * :select: 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 + if retval.nil? and association_proxy_class == BelongsToAssociation + instance_variable_set("@#{reflection.name}", nil) + return nil + end + instance_variable_set("@#{reflection.name}", association) + end + + association.target.nil? ? nil : 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| + # Loads proxy class instance (defined in collection_reader_method) if not already loaded + association = send(reflection.name) + 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 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, + :uniq, + :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, + :uniq, + :finder_sql, :delete_sql, :insert_sql, + :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_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) && ((scope && scope[:limit]) || options[:limit]) + + sql << "GROUP BY #{options[:group]} " if options[:group] + 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}_for_#{association_name}" + 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 # :nodoc: + 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 # :nodoc: + 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 # :nodoc: + 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 #.tr('.', '_') # start with the table name, sub out any .'s + @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.demodulize)] 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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb new file mode 100644 index 0000000..ec7af79 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb @@ -0,0 +1,195 @@ +require 'set' + +module ActiveRecord + module Associations + class AssociationCollection < AssociationProxy #:nodoc: + def to_ary + load_target + @target.to_ary + end + + def reset + 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) + reset_target! + end + + # Calculate sum using SQL, not Enumerable + def sum(*args, &block) + calculate(:sum, *args, &block) + 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 + + reset_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? && !@reflection.options[:uniq] + @target.size + elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array) + unsaved_records = Array(@target.detect { |r| r.new_record? }) + unsaved_records.size + count_records + 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) + seen = Set.new + collection.inject([]) do |kept, record| + unless seen.include?(record.id) + kept << record + seen << record.id + end + kept + end + 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 + + protected + def reset_target! + @target = Array.new + end + + def find_target + records = + if @reflection.options[:finder_sql] + @reflection.klass.find_by_sql(@finder_sql) + else + find(:all) + end + + @reflection.options[:uniq] ? uniq(records) : records + 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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb b/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb new file mode 100644 index 0000000..a94dacc --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb @@ -0,0 +1,152 @@ +module ActiveRecord + module Associations + class AssociationProxy #:nodoc: + attr_reader :reflection + alias_method :proxy_respond_to?, :respond_to? + alias_method :proxy_extend, :extend + delegate :to_param, :to => :proxy_target + instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ } + + def initialize(owner, reflection) + @owner, @reflection = owner, reflection + Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) } + reset + end + + def proxy_owner + @owner + end + + def proxy_reflection + @reflection + end + + def proxy_target + @target + 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 unless loaded? + rescue ActiveRecord::RecordNotFound + reset + end + end + + loaded + 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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb new file mode 100644 index 0000000..1752678 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -0,0 +1,56 @@ +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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb new file mode 100644 index 0000000..9549b95 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -0,0 +1,50 @@ +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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb new file mode 100644 index 0000000..fc1de92 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -0,0 +1,169 @@ +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 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) + insert_record(record) unless @owner.new_record? + record + end + 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 + + # Deprecated as of Rails 1.2. If your associations require attributes + # you should be using has_many :through + 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 + deprecate :push_with_attributes + + alias :concat_with_attributes :push_with_attributes + + 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 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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb new file mode 100644 index 0000000..e79fa85 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb @@ -0,0 +1,207 @@ +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 + record = @reflection.klass.new(attributes) + set_belongs_to_association_for(record) + + @target ||= [] unless loaded? + @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(*args) + 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 + column_name, options = @reflection.klass.send(:construct_count_options_from_legacy_args, *args) + options[:conditions] = options[:conditions].nil? ? + @finder_sql : + @finder_sql + " AND (#{sanitize_sql(options[:conditions])})" + options[:include] = @reflection.options[:include] + + @reflection.klass.count(column_name, options) + 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 load_target + if !@owner.new_record? || foreign_key_present + begin + if !loaded? + if @target.is_a?(Array) && @target.any? + @target = (find_target + @target).uniq + else + @target = find_target + end + end + rescue ActiveRecord::RecordNotFound + reset + end + end + + loaded if target + target + 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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb new file mode 100644 index 0000000..8f4ac10 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -0,0 +1,161 @@ +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 + + def <<(*args) + raise ActiveRecord::ReadOnlyAssociation.new(@reflection) + end + + [:push, :concat, :create, :build].each do |method| + alias_method method, :<< + end + + # Calculate sum using SQL, not Enumerable + def sum(*args, &block) + calculate(:sum, *args, &block) + 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 + records = @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] + ) + + @reflection.options[:uniq] ? records.to_set.to_a : records + 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 diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb new file mode 100644 index 0000000..e881d49 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb @@ -0,0 +1,80 @@ +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 diff --git a/vendor/rails/activerecord/lib/active_record/attribute_methods.rb b/vendor/rails/activerecord/lib/active_record/attribute_methods.rb new file mode 100644 index 0000000..adc6eb6 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/attribute_methods.rb @@ -0,0 +1,75 @@ +module ActiveRecord + module AttributeMethods #:nodoc: + DEFAULT_SUFFIXES = %w(= ? _before_type_cast) + + def self.included(base) + base.extend ClassMethods + base.attribute_method_suffix *DEFAULT_SUFFIXES + end + + # Declare and check for suffixed attribute methods. + module ClassMethods + # Declare a method available for all attributes with the given suffix. + # Uses method_missing and respond_to? to rewrite the method + # #{attr}#{suffix}(*args, &block) + # to + # attribute#{suffix}(#{attr}, *args, &block) + # + # An attribute#{suffix} instance method must exist and accept at least + # the attr argument. + # + # For example: + # class Person < ActiveRecord::Base + # attribute_method_suffix '_changed?' + # + # private + # def attribute_changed?(attr) + # ... + # end + # end + # + # person = Person.find(1) + # person.name_changed? # => false + # person.name = 'Hubert' + # person.name_changed? # => true + def attribute_method_suffix(*suffixes) + attribute_method_suffixes.concat suffixes + rebuild_attribute_method_regexp + end + + # Returns MatchData if method_name is an attribute method. + def match_attribute_method?(method_name) + rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp + @@attribute_method_regexp.match(method_name) + end + + private + # Suffixes a, ?, c become regexp /(a|\?|c)$/ + def rebuild_attribute_method_regexp + suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) } + @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze + end + + # Default to =, ?, _before_type_cast + def attribute_method_suffixes + @@attribute_method_suffixes ||= [] + end + end + + private + # Handle *? for method_missing. + def attribute?(attribute_name) + query_attribute(attribute_name) + end + + # Handle *= for method_missing. + def attribute=(attribute_name, value) + write_attribute(attribute_name, value) + end + + # Handle *_before_type_cast for method_missing. + def attribute_before_type_cast(attribute_name) + read_attribute_before_type_cast(attribute_name) + end + end +end diff --git a/vendor/rails/activerecord/lib/active_record/base.rb b/vendor/rails/activerecord/lib/active_record/base.rb new file mode 100755 index 0000000..5e9d18e --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/base.rb @@ -0,0 +1,2080 @@ +require 'base64' +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, array, or hash 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. The hash form works much like the array form, except + # only equality is possible. Examples: + # + # class 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 + # + # def self.authenticate_safely_simply(user_name, password) + # find(:first, :conditions => { :user_name => user_name, :password => password }) + # end + # end + # + # The authenticate_unsafely method inserts the parameters directly into the query and is thus susceptible to SQL-injection + # attacks if the user_name and +password+ parameters come directly from a HTTP request. The authenticate_safely and + # authenticate_safely_simply both will sanitize the user_name 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' } + # ]) + # + # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND + # operator. For instance: + # + # Student.find(:all, :conditions => { :first_name => "Harvey", :status => 1 }) + # Student.find(:all, :conditions => params[:student]) + # + # + # == 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 _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 find_by_ or find_all_by_, 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 + # Person.find(:first, ["user_name = ?", user_name]), you just do Person.find_by_user_name(user_name). + # And instead of writing Person.find(:all, ["last_name = ?", last_name]), you just do Person.find_all_by_last_name(last_name). + # + # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like + # Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing + # Person.find(:first, ["user_name = ? AND password = ?", user_name, password]), you just do + # Person.find_by_user_name_and_password(user_name, password). + # + # 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 Payment.find_all_by_amount(50, :order => "created_on"). + # + # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with + # find_or_create_by_ 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") + # + # Use the find_or_initialize_by_ finder if you want to return a new record without saving it first. Example: + # + # # No 'Winter' tag exists + # winter = Tag.find_or_initialize_by_name("Winter") + # winter.new_record? # true + # + # == 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 Base.inheritance_column). 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 establish_connection didn't include a + # :adapter key. + # * +AdapterNotFound+ -- the :adapter key used in establish_connection 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 establish_connection 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 + reset_subclasses + 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: + # + # * :conditions: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro. + # * :order: An SQL fragment like "created_at DESC, name". + # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :limit: An integer determining the limit on the number of rows that should be returned. + # * :offset: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. + # * :joins: 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. + # * :include: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer + # to already defined associations. See eager loading under Associations. + # * :select: 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. + # * :readonly: Mark the returned records read-only so they cannot be saved or updated. + # * :lock: An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE". + # :lock => true gives connection's default exclusive lock, usually "FOR UPDATE". + # + # 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") + # + # Example for find with a lock. Imagine two concurrent transactions: + # each will read person.visits == 2, add 1 to it, and save, resulting + # in two saves of person.visits = 3. By locking the row, the second + # transaction has to wait until the first is finished; we get the + # expected person.visits == 4. + # Person.transaction do + # person = Person.find(1, :lock => true) + # person.visits += 1 + # person.save! + # end + 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. + # You can also pass a set of SQL conditions. + # Example: + # Person.exists?(5) + # Person.exists?('5') + # Person.exists?(:name => "David") + # Person.exists?(['name LIKE ?', "%#{query}%"]) + def exists?(id_or_conditions) + !find(:first, :conditions => expand_id_conditions(id_or_conditions)).nil? + rescue ActiveRecord::ActiveRecordError + 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 DiscussionBoard.increment_counter("post_count", + # discussion_board_id) 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 new(attributes) and + # attributes=(attributes). 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 + # new(attributes) and attributes=(attributes). 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(value, column = nil) #:nodoc: + connection.quote(value,column) + end + + # Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to connection.quote. + 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 log_level, 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 use_silence 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 :conditions, :joins, + # :include, :offset, :limit, and :readonly 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 with_exclusive_scope 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, :order, :readonly, :lock ]) + 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} = #{quote(id,columns_hash[primary_key])}#{conditions}" + + # Use find_every(options).first since the primary key condition + # already ensures we have a single record. Using find_initial adds + # a superfluous :limit => 1. + if result = find_every(options).first + 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| quote(id,columns_hash[primary_key]) }.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] + + add_order!(sql, options[:order]) + add_limit!(sql, options, scope) + add_lock!(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)).uniq + end + + # Object#to_a is deprecated, though it does have the desired behavior + def safe_to_array(o) + case o + when NilClass + [] + when Array + o + else + [o] + end + end + + def add_order!(sql, order) + if order + sql << " ORDER BY #{order}" + sql << ", #{scope(:find, :order)}" if scoped?(:find, :order) + else + sql << " ORDER BY #{scope(:find, :order)}" if scoped?(:find, :order) + 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 + options = options.reverse_merge(:limit => scope[:limit], :offset => scope[:offset]) if scope + connection.add_limit_offset!(sql, options) + end + + # The optional scope argument is for the current :find scope. + # The :lock option has precedence over a scoped :lock. + def add_lock!(sql, options, scope = :auto) + scope = scope(:find) if :auto == :scope + options = options.reverse_merge(:lock => scope[:lock]) if scope + connection.add_lock!(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_(initialize|create)_by_([_a-zA-Z]\w*)/.match(method_id.to_s) + instantiator = determine_instantiator(match) + 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) || send(instantiator, 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 determine_instantiator(match) + match.captures.first == 'initialize' ? :new : :create + 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 + + # Interpret Array and Hash as conditions and anything else as an id. + def expand_id_conditions(id_or_conditions) + case id_or_conditions + when Array, Hash then id_or_conditions + else construct_conditions_from_arguments([primary_key], [id_or_conditions]) + 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, hash, or string of sql conditions and + #deals with them accordingly + # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" + # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'" + # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'" + def sanitize_sql(condition) + return sanitize_sql_array(condition) if condition.is_a?(Array) + return sanitize_sql_hash(condition) if condition.is_a?(Hash) + condition + end + + # Accepts a hash of conditions. The hash has each key/value or attribute/value pair + # sanitized and interpolated into the sql statement. + # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id= 4" + def sanitize_sql_hash(hash) + hash.collect { |attrib, value| + "#{table_name}.#{connection.quote_column_name(attrib)} = #{quote(value)}" + }.join(" AND ") + end + + # Accepts an array of conditions. 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_array(ary) + 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) + if value.respond_to?(:empty?) && value.empty? + connection.quote(nil) + else + value.map { |v| connection.quote(v) }.join(',') + end + 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, :lock ] + + 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. + def to_param + # We can't use alias_method here, because method 'id' optimizes itself on the fly. + (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes + end + + 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. + # The optional options argument is passed to find when reloading so you + # may do e.g. record.reload(:lock => true) to reload the same record with + # an exclusive row lock. + def reload(options = nil) + clear_aggregation_cache + clear_association_cache + @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes')) + self + end + + # Returns the value of the attribute identified by attr_name 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 attr_name 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 = self.class.match_attribute_method?(method.to_s) + 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 + + + private + def create_or_update + if new_record? then create else update end + true + end + + # Updates the associated record with values matching those of the instance attributes. + # Returns the number of affected rows. + 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" + ) + end + + # Creates a record with values matching those of the instance attributes + # and returns its id. + 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 + id + 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?(query_method_name = md.pre_match) and + method_name = query_method_name) + 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 = self.class.match_attribute_method?(method_name) + attribute_name, method_type = md.pre_match, md.to_s + if @attributes.include?(attribute_name) + __send__("attribute#{method_type}", attribute_name, *args, &block) + else + super + end + else + super + end + end + + # Returns the value of the attribute identified by attr_name 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 occurred 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 attr_name 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 ? (@@default_timezone == :utc ? klass.utc(*values) : 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 diff --git a/vendor/rails/activerecord/lib/active_record/calculations.rb b/vendor/rails/activerecord/lib/active_record/calculations.rb new file mode 100644 index 0000000..e0ac96c --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/calculations.rb @@ -0,0 +1,257 @@ +module ActiveRecord + module Calculations #:nodoc: + CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include] + 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: + # + # * :conditions: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro. + # * :joins: 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. + # * :include: 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. + # * :order: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations). + # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :select: 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. + # * :distinct: 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) + calculate(:count, *construct_count_options_from_legacy_args(*args)) + 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: + # * :conditions: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro. + # * :joins: 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. + # * :order: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations). + # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause. + # * :select: 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. + # * :distinct: 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 + catch :invalid_query do + if options[:group] + return execute_grouped_calculation(operation, column_name, column, options) + else + return execute_simple_calculation(operation, column_name, column, options) + end + end + 0 + end + + protected + def construct_count_options_from_legacy_args(*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 + [column_name, options] + end + + def construct_calculation_sql(operation, column_name, options) #:nodoc: + scope = scope(:find) + merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) + aggregate_alias = column_alias_for(operation, column_name) + use_workaround = !Base.connection.supports_count_distinct? && options[:distinct] && operation.to_s.downcase == 'count' + join_dependency = nil + + if merged_includes.any? && operation.to_s.downcase == 'count' + options[:distinct] = true + column_name = options[:select] || [table_name, primary_key] * '.' + end + + sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" + + # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. + sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround + + sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] + sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround + sql << " FROM #{table_name} " + if merged_includes.any? + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]) + sql << join_dependency.join_associations.collect{|join| join.association_join }.join + end + add_joins!(sql, options, scope) + add_conditions!(sql, options[:conditions], scope) + add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) + + if options[:group] + group_key = Base.connection.adapter_name == 'FrontBase' ? :group_alias : :group_field + sql << " GROUP BY #{options[group_key]} " + end + + if options[:group] && options[:having] + # FrontBase requires identifiers in the HAVING clause and chokes on function calls + if Base.connection.adapter_name == 'FrontBase' + options[:having].downcase! + options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias) + end + + sql << " HAVING #{options[:having]} " + end + + sql << " ORDER BY #{options[:order]} " if options[:order] + add_limit!(sql, options, scope) + sql << ')' if use_workaround + sql + end + + def execute_simple_calculation(operation, column_name, column, options) #:nodoc: + value = connection.select_value(construct_calculation_sql(operation, column_name, options)) + type_cast_calculated_value(value, column, operation) + end + + def execute_grouped_calculation(operation, column_name, column, 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(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias)) + calculated_data = connection.select_all(sql) + aggregate_alias = column_alias_for(operation, column_name) + + 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(ActiveSupport::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 = {}) + options.assert_valid_keys(CALCULATIONS_OPTIONS) + 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) + connection.table_alias_for(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 diff --git a/vendor/rails/activerecord/lib/active_record/callbacks.rb b/vendor/rails/activerecord/lib/active_record/callbacks.rb new file mode 100755 index 0000000..bfe96fd --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/callbacks.rb @@ -0,0 +1,367 @@ +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 (def after_find). In that case, all of the + # callback types will be called. + # + # == before_validation* returning statements + # + # If the returning value of a before_validation callback can be evaluated to false, the process will be aborted and Base#save will return false. + # If Base#save! is called it will raise a RecordNotSave error. + # Nothing will be appended to the errors object. + # + # == 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.included(base) #:nodoc: + base.extend(ClassMethods) + base.class_eval do + class << self + include Observable + alias_method_chain :instantiate, :callbacks + end + + [:initialize, :create_or_update, :valid?, :create, :update, :destroy].each do |method| + alias_method_chain method, :callbacks + end + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb new file mode 100644 index 0000000..ae59176 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -0,0 +1,269 @@ +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) + config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency) + self.connection = self.send(spec.adapter_method, 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb new file mode 100644 index 0000000..d91b919 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -0,0 +1,115 @@ +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 + + # Appends a locking clause to a SQL statement. *Modifies the +sql+ parameter*. + # # SELECT * FROM suppliers FOR UPDATE + # add_lock! 'SELECT * FROM suppliers', :lock => true + # add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE' + def add_lock!(sql, options) + case lock = options[:lock] + when true: sql << ' FOR UPDATE' + when String: sql << " #{lock}" + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb new file mode 100644 index 0000000..94d1d9c --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -0,0 +1,57 @@ +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) + # records are quoted as their primary key + return value.quoted_id if value.respond_to?(:quoted_id) + + 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 = column.type == :integer ? value.to_i : value.to_f + 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 + # BigDecimals need to be output in a non-normalized form and quoted. + when BigDecimal then value.to_s('F') + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb new file mode 100644 index 0000000..799b302 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -0,0 +1,343 @@ +require 'date' +require 'bigdecimal' +require 'bigdecimal/util' + +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, :precision, :scale + attr_accessor :primary + + # Instantiates a new column in the table. + # + # +name+ is the column's name, as in supplier_id int(11). + # +default+ is the type-casted default value, such as sales_stage varchar(20) default 'new'. + # +sql_type+ is only used to extract the column's length, if necessary. For example, company_name varchar(60). + # +null+ determines if this column allows +NULL+ values. + def initialize(name, default, sql_type = nil, null = true) + @name, @sql_type, @null = name, sql_type, null + @limit, @precision, @scale = extract_limit(sql_type), extract_precision(sql_type), extract_scale(sql_type) + @type = simplified_type(sql_type) + @default = type_cast(default) + + @primary = nil + end + + def text? + [:string, :text].include? type + end + + def number? + [:float, :integer, :decimal].include? type + 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 :decimal then BigDecimal + 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 :decimal then self.class.value_to_decimal(value) + 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 :decimal then "#{self.class.name}.value_to_decimal(#{var_name})" + 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_hash = Date._parse(string) + time_hash[:sec_fraction] = microseconds(time_hash) + time_array = time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction) + # treat 0000-00-00 00:00:00 as nil + Time.send(Base.default_timezone, *time_array) rescue DateTime.new(*time_array[0..5]) rescue nil + end + + def self.string_to_dummy_time(string) + return string unless string.is_a?(String) + return nil if string.empty? + time_hash = Date._parse(string) + time_hash[:sec_fraction] = microseconds(time_hash) + # pad the resulting array with dummy date information + time_array = [2000, 1, 1] + time_array += time_hash.values_at(:hour, :min, :sec, :sec_fraction) + Time.send(Base.default_timezone, *time_array) rescue nil + end + + # convert something to a boolean + def self.value_to_boolean(value) + if value == true || value == false + value + else + %w(true t 1).include?(value.to_s.downcase) + end + end + + # convert something to a BigDecimal + def self.value_to_decimal(value) + if value.is_a?(BigDecimal) + value + elsif value.respond_to?(:to_d) + value.to_d + else + value.to_s.to_d + end + end + + private + # '0.123456' -> 123456 + # '1.123456' -> 123456 + def self.microseconds(time) + ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i + end + + def extract_limit(sql_type) + $1.to_i if sql_type =~ /\((.*)\)/ + end + + def extract_precision(sql_type) + $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i + end + + def extract_scale(sql_type) + case sql_type + when /^(numeric|decimal|number)\((\d+)\)/i then 0 + when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i + end + end + + def simplified_type(field_type) + case field_type + when /int/i + :integer + when /float|double/i + :float + when /decimal|numeric|number/i + extract_scale(field_type) == 0 ? :integer : :decimal + 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, :precision, :scale, :default, :null) #:nodoc: + def to_sql + column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit, precision, scale)}" + add_column_options!(column_sql, :null => null, :default => default) + column_sql + end + alias to_s :to_sql + + private + def type_to_sql(name, limit, precision, scale) + base.type_to_sql(name, limit, precision, scale) 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: + # :primary_key, :string, :text, + # :integer, :float, :decimal, + # :datetime, :timestamp, :time, + # :date, :binary, :boolean. + # + # Available options are (none of these exists by default): + # * :limit: + # Requests a maximum column length (:string, :text, + # :binary or :integer columns only) + # * :default: + # 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. + # * :null: + # Allows or disallows +NULL+ values in the column. This option could + # have been named :null_allowed. + # * :precision: + # Specifies the precision for a :decimal column. + # * :scale: + # Specifies the scale for a :decimal column. + # + # Please be aware of different RDBMS implementations behavior with + # :decimal columns: + # * The SQL standard says the default scale should be 0, :scale <= + # :precision, and makes no comments about the requirements of + # :precision. + # * MySQL: :precision [1..63], :scale [0..30]. + # Default is (10,0). + # * PostGres?: :precision [1..infinity], + # :scale [0..infinity]. No default. + # * Sqlite2: Any :precision and :scale may be used. + # Internal storage as strings. No default. + # * Sqlite3: No restrictions on :precision and :scale, + # but the maximum supported :precision is 16. No default. + # * Oracle: :precision [1..38], :scale [-84..127]. + # Default is (38,0). + # * DB2: :precision [1..63], :scale [0..62]. + # Default unknown. + # * Firebird: :precision [1..18], :scale [0..18]. + # Default (9,0). Internal types NUMERIC and DECIMAL have different + # storage rules, decimal being better. + # * FrontBase?: :precision [1..38], :scale [0..38]. + # Default (38,0). WARNING Max :precision/:scale for + # NUMERIC is 19, and DECIMAL is 38. + # * SqlServer?: :precision [1..38], :scale [0..38]. + # Default (38,0). + # * Sybase: :precision [1..38], :scale [0..38]. + # Default (38,0). + # * OpenBase?: Documentation unclear. Claims storage in double. + # + # This method returns self. + # + # ===== Examples + # # Assuming td is an instance of TableDefinition + # td.column(:granted, :boolean) + # #=> granted BOOLEAN + # + # td.column(:picture, :binary, :limit => 2.megabytes) + # #=> picture BLOB(2097152) + # + # td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false) + # #=> sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL + # + # def.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2) + # #=> bill_gates_money DECIMAL(15,2) + # + # def.column(:sensor_reading, :decimal, :precision => 30, :scale => 20) + # #=> sensor_reading DECIMAL(30,20) + # + # # While :scale defaults to zero on most databases, it + # # probably wouldn't hurt to include it. + # def.column(:huge_integer, :decimal, :precision => 30) + # #=> huge_integer DECIMAL(30) + 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.precision = options[:precision] + column.scale = options[:scale] + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb new file mode 100644 index 0000000..cd13e26 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -0,0 +1,286 @@ +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: + # [:id] + # Whether to automatically add a primary key column. Defaults to true. + # Join tables for has_and_belongs_to_many should set :id => false. + # [:primary_key] + # The name of the primary key, if one is to be added automatically. + # Defaults to +id+. + # [:options] + # Any extra options you want appended to the table definition. + # [:temporary] + # Make a temporary table. + # [:force] + # 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], options[:precision], options[:scale])}" + 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 CREATE TABLE 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, precision = nil, scale = nil) #:nodoc: + native = native_database_types[type] + column_type_sql = native[:name] + if type == :decimal # ignore limit, use precison and scale + precision ||= native[:precision] + scale ||= native[:scale] + if precision + if scale + column_type_sql << "(#{precision},#{scale})" + else + column_type_sql << "(#{precision})" + end + else + raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specifed" if scale + end + column_type_sql + else + limit ||= native[:limit] + column_type_sql << "(#{limit})" if limit + column_type_sql + end + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb new file mode 100755 index 0000000..949b8f7 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -0,0 +1,155 @@ +require 'benchmark' +require 'date' +require 'bigdecimal' +require 'bigdecimal/util' + +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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/db2_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/db2_adapter.rb new file mode 100644 index 0000000..3ff6bdf --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/db2_adapter.rb @@ -0,0 +1,239 @@ +# Author/Maintainer: Maik Schmidt + +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: + # + # * :username -- Defaults to nothing + # * :password -- Defaults to nothing + # * :database -- The name of the database. No default, must be provided. + # * :schema -- 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' }, + :decimal => { :name => 'decimal' }, + :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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb new file mode 100644 index 0000000..6a8ef03 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb @@ -0,0 +1,731 @@ +# Author: Ken Kunz + +require 'active_record/connection_adapters/abstract_adapter' + +module FireRuby # :nodoc: all + NON_EXISTENT_DOMAIN_ERROR = "335544569" + class Database + def self.db_string_for(config) + unless config.has_key?(:database) + raise ArgumentError, "No database specified. Missing argument: database." + end + host_string = config.values_at(:host, :service, :port).compact.first(2).join("/") if config[:host] + [host_string, config[:database]].join(":") + end + + def self.new_from_config(config) + db = new db_string_for(config) + db.character_set = config[:charset] + return db + 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.symbolize_keys! + db = FireRuby::Database.new_from_config(config) + connection_params = config.values_at(:username, :password) + 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 = decide_limit(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 + + def default + type_cast(decide_default) if @default + end + + def self.value_to_boolean(value) + %W(#{FirebirdAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase + end + + private + def parse_default(default_source) + default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i + return $1 unless $1.upcase == "NULL" + end + + def decide_default + if @default =~ /^'?(\d*\.?\d+)'?$/ or + @default =~ /^'(.*)'$/ && [:text, :string, :binary, :boolean].include?(type) + $1 + else + firebird_cast_default + 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 firebird_cast_default + sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE" + if connection = Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' } + connection.execute(sql).to_a.first['CAST'] + else + raise ConnectionNotEstablished, "No Firebird connections established." + end + end + + def decide_limit(length) + if text? or number? + length + elsif @firebird_type == 'BLOB' + BLOB_MAX_LENGTH + end + 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 BEFORE INSERT + # trigger corresponding to a Firebird sequence generator when using + # ActiveRecord. In other words, you don't have to try to make Firebird + # simulate an AUTO_INCREMENT 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) OR VALUE IS NULL); + # + # 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 environment.rb 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 "TYPE" 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 + # ("TYPE") 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 now supports Migrations. + # + # ==== Create/Drop Table and Sequence Generators + # Creating or dropping a table will automatically create/drop a + # correpsonding sequence generator, using the default naming convension. + # You can specify a different name using the :sequence option; no + # generator is created if :sequence is set to +false+. + # + # ==== Rename Table + # The Firebird #rename_table Migration should be used with caution. + # Firebird 1.5 lacks built-in support for this feature, so it is + # implemented by making a copy of the original table (including column + # definitions, indexes and data records), and then dropping the original + # table. Constraints and Triggers are _not_ properly copied, so avoid + # this method if your original table includes constraints (other than + # the primary key) or triggers. (Consider manually copying your table + # or using a view instead.) + # + # == Connection Options + # The following options are supported by the Firebird adapter. None of the + # options have default values. + # + # :database:: + # Required option. Specifies one of: (i) a Firebird database alias; + # (ii) the full path of a database file; _or_ (iii) a full Firebird + # connection string. Do not specify :host, :service + # or :port as separate options when using a full connection + # string. + # :host:: + # Set to "remote.host.name" for remote database connections. + # May be omitted for local connections if a full database path is + # specified for :database. Some platforms require a value of + # "localhost" for local connections when using a Firebird + # database _alias_. + # :service:: + # Specifies a service name for the connection. Only used if :host + # is provided. Required when connecting to a non-standard service. + # :port:: + # Specifies the connection port. Only used if :host is provided + # and :service is not. Required when connecting to a non-standard + # port and :service is not defined. + # :username:: + # Specifies the database user. May be omitted or set to +nil+ (together + # with :password) to use the underlying operating system user + # credentials on supported platforms. + # :password:: + # Specifies the database password. Must be provided if :username + # is explicitly specified; should be omitted if OS user credentials are + # are being used. + # :charset:: + # Specifies the character set to be used by the connection. Refer to + # Firebird documentation for valid options. + class FirebirdAdapter < AbstractAdapter + TEMP_COLUMN_NAME = 'AR$TEMP_COLUMN' + + @@boolean_domain = { :name => "d_boolean", :type => "smallint", :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 + + def supports_migrations? # :nodoc: + true + end + + def native_database_types # :nodoc: + { + :primary_key => "BIGINT NOT NULL PRIMARY KEY", + :string => { :name => "varchar", :limit => 255 }, + :text => { :name => "blob sub_type text" }, + :integer => { :name => "bigint" }, + :float => { :name => "float" }, + :datetime => { :name => "timestamp" }, + :timestamp => { :name => "timestamp" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "blob sub_type 0" }, + :boolean => boolean_domain + } + 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 = nil) # :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.to_s)}") + end + + def quoted_true # :nodoc: + quote(boolean_domain[:true]) + end + + def quoted_false # :nodoc: + quote(boolean_domain[:false]) + end + + + # CONNECTION MANAGEMENT ==================================== + + def active? # :nodoc: + not @connection.closed? + end + + def disconnect! # :nodoc: + @connection.close rescue nil + end + + def reconnect! # :nodoc: + disconnect! + @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: + select(sql, name).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 current_database # :nodoc: + file = @connection.database.file.split(':').last + File.basename(file, '.*') + end + + def recreate_database! # :nodoc: + sql = "SELECT rdb$character_set_name FROM rdb$database" + charset = execute(sql).to_a.first[0].rstrip + disconnect! + @connection.database.drop(*@connection_params) + FireRuby::Database.create(@connection.database.file, + @connection_params[0], @connection_params[1], 4096, charset) + end + + def tables(name = nil) # :nodoc: + sql = "SELECT rdb$relation_name FROM rdb$relations WHERE rdb$system_flag = 0" + execute(sql, name).collect { |row| row[0].rstrip.downcase } + end + + def indexes(table_name, name = nil) # :nodoc: + index_metadata(table_name, false, name).inject([]) do |indexes, row| + if indexes.empty? or indexes.last.name != row[0] + indexes << IndexDefinition.new(table_name, row[0].rstrip.downcase, row[1] == 1, []) + end + indexes.last.columns << row[2].rstrip.downcase + indexes + end + end + + 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 + + def create_table(name, options = {}) # :nodoc: + begin + super + rescue StatementInvalid + raise unless non_existent_domain_error? + create_boolean_domain + super + end + unless options[:id] == false or options[:sequence] == false + sequence_name = options[:sequence] || default_sequence_name(name) + create_sequence(sequence_name) + end + end + + def drop_table(name, options = {}) # :nodoc: + super(name) + unless options[:sequence] == false + sequence_name = options[:sequence] || default_sequence_name(name) + drop_sequence(sequence_name) if sequence_exists?(sequence_name) + end + end + + def add_column(table_name, column_name, type, options = {}) # :nodoc: + super + rescue StatementInvalid + raise unless non_existent_domain_error? + create_boolean_domain + super + end + + def change_column(table_name, column_name, type, options = {}) # :nodoc: + change_column_type(table_name, column_name, type, options) + change_column_position(table_name, column_name, options[:position]) if options[:position] + change_column_default(table_name, column_name, options[:default]) if options.has_key?(:default) + end + + def change_column_default(table_name, column_name, default) # :nodoc: + table_name = table_name.to_s.upcase + sql = <<-end_sql + UPDATE rdb$relation_fields f1 + SET f1.rdb$default_source = + (SELECT f2.rdb$default_source FROM rdb$relation_fields f2 + WHERE f2.rdb$relation_name = '#{table_name}' + AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}'), + f1.rdb$default_value = + (SELECT f2.rdb$default_value FROM rdb$relation_fields f2 + WHERE f2.rdb$relation_name = '#{table_name}' + AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}') + WHERE f1.rdb$relation_name = '#{table_name}' + AND f1.rdb$field_name = '#{ar_to_fb_case(column_name.to_s)}' + end_sql + transaction do + add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default) + execute sql + remove_column(table_name, TEMP_COLUMN_NAME) + end + end + + def rename_column(table_name, column_name, new_column_name) # :nodoc: + execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TO #{new_column_name}" + end + + def remove_index(table_name, options) #:nodoc: + if Hash === options + index_name = options[:name] + else + index_name = "#{table_name}_#{options}_index" + end + execute "DROP INDEX #{index_name}" + end + + def rename_table(name, new_name) # :nodoc: + if table_has_constraints_or_dependencies?(name) + raise ActiveRecordError, + "Table #{name} includes constraints or dependencies that are not supported by " << + "the Firebird rename_table migration. Try explicitly removing the constraints/" << + "dependencies first, or manually renaming the table." + end + + transaction do + copy_table(name, new_name) + copy_table_indexes(name, new_name) + end + begin + copy_table_data(name, new_name) + copy_sequence_value(name, new_name) + rescue + drop_table(new_name) + raise + end + drop_table(name) + end + + def dump_schema_information # :nodoc: + super << ";\n" + end + + def type_to_sql(type, limit = nil) # :nodoc: + case type + when :integer then integer_sql_type(limit) + when :float then float_sql_type(limit) + when :string then super + else super(type) + end + end + + private + def integer_sql_type(limit) + case limit + when (1..2) then 'smallint' + when (3..4) then 'integer' + else 'bigint' + end + end + + def float_sql_type(limit) + limit.to_i <= 4 ? 'float' : 'double precision' + end + + 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 + + def primary_key(table_name) + if pk_row = index_metadata(table_name, true).to_a.first + pk_row[2].rstrip.downcase + end + end + + def index_metadata(table_name, pk, name = nil) + sql = <<-end_sql + SELECT i.rdb$index_name, i.rdb$unique_flag, s.rdb$field_name + FROM rdb$indices i + JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name + LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name + WHERE i.rdb$relation_name = '#{table_name.to_s.upcase}' + end_sql + if pk + sql << "AND c.rdb$constraint_type = 'PRIMARY KEY'\n" + else + sql << "AND (c.rdb$constraint_type IS NULL OR c.rdb$constraint_type != 'PRIMARY KEY')\n" + end + sql << "ORDER BY i.rdb$index_name, s.rdb$field_position\n" + execute sql, name + end + + def change_column_type(table_name, column_name, type, options = {}) + sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit])}" + execute sql + rescue StatementInvalid + raise unless non_existent_domain_error? + create_boolean_domain + execute sql + end + + def change_column_position(table_name, column_name, position) + execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} POSITION #{position}" + end + + def copy_table(from, to) + table_opts = {} + if pk = primary_key(from) + table_opts[:primary_key] = pk + else + table_opts[:id] = false + end + create_table(to, table_opts) do |table| + from_columns = columns(from).reject { |col| col.name == table_opts[:primary_key] } + from_columns.each do |column| + col_opts = [:limit, :default, :null].inject({}) { |opts, opt| opts.merge(opt => column.send(opt)) } + table.column column.name, column.type, col_opts + end + end + end + + def copy_table_indexes(from, to) + indexes(from).each do |index| + unless index.name[from.to_s] + raise ActiveRecordError, + "Cannot rename index #{index.name}, because the index name does not include " << + "the original table name (#{from}). Try explicitly removing the index on the " << + "original table and re-adding it on the new (renamed) table." + end + options = {} + options[:name] = index.name.gsub(from.to_s, to.to_s) + options[:unique] = index.unique + add_index(to, index.columns, options) + end + end + + def copy_table_data(from, to) + execute "INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}" + end + + def copy_sequence_value(from, to) + sequence_value = FireRuby::Generator.new(default_sequence_name(from), @connection).last + execute "SET GENERATOR #{default_sequence_name(to)} TO #{sequence_value}" + end + + def sequence_exists?(sequence_name) + FireRuby::Generator.exists?(sequence_name, @connection) + end + + def create_sequence(sequence_name) + FireRuby::Generator.create(sequence_name.to_s, @connection) + end + + def drop_sequence(sequence_name) + FireRuby::Generator.new(sequence_name.to_s, @connection).drop + end + + def create_boolean_domain + sql = <<-end_sql + CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]} + CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL) + end_sql + execute sql rescue nil + end + + def table_has_constraints_or_dependencies?(table_name) + table_name = table_name.to_s.upcase + sql = <<-end_sql + SELECT 1 FROM rdb$relation_constraints + WHERE rdb$relation_name = '#{table_name}' + AND rdb$constraint_type IN ('UNIQUE', 'FOREIGN KEY', 'CHECK') + UNION + SELECT 1 FROM rdb$dependencies + WHERE rdb$depended_on_name = '#{table_name}' + AND rdb$depended_on_type = 0 + end_sql + !select(sql).empty? + end + + def non_existent_domain_error? + $!.message.include? FireRuby::NON_EXISTENT_DOMAIN_ERROR + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb new file mode 100644 index 0000000..db9d2cc --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb @@ -0,0 +1,863 @@ +# Requires FrontBase Ruby bindings (gem install ruby-frontbase) + +require 'active_record/connection_adapters/abstract_adapter' + +FB_TRACE = false + +module ActiveRecord + + class Base + class << self + # Establishes a connection to the database that's used by all Active Record objects. + def frontbase_connection(config) # :nodoc: + # FrontBase only supports one unnamed sequence per table + define_attr_method(:set_sequence_name, :sequence_name, &Proc.new {|*args| nil}) + + config = config.symbolize_keys + database = config[:database] + port = config[:port] + host = config[:host] + username = config[:username] + password = config[:password] + dbpassword = config[:dbpassword] + session_name = config[:session_name] + + dbpassword = '' if dbpassword.nil? + + # Turn off colorization since it makes tail/less output difficult + self.colorize_logging = false + + require_library_or_gem 'frontbase' unless self.class.const_defined? :FBSQL_Connect + + # Check bindings version + version = "0.0.0" + version = FBSQL_Connect::FB_BINDINGS_VERSION if defined? FBSQL_Connect::FB_BINDINGS_VERSION + + if ActiveRecord::ConnectionAdapters::FrontBaseAdapter.compare_versions(version,"1.0.0") == -1 + raise AdapterNotFound, + 'The FrontBase adapter requires ruby-frontbase version 1.0.0 or greater; you appear ' << + "to be running an older version (#{version}) -- please update ruby-frontbase (gem install ruby-frontbase)." + end + connection = FBSQL_Connect.connect(host, port, database, username, password, dbpassword, session_name) + ConnectionAdapters::FrontBaseAdapter.new(connection, logger, [host, port, database, username, password, dbpassword, session_name], config) + end + end + end + + module ConnectionAdapters + + # From EOF Documentation.... + # buffer should have space for EOUniqueBinaryKeyLength (12) bytes. + # Assigns a world-wide unique ID made up of: + # < Sequence [2], ProcessID [2] , Time [4], IP Addr [4] > + + class TwelveByteKey < String #:nodoc + @@mutex = Mutex.new + @@sequence_number = rand(65536) + @@key_cached_pid_component = nil + @@key_cached_ip_component = nil + + def initialize(string = nil) + # Generate a unique key + if string.nil? + new_key = replace('_' * 12) + + new_key[0..1] = self.class.key_sequence_component + new_key[2..3] = self.class.key_pid_component + new_key[4..7] = self.class.key_time_component + new_key[8..11] = self.class.key_ip_component + new_key + else + if string.size == 24 + string.gsub!(/[[:xdigit:]]{2}/) { |x| x.hex.chr } + end + raise "string is not 12 bytes long" unless string.size == 12 + super(string) + end + end + + def inspect + unpack("H*").first.upcase + end + + alias_method :to_s, :inspect + + private + + class << self + def key_sequence_component + seq = nil + @@mutex.synchronize do + seq = @@sequence_number + @@sequence_number = (@@sequence_number + 1) % 65536 + end + + sequence_component = "__" + sequence_component[0] = seq >> 8 + sequence_component[1] = seq + sequence_component + end + + def key_pid_component + if @@key_cached_pid_component.nil? + @@mutex.synchronize do + pid = $$ + pid_component = "__" + pid_component[0] = pid >> 8 + pid_component[1] = pid + @@key_cached_pid_component = pid_component + end + end + @@key_cached_pid_component + end + + def key_time_component + time = Time.new.to_i + time_component = "____" + time_component[0] = (time & 0xFF000000) >> 24 + time_component[1] = (time & 0x00FF0000) >> 16 + time_component[2] = (time & 0x0000FF00) >> 8 + time_component[3] = (time & 0x000000FF) + time_component + end + + def key_ip_component + if @@key_cached_ip_component.nil? + @@mutex.synchronize do + old_lookup_flag = BasicSocket.do_not_reverse_lookup + BasicSocket.do_not_reverse_lookup = true + udpsocket = UDPSocket.new + udpsocket.connect("17.112.152.32",1) + ip_string = udpsocket.addr[3] + BasicSocket.do_not_reverse_lookup = old_lookup_flag + packed = Socket.pack_sockaddr_in(0,ip_string) + addr_subset = packed[4..7] + ip = addr_subset[0] << 24 | addr_subset[1] << 16 | addr_subset[2] << 8 | addr_subset[3] + ip_component = "____" + ip_component[0] = (ip & 0xFF000000) >> 24 + ip_component[1] = (ip & 0x00FF0000) >> 16 + ip_component[2] = (ip & 0x0000FF00) >> 8 + ip_component[3] = (ip & 0x000000FF) + @@key_cached_ip_component = ip_component + end + end + @@key_cached_ip_component + end + end + end + + class FrontBaseColumn < Column #:nodoc: + attr_reader :fb_autogen + + def initialize(base, name, type, typename, limit, precision, scale, default, nullable) + + @base = base + @name = name + @type = simplified_type(type,typename,limit) + @limit = limit + @precision = precision + @scale = scale + @default = default + @null = nullable == "YES" + @text = [:string, :text].include? @type + @number = [:float, :integer, :decimal].include? @type + @fb_autogen = false + + if @default + @default.gsub!(/^'(.*)'$/,'\1') if @text + @fb_autogen = @default.include?("SELECT UNIQUE FROM") + case @type + when :boolean + @default = @default == "TRUE" + when :binary + if @default != "X''" + buffer = "" + @default.scan(/../) { |h| buffer << h.hex.chr } + @default = buffer + else + @default = "" + end + else + @default = type_cast(@default) + end + end + end + + # Casts value (which is a String) to an appropriate instance. + def type_cast(value) + if type == :twelvebytekey + ActiveRecord::ConnectionAdapters::TwelveByteKey.new(value) + else + super(value) + end + end + + def type_cast_code(var_name) + if type == :twelvebytekey + "ActiveRecord::ConnectionAdapters::TwelveByteKey.new(#{var_name})" + else + super(var_name) + end + end + + private + def simplified_type(field_type, type_name,limit) + ret_type = :string + puts "typecode: [#{field_type}] [#{type_name}]" if FB_TRACE + + # 12 byte primary keys are a special case that Apple's EOF + # used heavily. Optimize for this case + if field_type == 11 && limit == 96 + ret_type = :twelvebytekey # BIT(96) + else + ret_type = case field_type + when 1 then :boolean # BOOLEAN + when 2 then :integer # INTEGER + when 4 then :float # FLOAT + when 10 then :string # CHARACTER VARYING + when 11 then :bitfield # BIT + when 13 then :date # DATE + when 14 then :time # TIME + when 16 then :timestamp # TIMESTAMP + when 20 then :text # CLOB + when 21 then :binary # BLOB + when 22 then :integer # TINYINT + else + puts "ERROR: Unknown typecode: [#{field_type}] [#{type_name}]" + end + end + puts "ret_type: #{ret_type.inspect}" if FB_TRACE + ret_type + end + end + + class FrontBaseAdapter < AbstractAdapter + + class << self + def compare_versions(v1, v2) + v1_seg = v1.split(".") + v2_seg = v2.split(".") + 0.upto([v1_seg.length,v2_seg.length].min) do |i| + step = (v1_seg[i].to_i <=> v2_seg[i].to_i) + return step unless step == 0 + end + return v1_seg.length <=> v2_seg.length + end + end + + def initialize(connection, logger, connection_options, config) + super(connection, logger) + @connection_options, @config = connection_options, config + @transaction_mode = :pessimistic + + # Start out in auto-commit mode + self.rollback_db_transaction + + # threaded_connections_test.rb will fail unless we set the session + # to optimistic locking mode +# set_pessimistic_transactions +# execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC" + end + + # Returns the human-readable name of the adapter. Use mixed case - one + # can always use downcase if needed. + def adapter_name #:nodoc: + 'FrontBase' + end + + # Does this adapter support migrations? Backend specific, as the + # abstract adapter always returns +false+. + def supports_migrations? #:nodoc: + true + end + + def native_database_types #:nodoc + { + :primary_key => "INTEGER DEFAULT UNIQUE PRIMARY KEY", + :string => { :name => "VARCHAR", :limit => 255 }, + :text => { :name => "CLOB" }, + :integer => { :name => "INTEGER" }, + :float => { :name => "FLOAT" }, + :decimal => { :name => "DECIMAL" }, + :datetime => { :name => "TIMESTAMP" }, + :timestamp => { :name => "TIMESTAMP" }, + :time => { :name => "TIME" }, + :date => { :name => "DATE" }, + :binary => { :name => "BLOB" }, + :boolean => { :name => "BOOLEAN" }, + :twelvebytekey => { :name => "BYTE", :limit => 12} + } + end + + + # QUOTING ================================================== + + # Quotes the column value to help prevent + # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. + def quote(value, column = nil) + return value.quoted_id if value.respond_to?(:quoted_id) + + retvalue = "" + + puts "quote(#{value.inspect}(#{value.class}),#{column.type.inspect})" if FB_TRACE + # If a column was passed in, use column type information + unless value.nil? + if column + retvalue = case column.type + when :string + if value.kind_of?(String) + "'#{quote_string(value.to_s)}'" # ' (for ruby-mode) + else + "'#{quote_string(value.to_yaml)}'" + end + when :integer + if value.kind_of?(TrueClass) + '1' + elsif value.kind_of?(FalseClass) + '0' + else + value.to_i.to_s + end + when :float + value.to_f.to_s + when :decimal + value.to_d.to_s("F") + when :datetime, :timestamp + "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'" + when :time + "TIME '#{value.strftime("%H:%M:%S")}'" + when :date + "DATE '#{value.strftime("%Y-%m-%d")}'" + when :twelvebytekey + value = value.to_s.unpack("H*").first unless value.kind_of?(TwelveByteKey) + "X'#{value.to_s}'" + when :boolean + value = quoted_true if value.kind_of?(TrueClass) + value = quoted_false if value.kind_of?(FalseClass) + value + when :binary + blob_handle = @connection.create_blob(value.to_s) + puts "SQL -> Insert #{value.to_s.length} byte blob as #{retvalue}" if FB_TRACE + blob_handle.handle + when :text + if value.kind_of?(String) + clobdata = value.to_s # ' (for ruby-mode) + else + clobdata = value.to_yaml + end + clob_handle = @connection.create_clob(clobdata) + puts "SQL -> Insert #{value.to_s.length} byte clob as #{retvalue}" if FB_TRACE + clob_handle.handle + else + raise "*** UNKNOWN TYPE: #{column.type.inspect}" + end # case + # Since we don't have column type info, make a best guess based + # on the Ruby class of the value + else + retvalue = case value + when ActiveRecord::ConnectionAdapters::TwelveByteKey + s = value.unpack("H*").first + "X'#{s}'" + when String + if column && column.type == :binary + s = value.unpack("H*").first + "X'#{s}'" + elsif column && [:integer, :float, :decimal].include?(column.type) + value.to_s + else + "'#{quote_string(value)}'" # ' (for ruby-mode) + end + when NilClass + "NULL" + when TrueClass + (column && column.type == :integer ? '1' : quoted_true) + when FalseClass + (column && column.type == :integer ? '0' : quoted_false) + when Float, Fixnum, Bignum, BigDecimal + value.to_s + when Time, Date, DateTime + if column + case column.type + when :date + "DATE '#{value.strftime("%Y-%m-%d")}'" + when :time + "TIME '#{value.strftime("%H:%M:%S")}'" + when :timestamp + "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'" + else + raise NotImplementedError, "Unknown column type!" + end # case + else # Column wasn't passed in, so try to guess the right type + if value.kind_of? Date + "DATE '#{value.strftime("%Y-%m-%d")}'" + else + if [:hour, :min, :sec].all? {|part| value.send(:part).zero? } + "TIME '#{value.strftime("%H:%M:%S")}'" + else + "TIMESTAMP '#{quoted_date(value)}'" + end + end + end #if column + else + "'#{quote_string(value.to_yaml)}'" + end #case + end + else + retvalue = "NULL" + end + + retvalue + end # def + + # Quotes a string, escaping any ' (single quote) characters. + def quote_string(s) + s.gsub(/'/, "''") # ' (for ruby-mode) + end + + def quote_column_name(name) #:nodoc: + %( "#{name}" ) + end + + def quoted_true + "true" + end + + def quoted_false + "false" + end + + + # CONNECTION MANAGEMENT ==================================== + + def active? + true if @connection.status == 1 + rescue => e + false + end + + def reconnect! + @connection.close rescue nil + @connection = FBSQL_Connect.connect(*@connection_options.first(7)) + end + + # Close this connection + def disconnect! + @connection.close rescue nil + @active = false + end + + # DATABASE STATEMENTS ====================================== + + # Returns an array of record hashes with the column names as keys and + # column values as values. + def select_all(sql, name = nil) #:nodoc: + fbsql = cleanup_fb_sql(sql) + return_value = [] + fbresult = execute(sql, name) + puts "select_all SQL -> #{fbsql}" if FB_TRACE + columns = fbresult.columns + + fbresult.each do |row| + puts "SQL <- #{row.inspect}" if FB_TRACE + hashed_row = {} + colnum = 0 + row.each do |col| + hashed_row[columns[colnum]] = col + if col.kind_of?(FBSQL_LOB) + hashed_row[columns[colnum]] = col.read + end + colnum += 1 + end + puts "raw row: #{hashed_row.inspect}" if FB_TRACE + return_value << hashed_row + end + return_value + end + + def select_one(sql, name = nil) #:nodoc: + fbsql = cleanup_fb_sql(sql) + return_value = [] + fbresult = execute(fbsql, name) + puts "SQL -> #{fbsql}" if FB_TRACE + columns = fbresult.columns + + fbresult.each do |row| + puts "SQL <- #{row.inspect}" if FB_TRACE + hashed_row = {} + colnum = 0 + row.each do |col| + hashed_row[columns[colnum]] = col + if col.kind_of?(FBSQL_LOB) + hashed_row[columns[colnum]] = col.read + end + colnum += 1 + end + return_value << hashed_row + break + end + fbresult.clear + return_value.first + end + + def query(sql, name = nil) #:nodoc: + fbsql = cleanup_fb_sql(sql) + puts "SQL(query) -> #{fbsql}" if FB_TRACE + log(fbsql, name) { @connection.query(fbsql) } + rescue => e + puts "FB Exception: #{e.inspect}" if FB_TRACE + raise e + end + + def execute(sql, name = nil) #:nodoc: + fbsql = cleanup_fb_sql(sql) + puts "SQL(execute) -> #{fbsql}" if FB_TRACE + log(fbsql, name) { @connection.query(fbsql) } + rescue ActiveRecord::StatementInvalid => e + if e.message.scan(/Table name - \w* - exists/).empty? + puts "FB Exception: #{e.inspect}" if FB_TRACE + raise e + end + end + + # Returns the last auto-generated ID from the affected table. + def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: + puts "SQL -> #{sql.inspect}" if FB_TRACE + execute(sql, name) + id_value || pk + end + + # Executes the update statement and returns the number of rows affected. + def update(sql, name = nil) #:nodoc: + puts "SQL -> #{sql.inspect}" if FB_TRACE + execute(sql, name).num_rows + end + + alias_method :delete, :update #:nodoc: + + def set_pessimistic_transactions + if @transaction_mode == :optimistic + execute "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC, READ WRITE" + @transaction_mode = :pessimistic + end + end + + def set_optimistic_transactions + if @transaction_mode == :pessimistic + execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC" + @transaction_mode = :optimistic + end + end + + def begin_db_transaction #:nodoc: + execute "SET COMMIT FALSE" rescue nil + end + + def commit_db_transaction #:nodoc: + execute "COMMIT" + ensure + execute "SET COMMIT TRUE" + end + + def rollback_db_transaction #:nodoc: + execute "ROLLBACK" + ensure + execute "SET COMMIT TRUE" + end + + def add_limit_offset!(sql, options) #:nodoc + if limit = options[:limit] + offset = options[:offset] || 0 + +# Here is the full syntax FrontBase supports: +# (from gclem@frontbase.com) +# +# TOP +# TOP ( , ) + + # "TOP 0" is not allowed, so we have + # to use a cheap trick. + if limit.zero? + case sql + when /WHERE/i + sql.sub!(/WHERE/i, 'WHERE 0 = 1 AND ') + when /ORDER\s+BY/i + sql.sub!(/ORDER\s+BY/i, 'WHERE 0 = 1 ORDER BY') + else + sql << 'WHERE 0 = 1' + end + else + if offset.zero? + sql.replace sql.gsub("SELECT ","SELECT TOP #{limit} ") + else + sql.replace sql.gsub("SELECT ","SELECT TOP(#{offset},#{limit}) ") + end + end + end + end + + def prefetch_primary_key?(table_name = nil) + true + 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) + unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") + # The test cases cannot handle a zero primary key + unique.zero? ? select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") : unique + end + + def default_sequence_name(table, column) + table + end + + # Set the sequence to the max value of the table's column. + def reset_sequence!(table, column, sequence = nil) + klasses = classes_for_table_name(table) + klass = klasses.nil? ? nil : klasses.first + pk = klass.primary_key unless klass.nil? + if pk && klass.columns_hash[pk].type == :integer + execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") + end + end + + def classes_for_table_name(table) + ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table} + end + + def reset_pk_sequence!(table, pk = nil, sequence = nil) + klasses = classes_for_table_name(table) + klass = klasses.nil? ? nil : klasses.first + pk = klass.primary_key unless klass.nil? + if pk && klass.columns_hash[pk].type == :integer + mpk = select_value("SELECT MAX(#{pk}) FROM #{table}") + execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") + end + end + + # SCHEMA STATEMENTS ======================================== + + def structure_dump #:nodoc: + select_all("SHOW TABLES").inject('') do |structure, table| + 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 #{name}" + end + + def current_database + select_value('SELECT "CATALOG_NAME" FROM INFORMATION_SCHEMA.CATALOGS').downcase + end + + def tables(name = nil) #:nodoc: + select_values(<<-SQL, nil) + SELECT "TABLE_NAME" + FROM INFORMATION_SCHEMA.TABLES AS T0, + INFORMATION_SCHEMA.SCHEMATA AS T1 + WHERE T0.SCHEMA_PK = T1.SCHEMA_PK + AND "SCHEMA_NAME" = CURRENT_SCHEMA + SQL + end + + def indexes(table_name, name = nil)#:nodoc: + indexes = [] + current_index = nil + sql = <<-SQL + SELECT INDEX_NAME, T2.ORDINAL_POSITION, INDEX_COLUMN_COUNT, INDEX_TYPE, + "COLUMN_NAME", IS_NULLABLE + FROM INFORMATION_SCHEMA.TABLES AS T0, + INFORMATION_SCHEMA.INDEXES AS T1, + INFORMATION_SCHEMA.INDEX_COLUMN_USAGE AS T2, + INFORMATION_SCHEMA.COLUMNS AS T3 + WHERE T0."TABLE_NAME" = '#{table_name}' + AND INDEX_TYPE <> 0 + AND T0.TABLE_PK = T1.TABLE_PK + AND T0.TABLE_PK = T2.TABLE_PK + AND T0.TABLE_PK = T3.TABLE_PK + AND T1.INDEXES_PK = T2.INDEX_PK + AND T2.COLUMN_PK = T3.COLUMN_PK + ORDER BY INDEX_NAME, T2.ORDINAL_POSITION + SQL + + columns = [] + query(sql).each do |row| + index_name = row[0] + ord_position = row[1] + ndx_colcount = row[2] + index_type = row[3] + column_name = row[4] + + is_unique = index_type == 1 + + columns << column_name + if ord_position == ndx_colcount + indexes << IndexDefinition.new(table_name, index_name, is_unique , columns) + columns = [] + end + end + indexes + end + + def columns(table_name, name = nil)#:nodoc: + sql = <<-SQL + SELECT "TABLE_NAME", "COLUMN_NAME", ORDINAL_POSITION, IS_NULLABLE, COLUMN_DEFAULT, + DATA_TYPE, DATA_TYPE_CODE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, + NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, DATETIME_PRECISION_LEADING + FROM INFORMATION_SCHEMA.TABLES T0, + INFORMATION_SCHEMA.COLUMNS T1, + INFORMATION_SCHEMA.DATA_TYPE_DESCRIPTOR T3 + WHERE "TABLE_NAME" = '#{table_name}' + AND T0.TABLE_PK = T1.TABLE_PK + AND T0.TABLE_PK = T3.TABLE_OR_DOMAIN_PK + AND T1.COLUMN_PK = T3.COLUMN_NAME_PK + ORDER BY T1.ORDINAL_POSITION + SQL + + rawresults = query(sql,name) + columns = [] + rawresults.each do |field| + args = [base = field[0], + name = field[1], + typecode = field[6], + typestring = field[5], + limit = field[7], + precision = field[8], + scale = field[9], + default = field[4], + nullable = field[3]] + columns << FrontBaseColumn.new(*args) + end + columns + end + + 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]}" + begin_db_transaction + execute create_sql + commit_db_transaction + rescue ActiveRecord::StatementInvalid => e + raise e unless e.message.match(/Table name - \w* - exists/) + end + + def rename_table(name, new_name) + columns = columns(name) + pkcol = columns.find {|c| c.fb_autogen} + execute "ALTER TABLE NAME #{name} TO #{new_name}" + if pkcol + change_column_default(new_name,pkcol.name,"UNIQUE") + begin_db_transaction + mpk = select_value("SELECT MAX(#{pkcol.name}) FROM #{new_name}") + mpk = 0 if mpk.nil? + execute "SET UNIQUE=#{mpk} FOR #{new_name}" + commit_db_transaction + end + end + + # Drops a table from the database. + def drop_table(name) + execute "DROP TABLE #{name} RESTRICT" + rescue ActiveRecord::StatementInvalid => e + raise e unless e.message.match(/Referenced TABLE - \w* - does not exist/) + 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 #{column_name} #{type_to_sql(type, options[:limit])}" + options[:type] = type + add_column_options!(add_column_sql, options) + execute(add_column_sql) + end + + def add_column_options!(sql, options) #:nodoc: + default_value = quote(options[:default], options[:column]) + if options[:default] + if options[:type] == :boolean + default_value = options[:default] == 0 ? quoted_false : quoted_true + end + end + sql << " DEFAULT #{default_value}" unless options[:default].nil? + sql << " NOT NULL" if options[:null] == false + 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 #{column_name} RESTRICT" + end + + def remove_index(table_name, options = {}) #:nodoc: + if options[:unique] + execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{quote_column_name(index_name(table_name, options))} RESTRICT" + else + execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}" + end + end + + def change_column_default(table_name, column_name, default) #:nodoc: + execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{default}" if default != "NULL" + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + change_column_sql = %( ALTER COLUMN "#{table_name}"."#{column_name}" TO #{type_to_sql(type, options[:limit])} ) + execute(change_column_sql) + change_column_sql = %( ALTER TABLE "#{table_name}" ALTER COLUMN "#{column_name}" ) + + default_value = quote(options[:default], options[:column]) + if options[:default] + if type == :boolean + default_value = options[:default] == 0 ? quoted_false : quoted_true + end + end + + if default_value != "NULL" + change_column_sql << " SET DEFAULT #{default_value}" + execute(change_column_sql) + end + +# 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: + execute %( ALTER COLUMN NAME "#{table_name}"."#{column_name}" TO "#{new_column_name}" ) + end + + private + + # Clean up sql to make it something FrontBase can digest + def cleanup_fb_sql(sql) #:nodoc: + # Turn non-standard != into standard <> + cleansql = sql.gsub("!=", "<>") + # Strip blank lines and comments + cleansql.split("\n").reject { |line| line.match(/^(?:\s*|--.*)$/) } * "\n" + end + end + end +end diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb new file mode 100755 index 0000000..b9fb5fa --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -0,0 +1,362 @@ +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: + # + # * :host -- Defaults to localhost + # * :port -- Defaults to 3306 + # * :socket -- Defaults to /tmp/mysql.sock + # * :username -- Defaults to root + # * :password -- Defaults to nothing + # * :database -- The name of the database. No default, must be provided. + # * :sslkey -- Necessary to use MySQL with an SSL connection + # * :sslcert -- Necessary to use MySQL with an SSL connection + # * :sslcapath -- Necessary to use MySQL with an SSL connection + # * :sslcipher -- 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" }, + :decimal => { :name => "decimal" }, + :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}'" + elsif value.kind_of?(BigDecimal) + "'#{value.to_s("F")}'" + 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: + if options[:default].nil? + options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"] + end + + change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb new file mode 100644 index 0000000..e52cde4 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb @@ -0,0 +1,350 @@ +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 :decimal 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: + # + # * :host -- Defaults to localhost + # * :username -- Defaults to nothing + # * :password -- Defaults to nothing + # * :database -- 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: derrick.spell@gmail.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" }, + :decimal => { :name => "decimal" }, + :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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb new file mode 100644 index 0000000..2e5260d --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/oracle_adapter.rb @@ -0,0 +1,643 @@ +# oracle_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g +# +# Original author: Graham Jenkins +# +# Current maintainer: Michael Schoen +# +######################################################################### +# +# 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.sql_type =~ /LOB$/i }.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: + + def type_cast(value) + return nil if value =~ /^\s*null\s*$/i + return guess_date_or_time(value) if type == :datetime && OracleAdapter.emulate_dates + super + end + + private + def simplified_type(field_type) + return :boolean if OracleAdapter.emulate_booleans && field_type == 'NUMBER(1)' + case field_type + when /date|time/i then :datetime + else super + end + 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: + # + # * :username + # * :password + # * :database + class OracleAdapter < AbstractAdapter + + @@emulate_booleans = true + cattr_accessor :emulate_booleans + + @@emulate_dates = false + cattr_accessor :emulate_dates + + 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" }, + :decimal => { :name => "DECIMAL" }, + :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(s) #:nodoc: + s.gsub(/'/, "''") + end + + def quote(value, column = nil) #:nodoc: + if column && [:text, :binary].include?(column.type) + %Q{empty_#{ column.sql_type rescue 'blob' }()} + else + super + end + end + + def quoted_true + "1" + end + + def quoted_false + "0" + 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'].to_i + log(sql.sub(/\B:id\b/, id_value.to_s), 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 uc.index_name FROM user_constraints uc WHERE uc.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 as name, data_type as sql_type, data_default, nullable, + decode(data_type, 'NUMBER', data_precision, + 'FLOAT', data_precision, + 'VARCHAR2', data_length, + null) as limit, + 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| + limit, scale = row['limit'], row['scale'] + if limit || scale + row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")") + end + + # clean up odd default spacing from Oracle + if row['data_default'] + row['data_default'].sub!(/^(.*?)\s*$/, '\1') + row['data_default'].sub!(/^'(.*)'$/, '\1') + end + + OracleColumn.new(oracle_downcase(row['name']), + row['data_default'], + row['sql_type'], + 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], options[:precision], options[:scale])}" + 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, async) + 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.non_blocking = true if async + 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] + @async = config[:allow_concurrency] + @factory = factory + @connection = @factory.new_connection @username, @password, @database, @async + 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, @async + __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, &block) + should_retry = self.class.auto_retry? && autocommit? + + begin + @connection.exec(sql, *bindvars, &block) + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb new file mode 100644 index 0000000..94584a5 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -0,0 +1,558 @@ +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 + 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: + # + # * :host -- Defaults to localhost + # * :port -- Defaults to 5432 + # * :username -- Defaults to nothing + # * :password -- Defaults to nothing + # * :database -- The name of the database. No default, must be provided. + # * :schema_search_path -- 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. + # * :encoding -- An optional client encoding that is using in a SET client_encoding TO call on connection. + # * :min_messages -- An optional client min messages that is using in a SET client_min_messages TO call on connection. + # * :allow_concurrency -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. + class PostgreSQLAdapter < AbstractAdapter + def adapter_name + 'PostgreSQL' + end + + def initialize(connection, logger, config = {}) + super(connection, logger) + @config = config + @async = config[:allow_concurrency] + 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" }, + :decimal => { :name => "decimal" }, + :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 + + def quoted_date(value) + value.strftime("%Y-%m-%d %H:%M:%S.#{sprintf("%06d", value.usec)}") + 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) do + if @async + @connection.async_query(sql) + else + @connection.query(sql) + end + end + end + + def execute(sql, name = nil) #:nodoc: + log(sql, name) do + if @async + @connection.async_exec(sql) + else + @connection.exec(sql) + end + end + 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, typmod| + # typmod now unused as limit, precision, scale all handled by superclass + 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 = query(<<-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 = query(<<-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 = {}) + default = options[:default] + notnull = options[:null] == false + + # Add the column. + execute("ALTER TABLE #{table_name} ADD COLUMN #{column_name} #{type_to_sql(type, options[:limit])}") + + # Set optional default. If not null, update nulls to the new default. + unless default.nil? + change_column_default(table_name, column_name, default) + if notnull + execute("UPDATE #{table_name} SET #{column_name}='#{default}' WHERE #{column_name} IS NULL") + end + end + + if notnull + execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL") + end + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + begin + execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + 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], options[:precision], options[:scale])})" + 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 #{quote_column_name(column_name)} SET DEFAULT '#{default}'" + end + + def rename_column(table_name, column_name, new_column_name) #:nodoc: + execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" + end + + def remove_index(table_name, options) #:nodoc: + execute "DROP INDEX #{index_name(table_name, options)}" + end + + def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: + return super unless type.to_s == 'integer' + + if limit.nil? || limit == 4 + 'integer' + elsif limit < 4 + 'smallint' + else + 'bigint' + end + end + + private + BYTEA_COLUMN_TYPE_OID = 17 + NUMERIC_COLUMN_TYPE_OID = 1700 + 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) + when NUMERIC_COLUMN_TYPE_OID + column = column.to_d if column.respond_to?(:to_d) + end + + hashed_row[fields[cel_index]] = column + end + rows << hashed_row + end + end + res.clear + 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 + # PostgreSQL array data types. + when /\[\]$/i then 'string' + 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, v.usec] + Time.send(Base.default_timezone, *time_array) rescue nil + end + end + end +end diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb new file mode 100644 index 0000000..308a211 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -0,0 +1,379 @@ +# Author: Luke Holden +# Updated for SQLite3: Jamis Buck + +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: + # + # * :database -- 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" }, + :decimal => { :name => "decimal" }, + :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 + + + # SELECT ... FOR UPDATE is redundant since the table is locked. + def add_lock!(sql, options) #:nodoc: + sql + 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] unless options[:default].nil? + 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} + from_columns = columns(from).collect {|col| col.name} + columns = columns.find_all{|col| from_columns.include?(column_mappings[col])} + @connection.execute "SELECT * FROM #{from}" do |row| + sql = "INSERT INTO #{to} ("+columns*','+") 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb new file mode 100644 index 0000000..a19da5a --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb @@ -0,0 +1,599 @@ +require 'active_record/connection_adapters/abstract_adapter' + +require 'bigdecimal' +require 'bigdecimal/util' + +# sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server +# +# Author: Joey Gibson +# Date: 10/14/2004 +# +# Modifications: DeLynn Berry +# Date: 3/22/2005 +# +# Modifications (ODBC): Mark Imbriaco +# Date: 6/26/2005 + +# Modifications (Migrations): Tom Ward +# Date: 27/10/2005 +# +# Modifications (Numerous fixes as maintainer): Ryan Tomayko +# Date: Up to July 2006 + +# Current maintainer: Tom Ward + +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 SQLServerColumn < Column# :nodoc: + attr_reader :identity, :is_special + + def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0 + super(name, default, sql_type, null) + @identity = identity + @is_special = sql_type =~ /text|ntext|image/i + # TODO: check ok to remove @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 /money/i then :decimal + when /image/i then :binary + when /bit/i then :boolean + when /uniqueidentifier/i then :string + else super + end + end + + def type_cast(value) + return nil if value.nil? || value =~ /^\s*null\s*$/i + case type + 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 super + end + end + + def cast_to_time(value) + return value if value.is_a?(Time) + time_array = ParseDate.parsedate(value) + Time.send(Base.default_timezone, *time_array) rescue nil + end + + def cast_to_datetime(value) + return value.to_time if value.is_a?(DBI::Timestamp) + + 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 + + if value.is_a?(DateTime) + return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec) + end + + return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil + value + end + + # TODO: Find less hack way to convert DateTime objects into Times + + def self.string_to_time(value) + if value.is_a?(DateTime) + return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec) + else + super + end + 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 + # src/lib/dbd_ado/ADO.rb + # to + # X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb + # (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: + # + # * :mode -- ADO or ODBC. Defaults to ADO. + # * :username -- Defaults to sa. + # * :password -- Defaults to empty string. + # + # ADO specific options: + # + # * :host -- Defaults to localhost. + # * :database -- The name of the database. No default, must be provided. + # + # ODBC specific options: + # + # * :dsn -- 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 }, + :decimal => { :name => "decimal" }, + :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 + + def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: + return super unless type.to_s == 'integer' + + if limit.nil? || limit == 4 + 'integer' + elsif limit < 4 + 'smallint' + else + 'bigint' + end + end + + # CONNECTION MANAGEMENT ====================================# + + # Returns true if the connection is active. + def active? + @connection.execute("SELECT 1").finish + 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 = %Q{ + SELECT + cols.COLUMN_NAME as ColName, + cols.COLUMN_DEFAULT as DefaultValue, + cols.NUMERIC_SCALE as numeric_scale, + cols.NUMERIC_PRECISION as numeric_precision, + cols.DATA_TYPE as ColType, + cols.IS_NULLABLE As IsNullable, + COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length, + COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity, + cols.NUMERIC_SCALE as Scale + FROM INFORMATION_SCHEMA.COLUMNS cols + WHERE cols.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] + if field[:ColType] =~ /numeric|decimal/i + type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})" + else + type = "#{field[:ColType]}(#{field[:Length]})" + end + is_identity = field[:IsIdentity] == 1 + is_nullable = field[:IsNullable] == 'YES' + columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable) + end + columns + end + + def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) + execute(sql, name) + id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"] + end + + def update(sql, name = nil) + execute(sql, name) do |handle| + handle.rows + end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"] + end + + alias_method :delete, :update + + def execute(sql, name = nil) + if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql)) + log(sql, name) do + with_identity_insert_enabled(table_name) do + @connection.execute(sql) do |handle| + yield(handle) if block_given? + end + end + end + else + log(sql, name) do + @connection.execute(sql) do |handle| + yield(handle) if block_given? + end + end + end + end + + 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) + return value.quoted_id if value.respond_to?(:quoted_id) + + case value + when TrueClass then '1' + when FalseClass then '0' + when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'" + else super + end + end + + def quote_string(string) + string.gsub(/\'/, "''") + 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(\s+DISTINCT)?\b/i, "SELECT#{$1} 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(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} 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 + elsif parts[0] =~ /\w+\.(\w+)/ + 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) do |sth| + sth.inject([]) do |tables, field| + table_name = field[0] + tables << table_name unless table_name == 'dtproperties' + tables + end + end + end + + def indexes(table_name, name = nil) + ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = false + indexes = [] + execute("EXEC sp_helpindex #{table_name}", name) do |sth| + sth.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 + end + indexes + ensure + ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true + end + + def rename_table(name, new_name) + execute "EXEC sp_rename '#{name}', '#{new_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], options[:precision], options[:scale])}" + add_column_options!(add_column_sql, options) + # TODO: Add support to mimic date columns, using constraints to mark them as such in the database + # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date + execute(add_column_sql) + 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], options[:precision], options[:scale])}"] + unless options[:default].nil? + remove_default_constraint(table_name, column_name) + sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default])} FOR #{column_name}" + end + sql_commands.each {|c| + execute(c) + } + end + + def remove_column(table_name, column_name) + remove_check_constraints(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) + constraints = 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" + + constraints.each do |constraint| + execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}" + end + end + + def remove_check_constraints(table_name, column_name) + # TODO remove all constraints in single method + constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'" + constraints.each do |constraint| + execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}" + end + end + + def remove_index(table_name, options = {}) + execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}" + end + + private + def select(sql, name = nil) + repair_special_columns(sql) + + result = [] + execute(sql) do |handle| + handle.each do |row| + row_hash = {} + row.each_with_index do |value, i| + if value.is_a? DBI::Timestamp + value = DateTime.new(value.year, value.month, value.day, value.hour, value.minute, value.sec) + end + row_hash[handle.column_names[i]] = value + end + result << row_hash + end + end + result + end + + # Turns IDENTITY_INSERT ON for table during execution of the block + # N.B. This sets the state of IDENTITY_INSERT to OFF after the + # block has been executed without regard to its previous state + + def with_identity_insert_enabled(table_name, &block) + set_identity_insert(table_name, true) + yield + ensure + set_identity_insert(table_name, false) + end + + def set_identity_insert(table_name, enable = true) + execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}" + rescue Exception => e + raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}" + 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 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_requires_identity_insert?(sql) + table_name = get_table_name(sql) + id_column = identity_column(table_name) + sql =~ /\[#{id_column}\]/ ? table_name : nil + 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 diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb new file mode 100644 index 0000000..dc1cce4 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/sybase_adapter.rb @@ -0,0 +1,679 @@ +# sybase_adaptor.rb +# Author: John Sheets +# +# 01 Mar 2006: Initial version. +# 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns. +# 13 Apr 2006: Improved column type support to properly handle dates and user-defined +# types; fixed quoting of integer columns. +# +# Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030) +# + +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, config, 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: + # + # * :host -- The name of the database server. No default, must be provided. + # * :database -- The name of the database. No default, must be provided. + # * :username -- Defaults to "sa". + # * :password -- 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|real/i then :float + when /decimal|money|numeric|smallmoney/i then :decimal + 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_time(string) + return string unless string.is_a?(String) + + # Since Sybase doesn't handle DATE or TIME, handle it here. + # Populate nil year/month/day with string_to_dummy_time() values. + time_array = ParseDate.parsedate(string)[0..5] + time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1; + Time.send(Base.default_timezone, *time_array) rescue nil + 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, config = {}, logger = nil) + super(connection, logger) + context = connection.context + context.init(logger) + @config = config + @numconvert = config.has_key?(:numconvert) ? config[:numconvert] : true + @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 }, + :decimal => { :name => "decimal" }, + :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 + + def select_all(sql, name = nil) + select(sql, name) + end + + 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 + 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 + 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 current_database + select_one("select DB_NAME() as name")["name"] + 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) + return value.quoted_id if value.respond_to?(:quoted_id) + + 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 @numconvert && force_numeric?(column) && 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 force_numeric?(column) ? value.to_s : "'#{value.to_s}'" + when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'" + else super + end + end + + # True if column is explicitly declared non-numeric, or + # if column is nil (not specified). + def force_numeric?(column) + (column.nil? || [:integer, :float, :decimal].include?(column.type)) + end + + def quote_string(s) + s.gsub(/'/, "''") # ' (for ruby-mode) + end + + def quote_column_name(name) + # If column name is close to max length, skip the quotes, since they + # seem to count as part of the length. + ((name.to_s.length + 2) <= table_alias_length) ? "[#{name}]" : name.to_s + 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: + begin + execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}" + rescue StatementInvalid => e + # Swallow exception if no-op. + raise e unless e.message =~ /no columns to drop, add or modify/ + end + + if options[:default] + remove_default_constraint(table_name, column_name) + execute "ALTER TABLE #{table_name} REPLACE #{column_name} DEFAULT #{options[:default]}" + end + 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 + + def enable_identity_insert(table_name, enable = true) + if has_identity_column(table_name) + execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}" + end + 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 = results.columns.map { |column| column.sub(/_$/, '') } + 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 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 ||= {} + @table_columns[table_name] ||= columns(table_name) + @table_columns[table_name].each do |col| + return col.name if col.identity + end + nil + end + + def query_contains_identity_column(sql, col) + sql =~ /\[#{col}\]/ + end + + def table_structure(table_name) + sql = <= 128 + primary = (sysstat2 & 8) == 8 + + columns << [name, default_value, type, nullable, identity, primary] + end + columns + else + nil + end + end + + # Resolve all user-defined types (udt) to their fundamental types. + def resolve_type(field_type) + (@udts ||= {})[field_type] ||= select_one("sp_help #{field_type}")["Storage_type"].strip + 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 = resolve_type(field_type.strip) + end + size = '' + if prec + size = "(#{prec})" + elsif length && !(type =~ /date|time/) + 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| + @connection.enable_identity_insert(table_name, true) + @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' + @connection.enable_identity_insert(table_name, false) + end + end +end + +rescue LoadError => cannot_require_sybase + # Couldn't load sybase adapter +end diff --git a/vendor/rails/activerecord/lib/active_record/deprecated_associations.rb b/vendor/rails/activerecord/lib/active_record/deprecated_associations.rb new file mode 100644 index 0000000..077ac1d --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/deprecated_associations.rb @@ -0,0 +1,90 @@ +module 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 diff --git a/vendor/rails/activerecord/lib/active_record/deprecated_finders.rb b/vendor/rails/activerecord/lib/active_record/deprecated_finders.rb new file mode 100644 index 0000000..03b2b3d --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/deprecated_finders.rb @@ -0,0 +1,44 @@ +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 + deprecate :find_on_conditions + + # 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 + deprecate :find_first + + # 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 + deprecate :find_all + end + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/lib/active_record/fixtures.rb b/vendor/rails/activerecord/lib/active_record/fixtures.rb new file mode 100755 index 0000000..9eb3b57 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/fixtures.rb @@ -0,0 +1,611 @@ +require '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 Test::Unit::TestCase.fixture_path=(path) (this is automatically configured for Rails, so you can just +# put your files in /test/fixtures/). The fixture file ends with the .yml file extension (Rails example: +# "/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: "/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 Test::Unit::TestCase.fixture_path=(path) (this is automatically configured for Rails, so you can just +# put your files in /test/fixtures// -- like /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 occurred 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 + klass = @class_name.constantize rescue nil + + list = @fixture.inject([]) do |fixtures, (key, value)| + col = klass.columns_hash[key] unless klass.nil? + fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('\\n', "\n").gsub('\\r', "\r") + end + list * ', ' + 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 = [] unless defined?(@uses_transaction) + @uses_transaction.concat methods.map(&:to_s) + end + + def self.uses_transaction?(method) + @uses_transaction = [] unless defined?(@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 + return unless defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank? + + 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.send :increment_open_transactions + 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 + return unless defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank? + + # Rollback changes. + if use_transactional_fixtures? + ActiveRecord::Base.connection.rollback_db_transaction + ActiveRecord::Base.send :decrement_open_transactions + 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 diff --git a/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb b/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb new file mode 100644 index 0000000..7bae573 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb @@ -0,0 +1,89 @@ +module ActiveRecord + module Locking + # Active Records support optimistic locking if the field lock_version 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 ActiveRecord::Base.lock_optimistically = false. + # To override the name of the lock_version column, invoke the set_locking_column method. + # This method uses the same syntax as set_table_name + module Optimistic + def self.included(base) #:nodoc: + super + base.extend ClassMethods + + base.cattr_accessor :lock_optimistically + base.lock_optimistically = true + + base.alias_method_chain :update, :lock + class << base + alias_method :locking_column=, :set_locking_column + end + end + + def locking_enabled? #:nodoc: + lock_optimistically && respond_to?(self.class.locking_column) + 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 #{self.class.quoted_locking_column} = #{quote(previous_value)} + end_sql + + unless affected_rows == 1 + raise ActiveRecord::StaleObjectError, "Attempted to update a stale object" + end + + return true + end + + module ClassMethods + DEFAULT_LOCKING_COLUMN = 'lock_version' + + # Set the column to use for optimistic locking. Defaults to lock_version. + def set_locking_column(value = nil, &block) + define_attr_method :locking_column, value, &block + value + end + + # The version column used for optimistic locking. Defaults to lock_version. + def locking_column + reset_locking_column + end + + # Quote the column name used for optimistic locking. + def quoted_locking_column + connection.quote_column_name(locking_column) + end + + # Reset the column used for optimistic locking back to the lock_version default. + def reset_locking_column + set_locking_column DEFAULT_LOCKING_COLUMN + end + end + end + end +end diff --git a/vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb b/vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb new file mode 100644 index 0000000..caad760 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb @@ -0,0 +1,77 @@ +# Copyright (c) 2006 Shugo Maeda +# +# 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. + + +module ActiveRecord + module Locking + # Locking::Pessimistic provides support for row-level locking using + # SELECT ... FOR UPDATE and other lock types. + # + # Pass :lock => true to ActiveRecord::Base.find to obtain an exclusive + # lock on the selected rows: + # # select * from accounts where id=1 for update + # Account.find(1, :lock => true) + # + # Pass :lock => 'some locking clause' to give a database-specific locking clause + # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. + # + # Example: + # Account.transaction do + # # select * from accounts where name = 'shugo' limit 1 for update + # shugo = Account.find(:first, :conditions => "name = 'shugo'", :lock => true) + # yuko = Account.find(:first, :conditions => "name = 'yuko'", :lock => true) + # shugo.balance -= 100 + # shugo.save! + # yuko.balance += 100 + # yuko.save! + # end + # + # You can also use ActiveRecord::Base#lock! method to lock one record by id. + # This may be better if you don't need to lock every row. Example: + # Account.transaction do + # # select * from accounts where ... + # accounts = Account.find(:all, :conditions => ...) + # account1 = accounts.detect { |account| ... } + # account2 = accounts.detect { |account| ... } + # # select * from accounts where id=? for update + # account1.lock! + # account2.lock! + # account1.balance -= 100 + # account1.save! + # account2.balance += 100 + # account2.save! + # end + # + # Database-specific information on row locking: + # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html + # PostgreSQL: http://www.postgresql.org/docs/8.1/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE + module Pessimistic + # Obtain a row lock on this record. Reloads the record to obtain the requested + # lock. Pass an SQL locking clause to append the end of the SELECT statement + # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns + # the locked record. + def lock!(lock = true) + reload(:lock => lock) unless new_record? + self + end + end + end +end diff --git a/vendor/rails/activerecord/lib/active_record/migration.rb b/vendor/rails/activerecord/lib/active_record/migration.rb new file mode 100644 index 0000000..acafda0 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/migration.rb @@ -0,0 +1,392 @@ +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 + # + # * create_table(name, options) 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. + # * drop_table(name): Drops the table called +name+. + # * rename_table(old_name, new_name): Renames the table called +old_name+ to +new_name+. + # * add_column(table_name, column_name, type, options): 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, :decimal, :datetime, :timestamp, :time, + # :date, :binary, :boolean. A default value can be specified by passing an + # +options+ hash like { :default => 11 }. + # * rename_column(table_name, column_name, new_column_name): Renames a column but keeps the type and content. + # * change_column(table_name, column_name, type, options): Changes the column to a different type using the same + # parameters as add_column. + # * remove_column(table_name, column_name): Removes the column named +column_name+ from the table called +table_name+. + # * add_index(table_name, column_names, index_type, index_name): 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). + # * remove_index(table_name, index_name): 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 IrreversibleMigration 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 script/generate migration MyNewMigration + # where MyNewMigration is the name of your migration. The generator will + # create a file nnn_my_new_migration.rb in the db/migrate/ + # directory, where nnn is the next largest migration number. + # You may then edit the self.up and self.down methods of + # n MyNewMigration. + # + # To run migrations against the currently configured database, use + # rake migrate. This will update the database by running all of the + # pending migrations, creating the schema_info table if missing. + # + # To roll the database back to a previous migration version, use + # rake migrate VERSION=X where X is the version to which + # you wish to downgrade. If any of the migrations throw an + # IrreversibleMigration 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 diff --git a/vendor/rails/activerecord/lib/active_record/observer.rb b/vendor/rails/activerecord/lib/active_record/observer.rb new file mode 100644 index 0000000..262f12d --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/observer.rb @@ -0,0 +1,178 @@ +require 'singleton' +require 'set' + +module ActiveRecord + module Observing # :nodoc: + def self.included(base) + class << base + include ClassMethods + alias_method_chain :reset, :observers + end + end + + module ClassMethods + def reset_with_observers # :nodoc: + reset_without_observers + instantiate_observers + end + + # 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 + end + + # Instantiate the global ActiveRecord observers + def instantiate_observers + return if @observers.blank? + @observers.each do |observer| + if observer.respond_to?(:to_sym) # Symbol or String + observer.to_s.camelize.constantize.instance + elsif observer.respond_to?(:instance) + observer.instance + else + raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance" + end + end + end + + protected + # Notify observers when the observed class is subclassed. + def inherited(subclass) + super + changed + notify_observers :observed_class_inherited, subclass + 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 config.active_record.observers configuration setting in your + # config/environment.rb 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 + + class << self + # Attaches the observer to the supplied model classes. + def observe(*models) + define_method(:observed_classes) { Set.new(models) } + end + + # The class observed by default is inferred from the observer's class name: + # assert_equal [Person], PersonObserver.observed_class + def observed_class + name.scan(/(.*)Observer/)[0][0].constantize + end + end + + # Start observing the declared classes and their subclasses. + def initialize + Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass } + end + + # Send observed_method(object) if the method exists. + def update(observed_method, object) #:nodoc: + send(observed_method, object) if respond_to?(observed_method) + end + + # Special method sent by the observed class when it is inherited. + # Passes the new subclass. + def observed_class_inherited(subclass) #:nodoc: + self.class.observe(observed_classes + [subclass]) + add_observer!(subclass) + end + + protected + def observed_classes + Set.new([self.class.observed_class].flatten) + end + + def observed_subclasses + observed_classes.sum(&:subclasses) + end + + def add_observer!(klass) + klass.add_observer(self) + klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find) + end + end +end diff --git a/vendor/rails/activerecord/lib/active_record/query_cache.rb b/vendor/rails/activerecord/lib/active_record/query_cache.rb new file mode 100644 index 0000000..e79b3e0 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/query_cache.rb @@ -0,0 +1,64 @@ +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 diff --git a/vendor/rails/activerecord/lib/active_record/reflection.rb b/vendor/rails/activerecord/lib/active_record/reflection.rb new file mode 100644 index 0000000..df23898 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/reflection.rb @@ -0,0 +1,204 @@ +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(active_record.name, self) + end + + if source_reflection.nil? + raise HasManyThroughSourceAssociationNotFoundError.new(self) + end + + if source_reflection.options[:polymorphic] + raise HasManyThroughAssociationPolymorphicError.new(active_record.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 diff --git a/vendor/rails/activerecord/lib/active_record/schema.rb b/vendor/rails/activerecord/lib/active_record/schema.rb new file mode 100644 index 0000000..dc85446 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/schema.rb @@ -0,0 +1,58 @@ +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 diff --git a/vendor/rails/activerecord/lib/active_record/schema_dumper.rb b/vendor/rails/activerecord/lib/active_record/schema_dumper.rb new file mode 100644 index 0000000..98c1163 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/schema_dumper.rb @@ -0,0 +1,149 @@ +require 'stringio' +require 'bigdecimal' + +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 <
      "#{pk}") + end + else + tbl.print ", :id => false" + end + tbl.print ", :force => true" + tbl.puts " do |t|" + + column_specs = columns.map do |column| + raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil? + next if column.name == pk + spec = {} + spec[:name] = column.name.inspect + spec[:type] = column.type.inspect + spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal + spec[:precision] = column.precision.inspect if !column.precision.nil? + spec[:scale] = column.scale.inspect if !column.scale.nil? + spec[:null] = 'false' if !column.null + spec[:default] = default_string(column.default) if !column.default.nil? + (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")} + spec + end.compact + keys = [:name, :type, :limit, :precision, :scale, :default, :null] & column_specs.map{ |spec| spec.keys }.inject([]){ |a,b| a | b } + lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max } + format_string = lengths.map{ |len| "%-#{len}s" }.join("") + column_specs.each do |colspec| + values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len } + tbl.print " t.column " + tbl.print((format_string % values).gsub(/,\s*$/, '')) + 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 default_string(value) + case value + when BigDecimal + value.to_s + when Date, DateTime, Time + "'" + value.to_s(:db) + "'" + else + value.inspect + end + 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 diff --git a/vendor/rails/activerecord/lib/active_record/timestamp.rb b/vendor/rails/activerecord/lib/active_record/timestamp.rb new file mode 100644 index 0000000..e20882d --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/timestamp.rb @@ -0,0 +1,51 @@ +module ActiveRecord + # Active Record automatically timestamps create and update if the table has fields + # created_at/created_on or updated_at/updated_on. + # + # Timestamping can be turned off by setting + # ActiveRecord::Base.record_timestamps = false + # + # Keep in mind that, via inheritance, you can turn off timestamps on a per + # model basis by setting record_timestamps to false in the desired + # models. + # + # class Feed < ActiveRecord::Base + # self.record_timestamps = false + # # ... + # end + # + # Timestamps are in the local timezone by default but can use UTC by setting + # ActiveRecord::Base.default_timezone = :utc + module Timestamp + def self.included(base) #:nodoc: + super + + base.alias_method_chain :create, :timestamps + base.alias_method_chain :update, :timestamps + + base.cattr_accessor :record_timestamps + base.record_timestamps = true + 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 +end diff --git a/vendor/rails/activerecord/lib/active_record/transactions.rb b/vendor/rails/activerecord/lib/active_record/transactions.rb new file mode 100644 index 0000000..5e82fd2 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/transactions.rb @@ -0,0 +1,123 @@ +require 'active_record/vendor/simple.rb' +Transaction::Simple.send(:remove_method, :transaction) +require 'thread' + +module ActiveRecord + module Transactions # :nodoc: + class TransactionError < ActiveRecordError # :nodoc: + end + + def self.included(base) + base.extend(ClassMethods) + + base.class_eval do + [:destroy, :save].each do |method| + alias_method_chain method, :transactions + end + 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" } + increment_open_transactions + + 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 + decrement_open_transactions + trap('TERM', previous_handler) + end + end + + private + def increment_open_transactions #:nodoc: + open = Thread.current['open_transactions'] ||= 0 + Thread.current['start_db_transaction'] = open.zero? + Thread.current['open_transactions'] = open + 1 + end + + def decrement_open_transactions #:nodoc: + Thread.current['open_transactions'] -= 1 + 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 diff --git a/vendor/rails/activerecord/lib/active_record/validations.rb b/vendor/rails/activerecord/lib/active_record/validations.rb new file mode 100755 index 0000000..03ad90d --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/validations.rb @@ -0,0 +1,835 @@ +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 on(attribute) + # for the same attribute and ensure that this error object returns false when asked if empty?. More than one + # error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute). + # 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) + errors = @errors[attribute.to_s] + return nil if errors.nil? + errors.size == 1 ? errors.first : errors + 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 + full_messages + end + + # Returns true if no errors have been added. + def empty? + @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 + @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size } + end + + alias_method :count, :size + alias_method :length, :size + + # Return an XML representation of this error object. + def to_xml(options={}) + options[:root] ||= "errors" + options[:indent] ||= 2 + options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + + options[:builder].instruct! unless options.delete(:skip_instruct) + options[:builder].errors do |e| + full_messages.each { |msg| e.error(msg) } + end + end + 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.included(base) # :nodoc: + base.extend ClassMethods + base.class_eval do + alias_method_chain :save, :validation + alias_method_chain :save!, :validation + alias_method_chain :update_attribute, :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: + # * on - Specifies when this validation is active (default is :save, other options :create, :update) + # * allow_nil - Skip validation if attribute is nil. + # * if - 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: + # * message - A custom error message (default is: "doesn't match confirmation") + # * on - Specifies when this validation is active (default is :save, other options :create, :update) + # * if - 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: + # * message - A custom error message (default is: "must be accepted") + # * on - Specifies when this validation is active (default is :save, other options :create, :update) + # * accept - Specifies value that is considered accepted. The default value is a string "1", which + # makes it easy to relate to an HTML checkbox. + # * if - 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: + # * message - A custom error message (default is: "can't be blank") + # * on - Specifies when this validation is active (default is :save, other options :create, :update) + # * if - 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: + # * minimum - The minimum size of the attribute + # * maximum - The maximum size of the attribute + # * is - The exact size of the attribute + # * within - A range specifying the minimum and maximum size of the attribute + # * in - A synonym(or alias) for :within + # * allow_nil - Attribute may be nil; skip validation. + # + # * too_long - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)") + # * too_short - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)") + # * wrong_length - 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)") + # * message - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message + # * on - Specifies when this validation is active (default is :save, other options :create, :update) + # * if - 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: + # * message - Specifies a custom error message (default is: "has already been taken") + # * scope - One or more columns by which to limit the scope of the uniquness constraint. + # * case_sensitive - Looks for an exact match. Ignored by non-text columns (true by default). + # * if - 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], :case_sensitive => true } + configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) + + validates_each(attr_names,configuration) do |record, attr_name, value| + if value.nil? || (configuration[:case_sensitive] || !columns_hash[attr_name.to_s].text?) + condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}" + condition_params = [value] + else + condition_sql = "UPPER(#{record.class.table_name}.#{attr_name}) #{attribute_condition(value)}" + condition_params = [value.upcase] + end + 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: + # * message - A custom error message (default is: "is invalid") + # * with - The regular expression used to validate the format with (note: must be supplied!) + # * on Specifies when this validation is active (default is :save, other options :create, :update) + # * if - 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: + # * in - An enumerable object of available items + # * message - Specifies a customer error message (default is: "is not included in the list") + # * allow_nil - If set to true, skips this validation if the attribute is null (default is: false) + # * if - 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: + # * in - An enumerable object of items that the value shouldn't be part of + # * message - Specifies a customer error message (default is: "is reserved") + # * allow_nil - If set to true, skips this validation if the attribute is null (default is: false) + # * if - 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: + # * on Specifies when this validation is active (default is :save, other options :create, :update) + # * if - 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 integer is false) or applying it to the regular expression + # /^[\+\-]?\d+$/ (if integer is set to true). + # + # class Person < ActiveRecord::Base + # validates_numericality_of :value, :on => :create + # end + # + # Configuration options: + # * message - A custom error message (default is: "is not a number") + # * on Specifies when this validation is active (default is :save, other options :create, :update) + # * only_integer Specifies whether the value has to be an integer, e.g. an integral value (default is false) + # * allow_nil Skip validation if attribute is nil (default is false). Notice that for fixnum and float columns empty strings are converted to nil + # * if - 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 ||= {} + 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 diff --git a/vendor/rails/activerecord/lib/active_record/vendor/db2.rb b/vendor/rails/activerecord/lib/active_record/vendor/db2.rb new file mode 100644 index 0000000..812c8cc --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/vendor/db2.rb @@ -0,0 +1,362 @@ +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 diff --git a/vendor/rails/activerecord/lib/active_record/vendor/mysql.rb b/vendor/rails/activerecord/lib/active_record/vendor/mysql.rb new file mode 100644 index 0000000..2599f43 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/vendor/mysql.rb @@ -0,0 +1,1195 @@ +# $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 diff --git a/vendor/rails/activerecord/lib/active_record/vendor/simple.rb b/vendor/rails/activerecord/lib/active_record/vendor/simple.rb new file mode 100644 index 0000000..7ac3cd0 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/vendor/simple.rb @@ -0,0 +1,693 @@ +# :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 + # Marshaled (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 Marshaled + # 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 diff --git a/vendor/rails/activerecord/lib/active_record/version.rb b/vendor/rails/activerecord/lib/active_record/version.rb new file mode 100644 index 0000000..3158040 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/version.rb @@ -0,0 +1,9 @@ +module ActiveRecord + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 14 + TINY = 2 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/rails/activerecord/lib/active_record/wrappers/yaml_wrapper.rb b/vendor/rails/activerecord/lib/active_record/wrappers/yaml_wrapper.rb new file mode 100644 index 0000000..74f40a5 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/wrappers/yaml_wrapper.rb @@ -0,0 +1,15 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/lib/active_record/wrappings.rb b/vendor/rails/activerecord/lib/active_record/wrappings.rb new file mode 100644 index 0000000..e8b6018 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/wrappings.rb @@ -0,0 +1,58 @@ +module 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.included(base) + 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 diff --git a/vendor/rails/activerecord/lib/active_record/xml_serialization.rb b/vendor/rails/activerecord/lib/active_record/xml_serialization.rb new file mode 100644 index 0000000..5324ae5 --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/xml_serialization.rb @@ -0,0 +1,311 @@ +module ActiveRecord #:nodoc: + module XmlSerialization + # Builds an XML document to represent the model. Some configuration is + # availble through +options+, however more complicated cases should use + # override ActiveRecord's to_xml. + # + # By default the generated XML document will include the processing + # instruction and all object's attributes. For example: + # + # + # + # The First Topic + # David + # 1 + # false + # 0 + # 2000-01-01T08:28:00+12:00 + # 2003-07-16T09:28:00+1200 + # Have a nice day + # david@loudthinking.com + # + # 2004-04-15 + # + # + # This behavior can be controlled with :only, :except, + # :skip_instruct, :skip_types and :dasherize. The :only and + # :except options are the same as for the #attributes method. + # The default is to dasherize all column names, to disable this, + # set :dasherize to false. To not have the column type included + # in the XML output, set :skip_types to false. + # + # For instance: + # + # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ]) + # + # + # The First Topic + # David + # false + # Have a nice day + # david@loudthinking.com + # + # 2004-04-15 + # + # + # To include first level associations use :include + # + # firm.to_xml :include => [ :account, :clients ] + # + # + # + # 1 + # 1 + # 37signals + # + # + # 1 + # Summit + # + # + # 1 + # Microsoft + # + # + # + # 1 + # 50 + # + # + # + # To include any methods on the object(s) being called use :methods + # + # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ] + # + # + # # ... normal attributes as shown above ... + # 100000000000000000 + # 5 + # + # + # To call any Proc's on the object(s) use :procs. The Proc's + # are passed a modified version of the options hash that was + # given to #to_xml. + # + # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') } + # firm.to_xml :procs => [ proc ] + # + # + # # ... normal attributes as shown above ... + # def + # + # + # You may override the to_xml method in your ActiveRecord::Base + # subclasses if you need to. The general form of doing this is + # + # class IHaveMyOwnXML < ActiveRecord::Base + # def to_xml(options = {}) + # options[:indent] ||= 2 + # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + # xml.instruct! unless options[:skip_instruct] + # xml.level_one do + # xml.tag!(:second_level, 'content') + # end + # end + # end + def to_xml(options = {}) + XmlSerializer.new(self, options).to_s + end + end + + class XmlSerializer #:nodoc: + attr_reader :options + + def initialize(record, options = {}) + @record, @options = record, options + end + + def builder + @builder ||= begin + options[:indent] ||= 2 + builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + + unless options[:skip_instruct] + builder.instruct! + options[:skip_instruct] = true + end + + builder + end + end + + def root + root = (options[:root] || @record.class.to_s.underscore).to_s + dasherize? ? root.dasherize : root + end + + def dasherize? + !options.has_key?(:dasherize) || options[:dasherize] + end + + + # To replicate the behavior in ActiveRecord#attributes, + # :except takes precedence over :only. If :only is not set + # for a N level model but is set for the N+1 level models, + # then because :except is set to a default value, the second + # level model can have both :except and :only set. So if + # :only is set, always delete :except. + def serializable_attributes + attribute_names = @record.attribute_names + + if options[:only] + options.delete(:except) + attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s } + else + options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column) + attribute_names = attribute_names - options[:except].collect { |n| n.to_s } + end + + attribute_names.collect { |name| Attribute.new(name, @record) } + end + + def serializable_method_attributes + Array(options.delete(:methods)).collect { |name| MethodAttribute.new(name.to_s, @record) } + end + + + def add_attributes + (serializable_attributes + serializable_method_attributes).each do |attribute| + add_tag(attribute) + end + end + + def add_includes + if include_associations = options.delete(:include) + root_only_or_except = { :except => options[:except], + :only => options[:only] } + + 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 + + opts = options.merge(association_options) + + case @record.class.reflect_on_association(association).macro + when :has_many, :has_and_belongs_to_many + records = @record.send(association).to_a + unless records.empty? + tag = records.first.class.to_s.underscore.pluralize + tag = tag.dasherize if dasherize? + + builder.tag!(tag) do + records.each { |r| r.to_xml(opts) } + end + end + when :has_one, :belongs_to + if record = @record.send(association) + record.to_xml(opts.merge(:root => association)) + end + end + end + + options[:include] = include_associations + end + end + + def add_procs + if procs = options.delete(:procs) + [ *procs ].each do |proc| + proc.call(options) + end + end + end + + + def add_tag(attribute) + if attribute.needs_encoding? + builder.tag!( + dasherize? ? attribute.name.dasherize : attribute.name, + attribute.value.to_s, + attribute.decorations(!options[:skip_types]) + ) + else + builder.tag!( + dasherize? ? attribute.name.dasherize : attribute.name, + attribute.decorations(!options[:skip_types])) do + builder << attribute.value.to_s + end + end + end + + def serialize + builder.tag!(root) do + add_attributes + add_includes + add_procs + end + end + + alias_method :to_s, :serialize + + class Attribute #:nodoc: + attr_reader :name, :value, :type + + def initialize(name, record) + @name, @record = name, record + + @type = compute_type + @value = compute_value + end + + # There is a significant speed improvement if the value + # does not need to be escaped, as #tag! escapes all values + # to ensure that valid XML is generated. For known binary + # values, it is at least an order of magnitude faster to + # Base64 encode binary values and directly put them in the + # output XML than to pass the original value or the Base64 + # encoded value to the #tag! method. It definitely makes + # no sense to Base64 encode the value and then give it to + # #tag!, since that just adds additional overhead. + def needs_encoding? + ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type) + end + + def decorations(include_types = true) + decorations = {} + + if type == :binary + decorations[:encoding] = 'base64' + end + + if include_types && type != :string + decorations[:type] = type + end + + decorations + end + + protected + def compute_type + type = @record.class.columns_hash[name].type + + case type + when :text + :string + when :time + :datetime + else + type + end + end + + def compute_value + value = @record.send(name) + + if formatter = Hash::XML_FORMATTING[type.to_s] + value ? formatter.call(value) : nil + else + value + end + end + end + + class MethodAttribute < Attribute #:nodoc: + protected + def compute_type + Hash::XML_TYPE_NAMES[@record.send(name).class] || :string + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/aaa_create_tables_test.rb b/vendor/rails/activerecord/test/aaa_create_tables_test.rb new file mode 100644 index 0000000..2b456c1 --- /dev/null +++ b/vendor/rails/activerecord/test/aaa_create_tables_test.rb @@ -0,0 +1,59 @@ +# 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 + if ActiveRecord::Base.connection.supports_migrations? + eval(File.read("#{File.dirname(__FILE__)}/fixtures/db_definitions/schema.rb")) + else + recreate ActiveRecord::Base, '3' + end + 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 diff --git a/vendor/rails/activerecord/test/abstract_unit.rb b/vendor/rails/activerecord/test/abstract_unit.rb new file mode 100755 index 0000000..f30e190 --- /dev/null +++ b/vendor/rails/activerecord/test/abstract_unit.rb @@ -0,0 +1,73 @@ +$:.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?(*types) + types.any? do |type| + ActiveRecord::ConnectionAdapters.const_defined?(type) && + ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters.const_get(type)) + end +end + +ActiveRecord::Base.connection.class.class_eval do + cattr_accessor :query_count + + # Array of regexes of queries that are not counted against query_count + @@ignore_list = [/^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/] + + alias_method :execute_without_query_counting, :execute + def execute_with_query_counting(sql, name = nil, &block) + self.query_count += 1 unless @@ignore_list.any? { |r| sql =~ r } + execute_without_query_counting(sql, name, &block) + end +end + +#ActiveRecord::Base.logger = Logger.new(STDOUT) +#ActiveRecord::Base.colorize_logging = false diff --git a/vendor/rails/activerecord/test/active_schema_test_mysql.rb b/vendor/rails/activerecord/test/active_schema_test_mysql.rb new file mode 100644 index 0000000..b722235 --- /dev/null +++ b/vendor/rails/activerecord/test/active_schema_test_mysql.rb @@ -0,0 +1,31 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/adapter_test.rb b/vendor/rails/activerecord/test/adapter_test.rb new file mode 100644 index 0000000..0c12c4d --- /dev/null +++ b/vendor/rails/activerecord/test/adapter_test.rb @@ -0,0 +1,87 @@ +require '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 + + if ActiveRecord::Base.connection.adapter_name != "FrontBase" + 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 + +end diff --git a/vendor/rails/activerecord/test/adapter_test_sqlserver.rb b/vendor/rails/activerecord/test/adapter_test_sqlserver.rb new file mode 100644 index 0000000..11f244e --- /dev/null +++ b/vendor/rails/activerecord/test/adapter_test_sqlserver.rb @@ -0,0 +1,67 @@ +require 'abstract_unit' +require 'fixtures/default' +require 'fixtures/post' +require 'fixtures/task' + +class SqlServerAdapterTest < Test::Unit::TestCase + fixtures :posts, :tasks + + def setup + @connection = ActiveRecord::Base.connection + end + + def test_execute_without_block_closes_statement + assert_all_statements_used_are_closed do + @connection.execute("SELECT 1") + end + end + + def test_execute_with_block_closes_statement + assert_all_statements_used_are_closed do + @connection.execute("SELECT 1") do |sth| + assert !sth.finished?, "Statement should still be alive within block" + end + end + end + + def test_insert_with_identity_closes_statement + assert_all_statements_used_are_closed do + @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)") + end + end + + def test_insert_without_identity_closes_statement + assert_all_statements_used_are_closed do + @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)") + end + end + + def test_active_closes_statement + assert_all_statements_used_are_closed do + @connection.active? + end + end + + def assert_all_statements_used_are_closed(&block) + existing_handles = [] + ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle} + GC.disable + + yield + + used_handles = [] + ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle} + + assert_block "No statements were used within given block" do + used_handles.size > 0 + end + + ObjectSpace.each_object(DBI::StatementHandle) do |handle| + assert_block "Statement should have been closed within given block" do + handle.finished? + end + end + ensure + GC.enable + end +end diff --git a/vendor/rails/activerecord/test/aggregations_test.rb b/vendor/rails/activerecord/test/aggregations_test.rb new file mode 100644 index 0000000..8cd4bfe --- /dev/null +++ b/vendor/rails/activerecord/test/aggregations_test.rb @@ -0,0 +1,95 @@ +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 + + def test_allow_nil_gps_is_nil + assert_equal nil, customers(:zaphod).gps_location + end + + def test_allow_nil_gps_set_to_nil + customers(:david).gps_location = nil + customers(:david).save + customers(:david).reload + assert_equal nil, customers(:david).gps_location + end + + def test_allow_nil_set_address_attributes_to_nil + customers(:zaphod).address = nil + assert_equal nil, customers(:zaphod).attributes[:address_street] + assert_equal nil, customers(:zaphod).attributes[:address_city] + assert_equal nil, customers(:zaphod).attributes[:address_country] + end + + def test_allow_nil_address_set_to_nil + customers(:zaphod).address = nil + customers(:zaphod).save + customers(:zaphod).reload + assert_equal nil, customers(:zaphod).address + end + + def test_nil_raises_error_when_allow_nil_is_false + assert_raises(NoMethodError) { customers(:david).balance = nil } + end +end diff --git a/vendor/rails/activerecord/test/all.sh b/vendor/rails/activerecord/test/all.sh new file mode 100755 index 0000000..a6712cc --- /dev/null +++ b/vendor/rails/activerecord/test/all.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ -z "$1" ]; then + echo "Usage: $0 connections/" 1>&2 + exit 1 +fi + +ruby -I $1 -e 'Dir.foreach(".") { |file| require file if file =~ /_test.rb$/ }' diff --git a/vendor/rails/activerecord/test/ar_schema_test.rb b/vendor/rails/activerecord/test/ar_schema_test.rb new file mode 100644 index 0000000..c700b85 --- /dev/null +++ b/vendor/rails/activerecord/test/ar_schema_test.rb @@ -0,0 +1,33 @@ +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 diff --git a/vendor/rails/activerecord/test/association_callbacks_test.rb b/vendor/rails/activerecord/test/association_callbacks_test.rb new file mode 100644 index 0000000..1426fb7 --- /dev/null +++ b/vendor/rails/activerecord/test/association_callbacks_test.rb @@ -0,0 +1,124 @@ +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 + diff --git a/vendor/rails/activerecord/test/association_inheritance_reload.rb b/vendor/rails/activerecord/test/association_inheritance_reload.rb new file mode 100644 index 0000000..a3d5722 --- /dev/null +++ b/vendor/rails/activerecord/test/association_inheritance_reload.rb @@ -0,0 +1,14 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/associations_cascaded_eager_loading_test.rb b/vendor/rails/activerecord/test/associations_cascaded_eager_loading_test.rb new file mode 100644 index 0000000..642e4fa --- /dev/null +++ b/vendor/rails/activerecord/test/associations_cascaded_eager_loading_test.rb @@ -0,0 +1,106 @@ +require '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 2, 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 diff --git a/vendor/rails/activerecord/test/associations_extensions_test.rb b/vendor/rails/activerecord/test/associations_extensions_test.rb new file mode 100644 index 0000000..e80a2b9 --- /dev/null +++ b/vendor/rails/activerecord/test/associations_extensions_test.rb @@ -0,0 +1,42 @@ +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_named_two_extensions_on_habtm + assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_twice.find_most_recent + assert_equal projects(:active_record), developers(:david).projects_extended_by_name_twice.find_least_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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/associations_go_eager_test.rb b/vendor/rails/activerecord/test/associations_go_eager_test.rb new file mode 100644 index 0000000..ddcd72d --- /dev/null +++ b/vendor/rails/activerecord/test/associations_go_eager_test.rb @@ -0,0 +1,363 @@ +require '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 + + def test_count_with_include + assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15") + end +end diff --git a/vendor/rails/activerecord/test/associations_join_model_test.rb b/vendor/rails/activerecord/test/associations_join_model_test.rb new file mode 100644 index 0000000..b5d48db --- /dev/null +++ b/vendor/rails/activerecord/test/associations_join_model_test.rb @@ -0,0 +1,388 @@ +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 authors(:david).categories.include?(categories(:general)) + end + + def test_has_many_inherited + assert authors(:mary).categories.include?(categories(:sti_test)) + end + + def test_inherited_has_many + assert categories(:sti_test).authors.include?(authors(:mary)) + end + + def test_has_many_uniq_through_join_model + assert_equal 2, authors(:mary).categorized_posts.size + assert_equal 1, authors(:mary).unique_categorized_posts.size + end + + def test_polymorphic_has_many + assert posts(:welcome).taggings.include?(taggings(:welcome_general)) + 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 + options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' } + assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options) + 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 + + def test_raise_error_when_adding_to_has_many_through + assert_raise(ActiveRecord::ReadOnlyAssociation) { posts(:thinking).tags << tags(:general) } + assert_raise(ActiveRecord::ReadOnlyAssociation) { posts(:thinking).tags.push tags(:general) } + assert_raise(ActiveRecord::ReadOnlyAssociation) { posts(:thinking).tags.concat tags(:general) } + assert_raise(ActiveRecord::ReadOnlyAssociation) { posts(:thinking).tags.build(:name => 'foo') } + assert_raise(ActiveRecord::ReadOnlyAssociation) { posts(:thinking).tags.create(:name => 'foo') } + end + + def test_has_many_through_sum_uses_calculations + assert_nothing_raised { authors(:david).comments.sum(:post_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 diff --git a/vendor/rails/activerecord/test/associations_test.rb b/vendor/rails/activerecord/test/associations_test.rb new file mode 100755 index 0000000..f99322d --- /dev/null +++ b/vendor/rails/activerecord/test/associations_test.rb @@ -0,0 +1,1654 @@ +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 AssociationProxyTest < Test::Unit::TestCase + fixtures :authors, :posts + + def test_proxy_accessors + welcome = posts(:welcome) + assert_equal welcome, welcome.author.proxy_owner + assert_equal welcome.class.reflect_on_association(:author), welcome.author.proxy_reflection + welcome.author.class # force load target + assert_equal welcome.author, welcome.author.proxy_target + + david = authors(:david) + assert_equal david, david.posts.proxy_owner + assert_equal david.class.reflect_on_association(:posts), david.posts.proxy_reflection + david.posts.first # force load target + assert_equal david.posts, david.posts.proxy_target + + assert_equal david, david.posts_with_extension.testing_proxy_owner + assert_equal david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection + david.posts_with_extension.first # force load target + assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target + 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_has_one_cache_nils + assert_nil companies(:another_firm).account + assert_queries(0) { companies(:another_firm).account } + 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 + + def test_not_resaved_when_unchanged + firm = Firm.find(:first, :include => :account) + assert_queries(1) { firm.save! } + + firm = Firm.find(:first) + firm.account = Account.find(:first) + assert_queries(1) { firm.save! } + + firm = Firm.find(:first).clone + firm.account = Account.find(:first) + assert_queries(2) { firm.save! } + + firm = Firm.find(:first).clone + firm.account = Account.find(:first).clone + assert_queries(2) { firm.save! } + 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_with_counter_sql + assert_equal 2, Firm.find(:first).clients.count + end + + def test_counting + assert_equal 2, Firm.find(:first).plain_clients.count + end + + def test_counting_with_single_conditions + assert_equal 2, Firm.find(:first).plain_clients.count('1=1') + end + + def test_counting_with_single_hash + assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1') + end + + def test_counting_with_column_name_and_hash + assert_equal 2, Firm.find(:first).plain_clients.count(:all, :conditions => '1=1') + 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_using_create + first_firm = companies(:first_firm) + assert_equal 2, first_firm.plain_clients.size + natural = first_firm.plain_clients.create(:name => "Natural Company") + assert_equal 3, first_firm.plain_clients.length + assert_equal 3, first_firm.plain_clients.size + 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_build_without_loading_association + first_topic = topics(:first) + Reply.column_names + + assert_equal 1, first_topic.replies.length + + assert_no_queries do + first_topic.replies.build(:title => "Not saved", :content => "Superstars") + assert_equal 2, first_topic.replies.size + end + + assert_equal 2, first_topic.replies.to_ary.size + end + + def test_create_without_loading_association + first_firm = companies(:first_firm) + Firm.column_names + Client.column_names + + assert_equal 1, first_firm.clients_of_firm.size + first_firm.clients_of_firm.reset + + assert_queries(1) do + first_firm.clients_of_firm.create(:name => "Superstars") + end + + assert_equal 2, first_firm.clients_of_firm.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}") }.reverse + + 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 + assert_equal developers, new_project.developers + end + + def test_habtm_unique_order_preserved + assert_equal [developers(:poor_jamis), developers(:jamis), developers(:david)], projects(:active_record).non_unique_developers + assert_equal [developers(:poor_jamis), developers(:jamis), developers(:david)], projects(:active_record).developers + 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 + assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated + end + + def test_build_by_new_record + devel = Developer.new(:name => "Marcel", :salary => 75000) + proj1 = devel.projects.build(:name => "Make bed") + proj2 = devel.projects.build(:name => "Lie in it") + assert_equal devel.projects.last, proj2 + assert proj2.new_record? + devel.save + assert !devel.new_record? + assert !proj2.new_record? + assert_equal devel.projects.last, proj2 + assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated + end + + def test_create + devel = Developer.find(1) + proj = devel.projects.create("name" => "Projekt") + assert_equal devel.projects.last, proj + assert !proj.new_record? + assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated + end + + def test_create_by_new_record + devel = Developer.new(:name => "Marcel", :salary => 75000) + proj1 = devel.projects.create(:name => "Make bed") + proj2 = devel.projects.create(:name => "Lie in it") + assert_equal devel.projects.last, proj2 + assert proj2.new_record? + devel.save + assert !devel.new_record? + assert !proj2.new_record? + assert_equal devel.projects.last, proj2 + assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated + 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 + + def test_join_with_group + group = Developer.columns.inject([]) do |g, c| + g << "developers.#{c.name}" + g << "developers_projects_2.#{c.name}" + end + Project.columns.each { |c| group << "projects.#{c.name}" } + + assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",")).size + end +end diff --git a/vendor/rails/activerecord/test/attribute_methods_test.rb b/vendor/rails/activerecord/test/attribute_methods_test.rb new file mode 100755 index 0000000..0fa13c6 --- /dev/null +++ b/vendor/rails/activerecord/test/attribute_methods_test.rb @@ -0,0 +1,49 @@ +require 'abstract_unit' + +class AttributeMethodsTest < Test::Unit::TestCase + def setup + @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup + @target = Class.new(ActiveRecord::Base) + @target.table_name = 'topics' + end + + def teardown + ActiveRecord::Base.send(:attribute_method_suffixes).clear + ActiveRecord::Base.attribute_method_suffix *@old_suffixes + end + + + def test_match_attribute_method_query_returns_match_data + assert_not_nil md = @target.match_attribute_method?('title=') + assert_equal 'title', md.pre_match + assert_equal ['='], md.captures + + %w(_hello_world ist! _maybe?).each do |suffix| + @target.class_eval "def attribute#{suffix}(*args) args end" + @target.attribute_method_suffix suffix + + assert_not_nil md = @target.match_attribute_method?("title#{suffix}") + assert_equal 'title', md.pre_match + assert_equal [suffix], md.captures + end + end + + def test_declared_attribute_method_affects_respond_to_and_method_missing + topic = @target.new(:title => 'Budget') + assert topic.respond_to?('title') + assert_equal 'Budget', topic.title + assert !topic.respond_to?('title_hello_world') + assert_raise(NoMethodError) { topic.title_hello_world } + + %w(_hello_world _it! _candidate= able?).each do |suffix| + @target.class_eval "def attribute#{suffix}(*args) args end" + @target.attribute_method_suffix suffix + + meth = "title#{suffix}" + assert topic.respond_to?(meth) + assert_equal ['title'], topic.send(meth) + assert_equal ['title', 'a'], topic.send(meth, 'a') + assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) + end + end +end diff --git a/vendor/rails/activerecord/test/base_test.rb b/vendor/rails/activerecord/test/base_test.rb new file mode 100755 index 0000000..9518e0a --- /dev/null +++ b/vendor/rails/activerecord/test/base_test.rb @@ -0,0 +1,1419 @@ +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, :accounts + + 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" + ) + + # For adapters which support microsecond resolution. + if current_adapter?(:PostgreSQLAdapter) + assert_equal 11, Topic.find(1).written_on.sec + assert_equal 223300, Topic.find(1).written_on.usec + assert_equal 9900, Topic.find(2).written_on.usec + end + end + + def test_destroy + topic = Topic.find(1) + assert_equal topic, topic.destroy, 'topic.destroy did not return self' + assert topic.frozen?, 'topic not frozen after destroy' + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } + 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 2, Topic.find(1).replies_count + + Topic.increment_counter("replies_count", 1) + assert_equal 3, 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 -2, Topic.find(2).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, :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_utc_as_time_zone_and_new + # Oracle and SQLServer do not have a TIME datatype. + return true if current_adapter?(:SQLServerAdapter, :OracleAdapter) + + Topic.default_timezone = :utc + attributes = { "bonus_time(1i)"=>"2000", + "bonus_time(2i)"=>"1", + "bonus_time(3i)"=>"1", + "bonus_time(4i)"=>"10", + "bonus_time(5i)"=>"35", + "bonus_time(6i)"=>"50" } + topic = Topic.new(attributes) + assert_equal Time.utc(2000, 1, 1, 10, 35, 50), 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, :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 + + class NumericData < ActiveRecord::Base + self.table_name = 'numeric_data' + end + + def test_numeric_fields + m = NumericData.new( + :bank_balance => 1586.43, + :big_bank_balance => BigDecimal("1000234000567.95"), + :world_population => 6000000000, + :my_house_population => 3 + ) + assert m.save + + m1 = NumericData.find(m.id) + assert_not_nil m1 + + # As with migration_test.rb, we should make world_population >= 2**62 + # to cover 64-bit platforms and test it is a Bignum, but the main thing + # is that it's an Integer. + assert_kind_of Integer, m1.world_population + assert_equal 6000000000, m1.world_population + + assert_kind_of Fixnum, m1.my_house_population + assert_equal 3, m1.my_house_population + + assert_kind_of BigDecimal, m1.bank_balance + assert_equal BigDecimal("1586.43"), m1.bank_balance + + assert_kind_of BigDecimal, m1.big_bank_balance + assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance + 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("", inverted["bar"]) + assert_equal("", inverted["quux"]) + end + + def test_sql_injection_via_find + assert_raises(ActiveRecord::RecordNotFound) do + Topic.find("123456 OR id > 0") + 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 + + def test_quoting_arrays + replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ]) + assert_equal topics(:first).replies.size, replies.size + + replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ]) + assert_equal 0, replies.size + 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 1, topics(:first).replies_count + topics(:first).increment! :replies_count + assert_equal 2, topics(:first, :reload).replies_count + + topics(:first).increment(:replies_count).increment!(:replies_count) + assert_equal 4, 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 3, topics(:first).replies_count + + topics(:first).decrement!(:replies_count) + assert_equal 2, topics(:first, :reload).replies_count + + topics(:first).decrement(:replies_count).decrement!(:replies_count) + assert_equal 0, 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 co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" + res5 = nil + assert_nothing_raised do + res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", + :joins => "p, comments co", + :select => "p.id") + end + + assert_equal res4, res5 + + res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" + res7 = nil + assert_nothing_raised do + res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", + :joins => "p, comments co", + :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_scoped_find_order + # Test order in scope + scoped_developers = Developer.with_scope(:find => { :limit => 1, :order => 'salary DESC' }) do + Developer.find(:all) + end + assert_equal 'Jamis', scoped_developers.first.name + assert scoped_developers.include?(developers(:jamis)) + # Test scope without order and order in find + scoped_developers = Developer.with_scope(:find => { :limit => 1 }) do + Developer.find(:all, :order => 'salary DESC') + end + # Test scope order + find order, find has priority + scoped_developers = Developer.with_scope(:find => { :limit => 3, :order => 'id DESC' }) do + Developer.find(:all, :order => 'salary ASC') + end + assert scoped_developers.include?(developers(:poor_jamis)) + assert scoped_developers.include?(developers(:david)) + assert scoped_developers.include?(developers(:dev_10)) + # Test without scoped find conditions to ensure we get the right thing + developers = Developer.find(:all, :order => 'id', :limit => 1) + assert scoped_developers.include?(developers(:david)) + end + + def test_scoped_find_limit_offset_including_has_many_association + topics = Topic.with_scope(:find => {:limit => 1, :offset => 1, :include => :replies}) do + Topic.find(:all, :order => "topics.id") + end + assert_equal 1, topics.size + assert_equal 2, topics.first.id + 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 "", xml.first(7) + assert xml.include?(%(The First Topic)) + assert xml.include?(%(David)) + assert xml.include?(%(1)) + assert xml.include?(%(1)) + assert xml.include?(%(#{written_on_in_current_timezone})) + assert xml.include?(%(Have a nice day)) + assert xml.include?(%(david@loudthinking.com)) + assert xml.match(%()) + if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter) + assert xml.include?(%(#{last_read_in_current_timezone})) + else + assert xml.include?(%(2004-04-15)) + end + # Oracle and DB2 don't have true boolean or time-only fields + unless current_adapter?(:OracleAdapter, :DB2Adapter) + assert xml.include?(%(false)), "Approved should be a boolean" + assert xml.include?(%(#{bonus_time_in_current_timezone})) + end + end + + def test_to_xml_skipping_attributes + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => :title) + assert_equal "", xml.first(7) + assert !xml.include?(%(The First Topic)) + assert xml.include?(%(David)) + + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [ :title, :author_name ]) + assert !xml.include?(%(The First Topic)) + assert !xml.include?(%(David)) + end + + def test_to_xml_including_has_many_association + xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies) + assert_equal "", xml.first(7) + assert xml.include?(%()) + assert xml.include?(%(The Second Topic's of the day)) + end + + def test_array_to_xml_including_has_one_association + xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account) + assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true)) + end + + def test_array_to_xml_including_belongs_to_association + xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true)) + 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?("") + + xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm) + assert xml.include?("") + end + + def test_to_xml_including_multiple_associations + xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ]) + assert_equal "", xml.first(6) + assert xml.include?(%()) + assert xml.include?(%()) + 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 "", xml.first(6) + assert xml.include?(%(Summit)) + assert xml.include?(%()) + end + + def test_to_xml_including_methods + xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true) + assert_equal "", xml.first(9) + assert xml.include?(%(I am Jack's profound disappointment)) + 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 + + def test_to_param_should_return_string + assert_kind_of String, Client.find(:first).to_param + 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 diff --git a/vendor/rails/activerecord/test/binary_test.rb b/vendor/rails/activerecord/test/binary_test.rb new file mode 100644 index 0000000..38a5d50 --- /dev/null +++ b/vendor/rails/activerecord/test/binary_test.rb @@ -0,0 +1,37 @@ +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 diff --git a/vendor/rails/activerecord/test/calculations_test.rb b/vendor/rails/activerecord/test/calculations_test.rb new file mode 100644 index 0000000..9a0d043 --- /dev/null +++ b/vendor/rails/activerecord/test/calculations_test.rb @@ -0,0 +1,199 @@ +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_kind_of Float, value + assert_in_delta 53.0, value, 0.001 + end + + def test_should_get_maximum_of_field + assert_equal 60, Account.maximum(:credit_limit) + end + + def test_should_get_maximum_of_field_with_include + assert_equal 50, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") + end + + def test_should_get_maximum_of_field_with_scoped_include + Account.with_scope :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do + assert_equal 50, Account.maximum(:credit_limit) + end + 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(#{QUOTED_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.#{QUOTED_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_not_overshadow_enumerable_sum + assert_equal 6, [1, 2, 3].sum(&:abs) + 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, :foo => :bar) } + assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) } + end + + def test_should_count_selected_field_with_include + assert_equal 5, Account.count(:distinct => true, :include => :firm) + assert_equal 3, Account.count(:distinct => true, :include => :firm, :select => :credit_limit) + end +end diff --git a/vendor/rails/activerecord/test/callbacks_test.rb b/vendor/rails/activerecord/test/callbacks_test.rb new file mode 100644 index 0000000..3a4dac9 --- /dev/null +++ b/vendor/rails/activerecord/test/callbacks_test.rb @@ -0,0 +1,364 @@ +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 diff --git a/vendor/rails/activerecord/test/class_inheritable_attributes_test.rb b/vendor/rails/activerecord/test/class_inheritable_attributes_test.rb new file mode 100644 index 0000000..a3f94e3 --- /dev/null +++ b/vendor/rails/activerecord/test/class_inheritable_attributes_test.rb @@ -0,0 +1,32 @@ +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 diff --git a/vendor/rails/activerecord/test/column_alias_test.rb b/vendor/rails/activerecord/test/column_alias_test.rb new file mode 100644 index 0000000..19526cf --- /dev/null +++ b/vendor/rails/activerecord/test/column_alias_test.rb @@ -0,0 +1,17 @@ +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 diff --git a/vendor/rails/activerecord/test/connection_test_firebird.rb b/vendor/rails/activerecord/test/connection_test_firebird.rb new file mode 100644 index 0000000..4760a46 --- /dev/null +++ b/vendor/rails/activerecord/test/connection_test_firebird.rb @@ -0,0 +1,8 @@ +require 'abstract_unit' + +class ConnectionTest < Test::Unit::TestCase + def test_charset_properly_set + fb_conn = ActiveRecord::Base.connection.instance_variable_get(:@connection) + assert_equal 'UTF8', fb_conn.database.character_set + end +end diff --git a/vendor/rails/activerecord/test/connections/native_db2/connection.rb b/vendor/rails/activerecord/test/connections/native_db2/connection.rb new file mode 100644 index 0000000..921ac28 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_db2/connection.rb @@ -0,0 +1,25 @@ +print "Using native DB2\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'db2', + :host => 'localhost', + :username => 'arunit', + :password => 'arunit', + :database => 'arunit' + }, + 'arunit2' => { + :adapter => 'db2', + :host => 'localhost', + :username => 'arunit', + :password => 'arunit', + :database => 'arunit2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_firebird/connection.rb b/vendor/rails/activerecord/test/connections/native_firebird/connection.rb new file mode 100644 index 0000000..c198a99 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_firebird/connection.rb @@ -0,0 +1,26 @@ +print "Using native Firebird\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'firebird', + :host => 'localhost', + :username => 'rails', + :password => 'rails', + :database => 'activerecord_unittest', + :charset => 'UTF8' + }, + 'arunit2' => { + :adapter => 'firebird', + :host => 'localhost', + :username => 'rails', + :password => 'rails', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_frontbase/connection.rb b/vendor/rails/activerecord/test/connections/native_frontbase/connection.rb new file mode 100644 index 0000000..11dd8d2 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_frontbase/connection.rb @@ -0,0 +1,27 @@ +puts 'Using native Frontbase' +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'frontbase', + :host => 'localhost', + :username => 'rails', + :password => '', + :database => 'activerecord_unittest', + :session_name => "unittest-#{$$}" + }, + 'arunit2' => { + :adapter => 'frontbase', + :host => 'localhost', + :username => 'rails', + :password => '', + :database => 'activerecord_unittest2', + :session_name => "unittest-#{$$}" + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_mysql/connection.rb b/vendor/rails/activerecord/test/connections/native_mysql/connection.rb new file mode 100644 index 0000000..9a91a75 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_mysql/connection.rb @@ -0,0 +1,22 @@ +print "Using native MySQL\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'mysql', + :username => 'rails', + :encoding => 'utf8', + :database => 'activerecord_unittest', + }, + 'arunit2' => { + :adapter => 'mysql', + :username => 'rails', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_openbase/connection.rb b/vendor/rails/activerecord/test/connections/native_openbase/connection.rb new file mode 100644 index 0000000..deaa73d --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_openbase/connection.rb @@ -0,0 +1,21 @@ +print "Using native OpenBase\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'openbase', + :username => 'admin', + :database => 'activerecord_unittest', + }, + 'arunit2' => { + :adapter => 'openbase', + :username => 'admin', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_oracle/connection.rb b/vendor/rails/activerecord/test/connections/native_oracle/connection.rb new file mode 100644 index 0000000..e127e36 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_oracle/connection.rb @@ -0,0 +1,27 @@ +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.configurations = { + 'arunit' => { + :adapter => 'oracle', + :username => 'arunit', + :password => 'arunit', + :database => db, + }, + 'arunit2' => { + :adapter => 'oracle', + :username => 'arunit2', + :password => 'arunit2', + :database => db + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_postgresql/connection.rb b/vendor/rails/activerecord/test/connections/native_postgresql/connection.rb new file mode 100644 index 0000000..a75fc6e --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_postgresql/connection.rb @@ -0,0 +1,23 @@ +print "Using native PostgreSQL\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'postgresql', + :username => 'postgres', + :database => 'activerecord_unittest', + :min_messages => 'warning' + }, + 'arunit2' => { + :adapter => 'postgresql', + :username => 'postgres', + :database => 'activerecord_unittest2', + :min_messages => 'warning' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_sqlite/connection.rb b/vendor/rails/activerecord/test/connections/native_sqlite/connection.rb new file mode 100644 index 0000000..14ae999 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_sqlite/connection.rb @@ -0,0 +1,34 @@ +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) + ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite', :database => db_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(clazz.name) + 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(clazz.name) + 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')) diff --git a/vendor/rails/activerecord/test/connections/native_sqlite3/connection.rb b/vendor/rails/activerecord/test/connections/native_sqlite3/connection.rb new file mode 100644 index 0000000..008ea68 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_sqlite3/connection.rb @@ -0,0 +1,34 @@ +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) + ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite3', :database => db_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(clazz.name) + 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(clazz.name) + 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')) diff --git a/vendor/rails/activerecord/test/connections/native_sqlite3/in_memory_connection.rb b/vendor/rails/activerecord/test/connections/native_sqlite3/in_memory_connection.rb new file mode 100644 index 0000000..32bf6a2 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_sqlite3/in_memory_connection.rb @@ -0,0 +1,18 @@ +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")) diff --git a/vendor/rails/activerecord/test/connections/native_sqlserver/connection.rb b/vendor/rails/activerecord/test/connections/native_sqlserver/connection.rb new file mode 100644 index 0000000..626a72b --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_sqlserver/connection.rb @@ -0,0 +1,23 @@ +print "Using native SQLServer\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'sqlserver', + :host => 'localhost', + :username => 'sa', + :database => 'activerecord_unittest' + }, + 'arunit2' => { + :adapter => 'sqlserver', + :host => 'localhost', + :username => 'sa', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_sqlserver_odbc/connection.rb b/vendor/rails/activerecord/test/connections/native_sqlserver_odbc/connection.rb new file mode 100644 index 0000000..41fd672 --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_sqlserver_odbc/connection.rb @@ -0,0 +1,25 @@ +print "Using native SQLServer via ODBC\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'sqlserver', + :mode => 'ODBC', + :host => 'localhost', + :username => 'sa', + :dsn => 'activerecord_unittest' + }, + 'arunit2' => { + :adapter => 'sqlserver', + :mode => 'ODBC', + :host => 'localhost', + :username => 'sa', + :dsn => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/connections/native_sybase/connection.rb b/vendor/rails/activerecord/test/connections/native_sybase/connection.rb new file mode 100644 index 0000000..ec3e9eb --- /dev/null +++ b/vendor/rails/activerecord/test/connections/native_sybase/connection.rb @@ -0,0 +1,23 @@ +print "Using native Sybase Open Client\n" +require_dependency 'fixtures/course' +require 'logger' + +ActiveRecord::Base.logger = Logger.new("debug.log") + +ActiveRecord::Base.configurations = { + 'arunit' => { + :adapter => 'sybase', + :host => 'database_ASE', + :username => 'sa', + :database => 'activerecord_unittest' + }, + 'arunit2' => { + :adapter => 'sybase', + :host => 'database_ASE', + :username => 'sa', + :database => 'activerecord_unittest2' + } +} + +ActiveRecord::Base.establish_connection 'arunit' +Course.establish_connection 'arunit2' diff --git a/vendor/rails/activerecord/test/copy_table_sqlite.rb b/vendor/rails/activerecord/test/copy_table_sqlite.rb new file mode 100644 index 0000000..f3f5f1c --- /dev/null +++ b/vendor/rails/activerecord/test/copy_table_sqlite.rb @@ -0,0 +1,64 @@ +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 diff --git a/vendor/rails/activerecord/test/datatype_test_postgresql.rb b/vendor/rails/activerecord/test/datatype_test_postgresql.rb new file mode 100644 index 0000000..c4c3318 --- /dev/null +++ b/vendor/rails/activerecord/test/datatype_test_postgresql.rb @@ -0,0 +1,52 @@ +require 'abstract_unit' + +class PostgresqlDatatype < ActiveRecord::Base +end + +class PGDataTypeTest < Test::Unit::TestCase + self.use_transactional_fixtures = false + + TABLE_NAME = 'postgresql_datatypes' + COLUMNS = [ + 'id SERIAL PRIMARY KEY', + 'commission_by_quarter INTEGER[]', + 'nicknames TEXT[]' + ] + + def setup + @connection = ActiveRecord::Base.connection + @connection.execute "CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" + @connection.execute "INSERT INTO #{TABLE_NAME} (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )" + @first = PostgresqlDatatype.find( 1 ) + end + + def teardown + @connection.execute "DROP TABLE #{TABLE_NAME}" + end + + def test_data_type_of_array_types + assert_equal :string, @first.column_for_attribute("commission_by_quarter").type + assert_equal :string, @first.column_for_attribute("nicknames").type + end + + def test_array_values + assert_equal '{35000,21000,18000,17000}', @first.commission_by_quarter + assert_equal '{foo,bar,baz}', @first.nicknames + end + + def test_update_integer_array + new_value = '{32800,95000,29350,17000}' + assert @first.commission_by_quarter = new_value + assert @first.save + assert @first.reload + assert_equal @first.commission_by_quarter, new_value + end + + def test_update_text_array + new_value = '{robby,robert,rob,robbie}' + assert @first.nicknames = new_value + assert @first.save + assert @first.reload + assert_equal @first.nicknames, new_value + end +end diff --git a/vendor/rails/activerecord/test/default_test_firebird.rb b/vendor/rails/activerecord/test/default_test_firebird.rb new file mode 100644 index 0000000..4f3d14c --- /dev/null +++ b/vendor/rails/activerecord/test/default_test_firebird.rb @@ -0,0 +1,16 @@ +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 diff --git a/vendor/rails/activerecord/test/defaults_test.rb b/vendor/rails/activerecord/test/defaults_test.rb new file mode 100644 index 0000000..aba3c66 --- /dev/null +++ b/vendor/rails/activerecord/test/defaults_test.rb @@ -0,0 +1,16 @@ +require 'abstract_unit' +require 'fixtures/default' + +if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter) + class DefaultsTest < Test::Unit::TestCase + def test_default_integers + default = Default.new + assert_instance_of Fixnum, default.positive_integer + assert_equal 1, default.positive_integer + assert_instance_of Fixnum, default.negative_integer + assert_equal -1, default.negative_integer + assert_instance_of BigDecimal, default.decimal_number + assert_equal BigDecimal.new("2.78"), default.decimal_number + end + end +end diff --git a/vendor/rails/activerecord/test/deprecated_associations_test.rb b/vendor/rails/activerecord/test/deprecated_associations_test.rb new file mode 100755 index 0000000..d3abe14 --- /dev/null +++ b/vendor/rails/activerecord/test/deprecated_associations_test.rb @@ -0,0 +1,352 @@ +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 diff --git a/vendor/rails/activerecord/test/deprecated_finder_test.rb b/vendor/rails/activerecord/test/deprecated_finder_test.rb new file mode 100755 index 0000000..c1065b0 --- /dev/null +++ b/vendor/rails/activerecord/test/deprecated_finder_test.rb @@ -0,0 +1,135 @@ +require 'abstract_unit' +require 'fixtures/company' +require 'fixtures/topic' +require 'fixtures/reply' +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 diff --git a/vendor/rails/activerecord/test/empty_date_time_test.rb b/vendor/rails/activerecord/test/empty_date_time_test.rb new file mode 100644 index 0000000..0b50b09 --- /dev/null +++ b/vendor/rails/activerecord/test/empty_date_time_test.rb @@ -0,0 +1,25 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/task' + +class EmptyDateTimeTest < Test::Unit::TestCase + def test_assign_empty_date_time + task = Task.new + task.starting = '' + task.ending = nil + assert_nil task.starting + assert_nil task.ending + end + + def test_assign_empty_date + topic = Topic.new + topic.last_read = '' + assert_nil topic.last_read + end + + def test_assign_empty_time + topic = Topic.new + topic.bonus_time = '' + assert_nil topic.bonus_time + end +end diff --git a/vendor/rails/activerecord/test/finder_test.rb b/vendor/rails/activerecord/test/finder_test.rb new file mode 100644 index 0000000..626fe13 --- /dev/null +++ b/vendor/rails/activerecord/test/finder_test.rb @@ -0,0 +1,464 @@ +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 + + # find should handle strings that come from URLs + # (example: Category.find(params[:id])) + def test_find_with_string + assert_equal(Topic.find(1).title,Topic.find("1").title) + end + + def test_exists + assert Topic.exists?(1) + assert Topic.exists?("1") + assert Topic.exists?(:author_name => "David") + assert Topic.exists?(:author_name => "Mary", :approved => true) + assert Topic.exists?(["parent_id = ?", 1]) + assert !Topic.exists?(45) + assert !Topic.exists?("foo") + assert_raise(NoMethodError) { 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_array_conditions + assert Topic.find(1, :conditions => ["approved = ?", false]) + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) } + end + + def test_find_on_hash_conditions + assert Topic.find(1, :conditions => { :approved => false }) + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) } + end + + def test_find_on_multiple_hash_conditions + assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }) + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) } + assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) } + end + + def test_condition_array_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_condition_hash_interpolation + assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"}) + assert_nil Company.find(:first, :conditions => { :name => "37signals!"}) + assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on + end + + def test_hash_condition_find_malformed + assert_raises(ActiveRecord::StatementInvalid) { + Company.find(:first, :conditions => { :id => 2, :dhh => true }) + } + end + + def test_hash_condition_find_with_escaped_characters + Company.create("name" => "Ain't noth'n like' \#stuff") + assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff"}) + 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_empty_enumerable + quoted_nil = ActiveRecord::Base.connection.quote(nil) + assert_equal quoted_nil, bind('?', []) + assert_equal " in (#{quoted_nil})", bind(' in (?)', []) + assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', []) + end + + def test_bind_string + assert_equal "''", bind('?', '') + end + + def test_bind_record + o = Struct.new(:quoted_id).new(1) + assert_equal '1', bind('?', o) + + os = [o] * 3 + assert_equal '1,1,1', bind('?', os) + 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") + assert !sig38.new_record? + 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") + assert !another.new_record? + end + + def test_find_or_initialize_from_one_attribute + sig38 = Company.find_or_initialize_by_name("38signals") + assert_equal "38signals", sig38.name + assert sig38.new_record? + end + + def test_find_or_initialize_from_two_attributes + another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John") + assert_equal "Another topic", another.title + assert_equal "John", another.author_name + assert another.new_record? + 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_find_by_empty_ids + assert_equal [], Post.find([]) + end + + def test_find_by_empty_in_condition + assert_equal [], Post.find(:all, :conditions => ['id in (?)', []]) + end + + def test_find_by_records + p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc') + assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc') + assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc') + 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 diff --git a/vendor/rails/activerecord/test/fixtures/accounts.yml b/vendor/rails/activerecord/test/fixtures/accounts.yml new file mode 100644 index 0000000..a3d6742 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/accounts.yml @@ -0,0 +1,23 @@ +signals37: + id: 1 + firm_id: 1 + credit_limit: 50 + +unknown: + id: 2 + credit_limit: 50 + +rails_core_account: + id: 3 + firm_id: 6 + credit_limit: 50 + +last_account: + id: 4 + firm_id: 2 + credit_limit: 60 + +rails_core_account_2: + id: 5 + firm_id: 6 + credit_limit: 55 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/author.rb b/vendor/rails/activerecord/test/fixtures/author.rb new file mode 100644 index 0000000..a9102a2 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/author.rb @@ -0,0 +1,90 @@ +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 :posts_with_extension, :class_name => "Post" do #, :extend => ProxyTestExtension + def testing_proxy_owner + proxy_owner + end + def testing_proxy_reflection + proxy_reflection + end + def testing_proxy_target + proxy_target + end + end + 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 :categorized_posts, :through => :categorizations, :source => :post + has_many :unique_categorized_posts, :through => :categorizations, :source => :post, :uniq => true + + 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' +end diff --git a/vendor/rails/activerecord/test/fixtures/author_favorites.yml b/vendor/rails/activerecord/test/fixtures/author_favorites.yml new file mode 100644 index 0000000..e81fdac --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/author_favorites.yml @@ -0,0 +1,4 @@ +david_mary: + id: 1 + author_id: 1 + favorite_author_id: 2 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/authors.yml b/vendor/rails/activerecord/test/fixtures/authors.yml new file mode 100644 index 0000000..f59b84f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/authors.yml @@ -0,0 +1,7 @@ +david: + id: 1 + name: David + +mary: + id: 2 + name: Mary diff --git a/vendor/rails/activerecord/test/fixtures/auto_id.rb b/vendor/rails/activerecord/test/fixtures/auto_id.rb new file mode 100644 index 0000000..d720e2b --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/auto_id.rb @@ -0,0 +1,4 @@ +class AutoId < ActiveRecord::Base + def self.table_name () "auto_id_tests" end + def self.primary_key () "auto_id" end +end diff --git a/vendor/rails/activerecord/test/fixtures/bad_fixtures/attr_with_numeric_first_char b/vendor/rails/activerecord/test/fixtures/bad_fixtures/attr_with_numeric_first_char new file mode 100644 index 0000000..ef27947 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/bad_fixtures/attr_with_numeric_first_char @@ -0,0 +1 @@ +1b => 1 diff --git a/vendor/rails/activerecord/test/fixtures/bad_fixtures/attr_with_spaces b/vendor/rails/activerecord/test/fixtures/bad_fixtures/attr_with_spaces new file mode 100644 index 0000000..46fd6f2 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/bad_fixtures/attr_with_spaces @@ -0,0 +1 @@ +a b => 1 diff --git a/vendor/rails/activerecord/test/fixtures/bad_fixtures/blank_line b/vendor/rails/activerecord/test/fixtures/bad_fixtures/blank_line new file mode 100644 index 0000000..3ea1f71 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/bad_fixtures/blank_line @@ -0,0 +1,3 @@ +a => 1 + +b => 2 diff --git a/vendor/rails/activerecord/test/fixtures/bad_fixtures/duplicate_attributes b/vendor/rails/activerecord/test/fixtures/bad_fixtures/duplicate_attributes new file mode 100644 index 0000000..cc0236f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/bad_fixtures/duplicate_attributes @@ -0,0 +1,3 @@ +a => 1 +b => 2 +a => 3 diff --git a/vendor/rails/activerecord/test/fixtures/bad_fixtures/missing_value b/vendor/rails/activerecord/test/fixtures/bad_fixtures/missing_value new file mode 100644 index 0000000..fb59ec3 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/bad_fixtures/missing_value @@ -0,0 +1 @@ +a => diff --git a/vendor/rails/activerecord/test/fixtures/binary.rb b/vendor/rails/activerecord/test/fixtures/binary.rb new file mode 100644 index 0000000..950c459 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/binary.rb @@ -0,0 +1,2 @@ +class Binary < ActiveRecord::Base +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/categories.yml b/vendor/rails/activerecord/test/fixtures/categories.yml new file mode 100644 index 0000000..b0770a0 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/categories.yml @@ -0,0 +1,14 @@ +general: + id: 1 + name: General + type: Category + +technology: + id: 2 + name: Technology + type: Category + +sti_test: + id: 3 + name: Special category + type: SpecialCategory diff --git a/vendor/rails/activerecord/test/fixtures/categories/special_categories.yml b/vendor/rails/activerecord/test/fixtures/categories/special_categories.yml new file mode 100644 index 0000000..517fc8f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/categories/special_categories.yml @@ -0,0 +1,9 @@ +sub_special_1: + id: 100 + name: A special category in a subdir file + type: SpecialCategory + +sub_special_2: + id: 101 + name: Another special category + type: SpecialCategory diff --git a/vendor/rails/activerecord/test/fixtures/categories/subsubdir/arbitrary_filename.yml b/vendor/rails/activerecord/test/fixtures/categories/subsubdir/arbitrary_filename.yml new file mode 100644 index 0000000..389a04a --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/categories/subsubdir/arbitrary_filename.yml @@ -0,0 +1,4 @@ +sub_special_3: + id: 102 + name: A special category in an arbitrarily named subsubdir file + type: SpecialCategory diff --git a/vendor/rails/activerecord/test/fixtures/categories_ordered.yml b/vendor/rails/activerecord/test/fixtures/categories_ordered.yml new file mode 100644 index 0000000..294a636 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/categories_ordered.yml @@ -0,0 +1,7 @@ +--- !omap +<% 100.times do |i| %> +- fixture_no_<%= i %>: + id: <%= i %> + name: <%= "Category #{i}" %> + type: Category +<% end %> diff --git a/vendor/rails/activerecord/test/fixtures/categories_posts.yml b/vendor/rails/activerecord/test/fixtures/categories_posts.yml new file mode 100644 index 0000000..9b67ab4 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/categories_posts.yml @@ -0,0 +1,23 @@ +general_welcome: + category_id: 1 + post_id: 1 + +technology_welcome: + category_id: 2 + post_id: 1 + +general_thinking: + category_id: 1 + post_id: 2 + +general_sti_habtm: + category_id: 1 + post_id: 6 + +sti_test_sti_habtm: + category_id: 3 + post_id: 6 + +general_hello: + category_id: 1 + post_id: 4 diff --git a/vendor/rails/activerecord/test/fixtures/categorization.rb b/vendor/rails/activerecord/test/fixtures/categorization.rb new file mode 100644 index 0000000..1059432 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/categorization.rb @@ -0,0 +1,5 @@ +class Categorization < ActiveRecord::Base + belongs_to :post + belongs_to :category + belongs_to :author +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/categorizations.yml b/vendor/rails/activerecord/test/fixtures/categorizations.yml new file mode 100644 index 0000000..c5b6fc9 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/categorizations.yml @@ -0,0 +1,17 @@ +david_welcome_general: + id: 1 + author_id: 1 + post_id: 1 + category_id: 1 + +mary_thinking_sti: + id: 2 + author_id: 2 + post_id: 2 + category_id: 3 + +mary_thinking_general: + id: 3 + author_id: 2 + post_id: 2 + category_id: 1 diff --git a/vendor/rails/activerecord/test/fixtures/category.rb b/vendor/rails/activerecord/test/fixtures/category.rb new file mode 100644 index 0000000..6917c51 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/category.rb @@ -0,0 +1,21 @@ +class 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 diff --git a/vendor/rails/activerecord/test/fixtures/column_name.rb b/vendor/rails/activerecord/test/fixtures/column_name.rb new file mode 100644 index 0000000..ec07205 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/column_name.rb @@ -0,0 +1,3 @@ +class ColumnName < ActiveRecord::Base + def self.table_name () "colnametests" end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/comment.rb b/vendor/rails/activerecord/test/fixtures/comment.rb new file mode 100644 index 0000000..3eab263 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/comment.rb @@ -0,0 +1,23 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/comments.yml b/vendor/rails/activerecord/test/fixtures/comments.yml new file mode 100644 index 0000000..236bdb2 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/comments.yml @@ -0,0 +1,59 @@ +greetings: + id: 1 + post_id: 1 + body: Thank you for the welcome + type: Comment + +more_greetings: + id: 2 + post_id: 1 + body: Thank you again for the welcome + type: Comment + +does_it_hurt: + id: 3 + post_id: 2 + body: Don't think too hard + type: SpecialComment + +eager_sti_on_associations_vs_comment: + id: 5 + post_id: 4 + body: Very Special type + type: VerySpecialComment + +eager_sti_on_associations_s_comment1: + id: 6 + post_id: 4 + body: Special type + type: SpecialComment + +eager_sti_on_associations_s_comment2: + id: 7 + post_id: 4 + body: Special type 2 + type: SpecialComment + +eager_sti_on_associations_comment: + id: 8 + post_id: 4 + body: Normal type + type: Comment + +check_eager_sti_on_associations: + id: 9 + post_id: 5 + body: Normal type + type: Comment + +check_eager_sti_on_associations2: + id: 10 + post_id: 5 + body: Special Type + type: SpecialComment + +eager_other_comment1: + id: 11 + post_id: 7 + body: go crazy + type: SpecialComment diff --git a/vendor/rails/activerecord/test/fixtures/companies.yml b/vendor/rails/activerecord/test/fixtures/companies.yml new file mode 100644 index 0000000..f2e638d --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/companies.yml @@ -0,0 +1,50 @@ +first_client: + id: 2 + type: Client + firm_id: 1 + client_of: 2 + name: Summit + ruby_type: Client + +first_firm: + id: 1 + type: Firm + name: 37signals + ruby_type: Firm + +second_client: + id: 3 + type: Client + firm_id: 1 + client_of: 1 + name: Microsoft + ruby_type: Client + +another_firm: + id: 4 + type: Firm + name: Flamboyant Software + ruby_type: Firm + +another_client: + id: 5 + type: Client + firm_id: 4 + client_of: 4 + name: Ex Nihilo + ruby_type: Client + +rails_core: + id: 6 + name: RailsCore + type: DependentFirm + +leetsoft: + id: 7 + name: Leetsoft + client_of: 6 + +jadedpixel: + id: 8 + name: Jadedpixel + client_of: 6 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/company.rb b/vendor/rails/activerecord/test/fixtures/company.rb new file mode 100755 index 0000000..250deeb --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/company.rb @@ -0,0 +1,90 @@ +class 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" + + def arbitrary_method + "I am Jack's profound disappointment" + end +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_many :plain_clients, :class_name => 'Client' + + 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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/company_in_module.rb b/vendor/rails/activerecord/test/fixtures/company_in_module.rb new file mode 100644 index 0000000..7372134 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/company_in_module.rb @@ -0,0 +1,61 @@ +module 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 diff --git a/vendor/rails/activerecord/test/fixtures/computer.rb b/vendor/rails/activerecord/test/fixtures/computer.rb new file mode 100644 index 0000000..cc8deb1 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/computer.rb @@ -0,0 +1,3 @@ +class Computer < ActiveRecord::Base + belongs_to :developer, :foreign_key=>'developer' +end diff --git a/vendor/rails/activerecord/test/fixtures/computers.yml b/vendor/rails/activerecord/test/fixtures/computers.yml new file mode 100644 index 0000000..daf969d --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/computers.yml @@ -0,0 +1,4 @@ +workstation: + id: 1 + developer: 1 + extendedWarranty: 1 diff --git a/vendor/rails/activerecord/test/fixtures/course.rb b/vendor/rails/activerecord/test/fixtures/course.rb new file mode 100644 index 0000000..8a40fa7 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/course.rb @@ -0,0 +1,3 @@ +class Course < ActiveRecord::Base + has_many :entrants +end diff --git a/vendor/rails/activerecord/test/fixtures/courses.yml b/vendor/rails/activerecord/test/fixtures/courses.yml new file mode 100644 index 0000000..5ee1916 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/courses.yml @@ -0,0 +1,7 @@ +ruby: + id: 1 + name: Ruby Development + +java: + id: 2 + name: Java Development diff --git a/vendor/rails/activerecord/test/fixtures/customer.rb b/vendor/rails/activerecord/test/fixtures/customer.rb new file mode 100644 index 0000000..ccbe035 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/customer.rb @@ -0,0 +1,55 @@ +class Customer < ActiveRecord::Base + composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true + composed_of :balance, :class_name => "Money", :mapping => %w(balance amount) + composed_of :gps_location, :allow_nil => true +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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/customers.yml b/vendor/rails/activerecord/test/fixtures/customers.yml new file mode 100644 index 0000000..f802aac --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/customers.yml @@ -0,0 +1,17 @@ +david: + id: 1 + name: David + balance: 50 + address_street: Funny Street + address_city: Scary Town + address_country: Loony Land + gps_location: 35.544623640962634x-105.9309951055148 + +zaphod: + id: 2 + name: Zaphod + balance: 62 + address_street: Avenue Road + address_city: Hamlet Town + address_country: Nation Land + gps_location: NULL \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/db2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/db2.drop.sql new file mode 100644 index 0000000..50cffe3 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/db2.drop.sql @@ -0,0 +1,31 @@ +DROP TABLE accounts; +DROP TABLE funny_jokes; +DROP TABLE companies; +DROP TABLE topics; +DROP TABLE developers; +DROP TABLE projects; +DROP TABLE developers_projects; +DROP TABLE orders; +DROP TABLE customers; +DROP TABLE movies; +DROP TABLE subscribers; +DROP TABLE booleantests; +DROP TABLE auto_id_tests; +DROP TABLE entrants; +DROP TABLE colnametests; +DROP TABLE mixins; +DROP TABLE people; +DROP TABLE readers; +DROP TABLE binaries; +DROP TABLE computers; +DROP TABLE posts; +DROP TABLE comments; +DROP TABLE authors; +DROP TABLE tasks; +DROP TABLE categories; +DROP TABLE categories_posts; +DROP TABLE fk_test_has_pk; +DROP TABLE fk_test_has_fk; +DROP TABLE keyboards; +DROP TABLE legacy_things; +DROP TABLE numeric_data; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/db2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/db2.sql new file mode 100644 index 0000000..ee76fc4 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/db2.sql @@ -0,0 +1,226 @@ +CREATE TABLE accounts ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + firm_id INT DEFAULT NULL, + credit_limit INT DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE funny_jokes ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE companies ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + type VARCHAR(50) DEFAULT NULL, + ruby_type VARCHAR(50) DEFAULT NULL, + firm_id INT DEFAULT NULL, + name VARCHAR(50) DEFAULT NULL, + client_of INT DEFAULT NULL, + rating INT DEFAULT 1, + PRIMARY KEY (id) +); + +CREATE TABLE topics ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + title VARCHAR(255) DEFAULT NULL, + author_name VARCHAR(255) DEFAULT NULL, + author_email_address VARCHAR(255) DEFAULT NULL, + written_on TIMESTAMP DEFAULT NULL, + bonus_time TIME DEFAULT NULL, + last_read DATE DEFAULT NULL, + content VARCHAR(3000), + approved SMALLINT DEFAULT 1, + replies_count INT DEFAULT 0, + parent_id INT DEFAULT NULL, + type VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE developers ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(100) DEFAULT NULL, + salary INT DEFAULT 70000, + created_at TIMESTAMP DEFAULT NULL, + updated_at TIMESTAMP DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE projects ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(100) DEFAULT NULL, + type VARCHAR(255) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE developers_projects ( + developer_id INT NOT NULL, + project_id INT NOT NULL, + joined_on DATE DEFAULT NULL, + access_level SMALLINT DEFAULT 1 +); + +CREATE TABLE orders ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(100) DEFAULT NULL, + billing_customer_id INT DEFAULT NULL, + shipping_customer_id INT DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE customers ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(100) DEFAULT NULL, + balance INT DEFAULT 0, + address_street VARCHAR(100) DEFAULT NULL, + address_city VARCHAR(100) DEFAULT NULL, + address_country VARCHAR(100) DEFAULT NULL, + gps_location VARCHAR(100) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE movies ( + movieid INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(100) DEFAULT NULL, + PRIMARY KEY (movieid) +); + +CREATE TABLE subscribers ( + nick VARCHAR(100) NOT NULL, + name VARCHAR(100) DEFAULT NULL, + PRIMARY KEY (nick) +); + +CREATE TABLE booleantests ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + value INT DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE auto_id_tests ( + auto_id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + value INT DEFAULT NULL, + PRIMARY KEY (auto_id) +); + +CREATE TABLE entrants ( + id INT NOT NULL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + course_id INT NOT NULL +); + +CREATE TABLE colnametests ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + references INT NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE mixins ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + parent_id INT DEFAULT NULL, + pos INT DEFAULT NULL, + created_at TIMESTAMP DEFAULT NULL, + updated_at TIMESTAMP DEFAULT NULL, + lft INT DEFAULT NULL, + rgt INT DEFAULT NULL, + root_id INT DEFAULT NULL, + type VARCHAR(40) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE people ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + first_name VARCHAR(40) NOT NULL, + lock_version INT DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE TABLE readers ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + post_id INT NOT NULL, + person_id INT NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE binaries ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + data BLOB(50000), + PRIMARY KEY (id) +); + +CREATE TABLE computers ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + developer INT NOT NULL, + extendedWarranty INT NOT NULL +); + +CREATE TABLE posts ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + author_id INT DEFAULT NULL, + title VARCHAR(255) DEFAULT NULL, + type VARCHAR(255) DEFAULT NULL, + body VARCHAR(3000) DEFAULT NULL +); + +CREATE TABLE comments ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + post_id INT DEFAULT NULL, + type VARCHAR(255) DEFAULT NULL, + body VARCHAR(3000) DEFAULT NULL +); + +CREATE TABLE authors ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(255) DEFAULT NULL +); + +CREATE TABLE tasks ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + starting TIMESTAMP DEFAULT NULL, + ending TIMESTAMP DEFAULT NULL +); + +CREATE TABLE categories ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(255) NOT NULL, + type VARCHAR(40) DEFAULT NULL +); + +CREATE TABLE categories_posts ( + category_id INT NOT NULL, + post_id INT NOT NULL +); + +CREATE TABLE keyboards ( + key_number INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + name VARCHAR(255) +); + +CREATE TABLE fk_test_has_pk ( + id INT NOT NULL PRIMARY KEY +); + +CREATE TABLE fk_test_has_fk ( + id INT NOT NULL PRIMARY KEY, + fk_id INT NOT NULL, + + FOREIGN KEY (fk_id) REFERENCES fk_test_has_pk(id) +); + +--This table has an altered lock_version column name +CREATE TABLE legacy_things ( + id INT GENERATED BY DEFAULT AS IDENTITY (START WITH 10000), + tps_report_number INT DEFAULT NULL, + version INT DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE TABLE numeric_data ( + id INT NOT NULL PRIMARY KEY, + bank_balance DECIMAL(10,2), + big_bank_balance DECIMAL(15,2), + world_population DECIMAL(10), + my_house_population DECIMAL(2), + decimal_number_with_default DECIMAL(3,2) DEFAULT 2.78 +); diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/db22.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/db22.drop.sql new file mode 100644 index 0000000..df00ffd --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/db22.drop.sql @@ -0,0 +1,2 @@ +DROP TABLE courses; + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/db22.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/db22.sql new file mode 100644 index 0000000..853e2c7 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/db22.sql @@ -0,0 +1,5 @@ +CREATE TABLE courses ( + id INT NOT NULL PRIMARY KEY, + name VARCHAR(255) NOT NULL +); + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/firebird.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird.drop.sql new file mode 100644 index 0000000..2e43243 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird.drop.sql @@ -0,0 +1,61 @@ +DROP TABLE accounts; +DROP TABLE funny_jokes; +DROP TABLE companies; +DROP TABLE topics; +DROP TABLE developers; +DROP TABLE projects; +DROP TABLE developers_projects; +DROP TABLE orders; +DROP TABLE customers; +DROP TABLE movies; +DROP TABLE subscribers; +DROP TABLE booleantests; +DROP TABLE auto_id_tests; +DROP TABLE entrants; +DROP TABLE colnametests; +DROP TABLE mixins; +DROP TABLE people; +DROP TABLE readers; +DROP TABLE binaries; +DROP TABLE computers; +DROP TABLE posts; +DROP TABLE comments; +DROP TABLE authors; +DROP TABLE tasks; +DROP TABLE categories; +DROP TABLE categories_posts; +DROP TABLE fk_test_has_fk; +DROP TABLE fk_test_has_pk; +DROP TABLE keyboards; +DROP TABLE defaults; +DROP TABLE legacy_things; +DROP TABLE numeric_data; + +DROP DOMAIN D_BOOLEAN; + +DROP GENERATOR accounts_seq; +DROP GENERATOR funny_jokes_seq; +DROP GENERATOR companies_nonstd_seq; +DROP GENERATOR topics_seq; +DROP GENERATOR developers_seq; +DROP GENERATOR projects_seq; +DROP GENERATOR orders_seq; +DROP GENERATOR customers_seq; +DROP GENERATOR movies_seq; +DROP GENERATOR booleantests_seq; +DROP GENERATOR auto_id_tests_seq; +DROP GENERATOR entrants_seq; +DROP GENERATOR colnametests_seq; +DROP GENERATOR mixins_seq; +DROP GENERATOR people_seq; +DROP GENERATOR binaries_seq; +DROP GENERATOR computers_seq; +DROP GENERATOR posts_seq; +DROP GENERATOR comments_seq; +DROP GENERATOR authors_seq; +DROP GENERATOR tasks_seq; +DROP GENERATOR categories_seq; +DROP GENERATOR keyboards_seq; +DROP GENERATOR defaults_seq; +DROP GENERATOR legacy_things_seq; +DROP GENERATOR numeric_data_seq; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/firebird.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird.sql new file mode 100644 index 0000000..8ca0bf6 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird.sql @@ -0,0 +1,297 @@ +CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1) OR VALUE IS NULL); + +CREATE TABLE accounts ( + id BIGINT NOT NULL, + firm_id BIGINT, + credit_limit INTEGER, + PRIMARY KEY (id) +); +CREATE GENERATOR accounts_seq; +SET GENERATOR accounts_seq TO 10000; + +CREATE TABLE funny_jokes ( + id BIGINT NOT NULL, + name VARCHAR(50), + PRIMARY KEY (id) +); +CREATE GENERATOR funny_jokes_seq; +SET GENERATOR funny_jokes_seq TO 10000; + +CREATE TABLE companies ( + id BIGINT NOT NULL, + "TYPE" VARCHAR(50), + ruby_type VARCHAR(50), + firm_id BIGINT, + name VARCHAR(50), + client_of INTEGER, + rating INTEGER DEFAULT 1, + PRIMARY KEY (id) +); +CREATE GENERATOR companies_nonstd_seq; +SET GENERATOR companies_nonstd_seq TO 10000; + +CREATE TABLE topics ( + id BIGINT NOT NULL, + title VARCHAR(255), + author_name VARCHAR(255), + author_email_address VARCHAR(255), + written_on TIMESTAMP, + bonus_time TIME, + last_read DATE, + content VARCHAR(4000), + approved D_BOOLEAN DEFAULT 1, + replies_count INTEGER DEFAULT 0, + parent_id BIGINT, + "TYPE" VARCHAR(50), + PRIMARY KEY (id) +); +CREATE GENERATOR topics_seq; +SET GENERATOR topics_seq TO 10000; + +CREATE TABLE developers ( + id BIGINT NOT NULL, + name VARCHAR(100), + salary INTEGER DEFAULT 70000, + created_at TIMESTAMP, + updated_at TIMESTAMP, + PRIMARY KEY (id) +); +CREATE GENERATOR developers_seq; +SET GENERATOR developers_seq TO 10000; + +CREATE TABLE projects ( + id BIGINT NOT NULL, + name VARCHAR(100), + "TYPE" VARCHAR(255), + PRIMARY KEY (id) +); +CREATE GENERATOR projects_seq; +SET GENERATOR projects_seq TO 10000; + +CREATE TABLE developers_projects ( + developer_id BIGINT NOT NULL, + project_id BIGINT NOT NULL, + joined_on DATE, + access_level SMALLINT DEFAULT 1 +); + +CREATE TABLE orders ( + id BIGINT NOT NULL, + name VARCHAR(100), + billing_customer_id BIGINT, + shipping_customer_id BIGINT, + PRIMARY KEY (id) +); +CREATE GENERATOR orders_seq; +SET GENERATOR orders_seq TO 10000; + +CREATE TABLE customers ( + id BIGINT NOT NULL, + name VARCHAR(100), + balance INTEGER DEFAULT 0, + address_street VARCHAR(100), + address_city VARCHAR(100), + address_country VARCHAR(100), + gps_location VARCHAR(100), + PRIMARY KEY (id) +); +CREATE GENERATOR customers_seq; +SET GENERATOR customers_seq TO 10000; + +CREATE TABLE movies ( + movieid BIGINT NOT NULL, + name varchar(100), + PRIMARY KEY (movieid) +); +CREATE GENERATOR movies_seq; +SET GENERATOR movies_seq TO 10000; + +CREATE TABLE subscribers ( + nick VARCHAR(100) NOT NULL, + name VARCHAR(100), + PRIMARY KEY (nick) +); + +CREATE TABLE booleantests ( + id BIGINT NOT NULL, + "VALUE" D_BOOLEAN, + PRIMARY KEY (id) +); +CREATE GENERATOR booleantests_seq; +SET GENERATOR booleantests_seq TO 10000; + +CREATE TABLE auto_id_tests ( + auto_id BIGINT NOT NULL, + "VALUE" INTEGER, + PRIMARY KEY (auto_id) +); +CREATE GENERATOR auto_id_tests_seq; +SET GENERATOR auto_id_tests_seq TO 10000; + +CREATE TABLE entrants ( + id BIGINT NOT NULL, + name VARCHAR(255) NOT NULL, + course_id INTEGER NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR entrants_seq; +SET GENERATOR entrants_seq TO 10000; + +CREATE TABLE colnametests ( + id BIGINT NOT NULL, + "REFERENCES" INTEGER NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR colnametests_seq; +SET GENERATOR colnametests_seq TO 10000; + +CREATE TABLE mixins ( + id BIGINT NOT NULL, + parent_id BIGINT, + pos INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP, + lft INTEGER, + rgt INTEGER, + root_id BIGINT, + "TYPE" VARCHAR(40), + PRIMARY KEY (id) +); +CREATE GENERATOR mixins_seq; +SET GENERATOR mixins_seq TO 10000; + +CREATE TABLE people ( + id BIGINT NOT NULL, + first_name VARCHAR(40), + lock_version INTEGER DEFAULT 0 NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR people_seq; +SET GENERATOR people_seq TO 10000; + +CREATE TABLE readers ( + id BIGINT NOT NULL, + post_id BIGINT NOT NULL, + person_id BIGINT NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR readers_seq; +SET GENERATOR readers_seq TO 10000; + +CREATE TABLE binaries ( + id BIGINT NOT NULL, + data BLOB, + PRIMARY KEY (id) +); +CREATE GENERATOR binaries_seq; +SET GENERATOR binaries_seq TO 10000; + +CREATE TABLE computers ( + id BIGINT NOT NULL, + developer INTEGER NOT NULL, + "extendedWarranty" INTEGER NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR computers_seq; +SET GENERATOR computers_seq TO 10000; + +CREATE TABLE posts ( + id BIGINT NOT NULL, + author_id BIGINT, + title VARCHAR(255) NOT NULL, + "TYPE" VARCHAR(255) NOT NULL, + body VARCHAR(3000) NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR posts_seq; +SET GENERATOR posts_seq TO 10000; + +CREATE TABLE comments ( + id BIGINT NOT NULL, + post_id BIGINT NOT NULL, + "TYPE" VARCHAR(255) NOT NULL, + body VARCHAR(3000) NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR comments_seq; +SET GENERATOR comments_seq TO 10000; + +CREATE TABLE authors ( + id BIGINT NOT NULL, + name VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR authors_seq; +SET GENERATOR authors_seq TO 10000; + +CREATE TABLE tasks ( + id BIGINT NOT NULL, + "STARTING" TIMESTAMP, + ending TIMESTAMP, + PRIMARY KEY (id) +); +CREATE GENERATOR tasks_seq; +SET GENERATOR tasks_seq TO 10000; + +CREATE TABLE categories ( + id BIGINT NOT NULL, + name VARCHAR(255) NOT NULL, + "TYPE" VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR categories_seq; +SET GENERATOR categories_seq TO 10000; + +CREATE TABLE categories_posts ( + category_id BIGINT NOT NULL, + post_id BIGINT NOT NULL, + PRIMARY KEY (category_id, post_id) +); + +CREATE TABLE fk_test_has_pk ( + id BIGINT NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE fk_test_has_fk ( + id BIGINT NOT NULL, + fk_id BIGINT NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (fk_id) REFERENCES fk_test_has_pk(id) +); + +CREATE TABLE keyboards ( + key_number BIGINT NOT NULL, + name VARCHAR(50), + PRIMARY KEY (key_number) +); +CREATE GENERATOR keyboards_seq; +SET GENERATOR keyboards_seq TO 10000; + +CREATE TABLE defaults ( + id BIGINT NOT NULL, + default_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +CREATE GENERATOR defaults_seq; +SET GENERATOR defaults_seq TO 10000; + +CREATE TABLE legacy_things ( + id BIGINT NOT NULL, + tps_report_number INTEGER, + version INTEGER DEFAULT 0 NOT NULL, + PRIMARY KEY (id) +); +CREATE GENERATOR legacy_things_seq; +SET GENERATOR legacy_things_seq TO 10000; + +CREATE TABLE numeric_data ( + id BIGINT NOT NULL, + bank_balance DECIMAL(10,2), + big_bank_balance DECIMAL(15,2), + world_population DECIMAL(10), + my_house_population DECIMAL(2), + decimal_number_with_default DECIMAL(3,2) DEFAULT 2.78, + PRIMARY KEY (id) +); +CREATE GENERATOR numeric_data_seq; +SET GENERATOR numeric_data_seq TO 10000; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/firebird2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird2.drop.sql new file mode 100644 index 0000000..c59fb1f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird2.drop.sql @@ -0,0 +1,2 @@ +DROP TABLE courses; +DROP GENERATOR courses_seq; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/firebird2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird2.sql new file mode 100644 index 0000000..c1bc251 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/firebird2.sql @@ -0,0 +1,6 @@ +CREATE TABLE courses ( + id BIGINT NOT NULL PRIMARY KEY, + name VARCHAR(255) NOT NULL +); +CREATE GENERATOR courses_seq; +SET GENERATOR courses_seq TO 10000; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase.drop.sql new file mode 100644 index 0000000..5fc64f7 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase.drop.sql @@ -0,0 +1,31 @@ +DROP TABLE accounts CASCADE; +DROP TABLE funny_jokes CASCADE; +DROP TABLE companies CASCADE; +DROP TABLE topics CASCADE; +DROP TABLE developers CASCADE; +DROP TABLE projects CASCADE; +DROP TABLE developers_projects CASCADE; +DROP TABLE orders CASCADE; +DROP TABLE customers CASCADE; +DROP TABLE movies CASCADE; +DROP TABLE subscribers CASCADE; +DROP TABLE booleantests CASCADE; +DROP TABLE auto_id_tests CASCADE; +DROP TABLE entrants CASCADE; +DROP TABLE colnametests CASCADE; +DROP TABLE mixins CASCADE; +DROP TABLE people CASCADE; +DROP TABLE readers CASCADE; +DROP TABLE binaries CASCADE; +DROP TABLE computers CASCADE; +DROP TABLE posts CASCADE; +DROP TABLE comments CASCADE; +DROP TABLE authors CASCADE; +DROP TABLE tasks CASCADE; +DROP TABLE categories CASCADE; +DROP TABLE categories_posts CASCADE; +DROP TABLE fk_test_has_fk CASCADE; +DROP TABLE fk_test_has_pk CASCADE; +DROP TABLE keyboards CASCADE; +DROP TABLE legacy_things CASCADE; +DROP TABLE numeric_data CASCADE; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase.sql new file mode 100644 index 0000000..e102f65 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase.sql @@ -0,0 +1,262 @@ +CREATE TABLE accounts ( + id integer DEFAULT unique, + firm_id integer, + credit_limit integer, + PRIMARY KEY (id) +); +SET UNIQUE FOR accounts(id); + +CREATE TABLE funny_jokes ( + id integer DEFAULT unique, + firm_id integer default NULL, + name character varying(50), + PRIMARY KEY (id) +); +SET UNIQUE FOR funny_jokes(id); + +CREATE TABLE companies ( + id integer DEFAULT unique, + "type" character varying(50), + "ruby_type" character varying(50), + firm_id integer, + name character varying(50), + client_of integer, + rating integer default 1, + PRIMARY KEY (id) +); +SET UNIQUE FOR companies(id); + +CREATE TABLE topics ( + id integer DEFAULT unique, + title character varying(255), + author_name character varying(255), + author_email_address character varying(255), + written_on timestamp, + bonus_time time, + last_read date, + content varchar(65536), + approved boolean default true, + replies_count integer default 0, + parent_id integer, + "type" character varying(50), + PRIMARY KEY (id) +); +SET UNIQUE FOR topics(id); + +CREATE TABLE developers ( + id integer DEFAULT unique, + name character varying(100), + salary integer DEFAULT 70000, + created_at timestamp, + updated_at timestamp, + PRIMARY KEY (id) +); +SET UNIQUE FOR developers(id); + +CREATE TABLE projects ( + id integer DEFAULT unique, + name character varying(100), + type varchar(255), + PRIMARY KEY (id) +); +SET UNIQUE FOR projects(id); + +CREATE TABLE developers_projects ( + developer_id integer NOT NULL, + project_id integer NOT NULL, + joined_on date, + access_level integer default 1 +); + +CREATE TABLE orders ( + id integer DEFAULT unique, + name character varying(100), + billing_customer_id integer, + shipping_customer_id integer, + PRIMARY KEY (id) +); +SET UNIQUE FOR orders(id); + +CREATE TABLE customers ( + id integer DEFAULT unique, + name character varying(100), + balance integer default 0, + address_street character varying(100), + address_city character varying(100), + address_country character varying(100), + gps_location character varying(100), + PRIMARY KEY (id) +); +SET UNIQUE FOR customers(id); + +CREATE TABLE movies ( + movieid integer DEFAULT unique, + name varchar(65536), + PRIMARY KEY (movieid) +); +SET UNIQUE FOR movies(movieid); + +CREATE TABLE subscribers ( + nick varchar(65536) NOT NULL, + name varchar(65536), + PRIMARY KEY (nick) +); + +CREATE TABLE booleantests ( + id integer DEFAULT unique, + value boolean, + PRIMARY KEY (id) +); +SET UNIQUE FOR booleantests(id); + +CREATE TABLE auto_id_tests ( + auto_id integer DEFAULT unique, + value integer, + PRIMARY KEY (auto_id) +); +SET UNIQUE FOR auto_id_tests(auto_id); + +CREATE TABLE entrants ( + id integer DEFAULT unique, + name varchar(65536), + course_id integer, + PRIMARY KEY (id) +); +SET UNIQUE FOR entrants(id); + +CREATE TABLE colnametests ( + id integer DEFAULT unique, + "references" integer NOT NULL, + PRIMARY KEY (id) +); +SET UNIQUE FOR colnametests(id); + +CREATE TABLE mixins ( + id integer DEFAULT unique, + parent_id integer, + type character varying(100), + pos integer, + lft integer, + rgt integer, + root_id integer, + created_at timestamp, + updated_at timestamp, + PRIMARY KEY (id) +); +SET UNIQUE FOR mixins(id); + +CREATE TABLE people ( + id integer DEFAULT unique, + first_name varchar(65536), + lock_version integer default 0, + PRIMARY KEY (id) +); +SET UNIQUE FOR people(id); + +CREATE TABLE readers ( + id integer DEFAULT unique, + post_id INTEGER NOT NULL, + person_id INTEGER NOT NULL, + PRIMARY KEY (id) +); +SET UNIQUE FOR readers(id); + +CREATE TABLE binaries ( + id integer DEFAULT unique, + data BLOB, + PRIMARY KEY (id) +); +SET UNIQUE FOR binaries(id); + +CREATE TABLE computers ( + id integer DEFAULT unique, + developer integer NOT NULL, + "extendedWarranty" integer NOT NULL, + PRIMARY KEY (id) +); +SET UNIQUE FOR computers(id); + +CREATE TABLE posts ( + id integer DEFAULT unique, + author_id integer, + title varchar(255), + type varchar(255), + body varchar(65536), + PRIMARY KEY (id) +); +SET UNIQUE FOR posts(id); + +CREATE TABLE comments ( + id integer DEFAULT unique, + post_id integer, + type varchar(255), + body varchar(65536), + PRIMARY KEY (id) +); +SET UNIQUE FOR comments(id); + +CREATE TABLE authors ( + id integer DEFAULT unique, + name varchar(255) default NULL, + PRIMARY KEY (id) +); +SET UNIQUE FOR authors(id); + +CREATE TABLE tasks ( + id integer DEFAULT unique, + starting timestamp, + ending timestamp, + PRIMARY KEY (id) +); +SET UNIQUE FOR tasks(id); + +CREATE TABLE categories ( + id integer DEFAULT unique, + name varchar(255), + type varchar(255), + PRIMARY KEY (id) +); +SET UNIQUE FOR categories(id); + +CREATE TABLE categories_posts ( + category_id integer NOT NULL, + post_id integer NOT NULL +); + +CREATE TABLE fk_test_has_pk ( + id INTEGER NOT NULL PRIMARY KEY +); +SET UNIQUE FOR fk_test_has_pk(id); + +CREATE TABLE fk_test_has_fk ( + id INTEGER NOT NULL PRIMARY KEY, + fk_id INTEGER NOT NULL REFERENCES fk_test_has_fk(id) +); +SET UNIQUE FOR fk_test_has_fk(id); + +CREATE TABLE keyboards ( + key_number integer DEFAULT unique, + "name" character varying(50), + PRIMARY KEY (key_number) +); +SET UNIQUE FOR keyboards(key_number); + +create table "legacy_things" +( + "id" int, + "tps_report_number" int default NULL, + "version" int default 0 not null, + primary key ("id") +); +SET UNIQUE FOR legacy_things(id); + +CREATE TABLE "numeric_data" ( + "id" integer NOT NULL + "bank_balance" DECIMAL(10,2), + "big_bank_balance" DECIMAL(15,2), + "world_population" DECIMAL(10), + "my_house_population" DECIMAL(2), + "decimal_number_with_default" DECIMAL(3,2) DEFAULT 2.78, + primary key ("id") +); +SET UNIQUE FOR numeric_data(id); diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase2.drop.sql new file mode 100644 index 0000000..17b9ad4 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase2.drop.sql @@ -0,0 +1 @@ +DROP TABLE courses CASCADE; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase2.sql new file mode 100644 index 0000000..42f4bb7 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/frontbase2.sql @@ -0,0 +1,4 @@ +CREATE TABLE courses ( + id integer DEFAULT unique, + name varchar(100) +); diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/mysql.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql.drop.sql new file mode 100644 index 0000000..cb6a870 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql.drop.sql @@ -0,0 +1,31 @@ +DROP TABLE accounts; +DROP TABLE funny_jokes; +DROP TABLE companies; +DROP TABLE topics; +DROP TABLE developers; +DROP TABLE projects; +DROP TABLE developers_projects; +DROP TABLE customers; +DROP TABLE orders; +DROP TABLE movies; +DROP TABLE subscribers; +DROP TABLE booleantests; +DROP TABLE auto_id_tests; +DROP TABLE entrants; +DROP TABLE colnametests; +DROP TABLE mixins; +DROP TABLE people; +DROP TABLE readers; +DROP TABLE binaries; +DROP TABLE computers; +DROP TABLE tasks; +DROP TABLE posts; +DROP TABLE comments; +DROP TABLE authors; +DROP TABLE categories; +DROP TABLE categories_posts; +DROP TABLE fk_test_has_fk; +DROP TABLE fk_test_has_pk; +DROP TABLE keyboards; +DROP TABLE legacy_things; +DROP TABLE numeric_data; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/mysql.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql.sql new file mode 100755 index 0000000..61ba43e --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql.sql @@ -0,0 +1,228 @@ +CREATE TABLE `accounts` ( + `id` int(11) NOT NULL auto_increment, + `firm_id` int(11) default NULL, + `credit_limit` int(5) default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `funny_jokes` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(50) default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `companies` ( + `id` int(11) NOT NULL auto_increment, + `type` varchar(50) default NULL, + `ruby_type` varchar(50) default NULL, + `firm_id` int(11) default NULL, + `name` varchar(50) default NULL, + `client_of` int(11) default NULL, + `rating` int(11) default NULL default 1, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + + +CREATE TABLE `topics` ( + `id` int(11) NOT NULL auto_increment, + `title` varchar(255) default NULL, + `author_name` varchar(255) default NULL, + `author_email_address` varchar(255) default NULL, + `written_on` datetime default NULL, + `bonus_time` time default NULL, + `last_read` date default NULL, + `content` text, + `approved` tinyint(1) default 1, + `replies_count` int(11) default 0, + `parent_id` int(11) default NULL, + `type` varchar(50) default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `developers` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(100) default NULL, + `salary` int(11) default 70000, + `created_at` datetime default NULL, + `updated_at` datetime default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `projects` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(100) default NULL, + `type` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `developers_projects` ( + `developer_id` int(11) NOT NULL, + `project_id` int(11) NOT NULL, + `joined_on` date default NULL, + `access_level` smallint default 1 +) TYPE=InnoDB; + +CREATE TABLE `orders` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(100) default NULL, + `billing_customer_id` int(11) default NULL, + `shipping_customer_id` int(11) default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `customers` ( + `id` int(11) NOT NULL auto_increment, + `name` varchar(100) default NULL, + `balance` int(6) default 0, + `address_street` varchar(100) default NULL, + `address_city` varchar(100) default NULL, + `address_country` varchar(100) default NULL, + `gps_location` varchar(100) default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `movies` ( + `movieid` int(11) NOT NULL auto_increment, + `name` varchar(100) default NULL, + PRIMARY KEY (`movieid`) +) TYPE=InnoDB; + +CREATE TABLE `subscribers` ( + `nick` varchar(100) NOT NULL, + `name` varchar(100) default NULL, + PRIMARY KEY (`nick`) +) TYPE=InnoDB; + +CREATE TABLE `booleantests` ( + `id` int(11) NOT NULL auto_increment, + `value` integer default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `auto_id_tests` ( + `auto_id` int(11) NOT NULL auto_increment, + `value` integer default NULL, + PRIMARY KEY (`auto_id`) +) TYPE=InnoDB; + +CREATE TABLE `entrants` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `course_id` INTEGER NOT NULL +); + +CREATE TABLE `colnametests` ( + `id` int(11) NOT NULL auto_increment, + `references` int(11) NOT NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `mixins` ( + `id` int(11) NOT NULL auto_increment, + `parent_id` int(11) default NULL, + `pos` int(11) default NULL, + `created_at` datetime default NULL, + `updated_at` datetime default NULL, + `lft` int(11) default NULL, + `rgt` int(11) default NULL, + `root_id` int(11) default NULL, + `type` varchar(40) default NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `people` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `first_name` VARCHAR(40) NOT NULL, + `lock_version` INTEGER NOT NULL DEFAULT 0 +) TYPE=InnoDB; + +CREATE TABLE `readers` ( + `id` int(11) NOT NULL PRIMARY KEY, + `post_id` INTEGER NOT NULL, + `person_id` INTEGER NOT NULL +) TYPE=InnoDB; + +CREATE TABLE `binaries` ( + `id` int(11) NOT NULL auto_increment, + `data` mediumblob, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `computers` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `developer` INTEGER NOT NULL, + `extendedWarranty` INTEGER NOT NULL +) TYPE=InnoDB; + +CREATE TABLE `posts` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `author_id` INTEGER, + `title` VARCHAR(255) NOT NULL, + `body` TEXT NOT NULL, + `type` VARCHAR(255) NOT NULL +) TYPE=InnoDB; + +CREATE TABLE `comments` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `post_id` INTEGER NOT NULL, + `body` TEXT NOT NULL, + `type` VARCHAR(255) NOT NULL +) TYPE=InnoDB; + +CREATE TABLE `authors` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `name` VARCHAR(255) NOT NULL +) TYPE=InnoDB; + +CREATE TABLE `tasks` ( + `id` int(11) NOT NULL auto_increment, + `starting` datetime NOT NULL default '0000-00-00 00:00:00', + `ending` datetime NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `categories` ( + `id` int(11) NOT NULL auto_increment, + `name` VARCHAR(255) NOT NULL, + `type` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `categories_posts` ( + `category_id` int(11) NOT NULL, + `post_id` int(11) NOT NULL +) TYPE=InnoDB; + +CREATE TABLE `fk_test_has_pk` ( + `id` INTEGER NOT NULL PRIMARY KEY +) TYPE=InnoDB; + +CREATE TABLE `fk_test_has_fk` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `fk_id` INTEGER NOT NULL, + + FOREIGN KEY (`fk_id`) REFERENCES `fk_test_has_pk`(`id`) +) TYPE=InnoDB; + + +CREATE TABLE `keyboards` ( + `key_number` int(11) NOT NULL auto_increment primary key, + `name` varchar(50) default NULL +); + +-- Altered lock_version column name. +CREATE TABLE `legacy_things` ( + `id` int(11) NOT NULL auto_increment, + `tps_report_number` int(11) default NULL, + `version` int(11) NOT NULL default 0, + PRIMARY KEY (`id`) +) TYPE=InnoDB; + +CREATE TABLE `numeric_data` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `bank_balance` decimal(10,2), + `big_bank_balance` decimal(15,2), + `world_population` decimal(10), + `my_house_population` decimal(2), + `decimal_number_with_default` decimal(3,2) DEFAULT 2.78 +) TYPE=InnoDB; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/mysql2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql2.drop.sql new file mode 100644 index 0000000..df00ffd --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql2.drop.sql @@ -0,0 +1,2 @@ +DROP TABLE courses; + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/mysql2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql2.sql new file mode 100644 index 0000000..0bfd2e6 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/mysql2.sql @@ -0,0 +1,5 @@ +CREATE TABLE `courses` ( + `id` INTEGER NOT NULL PRIMARY KEY, + `name` VARCHAR(255) NOT NULL +) TYPE=InnoDB; + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/openbase.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase.drop.sql new file mode 100644 index 0000000..fb40e3f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase.drop.sql @@ -0,0 +1,2 @@ +DROP ALL +go \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/openbase.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase.sql new file mode 100644 index 0000000..c3da2ef --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase.sql @@ -0,0 +1,294 @@ +CREATE TABLE accounts ( + id integer UNIQUE INDEX DEFAULT _rowid, + firm_id integer, + credit_limit integer +) +go +CREATE PRIMARY KEY accounts (id) +go + +CREATE TABLE funny_jokes ( + id integer UNIQUE INDEX DEFAULT _rowid, + name char(50) DEFAULT NULL +) +go +CREATE PRIMARY KEY funny_jokes (id) +go + +CREATE TABLE companies ( + id integer UNIQUE INDEX DEFAULT _rowid, + type char(50), + ruby_type char(50), + firm_id integer, + name char(50), + client_of integer, + rating integer default 1 +) +go +CREATE PRIMARY KEY companies (id) +go + +CREATE TABLE developers_projects ( + developer_id integer NOT NULL, + project_id integer NOT NULL, + joined_on date, + access_level integer default 1 +) +go + +CREATE TABLE developers ( + id integer UNIQUE INDEX DEFAULT _rowid, + name char(100), + salary integer DEFAULT 70000, + created_at datetime, + updated_at datetime +) +go +CREATE PRIMARY KEY developers (id) +go + +CREATE TABLE projects ( + id integer UNIQUE INDEX DEFAULT _rowid, + name char(100), + type char(255) +) +go +CREATE PRIMARY KEY projects (id) +go + +CREATE TABLE topics ( + id integer UNIQUE INDEX DEFAULT _rowid, + title char(255), + author_name char(255), + author_email_address char(255), + written_on datetime, + bonus_time time, + last_read date, + content char(4096), + approved boolean default true, + replies_count integer default 0, + parent_id integer, + type char(50) +) +go +CREATE PRIMARY KEY topics (id) +go + +CREATE TABLE customers ( + id integer UNIQUE INDEX DEFAULT _rowid, + name char, + balance integer default 0, + address_street char, + address_city char, + address_country char, + gps_location char +) +go +CREATE PRIMARY KEY customers (id) +go + +CREATE TABLE orders ( + id integer UNIQUE INDEX DEFAULT _rowid, + name char, + billing_customer_id integer, + shipping_customer_id integer +) +go +CREATE PRIMARY KEY orders (id) +go + +CREATE TABLE movies ( + movieid integer UNIQUE INDEX DEFAULT _rowid, + name text +) +go +CREATE PRIMARY KEY movies (movieid) +go + +CREATE TABLE subscribers ( + nick CHAR(100) NOT NULL DEFAULT _rowid, + name CHAR(100) +) +go +CREATE PRIMARY KEY subscribers (nick) +go + +CREATE TABLE booleantests ( + id integer UNIQUE INDEX DEFAULT _rowid, + value boolean +) +go +CREATE PRIMARY KEY booleantests (id) +go + +CREATE TABLE defaults ( + id integer UNIQUE INDEX , + modified_date date default CURDATE(), + modified_date_function date default NOW(), + fixed_date date default '2004-01-01', + modified_time timestamp default NOW(), + modified_time_function timestamp default NOW(), + fixed_time timestamp default '2004-01-01 00:00:00.000000-00', + char1 char(1) default 'Y', + char2 char(50) default 'a char field', + char3 text default 'a text field' +) +go + +CREATE TABLE auto_id_tests ( + auto_id integer UNIQUE INDEX DEFAULT _rowid, + value integer +) +go +CREATE PRIMARY KEY auto_id_tests (auto_id) +go + +CREATE TABLE entrants ( + id integer UNIQUE INDEX , + name text, + course_id integer +) +go + +CREATE TABLE colnametests ( + id integer UNIQUE INDEX , + references integer NOT NULL +) +go + +CREATE TABLE mixins ( + id integer UNIQUE INDEX DEFAULT _rowid, + parent_id integer, + type char, + pos integer, + lft integer, + rgt integer, + root_id integer, + created_at timestamp, + updated_at timestamp +) +go +CREATE PRIMARY KEY mixins (id) +go + +CREATE TABLE people ( + id integer UNIQUE INDEX DEFAULT _rowid, + first_name text, + lock_version integer default 0 +) +go +CREATE PRIMARY KEY people (id) +go + +CREATE TABLE readers ( + id integer UNIQUE INDEX DEFAULT _rowid, + post_id integer NOT NULL, + person_id integer NOT NULL +) +go +CREATE PRIMARY KEY readers (id) +go + +CREATE TABLE binaries ( + id integer UNIQUE INDEX DEFAULT _rowid, + data object +) +go +CREATE PRIMARY KEY binaries (id) +go + +CREATE TABLE computers ( + id integer UNIQUE INDEX , + developer integer NOT NULL, + extendedWarranty integer NOT NULL +) +go + +CREATE TABLE posts ( + id integer UNIQUE INDEX , + author_id integer, + title char(255), + type char(255), + body text +) +go + +CREATE TABLE comments ( + id integer UNIQUE INDEX , + post_id integer, + type char(255), + body text +) +go + +CREATE TABLE authors ( + id integer UNIQUE INDEX , + name char(255) default NULL +) +go + +CREATE TABLE tasks ( + id integer UNIQUE INDEX DEFAULT _rowid, + starting datetime, + ending datetime +) +go +CREATE PRIMARY KEY tasks (id) +go + +CREATE TABLE categories ( + id integer UNIQUE INDEX , + name char(255), + type char(255) +) +go + +CREATE TABLE categories_posts ( + category_id integer NOT NULL, + post_id integer NOT NULL +) +go + +CREATE TABLE fk_test_has_pk ( + id INTEGER NOT NULL DEFAULT _rowid +) +go +CREATE PRIMARY KEY fk_test_has_pk (id) +go + +CREATE TABLE fk_test_has_fk ( + id INTEGER NOT NULL DEFAULT _rowid, + fk_id INTEGER NOT NULL REFERENCES fk_test_has_pk.id +) +go +CREATE PRIMARY KEY fk_test_has_fk (id) +go + +CREATE TABLE keyboards ( + key_number integer UNIQUE INDEX DEFAULT _rowid, + name char(50) +) +go +CREATE PRIMARY KEY keyboards (key_number) +go + +CREATE TABLE legacy_things ( + id INTEGER NOT NULL DEFAULT _rowid, + tps_report_number INTEGER default NULL, + version integer NOT NULL default 0 +) +go +CREATE PRIMARY KEY legacy_things (id) +go + +CREATE TABLE numeric_data ( + id INTEGER NOT NULL DEFAULT _rowid, + bank_balance DECIMAL(10,2), + big_bank_balance DECIMAL(15,2), + world_population DECIMAL(10), + my_house_population DECIMAL(2), + decimal_number_with_default DECIMAL(3,2) DEFAULT 2.78 +); +go +CREATE PRIMARY KEY numeric_data (id) +go diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/openbase2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase2.drop.sql new file mode 100644 index 0000000..ea1571d --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase2.drop.sql @@ -0,0 +1,2 @@ +DROP TABLE courses +go diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/openbase2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase2.sql new file mode 100644 index 0000000..a37c4f4 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/openbase2.sql @@ -0,0 +1,7 @@ +CREATE TABLE courses ( + id integer UNIQUE INDEX DEFAULT _rowid, + name text +) +go +CREATE PRIMARY KEY courses (id) +go \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/oracle.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle.drop.sql new file mode 100644 index 0000000..6fa01cd --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle.drop.sql @@ -0,0 +1,63 @@ +drop table accounts; +drop table funny_jokes; +drop table companies; +drop table topics; +drop synonym subjects; +drop table developers_projects; +drop table computers; +drop table developers; +drop table projects; +drop table customers; +drop table orders; +drop table movies; +drop table subscribers; +drop table booleantests; +drop table auto_id_tests; +drop table entrants; +drop table colnametests; +drop table mixins; +drop table people; +drop table readers; +drop table binaries; +drop table comments; +drop table authors; +drop table tasks; +drop table categories_posts; +drop table categories; +drop table posts; +drop table fk_test_has_pk; +drop table fk_test_has_fk; +drop table keyboards; +drop table legacy_things; +drop table numeric_data; + +drop sequence accounts_seq; +drop sequence funny_jokes_seq; +drop sequence companies_nonstd_seq; +drop sequence topics_seq; +drop sequence developers_seq; +drop sequence projects_seq; +drop sequence developers_projects_seq; +drop sequence customers_seq; +drop sequence orders_seq; +drop sequence movies_seq; +drop sequence subscribers_seq; +drop sequence booleantests_seq; +drop sequence auto_id_tests_seq; +drop sequence entrants_seq; +drop sequence colnametests_seq; +drop sequence mixins_seq; +drop sequence people_seq; +drop sequence binaries_seq; +drop sequence posts_seq; +drop sequence comments_seq; +drop sequence authors_seq; +drop sequence tasks_seq; +drop sequence computers_seq; +drop sequence categories_seq; +drop sequence categories_posts_seq; +drop sequence fk_test_has_pk_seq; +drop sequence fk_test_has_fk_seq; +drop sequence keyboards_seq; +drop sequence legacy_things_seq; +drop sequence numeric_data_seq; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/oracle.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle.sql new file mode 100644 index 0000000..ccf472c --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle.sql @@ -0,0 +1,302 @@ +create table companies ( + id integer not null, + type varchar(50) default null, + ruby_type varchar(50) default null, + firm_id integer default null references companies initially deferred disable, + name varchar(50) default null, + client_of integer default null references companies initially deferred disable, + companies_count integer default 0, + rating integer default 1, + primary key (id) +); + +-- non-standard sequence name used to test set_sequence_name +-- +create sequence companies_nonstd_seq minvalue 10000; + +create table funny_jokes ( + id integer not null, + name varchar(50) default null, + primary key (id) +); +create sequence funny_jokes_seq minvalue 10000; + +create table accounts ( + id integer not null, + firm_id integer default null references companies initially deferred disable, + credit_limit integer default null +); +create sequence accounts_seq minvalue 10000; + +create table topics ( + id integer not null, + title varchar(255) default null, + author_name varchar(255) default null, + author_email_address varchar(255) default null, + written_on timestamp default null, + bonus_time timestamp default null, + last_read timestamp default null, + content varchar(4000), + approved number(1) default 1, + replies_count integer default 0, + parent_id integer references topics initially deferred disable, + type varchar(50) default null, + primary key (id) +); +-- try again for 8i +create table topics ( + id integer not null, + title varchar(255) default null, + author_name varchar(255) default null, + author_email_address varchar(255) default null, + written_on date default null, + bonus_time date default null, + last_read date default null, + content varchar(4000), + approved number(1) default 1, + replies_count integer default 0, + parent_id integer references topics initially deferred disable, + type varchar(50) default null, + primary key (id) +); +create sequence topics_seq minvalue 10000; + +create synonym subjects for topics; + +create table developers ( + id integer not null, + name varchar(100) default null, + salary integer default 70000, + created_at timestamp default null, + updated_at timestamp default null, + primary key (id) +); +create sequence developers_seq minvalue 10000; + +create table projects ( + id integer not null, + name varchar(100) default null, + type varchar(255) default null, + primary key (id) +); +create sequence projects_seq minvalue 10000; + +create table developers_projects ( + developer_id integer not null references developers initially deferred disable, + project_id integer not null references projects initially deferred disable, + joined_on timestamp default null, + access_level integer default 1 +); +-- Try again for 8i +create table developers_projects ( + developer_id integer not null references developers initially deferred disable, + project_id integer not null references projects initially deferred disable, + joined_on date default null +); +create sequence developers_projects_seq minvalue 10000; + +create table orders ( + id integer not null, + name varchar(100) default null, + billing_customer_id integer default null, + shipping_customer_id integer default null, + primary key (id) +); +create sequence orders_seq minvalue 10000; + +create table customers ( + id integer not null, + name varchar(100) default null, + balance integer default 0, + address_street varchar(100) default null, + address_city varchar(100) default null, + address_country varchar(100) default null, + gps_location varchar(100) default null, + primary key (id) +); +create sequence customers_seq minvalue 10000; + +create table movies ( + movieid integer not null, + name varchar(100) default null, + primary key (movieid) +); +create sequence movies_seq minvalue 10000; + +create table subscribers ( + nick varchar(100) not null, + name varchar(100) default null, + primary key (nick) +); +create sequence subscribers_seq minvalue 10000; + +create table booleantests ( + id integer not null, + value integer default null, + primary key (id) +); +create sequence booleantests_seq minvalue 10000; + +create table auto_id_tests ( + auto_id integer not null, + value integer default null, + primary key (auto_id) +); +create sequence auto_id_tests_seq minvalue 10000; + +create table entrants ( + id integer not null primary key, + name varchar(255) not null, + course_id integer not null +); +create sequence entrants_seq minvalue 10000; + +create table colnametests ( + id integer not null, + references integer not null, + primary key (id) +); +create sequence colnametests_seq minvalue 10000; + +create table mixins ( + id integer not null, + parent_id integer default null references mixins initially deferred disable, + type varchar(40) default null, + pos integer default null, + lft integer default null, + rgt integer default null, + root_id integer default null, + created_at timestamp default null, + updated_at timestamp default null, + primary key (id) +); +-- try again for 8i +create table mixins ( + id integer not null, + parent_id integer default null references mixins initially deferred disable, + type varchar(40) default null, + pos integer default null, + lft integer default null, + rgt integer default null, + root_id integer default null, + created_at date default null, + updated_at date default null, + primary key (id) +); +create sequence mixins_seq minvalue 10000; + +create table people ( + id integer not null, + first_name varchar(40) null, + lock_version integer default 0, + primary key (id) +); +create sequence people_seq minvalue 10000; + +create table readers ( + id integer not null, + post_id integer not null, + person_id integer not null, + primary key (id) +); +create sequence readers_seq minvalue 10000; + +create table binaries ( + id integer not null, + data blob null, + primary key (id) +); +create sequence binaries_seq minvalue 10000; + +create table computers ( + id integer not null primary key, + developer integer not null references developers initially deferred disable, + "extendedWarranty" integer not null +); +create sequence computers_seq minvalue 10000; + +create table posts ( + id integer not null primary key, + author_id integer default null, + title varchar(255) default null, + type varchar(255) default null, + body varchar(3000) default null +); +create sequence posts_seq minvalue 10000; + +create table comments ( + id integer not null primary key, + post_id integer default null, + type varchar(255) default null, + body varchar(3000) default null +); +create sequence comments_seq minvalue 10000; + +create table authors ( + id integer not null primary key, + name varchar(255) default null +); +create sequence authors_seq minvalue 10000; + +create table tasks ( + id integer not null primary key, + starting date default null, + ending date default null +); +create sequence tasks_seq minvalue 10000; + +create table categories ( + id integer not null primary key, + name varchar(255) default null, + type varchar(255) default null +); +create sequence categories_seq minvalue 10000; + +create table categories_posts ( + category_id integer not null references categories initially deferred disable, + post_id integer not null references posts initially deferred disable +); +create sequence categories_posts_seq minvalue 10000; + +create table fk_test_has_pk ( + id integer not null primary key +); +create sequence fk_test_has_pk_seq minvalue 10000; + +create table fk_test_has_fk ( + id integer not null primary key, + fk_id integer not null references fk_test_has_fk initially deferred disable +); +create sequence fk_test_has_fk_seq minvalue 10000; + +create table keyboards ( + key_number integer not null, + name varchar(50) default null +); +create sequence keyboards_seq minvalue 10000; + +create table test_oracle_defaults ( + id integer not null primary key, + test_char char(1) default 'X' not null, + test_string varchar2(20) default 'hello' not null, + test_int integer default 3 not null +); +create sequence test_oracle_defaults_seq minvalue 10000; + +--This table has an altered lock_version column name. +create table legacy_things ( + id integer not null primary key, + tps_report_number integer default null, + version integer default 0 +); +create sequence legacy_things_seq minvalue 10000; + +CREATE TABLE numeric_data ( + id integer NOT NULL PRIMARY KEY, + bank_balance decimal(10,2), + big_bank_balance decimal(15,2), + world_population decimal(10), + my_house_population decimal(2), + decimal_number_with_default decimal(3,2) DEFAULT 2.78 +); +create sequence numeric_data_seq minvalue 10000; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/oracle2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle2.drop.sql new file mode 100644 index 0000000..abe7e55 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle2.drop.sql @@ -0,0 +1,2 @@ +drop table courses; +drop sequence courses_seq; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/oracle2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle2.sql new file mode 100644 index 0000000..3c171f4 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/oracle2.sql @@ -0,0 +1,6 @@ +create table courses ( + id int not null primary key, + name varchar(255) not null +); + +create sequence courses_seq minvalue 10000; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql.drop.sql new file mode 100644 index 0000000..4910b0e --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql.drop.sql @@ -0,0 +1,36 @@ +DROP TABLE accounts; +DROP SEQUENCE accounts_id_seq; +DROP TABLE funny_jokes; +DROP TABLE companies; +DROP SEQUENCE companies_nonstd_seq; +DROP TABLE topics; +DROP TABLE developers; +DROP TABLE projects; +DROP TABLE developers_projects; +DROP TABLE customers; +DROP TABLE orders; +DROP TABLE movies; +DROP TABLE subscribers; +DROP TABLE booleantests; +DROP TABLE auto_id_tests; +DROP TABLE entrants; +DROP TABLE colnametests; +DROP TABLE mixins; +DROP TABLE people; +DROP TABLE readers; +DROP TABLE binaries; +DROP TABLE computers; +DROP TABLE posts; +DROP TABLE comments; +DROP TABLE authors; +DROP TABLE tasks; +DROP TABLE categories; +DROP TABLE categories_posts; +DROP TABLE defaults; +DROP TABLE fk_test_has_fk; +DROP TABLE fk_test_has_pk; +DROP TABLE geometrics; +DROP TABLE keyboards; +DROP TABLE legacy_things; +DROP TABLE numeric_data; +DROP TABLE column_data; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql.sql new file mode 100644 index 0000000..ce2c775 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql.sql @@ -0,0 +1,258 @@ +CREATE SEQUENCE public.accounts_id_seq START 100; + +CREATE TABLE accounts ( + id integer DEFAULT nextval('public.accounts_id_seq'), + firm_id integer, + credit_limit integer, + PRIMARY KEY (id) +); + +CREATE TABLE funny_jokes ( + id serial, + name character varying(50) +); + +CREATE SEQUENCE companies_nonstd_seq START 101; + +CREATE TABLE companies ( + id integer DEFAULT nextval('companies_nonstd_seq'), + "type" character varying(50), + "ruby_type" character varying(50), + firm_id integer, + name character varying(50), + client_of integer, + rating integer default 1, + PRIMARY KEY (id) +); + +CREATE TABLE developers_projects ( + developer_id integer NOT NULL, + project_id integer NOT NULL, + joined_on date, + access_level integer default 1 +); + +CREATE TABLE developers ( + id serial, + name character varying(100), + salary integer DEFAULT 70000, + created_at timestamp, + updated_at timestamp, + PRIMARY KEY (id) +); +SELECT setval('developers_id_seq', 100); + +CREATE TABLE projects ( + id serial, + name character varying(100), + type varchar(255), + PRIMARY KEY (id) +); +SELECT setval('projects_id_seq', 100); + +CREATE TABLE topics ( + id serial, + title character varying(255), + author_name character varying(255), + author_email_address character varying(255), + written_on timestamp without time zone, + bonus_time time, + last_read date, + content text, + approved boolean default true, + replies_count integer default 0, + parent_id integer, + "type" character varying(50), + PRIMARY KEY (id) +); +SELECT setval('topics_id_seq', 100); + +CREATE TABLE customers ( + id serial, + name character varying, + balance integer default 0, + address_street character varying, + address_city character varying, + address_country character varying, + gps_location character varying, + PRIMARY KEY (id) +); +SELECT setval('customers_id_seq', 100); + +CREATE TABLE orders ( + id serial, + name character varying, + billing_customer_id integer, + shipping_customer_id integer, + PRIMARY KEY (id) +); +SELECT setval('orders_id_seq', 100); + +CREATE TABLE movies ( + movieid serial, + name text, + PRIMARY KEY (movieid) +); + +CREATE TABLE subscribers ( + nick text NOT NULL, + name text, + PRIMARY KEY (nick) +); + +CREATE TABLE booleantests ( + id serial, + value boolean, + PRIMARY KEY (id) +); + +CREATE TABLE defaults ( + id serial, + modified_date date default CURRENT_DATE, + modified_date_function date default now(), + fixed_date date default '2004-01-01', + modified_time timestamp default CURRENT_TIMESTAMP, + modified_time_function timestamp default now(), + fixed_time timestamp default '2004-01-01 00:00:00.000000-00', + char1 char(1) default 'Y', + char2 character varying(50) default 'a varchar field', + char3 text default 'a text field', + positive_integer integer default 1, + negative_integer integer default -1, + decimal_number decimal(3,2) default 2.78 +); + +CREATE TABLE auto_id_tests ( + auto_id serial, + value integer, + PRIMARY KEY (auto_id) +); + +CREATE TABLE entrants ( + id serial, + name text, + course_id integer +); + +CREATE TABLE colnametests ( + id serial, + "references" integer NOT NULL +); + +CREATE TABLE mixins ( + id serial, + parent_id integer, + type character varying, + pos integer, + lft integer, + rgt integer, + root_id integer, + created_at timestamp, + updated_at timestamp, + PRIMARY KEY (id) +); + +CREATE TABLE people ( + id serial, + first_name text, + lock_version integer default 0, + PRIMARY KEY (id) +); + +CREATE TABLE readers ( + id serial, + post_id integer NOT NULL, + person_id integer NOT NULL, + primary key (id) +); + +CREATE TABLE binaries ( + id serial , + data bytea, + PRIMARY KEY (id) +); + +CREATE TABLE computers ( + id serial, + developer integer NOT NULL, + "extendedWarranty" integer NOT NULL +); + +CREATE TABLE posts ( + id serial, + author_id integer, + title varchar(255), + type varchar(255), + body text +); + +CREATE TABLE comments ( + id serial, + post_id integer, + type varchar(255), + body text +); + +CREATE TABLE authors ( + id serial, + name varchar(255) default NULL +); + +CREATE TABLE tasks ( + id serial, + starting timestamp, + ending timestamp, + PRIMARY KEY (id) +); + +CREATE TABLE categories ( + id serial, + name varchar(255), + type varchar(255) +); + +CREATE TABLE categories_posts ( + category_id integer NOT NULL, + post_id integer NOT NULL +); + +CREATE TABLE fk_test_has_pk ( + id INTEGER NOT NULL PRIMARY KEY +); + +CREATE TABLE fk_test_has_fk ( + id INTEGER NOT NULL PRIMARY KEY, + fk_id INTEGER NOT NULL REFERENCES fk_test_has_fk(id) +); + +CREATE TABLE geometrics ( + id serial primary key, + a_point point, + -- a_line line, (the line type is currently not implemented in postgresql) + a_line_segment lseg, + a_box box, + a_path path, + a_polygon polygon, + a_circle circle +); + +CREATE TABLE keyboards ( + key_number serial primary key, + "name" character varying(50) +); + +--Altered lock_version column name. +CREATE TABLE legacy_things ( + id serial primary key, + tps_report_number integer, + version integer default 0 +); + +CREATE TABLE numeric_data ( + id serial primary key, + bank_balance decimal(10,2), + big_bank_balance decimal(15,2), + world_population decimal(10), + my_house_population decimal(2), + decimal_number_with_default decimal(3,2) default 2.78 +); diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql2.drop.sql new file mode 100644 index 0000000..df00ffd --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql2.drop.sql @@ -0,0 +1,2 @@ +DROP TABLE courses; + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql2.sql new file mode 100644 index 0000000..c0d7f79 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/postgresql2.sql @@ -0,0 +1,5 @@ +CREATE TABLE courses ( + id serial, + name text +); + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/schema.rb b/vendor/rails/activerecord/test/fixtures/db_definitions/schema.rb new file mode 100644 index 0000000..a5f2c9d --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/schema.rb @@ -0,0 +1,42 @@ +ActiveRecord::Schema.define do + + # For Firebird, set the sequence values 10000 when create_table is called; + # this prevents primary key collisions between "normally" created records + # and fixture-based (YAML) records. + if adapter_name == "Firebird" + def create_table(*args, &block) + ActiveRecord::Base.connection.create_table(*args, &block) + ActiveRecord::Base.connection.execute "SET GENERATOR #{args.first}_seq TO 10000" + end + end + + 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 +end diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite.drop.sql new file mode 100644 index 0000000..cb6a870 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite.drop.sql @@ -0,0 +1,31 @@ +DROP TABLE accounts; +DROP TABLE funny_jokes; +DROP TABLE companies; +DROP TABLE topics; +DROP TABLE developers; +DROP TABLE projects; +DROP TABLE developers_projects; +DROP TABLE customers; +DROP TABLE orders; +DROP TABLE movies; +DROP TABLE subscribers; +DROP TABLE booleantests; +DROP TABLE auto_id_tests; +DROP TABLE entrants; +DROP TABLE colnametests; +DROP TABLE mixins; +DROP TABLE people; +DROP TABLE readers; +DROP TABLE binaries; +DROP TABLE computers; +DROP TABLE tasks; +DROP TABLE posts; +DROP TABLE comments; +DROP TABLE authors; +DROP TABLE categories; +DROP TABLE categories_posts; +DROP TABLE fk_test_has_fk; +DROP TABLE fk_test_has_pk; +DROP TABLE keyboards; +DROP TABLE legacy_things; +DROP TABLE numeric_data; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite.sql new file mode 100644 index 0000000..8f89c41 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite.sql @@ -0,0 +1,210 @@ +CREATE TABLE 'accounts' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'firm_id' INTEGER DEFAULT NULL, + 'credit_limit' INTEGER DEFAULT NULL +); + +CREATE TABLE 'funny_jokes' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' TEXT DEFAULT NULL +); + +CREATE TABLE 'companies' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'type' VARCHAR(255) DEFAULT NULL, + 'ruby_type' VARCHAR(255) DEFAULT NULL, + 'firm_id' INTEGER DEFAULT NULL, + 'name' TEXT DEFAULT NULL, + 'client_of' INTEGER DEFAULT NULL, + 'rating' INTEGER DEFAULT 1 +); + + +CREATE TABLE 'topics' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'title' VARCHAR(255) DEFAULT NULL, + 'author_name' VARCHAR(255) DEFAULT NULL, + 'author_email_address' VARCHAR(255) DEFAULT NULL, + 'written_on' DATETIME DEFAULT NULL, + 'bonus_time' TIME DEFAULT NULL, + 'last_read' DATE DEFAULT NULL, + 'content' TEXT, + 'approved' boolean DEFAULT 't', + 'replies_count' INTEGER DEFAULT 0, + 'parent_id' INTEGER DEFAULT NULL, + 'type' VARCHAR(255) DEFAULT NULL +); + +CREATE TABLE 'developers' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' TEXT DEFAULT NULL, + 'salary' INTEGER DEFAULT 70000, + 'created_at' DATETIME DEFAULT NULL, + 'updated_at' DATETIME DEFAULT NULL +); + +CREATE TABLE 'projects' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' TEXT DEFAULT NULL, + 'type' VARCHAR(255) DEFAULT NULL +); + +CREATE TABLE 'developers_projects' ( + 'developer_id' INTEGER NOT NULL, + 'project_id' INTEGER NOT NULL, + 'joined_on' DATE DEFAULT NULL, + 'access_level' INTEGER DEFAULT 1 +); + + +CREATE TABLE 'orders' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' VARCHAR(255) DEFAULT NULL, + 'billing_customer_id' INTEGER DEFAULT NULL, + 'shipping_customer_id' INTEGER DEFAULT NULL +); + +CREATE TABLE 'customers' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'name' VARCHAR(255) DEFAULT NULL, + 'balance' INTEGER DEFAULT 0, + 'address_street' TEXT DEFAULT NULL, + 'address_city' TEXT DEFAULT NULL, + 'address_country' TEXT DEFAULT NULL, + 'gps_location' TEXT DEFAULT NULL +); + +CREATE TABLE 'movies' ( + 'movieid' INTEGER PRIMARY KEY NOT NULL, + 'name' VARCHAR(255) DEFAULT NULL +); + +CREATE TABLE subscribers ( + 'nick' VARCHAR(255) PRIMARY KEY NOT NULL, + 'name' VARCHAR(255) DEFAULT NULL +); + +CREATE TABLE 'booleantests' ( + 'id' INTEGER PRIMARY KEY NOT NULL, + 'value' INTEGER DEFAULT NULL +); + +CREATE TABLE 'auto_id_tests' ( + 'auto_id' INTEGER PRIMARY KEY NOT NULL, + 'value' INTEGER DEFAULT NULL +); + +CREATE TABLE 'entrants' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'name' VARCHAR(255) NOT NULL, + 'course_id' INTEGER NOT NULL +); + +CREATE TABLE 'colnametests' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'references' INTEGER NOT NULL +); + +CREATE TABLE 'mixins' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'parent_id' INTEGER DEFAULT NULL, + 'type' VARCHAR(40) DEFAULT NULL, + 'pos' INTEGER DEFAULT NULL, + 'lft' INTEGER DEFAULT NULL, + 'rgt' INTEGER DEFAULT NULL, + 'root_id' INTEGER DEFAULT NULL, + 'created_at' DATETIME DEFAULT NULL, + 'updated_at' DATETIME DEFAULT NULL +); + +CREATE TABLE 'people' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'first_name' VARCHAR(40) DEFAULT NULL, + 'lock_version' INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE 'readers' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'post_id' INTEGER NOT NULL, + 'person_id' INTEGER NOT NULL +); + +CREATE TABLE 'binaries' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'data' BLOB DEFAULT NULL +); + +CREATE TABLE 'computers' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'developer' INTEGER NOT NULL, + 'extendedWarranty' INTEGER NOT NULL +); + +CREATE TABLE 'posts' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'author_id' INTEGER, + 'title' VARCHAR(255) NOT NULL, + 'type' VARCHAR(255) NOT NULL, + 'body' TEXT NOT NULL +); + +CREATE TABLE 'comments' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'post_id' INTEGER NOT NULL, + 'type' VARCHAR(255) NOT NULL, + 'body' TEXT NOT NULL +); + +CREATE TABLE 'authors' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'name' VARCHAR(255) NOT NULL +); + +CREATE TABLE 'tasks' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'starting' DATETIME DEFAULT NULL, + 'ending' DATETIME DEFAULT NULL +); + +CREATE TABLE 'categories' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'name' VARCHAR(255) NOT NULL, + 'type' VARCHAR(255) DEFAULT NULL +); + +CREATE TABLE 'categories_posts' ( + 'category_id' INTEGER NOT NULL, + 'post_id' INTEGER NOT NULL +); + +CREATE TABLE 'fk_test_has_pk' ( + 'id' INTEGER NOT NULL PRIMARY KEY +); + +CREATE TABLE 'fk_test_has_fk' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'fk_id' INTEGER NOT NULL, + + FOREIGN KEY ('fk_id') REFERENCES 'fk_test_has_pk'('id') +); + +CREATE TABLE 'keyboards' ( + 'key_number' INTEGER PRIMARY KEY NOT NULL, + 'name' VARCHAR(255) DEFAULT NULL +); + +--Altered lock_version column name. +CREATE TABLE 'legacy_things' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'tps_report_number' INTEGER DEFAULT NULL, + 'version' INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE 'numeric_data' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'bank_balance' DECIMAL(10,2), + 'big_bank_balance' DECIMAL(15,2), + 'world_population' DECIMAL(10), + 'my_house_population' DECIMAL(2), + 'decimal_number_with_default' DECIMAL(3,2) DEFAULT 2.78 +); diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite2.drop.sql new file mode 100644 index 0000000..df00ffd --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite2.drop.sql @@ -0,0 +1,2 @@ +DROP TABLE courses; + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite2.sql new file mode 100644 index 0000000..5c0d231 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlite2.sql @@ -0,0 +1,5 @@ +CREATE TABLE 'courses' ( + 'id' INTEGER NOT NULL PRIMARY KEY, + 'name' VARCHAR(255) NOT NULL +); + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver.drop.sql new file mode 100644 index 0000000..b157d7c --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver.drop.sql @@ -0,0 +1,32 @@ +DROP TABLE accounts; +DROP TABLE funny_jokes; +DROP TABLE companies; +DROP TABLE topics; +DROP TABLE developers; +DROP TABLE projects; +DROP TABLE developers_projects; +DROP TABLE customers; +DROP TABLE orders; +DROP TABLE movies; +DROP TABLE subscribers; +DROP TABLE booleantests; +DROP TABLE defaults; +DROP TABLE auto_id_tests; +DROP TABLE entrants; +DROP TABLE colnametests; +DROP TABLE mixins; +DROP TABLE people; +DROP TABLE readers; +DROP TABLE binaries; +DROP TABLE computers; +DROP TABLE posts; +DROP TABLE comments; +DROP TABLE authors; +DROP TABLE tasks; +DROP TABLE categories; +DROP TABLE categories_posts; +DROP TABLE fk_test_has_fk; +DROP TABLE fk_test_has_pk; +DROP TABLE keyboards; +DROP TABLE legacy_things; +DROP TABLE numeric_data; diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver.sql new file mode 100644 index 0000000..7b6f1d7 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver.sql @@ -0,0 +1,230 @@ +CREATE TABLE accounts ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + firm_id int default NULL, + credit_limit int default NULL +); + +CREATE TABLE funny_jokes ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(50) default NULL +); + +CREATE TABLE companies ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + type varchar(50) default NULL, + ruby_type varchar(50) default NULL, + firm_id int default NULL, + name varchar(50) default NULL, + client_of int default NULL, + rating int default 1 +); + +CREATE TABLE topics ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + title varchar(255) default NULL, + author_name varchar(255) default NULL, + author_email_address varchar(255) default NULL, + written_on datetime default NULL, + bonus_time datetime default NULL, + last_read datetime default NULL, + content varchar(255) default NULL, + approved bit default 1, + replies_count int default 0, + parent_id int default NULL, + type varchar(50) default NULL +); + +CREATE TABLE developers ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(100) default NULL, + salary int default 70000, + created_at datetime default NULL, + updated_at datetime default NULL +); + +CREATE TABLE projects ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(100) default NULL, + type varchar(255) default NULL +); + +CREATE TABLE developers_projects ( + developer_id int NOT NULL, + project_id int NOT NULL, + joined_on datetime default NULL, + access_level int default 1 +); + +CREATE TABLE orders ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(100) default NULL, + billing_customer_id int default NULL, + shipping_customer_id int default NULL +); + + +CREATE TABLE customers ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(100) default NULL, + balance int default 0, + address_street varchar(100) default NULL, + address_city varchar(100) default NULL, + address_country varchar(100) default NULL, + gps_location varchar(100) default NULL +); + +CREATE TABLE movies ( + movieid int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(100) default NULL +); + +CREATE TABLE subscribers ( + nick varchar(100) NOT NULL PRIMARY KEY, + name varchar(100) default NULL +); + +CREATE TABLE booleantests ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + value bit default NULL +); + +CREATE TABLE defaults ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, +-- these brought from the PostgreSQL defaults_test.rb but +-- tests only exist for integers and decimals, currently +-- modified_date date default CURRENT_DATE, +-- modified_date_function date default now(), +-- fixed_date date default '2004-01-01', +-- modified_time timestamp default CURRENT_TIMESTAMP, +-- modified_time_function timestamp default now(), +-- fixed_time timestamp default '2004-01-01 00:00:00.000000-00', +-- char1 char(1) default 'Y', +-- char2 character varying(50) default 'a varchar field', +-- char3 text default 'a text field', + positive_integer integer default 1, + negative_integer integer default -1, + decimal_number decimal(3,2) default 2.78 +); + +CREATE TABLE auto_id_tests ( + auto_id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + value int default NULL +); + +CREATE TABLE entrants ( + id int NOT NULL PRIMARY KEY, + name varchar(255) NOT NULL, + course_id int NOT NULL +); + +CREATE TABLE colnametests ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + [references] int NOT NULL +); + +CREATE TABLE mixins ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + parent_id int default NULL, + pos int default NULL, + created_at datetime default NULL, + updated_at datetime default NULL, + lft int default NULL, + rgt int default NULL, + root_id int default NULL, + type varchar(40) default NULL +); + +CREATE TABLE people ( + id int NOT NULL IDENTITY(1, 1), + first_name varchar(40) NULL, + lock_version int default 0, + PRIMARY KEY (id) +); + +CREATE TABLE readers ( + id int NOT NULL IDENTITY(1, 1), + post_id int NOT NULL, + person_id int NOT NULL, + primary key (id) +); + +CREATE TABLE binaries ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + data image NULL +); + +CREATE TABLE computers ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + developer int NOT NULL, + extendedWarranty int NOT NULL +); + +CREATE TABLE posts ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + author_id int default NULL, + title varchar(255) default NULL, + type varchar(255) default NULL, + body varchar(4096) default NULL +); + +CREATE TABLE comments ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + post_id int default NULL, + type varchar(255) default NULL, + body varchar(4096) default NULL +); + +CREATE TABLE authors ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(255) default NULL +); + +CREATE TABLE tasks ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + starting datetime default NULL, + ending datetime default NULL +); + +CREATE TABLE categories ( + id int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(255), + type varchar(255) default NULL +); + +CREATE TABLE categories_posts ( + category_id int NOT NULL, + post_id int NOT NULL +); + +CREATE TABLE fk_test_has_pk ( + id INTEGER NOT NULL PRIMARY KEY +); + +CREATE TABLE fk_test_has_fk ( + id INTEGER NOT NULL PRIMARY KEY, + fk_id INTEGER NOT NULL, + + FOREIGN KEY (fk_id) REFERENCES fk_test_has_pk(id) +); + +CREATE TABLE keyboards ( + key_number int NOT NULL IDENTITY(1, 1) PRIMARY KEY, + name varchar(50) default NULL +); + +--This table has an altered lock_version column name. +CREATE TABLE legacy_things ( + id int NOT NULL IDENTITY(1, 1), + tps_report_number int default NULL, + version int default 0, + PRIMARY KEY (id) +); + +CREATE TABLE numeric_data ( + id int NOT NULL IDENTITY(1, 1), + bank_balance decimal(10,2), + big_bank_balance decimal(15,2), + world_population decimal(10), + my_house_population decimal(2), + decimal_number_with_default decimal(3,2) DEFAULT 2.78 +); diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver2.drop.sql new file mode 100644 index 0000000..df00ffd --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver2.drop.sql @@ -0,0 +1,2 @@ +DROP TABLE courses; + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver2.sql new file mode 100644 index 0000000..9198cf5 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sqlserver2.sql @@ -0,0 +1,5 @@ +CREATE TABLE courses ( + id int NOT NULL PRIMARY KEY, + name varchar(255) NOT NULL +); + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sybase.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase.drop.sql new file mode 100644 index 0000000..fa51eef --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase.drop.sql @@ -0,0 +1,32 @@ +DROP TABLE accounts +DROP TABLE funny_jokes +DROP TABLE companies +DROP TABLE topics +DROP TABLE developers +DROP TABLE projects +DROP TABLE developers_projects +DROP TABLE customers +DROP TABLE orders +DROP TABLE movies +DROP TABLE subscribers +DROP TABLE booleantests +DROP TABLE auto_id_tests +DROP TABLE entrants +DROP TABLE colnametests +DROP TABLE mixins +DROP TABLE people +DROP TABLE readers +DROP TABLE binaries +DROP TABLE computers +DROP TABLE tasks +DROP TABLE posts +DROP TABLE comments +DROP TABLE authors +DROP TABLE categories +DROP TABLE categories_posts +DROP TABLE fk_test_has_fk +DROP TABLE fk_test_has_pk +DROP TABLE keyboards +DROP TABLE legacy_things +DROP TABLE numeric_data +go diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sybase.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase.sql new file mode 100644 index 0000000..79c7b94 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase.sql @@ -0,0 +1,213 @@ +CREATE TABLE accounts ( + id numeric(9,0) IDENTITY PRIMARY KEY, + firm_id int NULL, + credit_limit int NULL +) + +CREATE TABLE funny_jokes ( +id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(50) NULL +) + +CREATE TABLE companies ( + id numeric(9,0) IDENTITY PRIMARY KEY, + type varchar(50) NULL, + ruby_type varchar(50) NULL, + firm_id int NULL, + name varchar(50) NULL, + client_of int NULL, + rating int default 1 +) + + +CREATE TABLE topics ( + id numeric(9,0) IDENTITY PRIMARY KEY, + title varchar(255) NULL, + author_name varchar(255) NULL, + author_email_address varchar(255) NULL, + written_on datetime NULL, + bonus_time datetime NULL, + last_read datetime NULL, + content varchar(255) NULL, + approved bit default 1, + replies_count int default 0, + parent_id int NULL, + type varchar(50) NULL +) + +CREATE TABLE developers ( + id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(100) NULL, + salary int default 70000, + created_at datetime NULL, + updated_at datetime NULL +) + +CREATE TABLE projects ( + id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(100) NULL, + type varchar(255) NULL +) + +CREATE TABLE developers_projects ( + developer_id int NOT NULL, + project_id int NOT NULL, + joined_on datetime NULL, + access_level smallint default 1 +) + +CREATE TABLE orders ( + id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(100) NULL, + billing_customer_id int NULL, + shipping_customer_id int NULL +) + +CREATE TABLE customers ( + id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(100) NULL, + balance int default 0, + address_street varchar(100) NULL, + address_city varchar(100) NULL, + address_country varchar(100) NULL, + gps_location varchar(100) NULL +) + +CREATE TABLE movies ( + movieid numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(100) NULL +) + +CREATE TABLE subscribers ( + nick varchar(100) PRIMARY KEY, + name varchar(100) NULL +) + +CREATE TABLE booleantests ( + id numeric(9,0) IDENTITY PRIMARY KEY, + value int NULL +) + +CREATE TABLE auto_id_tests ( + auto_id numeric(9,0) IDENTITY PRIMARY KEY, + value int NULL +) + +CREATE TABLE entrants ( + id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(255) NOT NULL, + course_id int NOT NULL +) + +CREATE TABLE colnametests ( + id numeric(9,0) IDENTITY PRIMARY KEY, + [references] int NOT NULL +) + +CREATE TABLE mixins ( + id numeric(9,0) IDENTITY PRIMARY KEY, + parent_id int NULL, + pos int NULL, + created_at datetime NULL, + updated_at datetime NULL, + lft int NULL, + rgt int NULL, + root_id int NULL, + type varchar(40) NULL +) + +CREATE TABLE people ( + id numeric(9,0) IDENTITY PRIMARY KEY, + first_name varchar(40) NULL, + lock_version int DEFAULT 0 +) + +CREATE TABLE readers ( + id numeric(9,0) IDENTITY PRIMARY KEY, + post_id int NOT NULL, + person_id int NOT NULL +) + +CREATE TABLE binaries ( + id numeric(9,0) IDENTITY PRIMARY KEY, + data image NULL +) + +CREATE TABLE computers ( + id numeric(9,0) IDENTITY PRIMARY KEY, + developer int NOT NULL, + extendedWarranty int NOT NULL +) + +CREATE TABLE posts ( + id numeric(9,0) IDENTITY PRIMARY KEY, + author_id int NULL, + title varchar(255) NOT NULL, + body varchar(2048) NOT NULL, + type varchar(255) NOT NULL +) + +CREATE TABLE comments ( + id numeric(9,0) IDENTITY PRIMARY KEY, + post_id int NOT NULL, + body varchar(2048) NOT NULL, + type varchar(255) NOT NULL +) + +CREATE TABLE authors ( + id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(255) NOT NULL +) + +CREATE TABLE tasks ( + id numeric(9,0) IDENTITY PRIMARY KEY, + starting datetime NULL, + ending datetime NULL +) + +CREATE TABLE categories ( + id numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(255) NOT NULL, + type varchar(255) NOT NULL +) + +CREATE TABLE categories_posts ( + category_id int NOT NULL, + post_id int NOT NULL +) + +CREATE TABLE fk_test_has_pk ( + id numeric(9,0) IDENTITY PRIMARY KEY +) + +CREATE TABLE fk_test_has_fk ( + id numeric(9,0) PRIMARY KEY, + fk_id numeric(9,0) NOT NULL, + + FOREIGN KEY (fk_id) REFERENCES fk_test_has_pk(id) +) + + +CREATE TABLE keyboards ( + key_number numeric(9,0) IDENTITY PRIMARY KEY, + name varchar(50) NULL +) + +--This table has an altered lock_version column name. +CREATE TABLE legacy_things ( + id numeric(9,0) IDENTITY PRIMARY KEY, + tps_report_number int default NULL, + version int default 0, +) + + +CREATE TABLE numeric_data ( + id numeric((9,0) IDENTITY PRIMARY KEY, + bank_balance numeric(10,2), + big_bank_balance numeric(15,2), + world_population numeric(10), + my_house_population numeric(2), + decimal_number_with_default numeric(3,2) DEFAULT 2.78 +) + +go diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sybase2.drop.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase2.drop.sql new file mode 100644 index 0000000..f4ce96f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase2.drop.sql @@ -0,0 +1,4 @@ +DROP TABLE courses +go + + diff --git a/vendor/rails/activerecord/test/fixtures/db_definitions/sybase2.sql b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase2.sql new file mode 100644 index 0000000..88f9d32 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/db_definitions/sybase2.sql @@ -0,0 +1,5 @@ +CREATE TABLE courses ( + id int NOT NULL PRIMARY KEY, + name varchar(255) NOT NULL +) +go diff --git a/vendor/rails/activerecord/test/fixtures/default.rb b/vendor/rails/activerecord/test/fixtures/default.rb new file mode 100644 index 0000000..887e9cc --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/default.rb @@ -0,0 +1,2 @@ +class Default < ActiveRecord::Base +end diff --git a/vendor/rails/activerecord/test/fixtures/developer.rb b/vendor/rails/activerecord/test/fixtures/developer.rb new file mode 100644 index 0000000..75fb307 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/developer.rb @@ -0,0 +1,52 @@ +module DeveloperProjectsAssociationExtension + def find_most_recent + find(:first, :order => "id DESC") + end +end + +module DeveloperProjectsAssociationExtension2 + def find_least_recent + find(:first, :order => "id ASC") + 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 :projects_extended_by_name_twice, + :class_name => "Project", + :join_table => "developers_projects", + :association_foreign_key => "project_id", + :extend => [DeveloperProjectsAssociationExtension, DeveloperProjectsAssociationExtension2] + + 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 diff --git a/vendor/rails/activerecord/test/fixtures/developers.yml b/vendor/rails/activerecord/test/fixtures/developers.yml new file mode 100644 index 0000000..308bf75 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/developers.yml @@ -0,0 +1,21 @@ +david: + id: 1 + name: David + salary: 80000 + +jamis: + id: 2 + name: Jamis + salary: 150000 + +<% for digit in 3..10 %> +dev_<%= digit %>: + id: <%= digit %> + name: fixture_<%= digit %> + salary: 100000 +<% end %> + +poor_jamis: + id: 11 + name: Jamis + salary: 9000 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/developers_projects.yml b/vendor/rails/activerecord/test/fixtures/developers_projects.yml new file mode 100644 index 0000000..5729587 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/developers_projects.yml @@ -0,0 +1,17 @@ +david_action_controller: + developer_id: 1 + project_id: 2 + joined_on: 2004-10-10 + +david_active_record: + developer_id: 1 + project_id: 1 + joined_on: 2004-10-10 + +jamis_active_record: + developer_id: 2 + project_id: 1 + +poor_jamis_active_record: + developer_id: 11 + project_id: 1 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/developers_projects/david_action_controller b/vendor/rails/activerecord/test/fixtures/developers_projects/david_action_controller new file mode 100644 index 0000000..e6e9d0e --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/developers_projects/david_action_controller @@ -0,0 +1,3 @@ +developer_id => 1 +project_id => 2 +joined_on => 2004-10-10 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/developers_projects/david_active_record b/vendor/rails/activerecord/test/fixtures/developers_projects/david_active_record new file mode 100644 index 0000000..2ef474c --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/developers_projects/david_active_record @@ -0,0 +1,3 @@ +developer_id => 1 +project_id => 1 +joined_on => 2004-10-10 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/developers_projects/jamis_active_record b/vendor/rails/activerecord/test/fixtures/developers_projects/jamis_active_record new file mode 100644 index 0000000..91beb80 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/developers_projects/jamis_active_record @@ -0,0 +1,2 @@ +developer_id => 2 +project_id => 1 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/entrant.rb b/vendor/rails/activerecord/test/fixtures/entrant.rb new file mode 100644 index 0000000..4682ce4 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/entrant.rb @@ -0,0 +1,3 @@ +class Entrant < ActiveRecord::Base + belongs_to :course +end diff --git a/vendor/rails/activerecord/test/fixtures/entrants.yml b/vendor/rails/activerecord/test/fixtures/entrants.yml new file mode 100644 index 0000000..86f0108 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/entrants.yml @@ -0,0 +1,14 @@ +first: + id: 1 + course_id: 1 + name: Ruby Developer + +second: + id: 2 + course_id: 1 + name: Ruby Guru + +third: + id: 3 + course_id: 2 + name: Java Lover diff --git a/vendor/rails/activerecord/test/fixtures/fk_test_has_fk.yml b/vendor/rails/activerecord/test/fixtures/fk_test_has_fk.yml new file mode 100644 index 0000000..67d914e --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/fk_test_has_fk.yml @@ -0,0 +1,3 @@ +first: + id: 1 + fk_id: 1 diff --git a/vendor/rails/activerecord/test/fixtures/fk_test_has_pk.yml b/vendor/rails/activerecord/test/fixtures/fk_test_has_pk.yml new file mode 100644 index 0000000..c939521 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/fk_test_has_pk.yml @@ -0,0 +1,2 @@ +first: + id: 1 \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/flowers.jpg b/vendor/rails/activerecord/test/fixtures/flowers.jpg new file mode 100644 index 0000000..3687b09 Binary files /dev/null and b/vendor/rails/activerecord/test/fixtures/flowers.jpg differ diff --git a/vendor/rails/activerecord/test/fixtures/funny_jokes.yml b/vendor/rails/activerecord/test/fixtures/funny_jokes.yml new file mode 100644 index 0000000..834481b --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/funny_jokes.yml @@ -0,0 +1,14 @@ +a_joke: + id: 1 + name: Knock knock + +another_joke: + id: 2 + name: The Aristocrats +a_joke: + id: 1 + name: Knock knock + +another_joke: + id: 2 + name: The Aristocrats \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/joke.rb b/vendor/rails/activerecord/test/fixtures/joke.rb new file mode 100644 index 0000000..8006a43 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/joke.rb @@ -0,0 +1,6 @@ +class Joke < ActiveRecord::Base + set_table_name 'funny_jokes' +end +class Joke < ActiveRecord::Base + set_table_name 'funny_jokes' +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/keyboard.rb b/vendor/rails/activerecord/test/fixtures/keyboard.rb new file mode 100644 index 0000000..32a4a7f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/keyboard.rb @@ -0,0 +1,3 @@ +class Keyboard < ActiveRecord::Base + set_primary_key 'key_number' +end diff --git a/vendor/rails/activerecord/test/fixtures/legacy_thing.rb b/vendor/rails/activerecord/test/fixtures/legacy_thing.rb new file mode 100644 index 0000000..eaeb642 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/legacy_thing.rb @@ -0,0 +1,3 @@ +class LegacyThing < ActiveRecord::Base + set_locking_column :version +end diff --git a/vendor/rails/activerecord/test/fixtures/legacy_things.yml b/vendor/rails/activerecord/test/fixtures/legacy_things.yml new file mode 100644 index 0000000..a6d42aa --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/legacy_things.yml @@ -0,0 +1,3 @@ +obtuse: + id: 1 + tps_report_number: 500 diff --git a/vendor/rails/activerecord/test/fixtures/migrations/1_people_have_last_names.rb b/vendor/rails/activerecord/test/fixtures/migrations/1_people_have_last_names.rb new file mode 100644 index 0000000..009729b --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations/1_people_have_last_names.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "last_name", :string + end + + def self.down + remove_column "people", "last_name" + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/migrations/2_we_need_reminders.rb b/vendor/rails/activerecord/test/fixtures/migrations/2_we_need_reminders.rb new file mode 100644 index 0000000..ac5918f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations/2_we_need_reminders.rb @@ -0,0 +1,12 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/migrations/3_innocent_jointable.rb b/vendor/rails/activerecord/test/fixtures/migrations/3_innocent_jointable.rb new file mode 100644 index 0000000..21c9ca5 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations/3_innocent_jointable.rb @@ -0,0 +1,12 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb b/vendor/rails/activerecord/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb new file mode 100644 index 0000000..0aed7cb --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb @@ -0,0 +1,15 @@ +class GiveMeBigNumbers < ActiveRecord::Migration + def self.up + create_table :big_numbers do |table| + table.column :bank_balance, :decimal, :precision => 10, :scale => 2 + table.column :big_bank_balance, :decimal, :precision => 15, :scale => 2 + table.column :world_population, :decimal, :precision => 10 + table.column :my_house_population, :decimal, :precision => 2 + table.column :value_of_e, :decimal + end + end + + def self.down + drop_table :big_numbers + end +end diff --git a/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb new file mode 100644 index 0000000..009729b --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb @@ -0,0 +1,9 @@ +class PeopleHaveLastNames < ActiveRecord::Migration + def self.up + add_column "people", "last_name", :string + end + + def self.down + remove_column "people", "last_name" + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb new file mode 100644 index 0000000..ac5918f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb @@ -0,0 +1,12 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/3_foo.rb b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/3_foo.rb new file mode 100644 index 0000000..916fe58 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/3_foo.rb @@ -0,0 +1,7 @@ +class Foo < ActiveRecord::Migration + def self.up + end + + def self.down + end +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb new file mode 100644 index 0000000..21c9ca5 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb @@ -0,0 +1,12 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/mixin.rb b/vendor/rails/activerecord/test/fixtures/mixin.rb new file mode 100644 index 0000000..78cdbef --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/mixin.rb @@ -0,0 +1,48 @@ +class 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 diff --git a/vendor/rails/activerecord/test/fixtures/mixins.yml b/vendor/rails/activerecord/test/fixtures/mixins.yml new file mode 100644 index 0000000..cb21349 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/mixins.yml @@ -0,0 +1,89 @@ +# tree mixins +tree_1: + id: 1001 + type: TreeMixin + parent_id: + +tree_2: + id: 1002 + type: TreeMixin + parent_id: 1001 + +tree_3: + id: 1003 + type: TreeMixin + parent_id: 1002 + +tree_4: + id: 1004 + type: TreeMixin + parent_id: 1001 + +tree2_1: + id: 1005 + type: TreeMixin + parent_id: + +tree3_1: + id: 1006 + type: TreeMixin + parent_id: + +tree_without_order_1: + id: 1101 + type: TreeMixinWithoutOrder + parent_id: + +tree_without_order_2: + id: 1100 + type: TreeMixinWithoutOrder + parent_id: + +# List mixins + +<% (1..4).each do |counter| %> +list_<%= counter %>: + id: <%= counter+1006 %> + pos: <%= counter %> + type: ListMixin + parent_id: 5 +<% end %> + +# Nested set mixins + +<% (1..10).each do |counter| %> +set_<%= counter %>: + id: <%= counter+3000 %> + type: NestedSet +<% end %> + +# Big old set +<% +[[4001, 0, 1, 20], + [4002, 4001, 2, 7], + [4003, 4002, 3, 4], + [4004, 4002, 5, 6], + [4005, 4001, 8, 13], + [4006, 4005, 9, 10], + [4007, 4005, 11, 12], + [4008, 4001, 14, 19], + [4009, 4008, 15, 16], + [4010, 4008, 17, 18]].each do |set| %> +tree_<%= set[0] %>: + id: <%= set[0]%> + parent_id: <%= set[1]%> + type: NestedSetWithStringScope + lft: <%= set[2]%> + rgt: <%= set[3]%> + root_id: 42 + +<% end %> + +# subclasses of list items +<% (1..4).each do |i| %> +list_sub_<%= i %>: + id: <%= i + 5000 %> + pos: <%= i %> + parent_id: 5000 + type: <%= (i % 2 == 1) ? ListMixinSub1 : ListMixinSub2 %> +<% end %> diff --git a/vendor/rails/activerecord/test/fixtures/movie.rb b/vendor/rails/activerecord/test/fixtures/movie.rb new file mode 100644 index 0000000..6384b4c --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/movie.rb @@ -0,0 +1,5 @@ +class Movie < ActiveRecord::Base + def self.primary_key + "movieid" + end +end diff --git a/vendor/rails/activerecord/test/fixtures/movies.yml b/vendor/rails/activerecord/test/fixtures/movies.yml new file mode 100644 index 0000000..2e9154f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/movies.yml @@ -0,0 +1,7 @@ +first: + movieid: 1 + name: Terminator + +second: + movieid: 2 + name: Gladiator diff --git a/vendor/rails/activerecord/test/fixtures/naked/csv/accounts.csv b/vendor/rails/activerecord/test/fixtures/naked/csv/accounts.csv new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/naked/csv/accounts.csv @@ -0,0 +1 @@ + diff --git a/vendor/rails/activerecord/test/fixtures/naked/yml/accounts.yml b/vendor/rails/activerecord/test/fixtures/naked/yml/accounts.yml new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/naked/yml/accounts.yml @@ -0,0 +1 @@ + diff --git a/vendor/rails/activerecord/test/fixtures/naked/yml/companies.yml b/vendor/rails/activerecord/test/fixtures/naked/yml/companies.yml new file mode 100644 index 0000000..2c151c2 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/naked/yml/companies.yml @@ -0,0 +1 @@ +# i wonder what will happen here diff --git a/vendor/rails/activerecord/test/fixtures/naked/yml/courses.yml b/vendor/rails/activerecord/test/fixtures/naked/yml/courses.yml new file mode 100644 index 0000000..19f0805 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/naked/yml/courses.yml @@ -0,0 +1 @@ +qwerty diff --git a/vendor/rails/activerecord/test/fixtures/order.rb b/vendor/rails/activerecord/test/fixtures/order.rb new file mode 100644 index 0000000..ba114f2 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/order.rb @@ -0,0 +1,4 @@ +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 diff --git a/vendor/rails/activerecord/test/fixtures/people.yml b/vendor/rails/activerecord/test/fixtures/people.yml new file mode 100644 index 0000000..22c64af --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/people.yml @@ -0,0 +1,3 @@ +michael: + id: 1 + first_name: Michael \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/person.rb b/vendor/rails/activerecord/test/fixtures/person.rb new file mode 100644 index 0000000..7a9666f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/person.rb @@ -0,0 +1,4 @@ +class Person < ActiveRecord::Base + has_many :readers + has_many :posts, :through => :readers +end diff --git a/vendor/rails/activerecord/test/fixtures/post.rb b/vendor/rails/activerecord/test/fixtures/post.rb new file mode 100644 index 0000000..9b42fbd --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/post.rb @@ -0,0 +1,57 @@ +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 diff --git a/vendor/rails/activerecord/test/fixtures/posts.yml b/vendor/rails/activerecord/test/fixtures/posts.yml new file mode 100644 index 0000000..0f1445b --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/posts.yml @@ -0,0 +1,48 @@ +welcome: + id: 1 + author_id: 1 + title: Welcome to the weblog + body: Such a lovely day + type: Post + +thinking: + id: 2 + author_id: 1 + title: So I was thinking + body: Like I hopefully always am + type: SpecialPost + +authorless: + id: 3 + author_id: 0 + title: I don't have any comments + body: I just don't want to + type: Post + +sti_comments: + id: 4 + author_id: 1 + title: sti comments + body: hello + type: Post + +sti_post_and_comments: + id: 5 + author_id: 1 + title: sti me + body: hello + type: StiPost + +sti_habtm: + id: 6 + author_id: 1 + title: habtm sti test + body: hello + type: Post + +eager_other: + id: 7 + author_id: 2 + title: eager loading with OR'd conditions + body: hello + type: Post diff --git a/vendor/rails/activerecord/test/fixtures/project.rb b/vendor/rails/activerecord/test/fixtures/project.rb new file mode 100644 index 0000000..4d4e0e1 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/project.rb @@ -0,0 +1,26 @@ +class Project < ActiveRecord::Base + has_and_belongs_to_many :developers, :uniq => true, :order => 'developers.name desc, developers.id desc' + has_and_belongs_to_many :non_unique_developers, :order => 'developers.name desc, developers.id desc', :class_name => 'Developer' + 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 diff --git a/vendor/rails/activerecord/test/fixtures/projects.yml b/vendor/rails/activerecord/test/fixtures/projects.yml new file mode 100644 index 0000000..02800c7 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/projects.yml @@ -0,0 +1,7 @@ +action_controller: + id: 2 + name: Active Controller + +active_record: + id: 1 + name: Active Record diff --git a/vendor/rails/activerecord/test/fixtures/reader.rb b/vendor/rails/activerecord/test/fixtures/reader.rb new file mode 100644 index 0000000..27527bf --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/reader.rb @@ -0,0 +1,4 @@ +class Reader < ActiveRecord::Base + belongs_to :post + belongs_to :person +end diff --git a/vendor/rails/activerecord/test/fixtures/readers.yml b/vendor/rails/activerecord/test/fixtures/readers.yml new file mode 100644 index 0000000..6ed73c9 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/readers.yml @@ -0,0 +1,4 @@ +michael_welcome: + id: 1 + post_id: 1 + person_id: 1 diff --git a/vendor/rails/activerecord/test/fixtures/reply.rb b/vendor/rails/activerecord/test/fixtures/reply.rb new file mode 100755 index 0000000..bf7781e --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/reply.rb @@ -0,0 +1,37 @@ +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 diff --git a/vendor/rails/activerecord/test/fixtures/subject.rb b/vendor/rails/activerecord/test/fixtures/subject.rb new file mode 100644 index 0000000..3502943 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/subject.rb @@ -0,0 +1,4 @@ +# used for OracleSynonymTest, see test/synonym_test_oci.rb +# +class Subject < ActiveRecord::Base +end diff --git a/vendor/rails/activerecord/test/fixtures/subscriber.rb b/vendor/rails/activerecord/test/fixtures/subscriber.rb new file mode 100644 index 0000000..51335a8 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/subscriber.rb @@ -0,0 +1,6 @@ +class Subscriber < ActiveRecord::Base + set_primary_key 'nick' +end + +class SpecialSubscriber < Subscriber +end diff --git a/vendor/rails/activerecord/test/fixtures/subscribers/first b/vendor/rails/activerecord/test/fixtures/subscribers/first new file mode 100644 index 0000000..5287e26 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/subscribers/first @@ -0,0 +1,2 @@ +nick => alterself +name => Luke Holden diff --git a/vendor/rails/activerecord/test/fixtures/subscribers/second b/vendor/rails/activerecord/test/fixtures/subscribers/second new file mode 100644 index 0000000..2345e44 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/subscribers/second @@ -0,0 +1,2 @@ +nick => webster132 +name => David Heinemeier Hansson diff --git a/vendor/rails/activerecord/test/fixtures/tag.rb b/vendor/rails/activerecord/test/fixtures/tag.rb new file mode 100644 index 0000000..c12ec0c --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/tag.rb @@ -0,0 +1,5 @@ +class Tag < ActiveRecord::Base + has_many :taggings + has_many :taggables, :through => :taggings + has_one :tagging +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/tagging.rb b/vendor/rails/activerecord/test/fixtures/tagging.rb new file mode 100644 index 0000000..4695f07 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/tagging.rb @@ -0,0 +1,6 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/taggings.yml b/vendor/rails/activerecord/test/fixtures/taggings.yml new file mode 100644 index 0000000..617210d --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/taggings.yml @@ -0,0 +1,18 @@ +welcome_general: + id: 1 + tag_id: 1 + super_tag_id: 2 + taggable_id: 1 + taggable_type: Post + +thinking_general: + id: 2 + tag_id: 1 + taggable_id: 2 + taggable_type: Post + +fake: + id: 3 + tag_id: 1 + taggable_id: 1 + taggable_type: FakeModel \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/tags.yml b/vendor/rails/activerecord/test/fixtures/tags.yml new file mode 100644 index 0000000..471b96f --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/tags.yml @@ -0,0 +1,7 @@ +general: + id: 1 + name: General + +misc: + id: 2 + name: Misc \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/task.rb b/vendor/rails/activerecord/test/fixtures/task.rb new file mode 100644 index 0000000..ee0282c --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/task.rb @@ -0,0 +1,3 @@ +class Task < ActiveRecord::Base + +end diff --git a/vendor/rails/activerecord/test/fixtures/tasks.yml b/vendor/rails/activerecord/test/fixtures/tasks.yml new file mode 100644 index 0000000..1e6a061 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/tasks.yml @@ -0,0 +1,7 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first_task: + id: 1 + starting: 2005-03-30t06:30:00.00+01:00 + ending: 2005-03-30t08:30:00.00+01:00 +another_task: + id: 2 diff --git a/vendor/rails/activerecord/test/fixtures/topic.rb b/vendor/rails/activerecord/test/fixtures/topic.rb new file mode 100755 index 0000000..9b20f02 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/topic.rb @@ -0,0 +1,20 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activerecord/test/fixtures/topics.yml b/vendor/rails/activerecord/test/fixtures/topics.yml new file mode 100644 index 0000000..e61d17c --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures/topics.yml @@ -0,0 +1,22 @@ +first: + id: 1 + title: The First Topic + author_name: David + author_email_address: david@loudthinking.com + written_on: 2003-07-16t15:28:11.2233+01:00 + last_read: 2004-04-15 + bonus_time: 2005-01-30t15:28:00.00+01:00 + content: Have a nice day + approved: false + replies_count: 1 + +second: + id: 2 + title: The Second Topic's of the day + author_name: Mary + written_on: 2003-07-15t15:28:00.0099+01:00 + content: Have a nice day + approved: true + replies_count: 0 + parent_id: 1 + type: Reply diff --git a/vendor/rails/activerecord/test/fixtures_test.rb b/vendor/rails/activerecord/test/fixtures_test.rb new file mode 100755 index 0000000..88f01c8 --- /dev/null +++ b/vendor/rails/activerecord/test/fixtures_test.rb @@ -0,0 +1,345 @@ +require '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 diff --git a/vendor/rails/activerecord/test/inheritance_test.rb b/vendor/rails/activerecord/test/inheritance_test.rb new file mode 100755 index 0000000..db10e1f --- /dev/null +++ b/vendor/rails/activerecord/test/inheritance_test.rb @@ -0,0 +1,144 @@ +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, :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, :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 diff --git a/vendor/rails/activerecord/test/lifecycle_test.rb b/vendor/rails/activerecord/test/lifecycle_test.rb new file mode 100755 index 0000000..2fdeb41 --- /dev/null +++ b/vendor/rails/activerecord/test/lifecycle_test.rb @@ -0,0 +1,137 @@ +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 + + cattr_reader :last_inherited + @@last_inherited = nil + + def observed_class_inherited_with_testing(subclass) + observed_class_inherited_without_testing(subclass) + @@last_inherited = subclass + end + + alias_method_chain :observed_class_inherited, :testing + + 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 + ActiveRecord::Base.instantiate_observers + + 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 + ActiveRecord::Base.instantiate_observers + + 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.title, topic_observer.topic.title + end + + def test_inferred_auto_observer + topic_observer = TopicObserver.instance + + topic = Topic.find(1) + assert_equal topic.title, topic_observer.topic.title + end + + def test_observing_two_classes + multi_observer = MultiObserver.instance + + topic = Topic.find(1) + assert_equal topic.title, multi_observer.record.title + + developer = Developer.find(1) + assert_equal developer.name, multi_observer.record.name + end + + def test_observing_subclasses + multi_observer = MultiObserver.instance + + developer = SpecialDeveloper.find(1) + assert_equal developer.name, multi_observer.record.name + + klass = Class.new(Developer) + assert_equal klass, multi_observer.last_inherited + + developer = klass.find(1) + assert_equal developer.name, multi_observer.record.name + end + + def test_invalid_observer + assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers } + end +end diff --git a/vendor/rails/activerecord/test/locking_test.rb b/vendor/rails/activerecord/test/locking_test.rb new file mode 100644 index 0000000..00df4bb --- /dev/null +++ b/vendor/rails/activerecord/test/locking_test.rb @@ -0,0 +1,158 @@ +require 'abstract_unit' +require 'fixtures/person' +require 'fixtures/legacy_thing' + +class OptimisticLockingTest < Test::Unit::TestCase + fixtures :people, :legacy_things + + def test_lock_existing + p1 = Person.find(1) + p2 = Person.find(1) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.save! + assert_equal 1, p1.lock_version + assert_equal 0, p2.lock_version + + assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + end + + def test_lock_new + p1 = Person.new(:first_name => 'anika') + assert_equal 0, p1.lock_version + + p1.save! + p2 = Person.find(p1.id) + assert_equal 0, p1.lock_version + assert_equal 0, p2.lock_version + + p1.save! + assert_equal 1, p1.lock_version + assert_equal 0, p2.lock_version + + assert_raises(ActiveRecord::StaleObjectError) { p2.save! } + end + + def test_lock_column_name_existing + t1 = LegacyThing.find(1) + t2 = LegacyThing.find(1) + assert_equal 0, t1.version + assert_equal 0, t2.version + + t1.save! + assert_equal 1, t1.version + assert_equal 0, t2.version + + assert_raises(ActiveRecord::StaleObjectError) { t2.save! } + end + + def test_lock_column_is_mass_assignable + p1 = Person.create(:first_name => 'bianca') + assert_equal 0, p1.lock_version + assert_equal p1.lock_version, Person.new(p1.attributes).lock_version + + p1.save! + assert_equal 1, p1.lock_version + assert_equal p1.lock_version, Person.new(p1.attributes).lock_version + end +end + + +# TODO: test against the generated SQL since testing locking behavior itself +# is so cumbersome. Will deadlock Ruby threads if the underlying db.execute +# blocks, so separate script called by Kernel#system is needed. +# (See exec vs. async_exec in the PostgreSQL adapter.) + +# TODO: The SQL Server adapter currently has no support for pessimistic locking + +unless current_adapter?(:SQLServerAdapter) + class PessimisticLockingTest < Test::Unit::TestCase + self.use_transactional_fixtures = false + fixtures :people + + def setup + @allow_concurrency = ActiveRecord::Base.allow_concurrency + ActiveRecord::Base.allow_concurrency = true + end + + def teardown + ActiveRecord::Base.allow_concurrency = @allow_concurrency + end + + # Test that the adapter doesn't blow up on add_lock! + def test_sane_find_with_lock + assert_nothing_raised do + Person.transaction do + Person.find 1, :lock => true + end + end + end + + # Test no-blowup for scoped lock. + def test_sane_find_with_lock + assert_nothing_raised do + Person.transaction do + Person.with_scope(:find => { :lock => true }) do + Person.find 1 + end + end + end + end + + # Locking a record reloads it. + def test_sane_lock_method + assert_nothing_raised do + Person.transaction do + person = Person.find 1 + old, person.first_name = person.first_name, 'fooman' + person.lock! + assert_equal old, person.first_name + end + end + end + + if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) + def test_no_locks_no_wait + first, second = duel { Person.find 1 } + assert first.end > second.end + end + + def test_second_lock_waits + assert [0.2, 1, 5].any? { |zzz| + first, second = duel(zzz) { Person.find 1, :lock => true } + second.end > first.end + } + end + + protected + def duel(zzz = 5) + t0, t1, t2, t3 = nil, nil, nil, nil + + a = Thread.new do + t0 = Time.now + Person.transaction do + yield + sleep zzz # block thread 2 for zzz seconds + end + t1 = Time.now + end + + b = Thread.new do + sleep zzz / 2.0 # ensure thread 1 tx starts first + t2 = Time.now + Person.transaction { yield } + t3 = Time.now + end + + a.join + b.join + + assert t1 > t0 + zzz + assert t2 > t0 + assert t3 > t2 + [t0.to_f..t1.to_f, t2.to_f..t3.to_f] + end + end + end +end diff --git a/vendor/rails/activerecord/test/method_scoping_test.rb b/vendor/rails/activerecord/test/method_scoping_test.rb new file mode 100644 index 0000000..bceb386 --- /dev/null +++ b/vendor/rails/activerecord/test/method_scoping_test.rb @@ -0,0 +1,416 @@ +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 diff --git a/vendor/rails/activerecord/test/migration_test.rb b/vendor/rails/activerecord/test/migration_test.rb new file mode 100644 index 0000000..649ee54 --- /dev/null +++ b/vendor/rails/activerecord/test/migration_test.rb @@ -0,0 +1,656 @@ +require 'abstract_unit' +require 'bigdecimal/util' + +require 'fixtures/person' +require 'fixtures/topic' +require File.dirname(__FILE__) + '/fixtures/migrations/1_people_have_last_names' +require File.dirname(__FILE__) + '/fixtures/migrations/2_we_need_reminders' +require File.dirname(__FILE__) + '/fixtures/migrations_with_decimal/1_give_me_big_numbers' + +if ActiveRecord::Base.connection.supports_migrations? + class BigNumber < ActiveRecord::Base; end + + class Reminder < ActiveRecord::Base; end + + class ActiveRecord::Migration + class < 40) + Person.reset_column_information + end + + def test_add_index + # Limit size of last_name and key columns to support Firebird index limitations + Person.connection.add_column "people", "last_name", :string, :limit => 100 + Person.connection.add_column "people", "key", :string, :limit => 100 + Person.connection.add_column "people", "administrator", :boolean + + 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 + # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word + assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) } + assert_nothing_raised { Person.connection.remove_index("people", :name => "key_idx", :unique => true) } + + # 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 + assert_equal true, two.default + assert_equal false, three.default + assert_equal 1, four.default + + ensure + Person.connection.drop_table :testings rescue nil + end + + def test_create_table_with_limits + assert_nothing_raised do + Person.connection.create_table :testings do |t| + t.column :foo, :string, :limit => 255 + + t.column :default_int, :integer + + t.column :one_int, :integer, :limit => 1 + t.column :four_int, :integer, :limit => 4 + t.column :eight_int, :integer, :limit => 8 + end + end + + columns = Person.connection.columns(:testings) + foo = columns.detect { |c| c.name == "foo" } + assert_equal 255, foo.limit + + default = columns.detect { |c| c.name == "default_int" } + one = columns.detect { |c| c.name == "one_int" } + four = columns.detect { |c| c.name == "four_int" } + eight = columns.detect { |c| c.name == "eight_int" } + + if current_adapter?(:PostgreSQLAdapter) + assert_equal 'integer', default.sql_type + assert_equal 'smallint', one.sql_type + assert_equal 'integer', four.sql_type + assert_equal 'bigint', eight.sql_type + elsif current_adapter?(:OracleAdapter) + assert_equal 'NUMBER(38)', default.sql_type + assert_equal 'NUMBER(1)', one.sql_type + assert_equal 'NUMBER(4)', four.sql_type + assert_equal 'NUMBER(8)', eight.sql_type + end + 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, :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 + + con = Person.connection + Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" + assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" } + + assert_raises(ActiveRecord::StatementInvalid) do + Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" + end + ensure + Person.connection.drop_table :testings rescue nil + end + + # We specifically do a manual INSERT here, and then test only the SELECT + # functionality. This allows us to more easily catch INSERT being broken, + # but SELECT actually working fine. + def test_native_decimal_insert_manual_vs_automatic + # SQLite3 always uses float in violation of SQL + # 16 decimal places + correct_value = (current_adapter?(:SQLiteAdapter) ? '0.123456789012346E20' : '0012345678901234567890.0123456789').to_d + + Person.delete_all + Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10' + Person.reset_column_information + + # Do a manual insertion + if current_adapter?(:OracleAdapter) + Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)" + else + Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)" + end + + # SELECT + row = Person.find(:first) + assert_kind_of BigDecimal, row.wealth + + # If this assert fails, that means the SELECT is broken! + assert_equal correct_value, row.wealth + + # Reset to old state + Person.delete_all + + # Now use the Rails insertion + assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") } + + # SELECT + row = Person.find(:first) + assert_kind_of BigDecimal, row.wealth + + # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken! + assert_equal correct_value, row.wealth + + # Reset to old state + Person.connection.del_column "people", "wealth" rescue nil + Person.reset_column_information + 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", "wealth", :decimal, :precision => '30', :scale => '10' + 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, :wealth => BigDecimal.new("12345678901234567890.0123456789"), :birthday => 18.years.ago, :favorite_day => 10.days.ago, :male => true } + bob = Person.find(:first) + + assert_equal 'bob', bob.first_name + assert_equal 'bobsen', bob.last_name + assert_equal "I was born ....", bob.bio + assert_equal 18, bob.age + + # Test for 30 significent digits (beyond the 16 of float), 10 of them + # after the decimal place. + if current_adapter?(:SQLiteAdapter) + # SQLite3 uses float in violation of SQL. Test for 16 decimal places. + assert_equal BigDecimal.new('0.123456789012346E20'), bob.wealth + else + assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth + end + + assert_equal true, bob.male? + + 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, :OracleAdapter, :SybaseAdapter) + # 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 + assert_kind_of BigDecimal, bob.wealth + 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 + + # Using explicit id in insert for compatibility across all databases + con = ActiveRecord::Base.connection + assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" } + + 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 } + + old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } + assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } + new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } + assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } + assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } + 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_add_table_with_decimals + Person.connection.drop_table :big_numbers rescue nil + + assert !BigNumber.table_exists? + GiveMeBigNumbers.up + + assert BigNumber.create( + :bank_balance => 1586.43, + :big_bank_balance => BigDecimal("1000234000567.95"), + :world_population => 6000000000, + :my_house_population => 3, + :value_of_e => BigDecimal("2.7182818284590452353602875") + ) + + b = BigNumber.find(:first) + assert_not_nil b + + assert_not_nil b.bank_balance + assert_not_nil b.big_bank_balance + assert_not_nil b.world_population + assert_not_nil b.my_house_population + assert_not_nil b.value_of_e + + # TODO: set world_population >= 2**62 to cover 64-bit platforms and test + # is_a?(Bignum) + assert_kind_of Integer, b.world_population + assert_equal 6000000000, b.world_population + assert_kind_of Fixnum, b.my_house_population + assert_equal 3, b.my_house_population + assert_kind_of BigDecimal, b.bank_balance + assert_equal BigDecimal("1586.43"), b.bank_balance + assert_kind_of BigDecimal, b.big_bank_balance + assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance + + # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with + # precision/scale explictly left out. By the SQL standard, numbers + # assigned to this field should be truncated but that's seldom respected. + if current_adapter?(:PostgreSQLAdapter, :SQLite2Adapter) + # - PostgreSQL changes the SQL spec on columns declared simply as + # "decimal" to something more useful: instead of being given a scale + # of 0, they take on the compile-time limit for precision and scale, + # so the following should succeed unless you have used really wacky + # compilation options + # - SQLite2 has the default behavior of preserving all data sent in, + # so this happens there too + assert_kind_of BigDecimal, b.value_of_e + assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e + elsif current_adapter?(:SQLiteAdapter) + # - SQLite3 stores a float, in violation of SQL + assert_kind_of BigDecimal, b.value_of_e + assert_equal BigDecimal("2.71828182845905"), b.value_of_e + elsif current_adapter?(:SQLServer) + # - SQL Server rounds instead of truncating + assert_kind_of Fixnum, b.value_of_e + assert_equal 3, b.value_of_e + else + # - SQL standard is an integer + assert_kind_of Fixnum, b.value_of_e + assert_equal 2, b.value_of_e + end + + GiveMeBigNumbers.down + assert_raises(ActiveRecord::StatementInvalid) { BigNumber.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 + +# FrontBase does not support default values on BLOB/CLOB columns + unless current_adapter?(:FrontBaseAdapter) + 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 + 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 diff --git a/vendor/rails/activerecord/test/migration_test_firebird.rb b/vendor/rails/activerecord/test/migration_test_firebird.rb new file mode 100644 index 0000000..9d4b80c --- /dev/null +++ b/vendor/rails/activerecord/test/migration_test_firebird.rb @@ -0,0 +1,124 @@ +require 'abstract_unit' +require 'fixtures/course' + +class FirebirdMigrationTest < Test::Unit::TestCase + self.use_transactional_fixtures = false + + def setup + # using Course connection for tests -- need a db that doesn't already have a BOOLEAN domain + @connection = Course.connection + @fireruby_connection = @connection.instance_variable_get(:@connection) + end + + def teardown + @connection.drop_table :foo rescue nil + @connection.execute("DROP DOMAIN D_BOOLEAN") rescue nil + end + + def test_create_table_with_custom_sequence_name + assert_nothing_raised do + @connection.create_table(:foo, :sequence => 'foo_custom_seq') do |f| + f.column :bar, :string + end + end + assert !sequence_exists?('foo_seq') + assert sequence_exists?('foo_custom_seq') + + assert_nothing_raised { @connection.drop_table(:foo, :sequence => 'foo_custom_seq') } + assert !sequence_exists?('foo_custom_seq') + ensure + FireRuby::Generator.new('foo_custom_seq', @fireruby_connection).drop rescue nil + end + + def test_create_table_without_sequence + assert_nothing_raised do + @connection.create_table(:foo, :sequence => false) do |f| + f.column :bar, :string + end + end + assert !sequence_exists?('foo_seq') + assert_nothing_raised { @connection.drop_table :foo } + + assert_nothing_raised do + @connection.create_table(:foo, :id => false) do |f| + f.column :bar, :string + end + end + assert !sequence_exists?('foo_seq') + assert_nothing_raised { @connection.drop_table :foo } + end + + def test_create_table_with_boolean_column + assert !boolean_domain_exists? + assert_nothing_raised do + @connection.create_table :foo do |f| + f.column :bar, :string + f.column :baz, :boolean + end + end + assert boolean_domain_exists? + end + + def test_add_boolean_column + assert !boolean_domain_exists? + @connection.create_table :foo do |f| + f.column :bar, :string + end + + assert_nothing_raised { @connection.add_column :foo, :baz, :boolean } + assert boolean_domain_exists? + assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "baz" }.type + end + + def test_change_column_to_boolean + assert !boolean_domain_exists? + # Manually create table with a SMALLINT column, which can be changed to a BOOLEAN + @connection.execute "CREATE TABLE foo (bar SMALLINT)" + assert_equal :integer, @connection.columns(:foo).find { |c| c.name == "bar" }.type + + assert_nothing_raised { @connection.change_column :foo, :bar, :boolean } + assert boolean_domain_exists? + assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "bar" }.type + end + + def test_rename_table_with_data_and_index + @connection.create_table :foo do |f| + f.column :baz, :string, :limit => 50 + end + 100.times { |i| @connection.execute "INSERT INTO foo VALUES (GEN_ID(foo_seq, 1), 'record #{i+1}')" } + @connection.add_index :foo, :baz + + assert_nothing_raised { @connection.rename_table :foo, :bar } + assert !@connection.tables.include?("foo") + assert @connection.tables.include?("bar") + assert_equal "bar_baz_index", @connection.indexes("bar").first.name + assert_equal 100, FireRuby::Generator.new("bar_seq", @fireruby_connection).last + assert_equal 100, @connection.select_one("SELECT COUNT(*) FROM bar")["count"] + ensure + @connection.drop_table :bar rescue nil + end + + def test_renaming_table_with_fk_constraint_raises_error + @connection.create_table :parent do |p| + p.column :name, :string + end + @connection.create_table :child do |c| + c.column :parent_id, :integer + end + @connection.execute "ALTER TABLE child ADD CONSTRAINT fk_child_parent FOREIGN KEY(parent_id) REFERENCES parent(id)" + assert_raise(ActiveRecord::ActiveRecordError) { @connection.rename_table :child, :descendant } + ensure + @connection.drop_table :child rescue nil + @connection.drop_table :descendant rescue nil + @connection.drop_table :parent rescue nil + end + + private + def boolean_domain_exists? + !@connection.select_one("SELECT 1 FROM rdb$fields WHERE rdb$field_name = 'D_BOOLEAN'").nil? + end + + def sequence_exists?(sequence_name) + FireRuby::Generator.exists?(sequence_name, @fireruby_connection) + end +end diff --git a/vendor/rails/activerecord/test/mixin_nested_set_test.rb b/vendor/rails/activerecord/test/mixin_nested_set_test.rb new file mode 100644 index 0000000..8764a6a --- /dev/null +++ b/vendor/rails/activerecord/test/mixin_nested_set_test.rb @@ -0,0 +1,184 @@ +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 diff --git a/vendor/rails/activerecord/test/mixin_test.rb b/vendor/rails/activerecord/test/mixin_test.rb new file mode 100644 index 0000000..8ab73cf --- /dev/null +++ b/vendor/rails/activerecord/test/mixin_test.rb @@ -0,0 +1,512 @@ +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 + diff --git a/vendor/rails/activerecord/test/modules_test.rb b/vendor/rails/activerecord/test/modules_test.rb new file mode 100644 index 0000000..b2ef873 --- /dev/null +++ b/vendor/rails/activerecord/test/modules_test.rb @@ -0,0 +1,35 @@ +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 + + def test_find_account_and_include_company + account = MyApplication::Billing::Account.find(1, :include => :firm) + assert_kind_of MyApplication::Business::Firm, account.instance_variable_get('@firm') + assert_kind_of MyApplication::Business::Firm, account.firm + end + +end diff --git a/vendor/rails/activerecord/test/multiple_db_test.rb b/vendor/rails/activerecord/test/multiple_db_test.rb new file mode 100644 index 0000000..df2ef10 --- /dev/null +++ b/vendor/rails/activerecord/test/multiple_db_test.rb @@ -0,0 +1,60 @@ +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 diff --git a/vendor/rails/activerecord/test/pk_test.rb b/vendor/rails/activerecord/test/pk_test.rb new file mode 100644 index 0000000..604b263 --- /dev/null +++ b/vendor/rails/activerecord/test/pk_test.rb @@ -0,0 +1,81 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" +require 'fixtures/topic' +require 'fixtures/reply' +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 diff --git a/vendor/rails/activerecord/test/readonly_test.rb b/vendor/rails/activerecord/test/readonly_test.rb new file mode 100755 index 0000000..584eb73 --- /dev/null +++ b/vendor/rails/activerecord/test/readonly_test.rb @@ -0,0 +1,107 @@ +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 diff --git a/vendor/rails/activerecord/test/reflection_test.rb b/vendor/rails/activerecord/test/reflection_test.rb new file mode 100644 index 0000000..907d021 --- /dev/null +++ b/vendor/rails/activerecord/test/reflection_test.rb @@ -0,0 +1,159 @@ +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_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_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 14, Firm.reflect_on_all_associations.size + assert_equal 12, 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 diff --git a/vendor/rails/activerecord/test/schema_dumper_test.rb b/vendor/rails/activerecord/test/schema_dumper_test.rb new file mode 100644 index 0000000..330a4b1 --- /dev/null +++ b/vendor/rails/activerecord/test/schema_dumper_test.rb @@ -0,0 +1,96 @@ +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 standard_dump + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + stream.string + end + + def test_schema_dump + output = standard_dump + 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 assert_line_up(lines, pattern, required = false) + return assert(true) if lines.empty? + matches = lines.map { |line| line.match(pattern) } + assert matches.all? if required + matches.compact! + return assert(true) if matches.empty? + assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length + end + + def test_arguments_line_up + output = standard_dump + output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) }.each do |column_set| + assert_line_up(column_set, /:(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)/, true) + assert_line_up(column_set, /:default => /) + assert_line_up(column_set, /:limit => /) + assert_line_up(column_set, /:null => /) + end + end + + def test_no_dump_errors + output = standard_dump + assert_no_match %r{\# Could not dump table}, 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 + + def test_schema_dump_includes_decimal_options + stream = StringIO.new + ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/] + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream) + output = stream.string + assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output + end + end + +end diff --git a/vendor/rails/activerecord/test/schema_test_postgresql.rb b/vendor/rails/activerecord/test/schema_test_postgresql.rb new file mode 100644 index 0000000..c743a86 --- /dev/null +++ b/vendor/rails/activerecord/test/schema_test_postgresql.rb @@ -0,0 +1,64 @@ +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 diff --git a/vendor/rails/activerecord/test/synonym_test_oracle.rb b/vendor/rails/activerecord/test/synonym_test_oracle.rb new file mode 100644 index 0000000..fcfb2f3 --- /dev/null +++ b/vendor/rails/activerecord/test/synonym_test_oracle.rb @@ -0,0 +1,17 @@ +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 diff --git a/vendor/rails/activerecord/test/threaded_connections_test.rb b/vendor/rails/activerecord/test/threaded_connections_test.rb new file mode 100644 index 0000000..aaa56b3 --- /dev/null +++ b/vendor/rails/activerecord/test/threaded_connections_test.rb @@ -0,0 +1,48 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/reply' + +unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name + 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 +end diff --git a/vendor/rails/activerecord/test/transactions_test.rb b/vendor/rails/activerecord/test/transactions_test.rb new file mode 100644 index 0000000..a8584cf --- /dev/null +++ b/vendor/rails/activerecord/test/transactions_test.rb @@ -0,0 +1,228 @@ +require 'abstract_unit' +require 'fixtures/topic' +require 'fixtures/reply' +require 'fixtures/developer' + +class TransactionTest < Test::Unit::TestCase + self.use_transactional_fixtures = false + fixtures :topics, :developers + + def setup + @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 + + 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 + +if current_adapter?(:PostgreSQLAdapter) + class ConcurrentTransactionTest < TransactionTest + def setup + @allow_concurrency = ActiveRecord::Base.allow_concurrency + ActiveRecord::Base.allow_concurrency = true + super + end + + def teardown + super + ActiveRecord::Base.allow_concurrency = @allow_concurrency + 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..3).map do + Thread.new do + Topic.transaction do + topic = Topic.find(1) + 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..3).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 + end +end diff --git a/vendor/rails/activerecord/test/unconnected_test.rb b/vendor/rails/activerecord/test/unconnected_test.rb new file mode 100755 index 0000000..2d06410 --- /dev/null +++ b/vendor/rails/activerecord/test/unconnected_test.rb @@ -0,0 +1,32 @@ +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 diff --git a/vendor/rails/activerecord/test/validations_test.rb b/vendor/rails/activerecord/test/validations_test.rb new file mode 100755 index 0000000..ddfdbb7 --- /dev/null +++ b/vendor/rails/activerecord/test/validations_test.rb @@ -0,0 +1,1075 @@ +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_scoped_create_without_attributes + Reply.with_scope(:create => {}) do + assert_raises(ActiveRecord::RecordInvalid) { Reply.create! } + 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_case_insensitive_uniqueness + Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true) + + t = Topic.new("title" => "I'm unique!", :parent_id => 2) + 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!", :parent_id => 1) + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert t2.errors.on(:title) + assert t2.errors.on(:parent_id) + assert_equal "has already been taken", t2.errors.on(:title) + + t2.title = "I'm truly UNIQUE!" + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert_nil t2.errors.on(:title) + assert t2.errors.on(:parent_id) + + t2.parent_id = 3 + assert t2.save, "Should now save t2 as unique" + + t2.parent_id = nil + t2.title = nil + assert t2.valid?, "should validate with nil" + assert t2.save, "should save with nil" + 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 + + def test_errors_to_xml + r = Reply.new :title => "Wrong Create" + assert !r.valid? + xml = r.errors.to_xml(:skip_instruct => true) + assert_equal "", xml.first(8) + assert xml.include?("Title is Wrong Create") + assert xml.include?("Content Empty") + end +end + + +class ValidatesNumericalityTest + NIL = [nil, "", " ", " \t \r \n"] + BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits + FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5) + 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 + BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) } + 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 + BIGDECIMAL) + end + + def test_validates_numericality_of_with_nil_allowed + Topic.validates_numericality_of :approved, :allow_nil => true + + invalid!(JUNK) + valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL) + end + + def test_validates_numericality_of_with_integer_only + Topic.validates_numericality_of :approved, :only_integer => true + + invalid!(NIL + JUNK + FLOATS + BIGDECIMAL) + 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 + BIGDECIMAL) + 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 diff --git a/vendor/rails/activesupport/CHANGELOG b/vendor/rails/activesupport/CHANGELOG new file mode 100644 index 0000000..3ddfc19 --- /dev/null +++ b/vendor/rails/activesupport/CHANGELOG @@ -0,0 +1,595 @@ +*SVN* + +* DateTime#to_time gives hour/minute/second resolution. #5747 [jon.evans@pobox.com] + +* attr_internal to support namespacing and deprecation. Like attr_* except backed by internally-named instance variable. Set attr_internal_naming_format to change the format from the default '@_%s'. [Jeremy Kemper] + # def foo() @foo__rofl end + # def foo=(v) @foo__rofl = v end + self.attr_internal_naming_format = '@%s__rofl' + attr_internal :foo + +* Raise fully qualified names upon name errors. #5533 [lars@pinds.com, Nicholas Seckar] + +* Add extention to obtain the missing constant from NameError instances. [Nicholas Seckar] + +* Thoroughly document inflections. #5700 [petermichaux@gmail.com] + +* Added Module#alias_attribute [Jamis/DHH]. Example: + + class Content < ActiveRecord::Base + # has a title attribute + end + + class Email < ActiveRecord::Base + alias_attribute :subject, :title + end + + e = Email.find(1) + e.title # => "Superstars" + e.subject # => "Superstars" + e.subject? # => true + e.subject = "Megastars" + e.title # => "Megastars" + +* Deprecation: easier to work with warning behavior as procs; default behaviors for each environment so users needn't update env.rb; and testing pleasure with assert_deprecated, assert_not_deprecated. [Jeremy Kemper] + By default, test prints to $stderr, dev logs, production ignores. + Provide your own per-environment in e.g. config/environments/development.rb: + ActiveSupport::Deprecation.behavior = Proc.new { |message| raise message } + +* First cut of the Rails Deprecation system. [Koz] + +* Strip boolean XML content before checking for 'true' [Rick Olson] + +* Customize default BigDecimal formatting. References #5672 [dave@pragprog.com] + +* Correctly convert to nil when using Hash.create_from_xml. [Rick] + +* Optional identity for Enumerable#sum defaults to zero. #5657 [gensym@mac.com] + +* HashWithIndifferentAccess shouldn't confuse false and nil. #5601 [shugo@ruby-lang.org] + +* Fixed HashWithIndifferentAccess#default #5586 [chris@seagul.co.uk] + +* More compatible Hash.create_from_xml. #5523 [nunemaker@gmail.com] + +* Added Enumerable#sum for calculating a sum from the elements [DHH, jonathan@daikini.com]. Examples: + + [1, 2, 3].sum + payments.sum { |p| p.price * p.tax_rate } + payments.sum(&:price) + + This is instead of payments.inject(0) { |sum, p| sum + p.price } + +* Correct and clarify Array#to_sentence docs. #5458 [brad@madriska.com] + +* alias_method_chain preserves method punctuation so foo, foo?, and foo! may be chained with the same feature. [Jeremy Kemper] + Example: + alias_method_chain :save!, :validation + is equivalent to + alias_method :save_without_validation!, :save! + alias_method :save!, :save_with_validation! + +* Enhance Symbol#to_proc so it works with list objects, such as multi-dimensional arrays. Closes #5295 [nov@yo.rim.or.jp]. Example: + + {1 => "one", 2 => "two", 3 => "three"}.sort_by(&:first).map(&:last) + #=> ["one", "two", "three"] + +* Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [DHH]. Example: + + Hash.create_from_xml <<-EOT + + This is a note + 2004-10-10 + + EOT + + ...would return: + + { :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } } + +* Added Jim Weirich's excellent FlexMock class to vendor (Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.org)) -- it's not automatically required, though, so require 'flexmock' is still necessary [DHH] + +* Fixed that Module#alias_method_chain should work with both foo? foo! and foo at the same time #4954 [anna@wota.jp] + +* to_xml fixes, features, and speedup: introduce :dasherize option that converts updated_at to updated-at if true (the existing default); binary columns get encoding="base64" attribute; nil values get nil="true" attribute to distinguish empty values; add type information for float columns; allow arbitrarily deep :include; include SQL type information as the type attribute. #4989 [Blair Zajac ] + +* Add OrderedHash#values. [Sam Stephenson] + +* Added Array#to_s(:db) that'll produce a comma-separated list of ids [DHH]. Example: + + Purchase.find(:all, :conditions => "product_id IN (#{shops.products.to_s(:db)})" + +* Normalize classify's argument to a String so that it plays nice with Symbols. [Marcel Molina Jr.] + +* Strip out leading schema name in classify. References #5139. [schoenm@earthlink.net] + +* Remove Enumerable#first_match since break(value) handles the use case well enough. [Nicholas Seckar] + + Enumerable#first_match was like detect, but instead of returning the matching element, the yielded value returned. For example: + + user_xml = adapters(:from => User, :to => Xml).first_match do |adapter| + adapter.adapt @user + end + + But this is just as easily done with: + + user_xml = adapters(:from => User, :to => Xml).each do + break adapter.adapt(@user) + end + +* Make Array#in_groups_of just return the grouped collection if a block isn't given. [Marcel Molina Jr.] + +* Don't destroy a HashWithIndifferentAccess if symbolize_keys! or stringify_keys! is called on it. Closes #5076. [Marcel Molina Jr., guy.naor@famundo.com] + +* Document Module::delegate. #5002 [pergesu@gmail.com] + +* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.] + +* Strip out punctuation on predicates or bang methods being aliased with alias_method_chain since target?_without_feature is not a valid method name. Add tests for Module#alias_method_chain. [Marcel Molina Jr.] + +* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.] + +* Allow default options in with_options to be overridden. Closes #4480. [murphy@cYcnus.de] + +* Added Module#alias_method_chain [Jamis Buck] + +* Updated to Builder 2.0 [DHH] + +* Add Array#split for dividing arrays into one or more subarrays by value or block. [Sam Stephenson] + +*1.3.1* (April 6th, 2006) + +* Clean paths inside of exception messages and traces. [Nicholas Seckar] + +* Add Pathname.clean_within for cleaning all the paths inside of a string. [Nicholas Seckar] + +* provide an empty Dependencies::LoadingModule.load which prints deprecation warnings. Lets 1.0 applications function with .13-style environment.rb. + + +*1.3.0* (March 27th, 2006) + +* When possible, avoid incorrectly obtaining constants from parent modules. Fixes #4221. [Nicholas Seckar] + +* Add more tests for dependencies; refactor existing cases. [Nicholas Seckar] + +* Move Module#parent and Module#as_load_path into core_ext. Add Module#parent. [Nicholas Seckar] + +* Add CachingTools::HashCaching to simplify the creation of nested, autofilling hashes. [Nicholas Seckar] + +* Remove a hack intended to avoid unloading the same class twice, but which would not work anyways. [Nicholas Seckar] + +* Update Object.subclasses_of to locate nested classes. This affects Object.remove_subclasses_of in that nested classes will now be unloaded. [Nicholas Seckar] + +* Update Object.remove_subclasses_of to use Class.remove_class, reducing duplication. [Nicholas Seckar] + +* Added Fixnum#seconds for consistency, so you can say 5.minutes + 30.seconds instead of 5.minutes + 30 #4389 [François Beausoleil] + +* Added option to String#camelize to generate lower-cased camel case by passing in :lower, like "super_man".camelize(:lower) # => "superMan" [DHH] + +* Added Hash#diff to show the difference between two hashes [Chris McGrath] + +* Added Time#advance to do precise time time calculations for cases where a month being approximated to 30 days won't do #1860 [Rick Olson] + +* Enhance Inflector.underscore to convert '-' into '_' (as the inverse of Inflector.dasherize) [Jamis Buck] + +* Switched to_xml to use the xml schema format for datetimes. This allows the encoding of time zones and should improve operability. [Koz] + +* Added a note to the documentation for the Date related Numeric extensions to indicate that they're +approximations and shouldn't be used for critical calculations. [Koz] + +* Added Hash#to_xml and Array#to_xml that makes it much easier to produce XML from basic structures [DHH]. Examples: + + { :name => "David", :street_name => "Paulina", :age => 26, :moved_on => Date.new(2005, 11, 15) }.to_xml + + ...returns: + + + Paulina + David + 26 + 2005-11-15 + + +* Moved Jim Weirich's wonderful Builder from Action Pack to Active Support (it's simply too useful to be stuck in AP) [DHH] + +* Fixed that Array#to_sentence will return "" on an empty array instead of ", and" #3842, #4031 [rubyonrails@beautifulpixel.com] + +* Add Enumerable#group_by for grouping collections based on the result of some + block. Useful, for example, for grouping records by date. + + ex. + + 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" + + Add Array#in_groups_of, for iterating over an array in groups of a certain + size. + + ex. + + %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g} + ["1", "2", "3"] + ["4", "5", "6"] + ["7", nil, nil] + + [Marcel Molina Jr., Sam Stephenson] + +* Added Kernel#daemonize to turn the current process into a daemon that can be killed with a TERM signal [DHH] + +* Add 'around' methods to Logger, to make it easy to log before and after messages for a given block as requested in #3809. [Michael Koziarski] Example: + + logger.around_info("Start rendering component (#{options.inspect}): ", + "\n\nEnd of component rendering") { yield } + +* Added Time#beginning_of_quarter #3607 [cohen.jeff@gmail.com] + +* Fix Object.subclasses_of to only return currently defined objects [Jonathan Viney ] + +* Fix constantize to properly handle names beginning with '::'. [Nicholas Seckar] + +* Make String#last return the string instead of nil when it is shorter than the limit [Scott Barron]. + +* Added delegation support to Module that allows multiple delegations at once (unlike Forwardable in the stdlib) [DHH]. Example: + + class Account < ActiveRecord::Base + has_one :subscription + delegate :free?, :paying?, :to => :subscription + delegate :overdue?, :to => "subscription.last_payment" + end + + account.free? # => account.subscription.free? + account.overdue? # => account.subscription.last_payment.overdue? + +* Fix Reloadable to handle the case where a class that has been 'removed' has not yet been garbage collected. [Nicholas Seckar] + +* Don't allow Reloadable to be included into Modules. + +* Remove LoadingModule. [Nicholas Seckar] + +* Add documentation for Reloadable::Subclasses. [Nicholas Seckar] + +* Add Reloadable::Subclasses which handles the common case where a base class should not be reloaded, but its subclasses should be. [Nicholas Seckar] + +* Further improvements to reloading code [Nicholas Seckar, Trevor Squires] + + - All classes/modules which include Reloadable can define reloadable? for fine grained control of reloading + - Class.remove_class uses Module#parent to access the parent module + - Class.remove_class expanded to handle multiple classes in a single call + - LoadingModule.clear! has been removed as it is no longer required + - Module#remove_classes_including has been removed in favor of Reloadable.reloadable_classes + +* Added reusable reloading support through the inclusion of the Relodable module that all subclasses of ActiveRecord::Base, ActiveRecord::Observer, ActiveController::Base, and ActionMailer::Base automatically gets. This means that these classes will be reloaded by the dispatcher when Dependencies.mechanism = :load. You can make your own models reloadable easily: + + class Setting + include Reloadable + end + + Reloading a class is done by removing its constant which will cause it to be loaded again on the next reference. [DHH] + +* Added auto-loading support for classes in modules, so Conductor::Migration will look for conductor/migration.rb and Conductor::Database::Settings will look for conductor/database/settings.rb [Nicholas Seckar] + +* Add Object#instance_exec, like instance_eval but passes its arguments to the block. (Active Support will not override the Ruby 1.9 implementation of this method.) [Sam Stephenson] + +* Add Proc#bind(object) for changing a proc or block's self by returning a Method bound to the given object. Based on why the lucky stiff's "cloaker" method. [Sam Stephenson] + +* Fix merge and dup for hashes with indifferent access #3404 [kenneth.miller@bitfield.net] + +* Fix the requires in option_merger_test to unbreak AS tests. [Sam Stephenson] + +* Make HashWithIndifferentAccess#update behave like Hash#update by returning the hash. #3419, #3425 [asnem@student.ethz.ch, JanPrill@blauton.de, Marcel Molina Jr.] + +* Add ActiveSupport::JSON and Object#to_json for converting Ruby objects to JSON strings. [Sam Stephenson] + +* Add Object#with_options for DRYing up multiple calls to methods having shared options. [Sam Stephenson] Example: + + ActionController::Routing::Routes.draw do |map| + # Account routes + map.with_options(:controller => 'account') do |account| + account.home '', :action => 'dashboard' + account.signup 'signup', :action => 'new' + account.logout 'logout', :action => 'logout' + end + end + +* Introduce Dependencies.warnings_on_first_load setting. If true, enables warnings on first load of a require_dependency. Otherwise, loads without warnings. Disabled (set to false) by default. [Jeremy Kemper] + +* Active Support is warnings-safe. #1792 [Eric Hodel] + +* Introduce enable_warnings counterpart to silence_warnings. Turn warnings on when loading a file for the first time if Dependencies.mechanism == :load. Common mistakes such as redefined methods will print warnings to stderr. [Jeremy Kemper] + +* Add Symbol#to_proc, which allows for, e.g. [:foo, :bar].map(&:to_s). [Marcel Molina Jr.] + +* Added the following methods [Marcel Molina Jr., Sam Stephenson]: + * Object#copy_instance_variables_from(object) to copy instance variables from one object to another + * Object#extended_by to get an instance's included/extended modules + * Object#extend_with_included_modules_from(object) to extend an instance with the modules from another instance + +*1.2.5* (December 13th, 2005) + +* Become part of Rails 1.0 + +* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.] + +*1.2.3* (November 7th, 2005) + +* Change Inflector#constantize to use eval instead of const_get. [Nicholas Seckar] + +* Fix const_missing handler to ignore the trailing '.rb' on files when comparing paths. [Nicholas Seckar] + +* Define kernel.rb methods in "class Object" instead of "module Kernel" to work around a Windows peculiarity [Sam Stephenson] + +* Fix broken tests caused by incomplete loading of active support. [Nicholas Seckar] + +* Fix status pluralization bug so status_codes doesn't get pluralized as statuses_code. #2758 [keithm@infused.org] + +* Added Kernel#silence_stderr to silence stderr for the duration of the given block [Sam Stephenson] + +* Changed Kernel#` to print a message to stderr (like Unix) instead of raising Errno::ENOENT on Win32 [Sam Stephenson] + +* Changed 0.blank? to false rather than true since it violates everyone's expectation of blankness. #2518, #2705 [rails@jeffcole.net] + +* When loading classes using const_missing, raise a NameError if and only if the file we tried to load was not present. [Nicholas Seckar] + +* Added petabytes and exebytes to numeric extensions #2397 [timct@mac.com] + +* Added Time#end_of_month to accompany Time#beginning_of_month #2514 [Jens-Christian Fischer] + + +*1.2.2* (October 26th, 2005) + +* Set Logger.silencer = false to disable Logger#silence. Useful for debugging fixtures. + +* Add title case method to String to do, e.g., 'action_web_service'.titlecase # => 'Action Web Service'. [Marcel Molina Jr.] + + +*1.2.1* (October 19th, 2005) + +* Classify generated routing code as framework code to avoid appearing in application traces. [Nicholas Seckar] + +* Show all framework frames in the framework trace. [Nicholas Seckar] + + +*1.2.0* (October 16th, 2005) + +* Update Exception extension to show the first few framework frames in an application trace. [Nicholas Seckar] + +* Added Exception extension to provide support for clean backtraces. [Nicholas Seckar] + +* Updated whiny nil to be more concise and useful. [Nicholas Seckar] + +* Added Enumerable#first_match [Nicholas Seckar] + +* Fixed that Time#change should also reset usec when also resetting minutes #2459 [ikeda@dream.big.or.jp] + +* Fix Logger compatibility for distributions that don't keep Ruby and its standard library in sync. + +* Replace '%e' from long and short time formats as Windows does not support it. #2344. [Tom Ward ] + +* Added to_s(:db) to Range, so you can get "BETWEEN '2005-12-10' AND '2005-12-12'" from Date.new(2005, 12, 10)..Date.new(2005, 12, 12) (and likewise with Times) + +* Moved require_library_or_gem into Kernel. #1992 [Michael Schuerig ] + +* Add :rfc822 as an option for Time#to_s (to get rfc822-formatted times) + +* Chain the const_missing hook to any previously existing hook so rails can play nicely with rake + +* Clean logger is compatible with both 1.8.2 and 1.8.3 Logger. #2263 [Michael Schuerig ] + +* Added native, faster implementations of .blank? for the core types #2286 [skae] + +* Fixed clean logger to work with Ruby 1.8.3 Logger class #2245 + +* Fixed memory leak with Active Record classes when Dependencies.mechanism = :load #1704 [c.r.mcgrath@gmail.com] + +* Fixed Inflector.underscore for use with acronyms, so HTML becomes html instead of htm_l #2173 [k@v2studio.com] + +* Fixed dependencies related infinite recursion bug when a controller file does not contain a controller class. Closes #1760. [rcolli2@tampabay.rr.com] + +* Fixed inflections for status, quiz, move #2056 [deirdre@deirdre.net] + +* Added Hash#reverse_merge, Hash#reverse_merge!, and Hash#reverse_update to ease the use of default options + +* Added Array#to_sentence that'll turn ['one', 'two', 'three'] into "one, two, and three" #2157 [m.stienstra@fngtps.com] + +* Added Kernel#silence_warnings to turn off warnings temporarily for the passed block + +* Added String#starts_with? and String#ends_with? #2118 [thijs@vandervossen.net] + +* Added easy extendability to the inflector through Inflector.inflections (using the Inflector::Inflections singleton class). 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 + +* Added String#at, String#from, String#to, String#first, String#last in ActiveSupport::CoreExtensions::String::Access to ease access to individual characters and substrings in a string serving basically as human names for range access. + +* Make Time#last_month work when invoked on the 31st of a month. + +* Add Time.days_in_month, and make Time#next_month work when invoked on the 31st of a month + +* Fixed that Time#midnight would have a non-zero usec on some platforms #1836 + +* Fixed inflections of "index/indices" #1766 [damn_pepe@gmail.com] + +* Added stripping of _id to String#humanize, so "employee_id" becomes "Employee" #1574 [Justin French] + +* Factor Fixnum and Bignum extensions into Integer extensions [Nicholas Seckar] + +* Hooked #ordinalize into Fixnum and Bignum classes. [Nicholas Seckar, danp] + +* Added Fixnum#ordinalize to turn 1.ordinalize to "1st", 3.ordinalize to "3rd", and 10.ordinalize to "10th" and so on #1724 [paul@cnt.org] + + +*1.1.1* (11 July, 2005) + +* Added more efficient implementation of the development mode reset of classes #1638 [Chris McGrath] + + +*1.1.0* (6 July, 2005) + +* Fixed conflict with Glue gem #1606 [Rick Olson] + +* Added new rules to the Inflector to deal with more unusual plurals mouse/louse => mice/lice, information => information, ox => oxen, virus => viri, archive => archives #1571, #1583, #1490, #1599, #1608 [foamdino@gmail.com/others] + +* Fixed memory leak with Object#remove_subclasses_of, which inflicted a Rails application running in development mode with a ~20KB leak per request #1289 [c.r.mcgrath@gmail.com] + +* Made 1.year == 365.25.days to account for leap years. This allows you to do User.find(:all, :conditions => ['birthday > ?', 50.years.ago]) without losing a lot of days. #1488 [tuxie@dekadance.se] + +* Added an exception if calling id on nil to WhinyNil #584 [kevin-temp@writesoon.com] + +* Added Fix/Bignum#multiple_of? which returns true on 14.multiple_of?(7) and false on 16.multiple_of?(7) #1464 [Thomas Fuchs] + +* Added even? and odd? to work with Bignums in addition to Fixnums #1464 [Thomas Fuchs] + +* Fixed Time#at_beginning_of_week returned the next Monday instead of the previous one when called on a Sunday #1403 [jean.helou@gmail.com] + +* Increased the speed of indifferent hash access by using Hash#default. #1436 [Nicholas Seckar] + +* Added that " " is now also blank? (using strip if available) + +* Fixed Dependencies so all modules are able to load missing constants #1173 [Nicholas Seckar] + +* Fixed the Inflector to underscore strings containing numbers, so Area51Controller becomes area51_controller #1176 [Nicholas Seckar] + +* Fixed that HashWithIndifferentAccess stringified all keys including symbols, ints, objects, and arrays #1162 [Nicholas Seckar] + +* Fixed Time#last_year to go back in time, not forward #1278 [fabien@odilat.com] + +* Fixed the pluralization of analysis to analyses #1295 [seattle@rootimage.msu.edu] + +* Fixed that Time.local(2005,12).months_since(1) would raise "ArgumentError: argument out of range" #1311 [jhahn@niveon.com] + +* Added silencing to the default Logger class + + +*1.0.4* (19th April, 2005) + +* Fixed that in some circumstances controllers outside of modules may have hidden ones inside modules. For example, admin/content might have been hidden by /content. #1075 [Nicholas Seckar] + +* Fixed inflection of perspectives and similar words #1045 [thijs@vandervossen.net] + +* Added Fixnum#even? and Fixnum#odd? + +* Fixed problem with classes being required twice. Object#const_missing now uses require_dependency to load files. It used to use require_or_load which would cause models to be loaded twice, which was not good for validations and other class methods #971 [Nicholas Seckar] + + +*1.0.3* (27th March, 2005) + +* Fixed Inflector.pluralize to handle capitalized words #932 [Jeremy Kemper] + +* Added Object#suppress which allows you to make a saner choice around with exceptions to swallow #980. Example: + + suppress(ZeroDivisionError) { 1/0 } + + ...instead of: + + 1/0 rescue nil # BAD, EVIL, DIRTY. + + +*1.0.2* (22th March, 2005) + +* Added Kernel#returning -- 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'] + + +*1.0.1* (7th March, 2005) + +* Fixed Hash#indifferent_access to also deal with include? and fetch and nested hashes #726 [Nicholas Seckar] + +* Added Object#blank? -- see http://redhanded.hobix.com/inspect/objectBlank.html #783 [_why the lucky stiff] + +* Added inflection rules for "sh" words, like "wish" and "fish" #755 [phillip@pjbsoftware.com] + +* Fixed an exception when using Ajax based requests from Safari because Safari appends a \000 to the post body. Symbols can't have \000 in them so indifferent access would throw an exception in the constructor. Indifferent hashes now use strings internally instead. #746 [Tobias Luetke] + +* Added String#to_time and String#to_date for wrapping ParseDate + + +*1.0.0* (24th February, 2005) + +* Added TimeZone as the first of a number of value objects that among others Active Record can use rich value objects using composed_of #688 [Jamis Buck] + +* Added Date::Conversions for getting dates in different convenient string representations and other objects + +* Added Time::Conversions for getting times in different convenient string representations and other objects + +* Added Time::Calculations to ask for things like Time.now.tomorrow, Time.now.yesterday, Time.now.months_ago(4) #580 [DP|Flurin]. Examples: + + "Later today" => now.in(3.hours), + "Tomorrow morning" => now.tomorrow.change(:hour => 9), + "Tomorrow afternoon" => now.tomorrow.change(:hour => 14), + "In a couple of days" => now.tomorrow.tomorrow.change(:hour => 9), + "Next monday" => now.next_week.change(:hour => 9), + "In a month" => now.next_month.change(:hour => 9), + "In 6 months" => now.months_since(6).change(:hour => 9), + "In a year" => now.in(1.year).change(:hour => 9) + +* Upgraded to breakpoint 92 which fixes: + + * overload IRB.parse_opts(), fixes #443 + => breakpoints in tests work even when running them via rake + * untaint handlers, might fix an issue discussed on the Rails ML + * added verbose mode to breakpoint_client + * less noise caused by breakpoint_client by default + * ignored TerminateLineInput exception in signal handler + => quiet exit on Ctrl-C + +* Fixed Inflector for words like "news" and "series" that are the same in plural and singular #603 [echion], #615 [marcenuc] + +* Added Hash#stringify_keys and Hash#stringify_keys! + +* Added IndifferentAccess as a way to wrap a hash by a symbol-based store that also can be accessed by string keys + +* Added Inflector.constantize to turn "Admin::User" into a reference for the constant Admin::User + +* Added that Inflector.camelize and Inflector.underscore can deal with modules like turning "Admin::User" into "admin/user" and back + +* Added Inflector.humanize to turn attribute names like employee_salary into "Employee salary". Used by automated error reporting in AR. + +* Added availability of class inheritable attributes to the masses #477 [Jeremy Kemper] + + class Foo + class_inheritable_reader :read_me + class_inheritable_writer :write_me + class_inheritable_accessor :read_and_write_me + class_inheritable_array :read_and_concat_me + class_inheritable_hash :read_and_update_me + end + + # Bar gets a clone of (not a reference to) Foo's attributes. + class Bar < Foo + end + + Bar.read_and_write_me == Foo.read_and_write_me + Bar.read_and_write_me = 'bar' + Bar.read_and_write_me != Foo.read_and_write_me + +* Added Inflections as an extension on String, so Inflector.pluralize(Inflector.classify(name)) becomes name.classify.pluralize #476 [Jeremy Kemper] + +* Added Byte operations to Numeric, so 5.5.megabytes + 200.kilobytes #461 [Marcel Molina] + +* Fixed that Dependencies.reload can't load the same file twice #420 [Kent Sibilev] + +* Added Fixnum#ago/until, Fixnum#since/from_now #450 [Jeremy Kemper] + +* Added that Inflector now accepts Symbols and Classes by calling .to_s on the word supplied + +* Added time unit extensions to Fixnum that'll return the period in seconds, like 2.days + 4.hours. diff --git a/vendor/rails/activesupport/README b/vendor/rails/activesupport/README new file mode 100644 index 0000000..9fb9a80 --- /dev/null +++ b/vendor/rails/activesupport/README @@ -0,0 +1,43 @@ += Active Support -- Utility classes and standard library extensions from Rails + +Active Support is a collection of various utility classes and standard library extensions that were found useful +for Rails. All these additions have hence been collected in this bundle as way to gather all that sugar that makes +Ruby sweeter. + + +== Download + +The latest version of Active Support can be found at + +* http://rubyforge.org/project/showfiles.php?group_id=182 + +Documentation can be found at + +* http://as.rubyonrails.com + + +== Installation + +The preferred method of installing Active Support is through its GEM file. You'll need to have +RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have it, +then use: + + % [sudo] gem install activesupport-1.0.0.gem + + +== License + +Active Support is released under the MIT license. + + +== Support + +The Active Support homepage is http://www.rubyonrails.com. You can find the Active Support +RubyForge page at http://rubyforge.org/projects/activesupport. And as Jim from Rake says: + + Feel free to submit commits or feature requests. If you send a patch, + remember to update the corresponding unit tests. If fact, I prefer + new feature to be submitted in the form of new unit tests. + +For other information, feel free to ask on the ruby-talk mailing list +(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com. diff --git a/vendor/rails/activesupport/Rakefile b/vendor/rails/activesupport/Rakefile new file mode 100644 index 0000000..7a6825c --- /dev/null +++ b/vendor/rails/activesupport/Rakefile @@ -0,0 +1,82 @@ +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/gempackagetask' +require 'rake/contrib/rubyforgepublisher' +require File.join(File.dirname(__FILE__), 'lib', 'active_support', 'version') + +PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' +PKG_NAME = 'activesupport' +PKG_VERSION = ActiveSupport::VERSION::STRING + PKG_BUILD +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" + +RELEASE_NAME = "REL #{PKG_VERSION}" + +RUBY_FORGE_PROJECT = "activesupport" +RUBY_FORGE_USER = "webster132" + +task :default => :test +Rake::TestTask.new { |t| + t.pattern = 'test/**/*_test.rb' + t.verbose = true + t.warning = false +} + +# Create compressed packages +dist_dirs = [ "lib", "test"] + +# Genereate the RDoc documentation + +Rake::RDocTask.new { |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "Active Support -- Utility classes and standard library extensions from Rails" + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README', 'CHANGELOG') + rdoc.rdoc_files.include('lib/active_support.rb') + rdoc.rdoc_files.include('lib/active_support/*.rb') + rdoc.rdoc_files.include('lib/active_support/**/*.rb') +} + +spec = Gem::Specification.new do |s| + s.name = PKG_NAME + s.version = PKG_VERSION + s.summary = "Support and utility classes used by the Rails framework." + s.description = %q{Utility library which carries commonly used classes and goodies from the Rails framework} + + s.files = [ "CHANGELOG", "README" ] + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) } + s.require_path = 'lib' + s.has_rdoc = true + + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://www.rubyonrails.org" + s.rubyforge_project = "activesupport" +end + +Rake::GemPackageTask.new(spec) do |p| + p.gem_spec = spec + p.need_tar = true + p.need_zip = true +end + +desc "Publish the beta gem" +task :pgem => [:package] do + Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` +end + +desc "Publish the API documentation" +task :pdoc => [:rdoc] do + Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/as", "doc").upload +end + +desc "Publish the release files to RubyForge." +task :release => [ :package ] do + `rubyforge login` + + for ext in %w( gem tgz zip ) + release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" + puts release_command + system(release_command) + end +end \ No newline at end of file diff --git a/vendor/rails/activesupport/install.rb b/vendor/rails/activesupport/install.rb new file mode 100644 index 0000000..f84f06b --- /dev/null +++ b/vendor/rails/activesupport/install.rb @@ -0,0 +1,30 @@ +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("active_support", "active_support.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 +} diff --git a/vendor/rails/activesupport/lib/active_support.rb b/vendor/rails/activesupport/lib/active_support.rb new file mode 100644 index 0000000..0c0762a --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support.rb @@ -0,0 +1,42 @@ +#-- +# 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/deprecation' + +require 'active_support/ordered_options' +require 'active_support/option_merger' + +require 'active_support/values/time_zone' + +require 'active_support/json' \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/binding_of_caller.rb b/vendor/rails/activesupport/lib/active_support/binding_of_caller.rb new file mode 100644 index 0000000..e224c99 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/binding_of_caller.rb @@ -0,0 +1,84 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/breakpoint.rb b/vendor/rails/activesupport/lib/active_support/breakpoint.rb new file mode 100755 index 0000000..785dac7 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/breakpoint.rb @@ -0,0 +1,523 @@ +# 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(#):001:0> local_variables + # => ["name", "age", "_", "__"] + # irb(#):002:0> [name, age] + # => ["Random Person", 23] + # irb(#):003:0> [@name, @age] + # => ["Random Person", 23] + # irb(#):004:0> self + # => # + # irb(#):005:0> @age += 1; self + # => # + # irb(#):006:0> exit + # Executing break point "Person#name" at file.rb:9 in `name' + # irb(#):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 diff --git a/vendor/rails/activesupport/lib/active_support/caching_tools.rb b/vendor/rails/activesupport/lib/active_support/caching_tools.rb new file mode 100644 index 0000000..c889c14 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/caching_tools.rb @@ -0,0 +1,62 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/clean_logger.rb b/vendor/rails/activesupport/lib/active_support/clean_logger.rb new file mode 100644 index 0000000..376896c --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/clean_logger.rb @@ -0,0 +1,38 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext.rb b/vendor/rails/activesupport/lib/active_support/core_ext.rb new file mode 100644 index 0000000..573313e --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext.rb @@ -0,0 +1 @@ +Dir[File.dirname(__FILE__) + "/core_ext/*.rb"].each { |file| require(file) } diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/array.rb b/vendor/rails/activesupport/lib/active_support/core_ext/array.rb new file mode 100644 index 0000000..d47b988 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/array.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/array/conversions' +require File.dirname(__FILE__) + '/array/grouping' + +class Array #:nodoc: + include ActiveSupport::CoreExtensions::Array::Conversions + include ActiveSupport::CoreExtensions::Array::Grouping +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb new file mode 100644 index 0000000..1a5ddee --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -0,0 +1,72 @@ +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: + # * :connector: The word used to join the last element in arrays with two or more elements (default: "and") + # * :skip_last_comma: 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 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) + case format + when :db + if respond_to?(:empty?) && self.empty? + "null" + else + collect { |element| element.id }.join(",") + end + else + to_default_s + end + 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).to_s + children = options.delete(:children) + + if !options.has_key?(:dasherize) || options[:dasherize] + root = root.dasherize + end + + options[:builder].instruct! unless options.delete(:skip_instruct) + + opts = options.merge({ :skip_instruct => true, :root => children }) + + options[:builder].tag!(root) { each { |e| e.to_xml(opts) } } + end + + end + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/array/grouping.rb b/vendor/rails/activesupport/lib/active_support/core_ext/array/grouping.rb new file mode 100644 index 0000000..6f28e1e --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -0,0 +1,46 @@ +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module Array #:nodoc: + module Grouping + # Iterate over an array in groups of a certain size, padding any remaining + # slots with specified value (nil 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? + grouped_collection = [] unless block_given? + collection.each_slice(number) do |group| + block_given? ? yield(group) : grouped_collection << group + end + grouped_collection unless block_given? + end + + # Divide the array into one or more subarrays based on a delimiting +value+ + # or the result of an optional block. + # + # ex. + # + # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] + # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] + def split(value = nil, &block) + block ||= Proc.new { |e| e == value } + inject([[]]) do |results, element| + if block.call(element) + results << [] + else + results.last << element + end + results + end + end + end + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/bigdecimal.rb b/vendor/rails/activesupport/lib/active_support/core_ext/bigdecimal.rb new file mode 100644 index 0000000..436b3e0 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/bigdecimal.rb @@ -0,0 +1,3 @@ +require 'bigdecimal' + +require File.dirname(__FILE__) + '/bigdecimal/formatting.rb' diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/bigdecimal/formatting.rb b/vendor/rails/activesupport/lib/active_support/core_ext/bigdecimal/formatting.rb new file mode 100644 index 0000000..d86adbe --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/bigdecimal/formatting.rb @@ -0,0 +1,7 @@ +class BigDecimal #:nodoc: + + alias :_original_to_s :to_s + def to_s(format="F") + _original_to_s(format) + end +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/blank.rb b/vendor/rails/activesupport/lib/active_support/core_ext/blank.rb new file mode 100644 index 0000000..f7fbea3 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/blank.rb @@ -0,0 +1,50 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/cgi.rb b/vendor/rails/activesupport/lib/active_support/core_ext/cgi.rb new file mode 100644 index 0000000..072a7c9 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/cgi.rb @@ -0,0 +1,5 @@ +require File.dirname(__FILE__) + '/cgi/escape_skipping_slashes' + +class CGI #:nodoc: + extend(ActiveSupport::CoreExtensions::CGI::EscapeSkippingSlashes) +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb b/vendor/rails/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb new file mode 100644 index 0000000..a21e98f --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb @@ -0,0 +1,14 @@ +module 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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/class.rb b/vendor/rails/activesupport/lib/active_support/core_ext/class.rb new file mode 100644 index 0000000..7bacdb3 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/class.rb @@ -0,0 +1,3 @@ +require File.dirname(__FILE__) + '/class/attribute_accessors' +require File.dirname(__FILE__) + '/class/inheritable_attributes' +require File.dirname(__FILE__) + '/class/removal' \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb new file mode 100644 index 0000000..93a7d48 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -0,0 +1,44 @@ +# 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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/vendor/rails/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb new file mode 100644 index 0000000..2e70a64 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -0,0 +1,115 @@ +# 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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/class/removal.rb b/vendor/rails/activesupport/lib/active_support/core_ext/class/removal.rb new file mode 100644 index 0000000..b217c19 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/class/removal.rb @@ -0,0 +1,24 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/date.rb b/vendor/rails/activesupport/lib/active_support/core_ext/date.rb new file mode 100644 index 0000000..239b8c1 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/date.rb @@ -0,0 +1,6 @@ +require 'date' +require File.dirname(__FILE__) + '/date/conversions' + +class Date#:nodoc: + include ActiveSupport::CoreExtensions::Date::Conversions +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/date/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/date/conversions.rb new file mode 100644 index 0000000..4b9388d --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -0,0 +1,39 @@ +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) + if respond_to?(:hour) + ::Time.send(form, year, month, day, hour, min, sec) + else + ::Time.send(form, year, month, day) + end + end + + def xmlschema + to_time.xmlschema + end + end + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb b/vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb new file mode 100644 index 0000000..5912800 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/enumerable.rb @@ -0,0 +1,62 @@ +module Enumerable #:nodoc: + # 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 + + # Calculates a sum from the elements. Examples: + # + # payments.sum { |p| p.price * p.tax_rate } + # payments.sum(&:price) + # + # This is instead of payments.inject { |sum, p| sum + p.price } + # + # Also calculates sums without the use of a block: + # [5, 15, 10].sum # => 30 + # + # The default identity (sum of an empty list) is zero. + # However, you can override this default: + # + # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + # + def sum(identity = 0, &block) + return identity unless size > 0 + if block_given? + map(&block).sum + else + inject { |sum, element| sum + element } + end + end + + # Convert an enumerable to a hash. Examples: + # + # people.index_by(&:login) + # => { "nextangle" => , "chade-" => , ...} + # people.index_by { |person| "#{person.first_name} #{person.last_name}" } + # => { "Chade- Fowlersburg-e" => , "David Heinemeier Hansson" => , ...} + # + def index_by + inject({}) do |accum, elem| + accum[yield(elem)] = elem + accum + end + end + +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/exception.rb b/vendor/rails/activesupport/lib/active_support/core_ext/exception.rb new file mode 100644 index 0000000..2e39651 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/exception.rb @@ -0,0 +1,33 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash.rb new file mode 100644 index 0000000..7d94d70 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash.rb @@ -0,0 +1,13 @@ +require 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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash/conversions.rb new file mode 100644 index 0000000..38992f2 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -0,0 +1,148 @@ +require 'date' +require 'xml_simple' + +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module Hash #:nodoc: + module Conversions + XML_TYPE_NAMES = { + ::Fixnum => "integer", + ::Float => "float", + ::Date => "date", + ::DateTime => "datetime", + ::Time => "datetime", + ::TrueClass => "boolean", + ::FalseClass => "boolean" + } unless defined? XML_TYPE_NAMES + + XML_FORMATTING = { + "date" => Proc.new { |date| date.to_s(:db) }, + "datetime" => Proc.new { |time| time.xmlschema }, + "binary" => Proc.new { |binary| Base64.encode64(binary) } + } unless defined? XML_FORMATTING + + def self.included(klass) + klass.extend(ClassMethods) + end + + 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) + dasherize = !options.has_key?(:dasherize) || options[:dasherize] + root = dasherize ? options[:root].to_s.dasherize : options[:root].to_s + + options[:builder].__send__(root) 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})) + when ::Method, ::Proc + # If the Method or Proc takes two arguments, then + # pass the suggested child element name. This is + # used if the Method or Proc will be operating over + # multiple records and needs to create an containing + # element that will contain the objects being + # serialized. + if 1 == value.arity + value.call(options.merge({ :root => key, :skip_instruct => true })) + else + value.call(options.merge({ :root => key, :skip_instruct => true }), key.to_s.singularize) + end + else + if value.respond_to?(:to_xml) + value.to_xml(options.merge({ :root => key, :skip_instruct => true })) + else + type_name = XML_TYPE_NAMES[value.class] + + key = dasherize ? key.to_s.dasherize : key.to_s + + attributes = options[:skip_types] || value.nil? || type_name.nil? ? { } : { :type => type_name } + if value.nil? + attributes[:nil] = true + end + + options[:builder].tag!(key, + XML_FORMATTING[type_name] ? XML_FORMATTING[type_name].call(value) : value, + attributes + ) + end + end + end + end + + end + + module ClassMethods + def create_from_xml(xml) + # TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple + undasherize_keys(typecast_xml_value(XmlSimple.xml_in(xml, + 'forcearray' => false, + 'forcecontent' => true, + 'keeproot' => true, + 'contentkey' => '__content__') + )) + end + + private + def typecast_xml_value(value) + case value.class.to_s + 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.strip == "true" + when "datetime" then ::Time.parse(content).utc + when "date" then ::Date.parse(content) + else content + end + else + value.empty? || value['nil'] == 'true' ? 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 + when "String" + value + else + raise "can't typecast #{value.inspect}" + end + end + + def translate_xml_entities(value) + value.gsub(/</, "<"). + gsub(/>/, ">"). + gsub(/"/, '"'). + gsub(/'/, "'"). + gsub(/&/, "&") + end + + def undasherize_keys(params) + case params.class.to_s + when "Hash" + params.inject({}) do |h,(k,v)| + h[k.to_s.tr("-", "_")] = undasherize_keys(v) + h + end + when "Array" + params.map { |v| undasherize_keys(v) } + else + params + end + end + end + end + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash/diff.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash/diff.rb new file mode 100644 index 0000000..deace40 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash/diff.rb @@ -0,0 +1,11 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb new file mode 100644 index 0000000..9a3b02e --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -0,0 +1,88 @@ +# 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 = nil) + if key.is_a?(Symbol) && include?(key = key.to_s) + self[key] + else + super + end + 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 + + def stringify_keys!; self end + def symbolize_keys!; self 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 + hash = HashWithIndifferentAccess.new(self) + hash.default = self.default + hash + end + end + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash/keys.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash/keys.rb new file mode 100644 index 0000000..3c8a59f --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -0,0 +1,53 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/vendor/rails/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb new file mode 100644 index 0000000..46c5387 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb @@ -0,0 +1,25 @@ +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 other_hash. + # 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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/integer.rb b/vendor/rails/activesupport/lib/active_support/core_ext/integer.rb new file mode 100644 index 0000000..9346b88 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/integer.rb @@ -0,0 +1,7 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/integer/even_odd.rb b/vendor/rails/activesupport/lib/active_support/core_ext/integer/even_odd.rb new file mode 100644 index 0000000..3762308 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/integer/even_odd.rb @@ -0,0 +1,24 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/integer/inflections.rb b/vendor/rails/activesupport/lib/active_support/core_ext/integer/inflections.rb new file mode 100644 index 0000000..94721cf --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/integer/inflections.rb @@ -0,0 +1,15 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/kernel.rb b/vendor/rails/activesupport/lib/active_support/core_ext/kernel.rb new file mode 100644 index 0000000..1aa4f72 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/kernel.rb @@ -0,0 +1,4 @@ +require File.dirname(__FILE__) + '/kernel/daemonizing' +require File.dirname(__FILE__) + '/kernel/reporting' +require File.dirname(__FILE__) + '/kernel/agnostics' +require File.dirname(__FILE__) + '/kernel/requires' diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/kernel/agnostics.rb b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/agnostics.rb new file mode 100644 index 0000000..c0cb4fb --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/agnostics.rb @@ -0,0 +1,11 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/kernel/daemonizing.rb b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/daemonizing.rb new file mode 100644 index 0000000..0e78819 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/daemonizing.rb @@ -0,0 +1,15 @@ +module 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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/reporting.rb new file mode 100644 index 0000000..a5cec50 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -0,0 +1,51 @@ +module 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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/kernel/requires.rb b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/requires.rb new file mode 100644 index 0000000..323fea4 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/kernel/requires.rb @@ -0,0 +1,24 @@ +module 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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/load_error.rb b/vendor/rails/activesupport/lib/active_support/core_ext/load_error.rb new file mode 100644 index 0000000..6165e95 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/load_error.rb @@ -0,0 +1,38 @@ +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] + ] unless defined?(REGEXPS) +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/logger.rb b/vendor/rails/activesupport/lib/active_support/core_ext/logger.rb new file mode 100644 index 0000000..9c1fd27 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/logger.rb @@ -0,0 +1,16 @@ +# 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 \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module.rb new file mode 100644 index 0000000..8d48de5 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/module/inclusion' +require File.dirname(__FILE__) + '/module/attribute_accessors' +require File.dirname(__FILE__) + '/module/attr_internal' +require File.dirname(__FILE__) + '/module/delegation' +require File.dirname(__FILE__) + '/module/introspection' +require File.dirname(__FILE__) + '/module/loading' +require File.dirname(__FILE__) + '/module/aliasing' diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/aliasing.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/aliasing.rb new file mode 100644 index 0000000..a4c7e53 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -0,0 +1,57 @@ +class Module + # Encapsulates the common pattern of: + # + # alias_method :foo_without_feature, :foo + # alias_method :foo, :foo_with_feature + # + # With this, you simply do: + # + # alias_method_chain :foo, :feature + # + # And both aliases are set up for you. + # + # Query and bang methods (foo?, foo!) keep the same punctuation: + # + # alias_method_chain :foo?, :feature + # + # is equivalent to + # + # alias_method :foo_without_feature?, :foo? + # alias_method :foo?, :foo_with_feature? + # + # so you can safely chain foo, foo?, and foo! with the same feature. + def alias_method_chain(target, feature) + # Strip out punctuation on predicates or bang methods since + # e.g. target?_without_feature is not a valid method name. + aliased_target, punctuation = target.to_s.sub(/([?!])$/, ''), $1 + alias_method "#{aliased_target}_without_#{feature}#{punctuation}", target + alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}" + end + + # Allows you to make aliases for attributes, which includes + # getter, setter, and query methods. + # + # Example: + # + # class Content < ActiveRecord::Base + # # has a title attribute + # end + # + # class Email < ActiveRecord::Base + # alias_attribute :subject, :title + # end + # + # e = Email.find(1) + # e.title # => "Superstars" + # e.subject # => "Superstars" + # e.subject? # => true + # e.subject = "Megastars" + # e.title # => "Megastars" + def alias_attribute(new_name, old_name) + module_eval <<-STR, __FILE__, __LINE__+1 + def #{new_name}; #{old_name}; end + def #{new_name}?; #{old_name}?; end + def #{new_name}=(v); self.#{old_name} = v; end + STR + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/attr_internal.rb new file mode 100644 index 0000000..c793da7 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -0,0 +1,31 @@ +class Module + # Declare an attribute reader backed by an internally-named instance variable. + def attr_internal_reader(*attrs) + attrs.each do |attr| + module_eval "def #{attr}() #{attr_internal_ivar_name(attr)} end" + end + end + + # Declare an attribute writer backed by an internally-named instance variable. + def attr_internal_writer(*attrs) + attrs.each do |attr| + module_eval "def #{attr}=(v) #{attr_internal_ivar_name(attr)} = v end" + end + end + + # Declare attributes backed by 'internal' instance variables names. + def attr_internal_accessor(*attrs) + attr_internal_reader *attrs + attr_internal_writer *attrs + end + + alias_method :attr_internal, :attr_internal_accessor + + private + mattr_accessor :attr_internal_naming_format + self.attr_internal_naming_format = '@_%s' + + def attr_internal_ivar_name(attr) + attr_internal_naming_format % attr + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb new file mode 100644 index 0000000..fe4f8a4 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -0,0 +1,44 @@ +# 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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb new file mode 100644 index 0000000..b8cf4e0 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -0,0 +1,41 @@ +class Module + # Provides a delegate class method to easily expose contained objects' methods + # as your own. Pass one or more methods (specified as symbols or strings) + # and the name of the target object as the final :to option (also a symbol + # or string). At least one method and the :to option are required. + # + # Delegation is particularly useful with Active Record associations: + # class Greeter < ActiveRecord::Base + # def hello() "hello" end + # def goodbye() "goodbye" end + # end + # + # class Foo < ActiveRecord::Base + # belongs_to :greeter + # delegate :hello, :to => :greeter + # end + # + # Foo.new.hello # => "hello" + # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for # + # + # Multiple delegates to the same target are allowed: + # class Foo < ActiveRecord::Base + # delegate :hello, :goodbye, :to => :greeter + # end + # + # Foo.new.goodbye # => "goodbye" + 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 as the last argument (e.g. delegate :hello, :to => :greeter)." + end + + methods.each do |method| + module_eval(<<-EOS, "(__DELEGATION__)", 1) + def #{method}(*args, &block) + #{to}.__send__(#{method.inspect}, *args, &block) + end + EOS + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/inclusion.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/inclusion.rb new file mode 100644 index 0000000..efc00d6 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/inclusion.rb @@ -0,0 +1,11 @@ +class 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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/introspection.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/introspection.rb new file mode 100644 index 0000000..0cd0d1f --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -0,0 +1,21 @@ +class 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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/module/loading.rb b/vendor/rails/activesupport/lib/active_support/core_ext/module/loading.rb new file mode 100644 index 0000000..36c0c61 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/module/loading.rb @@ -0,0 +1,13 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/name_error.rb b/vendor/rails/activesupport/lib/active_support/core_ext/name_error.rb new file mode 100644 index 0000000..2b617b0 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/name_error.rb @@ -0,0 +1,20 @@ + +# Add a +missing_name+ method to NameError instances. +class NameError < StandardError + + # Add a method to obtain the missing name from a NameError. + def missing_name + $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message + end + + # Was this exception raised because the given name was missing? + def missing_name?(name) + if name.is_a? Symbol + last_name = (missing_name || '').split('::').last + last_name == name.to_s + else + missing_name == name.to_s + end + end + +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/numeric.rb b/vendor/rails/activesupport/lib/active_support/core_ext/numeric.rb new file mode 100644 index 0000000..88fead7 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/numeric.rb @@ -0,0 +1,7 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/vendor/rails/activesupport/lib/active_support/core_ext/numeric/bytes.rb new file mode 100644 index 0000000..5647767 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/numeric/bytes.rb @@ -0,0 +1,44 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/numeric/time.rb b/vendor/rails/activesupport/lib/active_support/core_ext/numeric/time.rb new file mode 100644 index 0000000..9374004 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -0,0 +1,72 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/object.rb b/vendor/rails/activesupport/lib/active_support/core_ext/object.rb new file mode 100644 index 0000000..3dd6dea --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/object.rb @@ -0,0 +1,2 @@ +require File.dirname(__FILE__) + '/object/extending' +require File.dirname(__FILE__) + '/object/misc' \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/object/extending.rb b/vendor/rails/activesupport/lib/active_support/core_ext/object/extending.rb new file mode 100644 index 0000000..e15b4bf --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/object/extending.rb @@ -0,0 +1,47 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/object/misc.rb b/vendor/rails/activesupport/lib/active_support/core_ext/object/misc.rb new file mode 100644 index 0000000..ea8e3a1 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/object/misc.rb @@ -0,0 +1,34 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/pathname.rb b/vendor/rails/activesupport/lib/active_support/core_ext/pathname.rb new file mode 100644 index 0000000..9e78c27 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/pathname.rb @@ -0,0 +1,7 @@ +require 'pathname' +require File.dirname(__FILE__) + '/pathname/clean_within' + +class Pathname#:nodoc: + extend ActiveSupport::CoreExtensions::Pathname::CleanWithin +end + diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/pathname/clean_within.rb b/vendor/rails/activesupport/lib/active_support/core_ext/pathname/clean_within.rb new file mode 100644 index 0000000..ae03e1b --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/pathname/clean_within.rb @@ -0,0 +1,14 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/proc.rb b/vendor/rails/activesupport/lib/active_support/core_ext/proc.rb new file mode 100644 index 0000000..2ca23f6 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/proc.rb @@ -0,0 +1,12 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/range.rb b/vendor/rails/activesupport/lib/active_support/core_ext/range.rb new file mode 100644 index 0000000..ca77511 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/range.rb @@ -0,0 +1,5 @@ +require File.dirname(__FILE__) + '/range/conversions' + +class Range #:nodoc: + include ActiveSupport::CoreExtensions::Range::Conversions +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/range/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/range/conversions.rb new file mode 100644 index 0000000..677ba63 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -0,0 +1,21 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string.rb new file mode 100644 index 0000000..240e1ff --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string.rb @@ -0,0 +1,13 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string/access.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string/access.rb new file mode 100644 index 0000000..5d0e0c2 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string/access.rb @@ -0,0 +1,58 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string/conversions.rb new file mode 100644 index 0000000..cfb767d --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -0,0 +1,19 @@ +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 +end \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb new file mode 100644 index 0000000..b4c9cab --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -0,0 +1,153 @@ +require File.dirname(__FILE__) + '/../../inflector' unless defined? Inflector + +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module String #:nodoc: + # String inflections define new methods on the String class to transform names for different purposes. + # For instance, you can figure out the name of a database from the name of a class. + # "ScaleScore".tableize => "scale_scores" + module Inflections + # Returns the plural form of the word in the string. + # + # Examples + # "post".pluralize #=> "posts" + # "octopus".pluralize #=> "octopi" + # "sheep".pluralize #=> "sheep" + # "words".pluralize #=> "words" + # "the blue mailman".pluralize #=> "the blue mailmen" + # "CamelOctopus".pluralize #=> "CamelOctopi" + def pluralize + Inflector.pluralize(self) + end + + # The reverse of pluralize, returns the singular form of a word in a string. + # + # Examples + # "posts".singularize #=> "post" + # "octopi".singularize #=> "octopus" + # "sheep".singluarize #=> "sheep" + # "word".singluarize #=> "word" + # "the blue mailmen".singularize #=> "the blue mailman" + # "CamelOctopi".singularize #=> "CamelOctopus" + def singularize + Inflector.singularize(self) + end + + # By default, camelize converts strings to UpperCamelCase. If the argument to camelize + # is set to ":lower" then camelize produces lowerCamelCase. + # + # camelize will also convert '/' to '::' which is useful for converting paths to namespaces + # + # Examples + # "active_record".camelize #=> "ActiveRecord" + # "active_record".camelize(:lower) #=> "activeRecord" + # "active_record/errors".camelize #=> "ActiveRecord::Errors" + # "active_record/errors".camelize(:lower) #=> "activeRecord::Errors" + 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 + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. Titleize is meant for creating pretty output. It is not + # used in the Rails internals. + # + # titleize is also aliased as as titlecase + # + # Examples + # "man from the boondocks".titleize #=> "Man From The Boondocks" + # "x-men: the last stand".titleize #=> "X Men: The Last Stand" + def titleize + Inflector.titleize(self) + end + alias_method :titlecase, :titleize + + # The reverse of +camelize+. Makes an underscored form from the expression in the string. + # + # Changes '::' to '/' to convert namespaces to paths. + # + # Examples + # "ActiveRecord".underscore #=> "active_record" + # "ActiveRecord::Errors".underscore #=> active_record/errors + def underscore + Inflector.underscore(self) + end + + # Replaces underscores with dashes in the string. + # + # Example + # "puni_puni" #=> "puni-puni" + def dasherize + Inflector.dasherize(self) + end + + # Removes the module part from the expression in the string + # + # Examples + # "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections" + # "Inflections".demodulize #=> "Inflections" + def demodulize + Inflector.demodulize(self) + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the pluralize method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize #=> "raw_scaled_scorers" + # "egg_and_ham".tableize #=> "egg_and_hams" + # "fancyCategory".tableize #=> "fancy_categories" + def tableize + Inflector.tableize(self) + end + + # Create a class name from a table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow classify with constantize.) + # + # Examples + # "egg_and_hams".classify #=> "EggAndHam" + # "post".classify #=> "Post" + def classify + Inflector.classify(self) + end + + # Capitalizes the first word and turns underscores into spaces and strips _id. + # Like titleize, this is meant for creating pretty output. + # + # Examples + # "employee_salary" #=> "Employee salary" + # "author_id" #=> "Author" + def humanize + Inflector.humanize(self) + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # Examples + # "Message".foreign_key #=> "message_id" + # "Message".foreign_key(false) #=> "messageid" + # "Admin::Post".foreign_key #=> "post_id" + def foreign_key(separate_class_name_and_id_with_underscore = true) + Inflector.foreign_key(self, separate_class_name_and_id_with_underscore) + end + + # Constantize tries to find a declared constant with the name specified + # in the string. It raises a NameError when the name is not in CamelCase + # or is not initialized. + # + # Examples + # "Module".constantize #=> Module + # "Class".constantize #=> Class + def constantize + Inflector.constantize(self) + end + end + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string/iterators.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string/iterators.rb new file mode 100644 index 0000000..73114d9 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string/iterators.rb @@ -0,0 +1,17 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb b/vendor/rails/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb new file mode 100644 index 0000000..6717401 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb @@ -0,0 +1,20 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/symbol.rb b/vendor/rails/activesupport/lib/active_support/core_ext/symbol.rb new file mode 100644 index 0000000..a3dc794 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/symbol.rb @@ -0,0 +1,12 @@ +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 { |*args| args.shift.__send__(self, *args) } + end +end diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/time.rb b/vendor/rails/activesupport/lib/active_support/core_ext/time.rb new file mode 100644 index 0000000..9e3c7a0 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/time.rb @@ -0,0 +1,7 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb b/vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb new file mode 100644 index 0000000..8511a74 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -0,0 +1,188 @@ +module ActiveSupport #:nodoc: + module CoreExtensions #:nodoc: + module Time #:nodoc: + # Enables the use of time calculations within Time itself + module Calculations + def self.included(base) #:nodoc: + 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 diff --git a/vendor/rails/activesupport/lib/active_support/core_ext/time/conversions.rb b/vendor/rails/activesupport/lib/active_support/core_ext/time/conversions.rb new file mode 100644 index 0000000..c141d08 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -0,0 +1,36 @@ +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.included(klass) + 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 diff --git a/vendor/rails/activesupport/lib/active_support/dependencies.rb b/vendor/rails/activesupport/lib/active_support/dependencies.rb new file mode 100644 index 0000000..a589891 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/dependencies.rb @@ -0,0 +1,187 @@ +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 + + # Return the a constant path for the provided parent and constant name + def constant_path_for(mod, name) + ([Object, Kernel].include? mod) ? name.to_s : "#{mod}::#{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 + + qualified_name = Dependencies.constant_path_for self, class_id + raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e) + end + end +end + +class Class + def const_missing(class_id) + if [Object, Kernel].include?(self) || parent == self + super + else + begin + parent.send :const_missing, class_id + rescue NameError => e + # Make sure that the name we are missing is the one that caused the error + parent_qualified_name = Dependencies.constant_path_for parent, class_id + raise unless e.missing_name? parent_qualified_name + qualified_name = Dependencies.constant_path_for self, class_id + raise NameError.new("uninitialized constant #{qualified_name}").copy_blame!(e) + end + 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 occurred while loading the following files:\n #{blamed_files.join "\n "}" + end + + def copy_blame!(exc) + @blamed_files = exc.blamed_files.clone + self + end +end diff --git a/vendor/rails/activesupport/lib/active_support/deprecation.rb b/vendor/rails/activesupport/lib/active_support/deprecation.rb new file mode 100644 index 0000000..5aff753 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/deprecation.rb @@ -0,0 +1,106 @@ +module ActiveSupport + module Deprecation + # Choose the default warn behavior according to RAILS_ENV. + # Ignore deprecation warnings in production. + DEFAULT_BEHAVIORS = { + 'test' => Proc.new { |message| $stderr.puts message }, + 'development' => Proc.new { |message| RAILS_DEFAULT_LOGGER.warn message }, + } + + class << self + def warn(message = nil, callstack = caller) + behavior.call(deprecation_message(callstack, message)) if behavior + end + + def default_behavior + DEFAULT_BEHAVIORS[RAILS_ENV.to_s] if defined?(RAILS_ENV) + end + + private + def deprecation_message(callstack, message = nil) + file, line, method = extract_callstack(callstack) + message ||= "WARNING: #{method} is deprecated and will be removed from the next Rails release" + "#{message} (#{method} at #{file}:#{line})" + end + + def extract_callstack(callstack) + callstack.first.match(/^(.+?):(\d+)(?::in `(.*?)')?/).captures + end + end + + # Behavior is a block that takes a message argument. + mattr_accessor :behavior + self.behavior = default_behavior + + module ClassMethods + # Declare that a method has been deprecated. + def deprecate(*method_names) + method_names.each do |method_name| + class_eval(<<-EOS, __FILE__, __LINE__) + def #{method_name}_with_deprecation(*args, &block) + ::ActiveSupport::Deprecation.warn + #{method_name}_without_deprecation(*args, &block) + end + EOS + alias_method_chain(method_name, :deprecation) + end + end + end + + module Assertions + def assert_deprecated(match = nil, &block) + last = with_last_message_tracking_deprecation_behavior(&block) + assert last, "Expected a deprecation warning within the block but received none" + match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) + assert_match match, last, "Deprecation warning didn't match #{match}: #{last}" + end + + def assert_not_deprecated(&block) + last = with_last_message_tracking_deprecation_behavior(&block) + assert_nil last, "Expected no deprecation warning within the block but received one: #{last}" + end + + private + def with_last_message_tracking_deprecation_behavior + old_behavior = ActiveSupport::Deprecation.behavior + last_message = nil + ActiveSupport::Deprecation.behavior = Proc.new { |message| last_message = message; old_behavior.call(message) if old_behavior } + yield + last_message + ensure + ActiveSupport::Deprecation.behavior = old_behavior + end + end + + # Stand-in for @request, @attributes, etc. + class DeprecatedInstanceVariableProxy + instance_methods.each { |m| undef_method m unless m =~ /^__/ } + + def initialize(instance, method, var = "@#{method}") + @instance, @method, @var = instance, method, var + end + + private + def warn(callstack, called, args) + ActiveSupport::Deprecation.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack) + end + + def method_missing(called, *args, &block) + warn caller, called, args + @instance.__send__(@method).__send__(called, *args, &block) + end + end + end +end + +class Class + include ActiveSupport::Deprecation::ClassMethods +end + +module Test + module Unit + class TestCase + include ActiveSupport::Deprecation::Assertions + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/inflections.rb b/vendor/rails/activesupport/lib/active_support/inflections.rb new file mode 100644 index 0000000..ba8a395 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/inflections.rb @@ -0,0 +1,53 @@ +Inflector.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 diff --git a/vendor/rails/activesupport/lib/active_support/inflector.rb b/vendor/rails/activesupport/lib/active_support/inflector.rb new file mode 100644 index 0000000..a578ecb --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/inflector.rb @@ -0,0 +1,179 @@ +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) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + 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' diff --git a/vendor/rails/activesupport/lib/active_support/json.rb b/vendor/rails/activesupport/lib/active_support/json.rb new file mode 100644 index 0000000..7d5304d --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/json.rb @@ -0,0 +1,37 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/json/encoders.rb b/vendor/rails/activesupport/lib/active_support/json/encoders.rb new file mode 100644 index 0000000..c3e3619 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/json/encoders.rb @@ -0,0 +1,25 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/json/encoders/core.rb b/vendor/rails/activesupport/lib/active_support/json/encoders/core.rb new file mode 100644 index 0000000..97f994a --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/json/encoders/core.rb @@ -0,0 +1,65 @@ +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 diff --git a/vendor/rails/activesupport/lib/active_support/option_merger.rb b/vendor/rails/activesupport/lib/active_support/option_merger.rb new file mode 100644 index 0000000..f944729 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/option_merger.rb @@ -0,0 +1,25 @@ +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? :to_hash + @options.merge(arguments.pop) + else + @options.dup + end + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/ordered_options.rb b/vendor/rails/activesupport/lib/active_support/ordered_options.rb new file mode 100644 index 0000000..0704549 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/ordered_options.rb @@ -0,0 +1,50 @@ +# OrderedHash is namespaced to prevent conflicts with other implementations +module ActiveSupport + 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 + collect { |key, value| key } + end + + def values + collect { |key, value| value } + end + + private + def find_pair(key) + self.each { |i| return i if i.first == key } + return false + end + end +end + +class OrderedOptions < ActiveSupport::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 diff --git a/vendor/rails/activesupport/lib/active_support/reloadable.rb b/vendor/rails/activesupport/lib/active_support/reloadable.rb new file mode 100644 index 0000000..3fd13e3 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/reloadable.rb @@ -0,0 +1,30 @@ +# 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 \ No newline at end of file diff --git a/vendor/rails/activesupport/lib/active_support/values/time_zone.rb b/vendor/rails/activesupport/lib/active_support/values/time_zone.rb new file mode 100644 index 0000000..c67af60 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/values/time_zone.rb @@ -0,0 +1,180 @@ +# 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/ unless defined?(US_ZONES) + + # 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 diff --git a/vendor/rails/activesupport/lib/active_support/vendor/builder.rb b/vendor/rails/activesupport/lib/active_support/vendor/builder.rb new file mode 100644 index 0000000..9719277 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/builder.rb @@ -0,0 +1,13 @@ +#!/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' diff --git a/vendor/rails/activesupport/lib/active_support/vendor/builder/blankslate.rb b/vendor/rails/activesupport/lib/active_support/vendor/builder/blankslate.rb new file mode 100644 index 0000000..23b9517 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/builder/blankslate.rb @@ -0,0 +1,63 @@ +#!/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 \_\_send__ and \_\_id__). + # BlankSlate is useful as a base class when writing classes that + # depend upon method_missing (e.g. dynamic proxies). + class BlankSlate + class << self + + # Hide the method named +name+ in the BlankSlate class. Don't + # hide +instance_eval+ or any method beginning with "__". + 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 after BlankSlate is defined 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 + + # Detect method additions to Kernel and remove them in the + # BlankSlate class. + 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 + + # Detect method additions to Object and remove them in the + # BlankSlate class. + def method_added(name) + blank_slate_method_added(name) + return if self != Object + Builder::BlankSlate.hide(name) + end + end +end diff --git a/vendor/rails/activesupport/lib/active_support/vendor/builder/xchar.rb b/vendor/rails/activesupport/lib/active_support/vendor/builder/xchar.rb new file mode 100644 index 0000000..f2bfe17 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/builder/xchar.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby + +# The XChar library is provided courtesy of Sam Ruby (See +# http://intertwingly.net/stories/2005/09/28/xchar.rb) + +# -------------------------------------------------------------------- + +# If the Builder::XChar module is not currently defined, fail on any +# name clashes in standard library classes. + +module Builder + def self.check_for_name_collision(klass, method_name, defined_constant=nil) + if klass.instance_methods.include?(method_name) + fail RuntimeError, + "Name Collision: Method '#{method_name}' is already defined in #{klass}" + end + end +end + +if ! defined?(Builder::XChar) + Builder.check_for_name_collision(String, "to_xs") + Builder.check_for_name_collision(Fixnum, "xchr") +end + +###################################################################### +module Builder + + #################################################################### + # XML Character converter, from Sam Ruby: + # (see http://intertwingly.net/stories/2005/09/28/xchar.rb). + # + module XChar # :nodoc: + + # See + # http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows + # for details. + CP1252 = { # :nodoc: + 128 => 8364, # euro sign + 130 => 8218, # single low-9 quotation mark + 131 => 402, # latin small letter f with hook + 132 => 8222, # double low-9 quotation mark + 133 => 8230, # horizontal ellipsis + 134 => 8224, # dagger + 135 => 8225, # double dagger + 136 => 710, # modifier letter circumflex accent + 137 => 8240, # per mille sign + 138 => 352, # latin capital letter s with caron + 139 => 8249, # single left-pointing angle quotation mark + 140 => 338, # latin capital ligature oe + 142 => 381, # latin capital letter z with caron + 145 => 8216, # left single quotation mark + 146 => 8217, # right single quotation mark + 147 => 8220, # left double quotation mark + 148 => 8221, # right double quotation mark + 149 => 8226, # bullet + 150 => 8211, # en dash + 151 => 8212, # em dash + 152 => 732, # small tilde + 153 => 8482, # trade mark sign + 154 => 353, # latin small letter s with caron + 155 => 8250, # single right-pointing angle quotation mark + 156 => 339, # latin small ligature oe + 158 => 382, # latin small letter z with caron + 159 => 376, # latin capital letter y with diaeresis + } + + # See http://www.w3.org/TR/REC-xml/#dt-chardata for details. + PREDEFINED = { + 38 => '&', # ampersand + 60 => '<', # left angle bracket + 62 => '>', # right angle bracket + } + + # See http://www.w3.org/TR/REC-xml/#charsets for details. + VALID = [ + [0x9, 0xA, 0xD], + (0x20..0xD7FF), + (0xE000..0xFFFD), + (0x10000..0x10FFFF) + ] + end + +end + + +###################################################################### +# Enhance the Fixnum class with a XML escaped character conversion. +# +class Fixnum #:nodoc: + XChar = Builder::XChar if ! defined?(XChar) + + # XML escaped version of chr + def xchr + n = XChar::CP1252[self] || self + n = 42 unless XChar::VALID.find {|range| range.include? n} + XChar::PREDEFINED[n] or (n<128 ? n.chr : "&##{n};") + end +end + + +###################################################################### +# Enhance the String class with a XML escaped character version of +# to_s. +# +class String #:nodoc: + # XML escaped version of to_s + def to_xs + unpack('U*').map {|n| n.xchr}.join # ASCII, UTF-8 + rescue + unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252 + end +end diff --git a/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlbase.rb b/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlbase.rb new file mode 100644 index 0000000..950d589 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlbase.rb @@ -0,0 +1,145 @@ +#!/usr/bin/env ruby + +require 'builder/blankslate' + +module Builder + + # 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. +out+ must respond to + # <<. + # 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 method_missing. + 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 brakets as: + # + # builder.p { |b| b.br; b.text! "HI" } #=>


      HI

      + def text!(text) + _text(_escape(text)) + end + + # Append text to the output target without escaping any markup. + # May be used within the markup brakets as: + # + # builder.p { |x| x << "
      HI" } #=>


      HI

      + # + # 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 << 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 + + require 'builder/xchar' + def _escape(text) + text.to_xs + end + + def _escape_quote(text) + _escape(text).gsub(%r{"}, '"') # " WART + 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 diff --git a/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlevents.rb b/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlevents.rb new file mode 100644 index 0000000..15dc7b6 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlevents.rb @@ -0,0 +1,63 @@ +#!/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. + # + # [start_tag(tag, attrs)] + # 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. + # + # [end_tag(tag)] + # Announces that an end tag for +tag+ has been found. + # + # [text(text)] + # 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 diff --git a/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlmarkup.rb b/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlmarkup.rb new file mode 100644 index 0000000..12b6ff9 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/builder/xmlmarkup.rb @@ -0,0 +1,328 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2005 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") # => emphasized + # xm.em { xmm.b("emp & bold") } # => emph & bold + # xm.a("A Link", "href"=>"http://onestepback.org") + # # => A Link + # xm.div { br } # =>

      + # xm.target("name"=>"compile", "option"=>"fast") + # # => + # # NOTE: order of attributes is not specified. + # + # xm.instruct! # + # xm.html { # + # xm.head { # + # xm.title("History") # History + # } # + # xm.body { # + # xm.comment! "HI" # + # xm.h1("Header") #

      Header

      + # xm.p("paragraph") #

      paragraph

      + # } # + # } # + # + # == Notes: + # + # * The order that attributes are inserted in markup tags is + # undefined. + # + # * Sometimes you wish to insert text without enclosing tags. Use + # the text! method to accomplish this. + # + # Example: + # + # xm.div { #
      + # xm.text! "line"; xm.br # line
      + # xm.text! "another line"; xmbr # another line
      + # } #
      + # + # * The special XML characters <, >, and & are converted to <, + # > and & automatically. Use the << operation to + # insert text without modification. + # + # * Sometimes tags use special characters not allowed in ruby + # identifiers. Use the tag! method to handle these + # cases. + # + # Example: + # + # xml.tag!("SOAP:Envelope") { ... } + # + # will produce ... + # + # ... " + # + # tag! 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. "SOAP:Envelope" => + # "xml.SOAP :Envelope") + # + # * XmlMarkup builds the markup in any object (called a _target_) + # that accepts the << 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: + # # + # # Jim + # # Weirich + # # + # + # * 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=>target_object:: + # Object receiving the markup. +out+ must respond to the + # << operator. The default is a plain string target. + # + # :indent=>indentation:: + # Number of spaces used for indentation. The default is no + # indentation and no line breaks. + # + # :margin=>initial_indentation_level:: + # Amount of initial indentation (specified in levels, not + # spaces). + # + # :escape_attrs=>OBSOLETE
      :: + # The :escape_attrs option is no longer supported by builder + # (and will be quietly ignored). String attribute values are + # now automatically escaped. If you need unescaped attribute + # values (perhaps you are using entities in the attribute + # values), then give the value as a Symbol. This allows much + # finer control over escaping attribute values. + # + 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" + # # => + def declare!(inst, *args, &block) + _indent + @target << "" + _newline + end + + # Insert a processing instruction into the XML markup. E.g. + # + # For example: + # + # xml.instruct! + # #=> + # xml.instruct! :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( + "", + nil, + attrs, + [:version, :encoding, :standalone]) + end + + # Insert a CDATA section into the XML markup. + # + # For example: + # + # xml.cdata!("text to be included in cdata") + # #=> + # + def cdata!(text) + _ensure_no_block block_given? + _special("", 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 end_too is true, then the start + # tag is also the end tag (e.g.
      + 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 << "" + 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}="#{_attr_value(v)}"} if v + end + attrs.each do |k, v| + @target << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) + end + end + + def _attr_value(value) + case value + when Symbol + value.to_s + else + _escape_quote(value.to_s) + end + end + + def _ensure_no_block(got_block) + if got_block + fail IllegalBlockError, + "Blocks are not allowed on XML instructions" + end + end + + end + +end diff --git a/vendor/rails/activesupport/lib/active_support/vendor/flexmock.rb b/vendor/rails/activesupport/lib/active_support/vendor/flexmock.rb new file mode 100644 index 0000000..59d2f89 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/flexmock.rb @@ -0,0 +1,84 @@ +#!/usr/bin/env ruby + +#--- +# Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.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 'test/unit' + +# FlexMock is a flexible mock object suitable for using with Ruby's +# Test::Unit unit test framework. FlexMock has a simple interface +# that's easy to remember, and leaves the hard stuff to all those +# other mock object implementations. +# +# Usage: See TestSamples for example usage. + +class FlexMock + include Test::Unit::Assertions + + # Create a FlexMock object. + def initialize + @handlers = Hash.new + @counts = Hash.new(0) + @expected_counts = Hash.new + end + + # Handle all messages denoted by +sym+ by calling the given block + # and passing any parameters to the block. If we know exactly how + # many calls are to be made to a particular method, we may check + # that by passing in the number of expected calls as a second + # paramter. + def mock_handle(sym, expected_count=nil, &block) + if block_given? + @handlers[sym] = block + else + @handlers[sym] = proc { } + end + @expected_counts[sym] = expected_count if expected_count + end + + # Verify that each method that had an explicit expected count was + # actually called that many times. + def mock_verify + @expected_counts.keys.each do |key| + assert_equal @expected_counts[key], @counts[key], + "Expected method #{key} to be called #{@expected_counts[key]} times, " + + "got #{@counts[key]}" + end + end + + # Report how many times a method was called. + def mock_count(sym) + @counts[sym] + end + + # Ignore all undefined (missing) method calls. + def mock_ignore_missing + @ignore_missing = true + end + + # Handle missing methods by attempting to look up a handler. + def method_missing(sym, *args, &block) + if handler = @handlers[sym] + @counts[sym] += 1 + args << block if block_given? + handler.call(*args) + else + super(sym, *args, &block) unless @ignore_missing + end + end + + # Class method to make sure that verify is called at the end of a + # test. + def self.use + mock = new + yield mock + ensure + mock.mock_verify + end +end diff --git a/vendor/rails/activesupport/lib/active_support/vendor/xml_simple.rb b/vendor/rails/activesupport/lib/active_support/vendor/xml_simple.rb new file mode 100644 index 0000000..e6ce63a --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/vendor/xml_simple.rb @@ -0,0 +1,1019 @@ +# = XmlSimple +# +# Author:: Maik Schmidt +# 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 '.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 = "" + 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 + # value + # 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), '', 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 << '' << 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 << '' << 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 << '' << 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!('&', '&') + result.gsub!('<', '<') + result.gsub!('>', '>') + result.gsub!('"', '"') + result.gsub!("'", ''') + 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 diff --git a/vendor/rails/activesupport/lib/active_support/version.rb b/vendor/rails/activesupport/lib/active_support/version.rb new file mode 100644 index 0000000..8e0ef72 --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/version.rb @@ -0,0 +1,9 @@ +module ActiveSupport + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 3 + TINY = 1 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/rails/activesupport/lib/active_support/whiny_nil.rb b/vendor/rails/activesupport/lib/active_support/whiny_nil.rb new file mode 100644 index 0000000..cee510a --- /dev/null +++ b/vendor/rails/activesupport/lib/active_support/whiny_nil.rb @@ -0,0 +1,38 @@ +# 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 occurred while evaluating nil.#{selector}" if selector + + raise NoMethodError, message, with_caller || caller + end +end + diff --git a/vendor/rails/activesupport/test/abstract_unit.rb b/vendor/rails/activesupport/test/abstract_unit.rb new file mode 100644 index 0000000..7e3ff50 --- /dev/null +++ b/vendor/rails/activesupport/test/abstract_unit.rb @@ -0,0 +1,4 @@ +require 'test/unit' + +$:.unshift "#{File.dirname(__FILE__)}/../lib" +require 'active_support' diff --git a/vendor/rails/activesupport/test/autoloading_fixtures/a/b.rb b/vendor/rails/activesupport/test/autoloading_fixtures/a/b.rb new file mode 100644 index 0000000..9c9e645 --- /dev/null +++ b/vendor/rails/activesupport/test/autoloading_fixtures/a/b.rb @@ -0,0 +1,2 @@ +class A::B +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/autoloading_fixtures/a/c/d.rb b/vendor/rails/activesupport/test/autoloading_fixtures/a/c/d.rb new file mode 100644 index 0000000..0f40d6f --- /dev/null +++ b/vendor/rails/activesupport/test/autoloading_fixtures/a/c/d.rb @@ -0,0 +1,2 @@ +class A::C::D +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/autoloading_fixtures/a/c/e/f.rb b/vendor/rails/activesupport/test/autoloading_fixtures/a/c/e/f.rb new file mode 100644 index 0000000..57dba5a --- /dev/null +++ b/vendor/rails/activesupport/test/autoloading_fixtures/a/c/e/f.rb @@ -0,0 +1,2 @@ +class A::C::E::F +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/autoloading_fixtures/e.rb b/vendor/rails/activesupport/test/autoloading_fixtures/e.rb new file mode 100644 index 0000000..2f59e4f --- /dev/null +++ b/vendor/rails/activesupport/test/autoloading_fixtures/e.rb @@ -0,0 +1,2 @@ +class E +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb b/vendor/rails/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb new file mode 100644 index 0000000..39ee0e5 --- /dev/null +++ b/vendor/rails/activesupport/test/autoloading_fixtures/module_folder/nested_class.rb @@ -0,0 +1,2 @@ +class ModuleFolder::NestedClass +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb b/vendor/rails/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb new file mode 100644 index 0000000..80244b8 --- /dev/null +++ b/vendor/rails/activesupport/test/autoloading_fixtures/module_folder/nested_sibling.rb @@ -0,0 +1,2 @@ +class ModuleFolder::NestedSibling +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/caching_tools_test.rb b/vendor/rails/activesupport/test/caching_tools_test.rb new file mode 100644 index 0000000..c9890e4 --- /dev/null +++ b/vendor/rails/activesupport/test/caching_tools_test.rb @@ -0,0 +1,78 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class HashCachingTests < Test::Unit::TestCase + def cached(&proc) + return @cached if defined?(@cached) + + @cached_class = Class.new(&proc) + @cached_class.class_eval do + extend ActiveSupport::CachingTools::HashCaching + hash_cache :slow_method + end + @cached = @cached_class.new + end + + def test_cache_access_should_call_method + cached do + def slow_method(a) raise "I should be here: #{a}"; end + end + assert_raises(RuntimeError) { cached.slow_method_cache[1] } + end + + def test_cache_access_should_actually_cache + cached do + def slow_method(a) + (@x ||= []) + if @x.include?(a) then raise "Called twice for #{a}!" + else + @x << a + a + 1 + end + end + end + assert_equal 11, cached.slow_method_cache[10] + assert_equal 12, cached.slow_method_cache[11] + assert_equal 11, cached.slow_method_cache[10] + assert_equal 12, cached.slow_method_cache[11] + end + + def test_cache_should_be_clearable + cached do + def slow_method(a) + @x ||= 0 + @x += 1 + end + end + assert_equal 1, cached.slow_method_cache[:a] + assert_equal 2, cached.slow_method_cache[:b] + assert_equal 3, cached.slow_method_cache[:c] + + assert_equal 1, cached.slow_method_cache[:a] + assert_equal 2, cached.slow_method_cache[:b] + assert_equal 3, cached.slow_method_cache[:c] + + cached.slow_method_cache.clear + + assert_equal 4, cached.slow_method_cache[:a] + assert_equal 5, cached.slow_method_cache[:b] + assert_equal 6, cached.slow_method_cache[:c] + end + + def test_deep_caches_should_work_too + cached do + def slow_method(a, b, c) + a + b + c + end + end + assert_equal 3, cached.slow_method_cache[1][1][1] + assert_equal 7, cached.slow_method_cache[1][2][4] + assert_equal 7, cached.slow_method_cache[1][2][4] + assert_equal 7, cached.slow_method_cache[4][2][1] + + assert_equal({ + 1 => {1 => {1 => 3}, 2 => {4 => 7}}, + 4 => {2 => {1 => 7}}}, + cached.slow_method_cache + ) + end +end diff --git a/vendor/rails/activesupport/test/class_inheritable_attributes_test.rb b/vendor/rails/activesupport/test/class_inheritable_attributes_test.rb new file mode 100644 index 0000000..54e533e --- /dev/null +++ b/vendor/rails/activesupport/test/class_inheritable_attributes_test.rb @@ -0,0 +1,140 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class ClassInheritableAttributesTest < Test::Unit::TestCase + def setup + @klass = Class.new + end + + def test_reader_declaration + assert_nothing_raised do + @klass.class_inheritable_reader :a + assert_respond_to @klass, :a + assert_respond_to @klass.new, :a + end + end + + def test_writer_declaration + assert_nothing_raised do + @klass.class_inheritable_writer :a + assert_respond_to @klass, :a= + assert_respond_to @klass.new, :a= + end + end + + def test_accessor_declaration + assert_nothing_raised do + @klass.class_inheritable_accessor :a + assert_respond_to @klass, :a + assert_respond_to @klass.new, :a + assert_respond_to @klass, :a= + assert_respond_to @klass.new, :a= + end + end + + def test_array_declaration + assert_nothing_raised do + @klass.class_inheritable_array :a + assert_respond_to @klass, :a + assert_respond_to @klass.new, :a + assert_respond_to @klass, :a= + assert_respond_to @klass.new, :a= + end + end + + def test_hash_declaration + assert_nothing_raised do + @klass.class_inheritable_hash :a + assert_respond_to @klass, :a + assert_respond_to @klass.new, :a + assert_respond_to @klass, :a= + assert_respond_to @klass.new, :a= + end + end + + def test_reader + @klass.class_inheritable_reader :a + assert_nil @klass.a + assert_nil @klass.new.a + + @klass.send(:write_inheritable_attribute, :a, 'a') + + assert_equal 'a', @klass.a + assert_equal 'a', @klass.new.a + assert_equal @klass.a, @klass.new.a + assert_equal @klass.a.object_id, @klass.new.a.object_id + end + + def test_writer + @klass.class_inheritable_reader :a + @klass.class_inheritable_writer :a + + assert_nil @klass.a + assert_nil @klass.new.a + + @klass.a = 'a' + assert_equal 'a', @klass.a + @klass.new.a = 'A' + assert_equal 'A', @klass.a + end + + def test_array + @klass.class_inheritable_array :a + + assert_nil @klass.a + assert_nil @klass.new.a + + @klass.a = %w(a b c) + assert_equal %w(a b c), @klass.a + assert_equal %w(a b c), @klass.new.a + + @klass.new.a = %w(A B C) + assert_equal %w(a b c A B C), @klass.a + assert_equal %w(a b c A B C), @klass.new.a + end + + def test_hash + @klass.class_inheritable_hash :a + + assert_nil @klass.a + assert_nil @klass.new.a + + @klass.a = { :a => 'a' } + assert_equal({ :a => 'a' }, @klass.a) + assert_equal({ :a => 'a' }, @klass.new.a) + + @klass.new.a = { :b => 'b' } + assert_equal({ :a => 'a', :b => 'b' }, @klass.a) + assert_equal({ :a => 'a', :b => 'b' }, @klass.new.a) + end + + def test_inheritance + @klass.class_inheritable_accessor :a + @klass.a = 'a' + + @sub = eval("class FlogMe < @klass; end; FlogMe") + + @klass.class_inheritable_accessor :b + + assert_respond_to @sub, :a + assert_respond_to @sub, :b + assert_equal @klass.a, @sub.a + assert_equal @klass.b, @sub.b + assert_equal 'a', @sub.a + assert_nil @sub.b + + @klass.b = 'b' + assert_not_equal @klass.b, @sub.b + assert_equal 'b', @klass.b + assert_nil @sub.b + + @sub.a = 'A' + assert_not_equal @klass.a, @sub.a + assert_equal 'a', @klass.a + assert_equal 'A', @sub.a + + @sub.b = 'B' + assert_not_equal @klass.b, @sub.b + assert_equal 'b', @klass.b + assert_equal 'B', @sub.b + end +end diff --git a/vendor/rails/activesupport/test/clean_logger_test.rb b/vendor/rails/activesupport/test/clean_logger_test.rb new file mode 100644 index 0000000..a15bfbc --- /dev/null +++ b/vendor/rails/activesupport/test/clean_logger_test.rb @@ -0,0 +1,80 @@ +require File.dirname(__FILE__) + '/abstract_unit' +require 'stringio' + +class CleanLoggerTest < Test::Unit::TestCase + def setup + @out = StringIO.new + @logger = Logger.new(@out) + end + + def test_format_message + @logger.error 'error' + assert_equal "error\n", @out.string + end + + def test_silence + # Without yielding self. + @logger.silence do + @logger.debug 'debug' + @logger.info 'info' + @logger.warn 'warn' + @logger.error 'error' + @logger.fatal 'fatal' + end + + # Yielding self. + @logger.silence do |logger| + logger.debug 'debug' + logger.info 'info' + logger.warn 'warn' + logger.error 'error' + logger.fatal 'fatal' + end + + # Silencer off. + Logger.silencer = false + @logger.silence do |logger| + logger.warn 'unsilenced' + end + Logger.silencer = true + + assert_equal "error\nfatal\nerror\nfatal\nunsilenced\n", @out.string + end +end + +class CleanLogger_182_to_183_Test < Test::Unit::TestCase + def setup + silence_warnings do + if Logger.method_defined?(:formatter=) + Logger.send(:alias_method, :hide_formatter=, :formatter=) + Logger.send(:undef_method, :formatter=) + else + Logger.send(:define_method, :formatter=) { } + end + load File.dirname(__FILE__) + '/../lib/active_support/clean_logger.rb' + end + + @out = StringIO.new + @logger = Logger.new(@out) + @logger.progname = 'CLEAN LOGGER TEST' + end + + def teardown + silence_warnings do + if Logger.method_defined?(:hide_formatter=) + Logger.send(:alias_method, :formatter=, :hide_formatter=) + else + Logger.send(:undef_method, :formatter=) + end + load File.dirname(__FILE__) + '/../lib/active_support/clean_logger.rb' + end + end + + # Since we've fooled Logger into thinking we're on 1.8.2 if we're on 1.8.3 + # and on 1.8.3 if we're on 1.8.2, it'll define format_message with the + # wrong order of arguments and therefore print progname instead of msg. + def test_format_message_with_faked_version + @logger.error 'error' + assert_equal "CLEAN LOGGER TEST\n", @out.string + end +end diff --git a/vendor/rails/activesupport/test/core_ext/array_ext_test.rb b/vendor/rails/activesupport/test/core_ext/array_ext_test.rb new file mode 100644 index 0000000..955b2b3 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/array_ext_test.rb @@ -0,0 +1,158 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class ArrayExtToParamTests < Test::Unit::TestCase + def test_string_array + assert_equal '', %w().to_param + assert_equal 'hello/world', %w(hello world).to_param + assert_equal 'hello/10', %w(hello 10).to_param + end + + def test_number_array + assert_equal '10/20', [10, 20].to_param + end +end + +class ArrayExtToSentenceTests < Test::Unit::TestCase + def test_plain_array_to_sentence + assert_equal "", [].to_sentence + assert_equal "one", ['one'].to_sentence + assert_equal "one and two", ['one', 'two'].to_sentence + assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence + + end + + def test_to_sentence_with_connector + assert_equal "one, two, and also three", ['one', 'two', 'three'].to_sentence(:connector => 'and also') + end + + def test_to_sentence_with_skip_last_comma + assert_equal "one, two, and three", ['one', 'two', 'three'].to_sentence(:skip_last_comma => false) + end + + def test_two_elements + assert_equal "one and two", ['one', 'two'].to_sentence + end + + def test_one_element + assert_equal "one", ['one'].to_sentence + end +end + +class ArrayExtToSTests < Test::Unit::TestCase + def test_to_s_db + collection = [ + Class.new { def id() 1 end }.new, + Class.new { def id() 2 end }.new, + Class.new { def id() 3 end }.new + ] + + assert_equal "null", [].to_s(:db) + assert_equal "1,2,3", collection.to_s(:db) + end +end + +class ArrayExtGroupingTests < Test::Unit::TestCase + def test_group_by_with_perfect_fit + groups = [] + ('a'..'i').to_a.in_groups_of(3) do |group| + groups << group + end + + assert_equal [%w(a b c), %w(d e f), %w(g h i)], groups + assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3) + end + + def test_group_by_with_padding + groups = [] + ('a'..'g').to_a.in_groups_of(3) do |group| + groups << group + end + + assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups + end + + def test_group_by_pads_with_specified_values + groups = [] + + ('a'..'g').to_a.in_groups_of(3, false) do |group| + groups << group + end + + assert_equal [%w(a b c), %w(d e f), ['g', false, false]], groups + end +end + +class ArraySplitTests < Test::Unit::TestCase + def test_split_with_empty_array + assert_equal [[]], [].split(0) + end + + def test_split_with_argument + assert_equal [[1, 2], [4, 5]], [1, 2, 3, 4, 5].split(3) + assert_equal [[1, 2, 3, 4, 5]], [1, 2, 3, 4, 5].split(0) + end + + def test_split_with_block + assert_equal [[1, 2], [4, 5], [7, 8], [10]], (1..10).to_a.split { |i| i % 3 == 0 } + end + + def test_split_with_edge_values + assert_equal [[], [2, 3, 4, 5]], [1, 2, 3, 4, 5].split(1) + assert_equal [[1, 2, 3, 4], []], [1, 2, 3, 4, 5].split(5) + assert_equal [[], [2, 3, 4], []], [1, 2, 3, 4, 5].split { |i| i == 1 || i == 5 } + end +end + +class ArrayToXmlTests < Test::Unit::TestCase + def test_to_xml + xml = [ + { :name => "David", :age => 26 }, { :name => "Jason", :age => 31 } + ].to_xml(:skip_instruct => true, :indent => 0) + + assert_equal "", xml.first(17) + assert xml.include?(%(26)) + assert xml.include?(%(David)) + assert xml.include?(%(31)) + assert xml.include?(%(Jason)) + end + + def test_to_xml_with_dedicated_name + xml = [ + { :name => "David", :age => 26 }, { :name => "Jason", :age => 31 } + ].to_xml(:skip_instruct => true, :indent => 0, :root => "people") + + assert_equal "", xml.first(16) + end + + def test_to_xml_with_options + xml = [ + { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } + ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0) + + assert_equal "", xml.first(17) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + assert xml.include?(%(Evergreen)) + assert xml.include?(%(Jason)) + end + + def test_to_xml_with_dasherize_false + xml = [ + { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } + ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => false) + + assert_equal "", xml.first(17) + assert xml.include?(%(Paulina)) + assert xml.include?(%(Evergreen)) + end + + def test_to_xml_with_dasherize_true + xml = [ + { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } + ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 0, :dasherize => true) + + assert_equal "", xml.first(17) + assert xml.include?(%(Paulina)) + assert xml.include?(%(Evergreen)) + end +end diff --git a/vendor/rails/activesupport/test/core_ext/blank_test.rb b/vendor/rails/activesupport/test/core_ext/blank_test.rb new file mode 100644 index 0000000..0fce470 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/blank_test.rb @@ -0,0 +1,11 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class BlankTest < Test::Unit::TestCase + BLANK = [nil, false, '', ' ', " \n\t \r ", [], {}] + NOT = [true, 0, 1, 'a', [nil], { nil => 0 }] + + def test_blank + BLANK.each { |v| assert v.blank? } + NOT.each { |v| assert !v.blank? } + end +end diff --git a/vendor/rails/activesupport/test/core_ext/cgi_ext_test.rb b/vendor/rails/activesupport/test/core_ext/cgi_ext_test.rb new file mode 100644 index 0000000..229715c --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/cgi_ext_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class EscapeSkippingSlashesTest < Test::Unit::TestCase + def test_array + assert_equal 'hello/world', CGI.escape_skipping_slashes(%w(hello world)) + assert_equal 'hello+world/how/are/you', CGI.escape_skipping_slashes(['hello world', 'how', 'are', 'you']) + end + + def test_typical + assert_equal 'hi', CGI.escape_skipping_slashes('hi') + assert_equal 'hi/world', CGI.escape_skipping_slashes('hi/world') + assert_equal 'hi/world+you+funky+thing', CGI.escape_skipping_slashes('hi/world you funky thing') + end +end diff --git a/vendor/rails/activesupport/test/core_ext/class_test.rb b/vendor/rails/activesupport/test/core_ext/class_test.rb new file mode 100644 index 0000000..bcb96f5 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/class_test.rb @@ -0,0 +1,36 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class A +end + +module X + class B + end +end + +module Y + module Z + class C + end + end +end + +class ClassTest < Test::Unit::TestCase + def test_removing_class_in_root_namespace + assert A.is_a?(Class) + Class.remove_class(A) + assert_raises(NameError) { A.is_a?(Class) } + end + + def test_removing_class_in_one_level_namespace + assert X::B.is_a?(Class) + Class.remove_class(X::B) + assert_raises(NameError) { X::B.is_a?(Class) } + end + + def test_removing_class_in_two_level_namespace + assert Y::Z::C.is_a?(Class) + Class.remove_class(Y::Z::C) + assert_raises(NameError) { Y::Z::C.is_a?(Class) } + end +end diff --git a/vendor/rails/activesupport/test/core_ext/date_ext_test.rb b/vendor/rails/activesupport/test/core_ext/date_ext_test.rb new file mode 100644 index 0000000..5616195 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/date_ext_test.rb @@ -0,0 +1,20 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class DateExtCalculationsTest < Test::Unit::TestCase + def test_to_s + assert_equal "21 Feb", Date.new(2005, 2, 21).to_s(:short) + assert_equal "February 21, 2005", Date.new(2005, 2, 21).to_s(:long) + end + + def test_to_time + assert_equal Time.local(2005, 2, 21), Date.new(2005, 2, 21).to_time + end + + def test_to_time_on_datetime + assert_equal Time.local(2005, 2, 21, 10, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12).to_time + end + + def test_to_date + assert_equal Date.new(2005, 2, 21), Date.new(2005, 2, 21).to_date + end +end diff --git a/vendor/rails/activesupport/test/core_ext/enumerable_test.rb b/vendor/rails/activesupport/test/core_ext/enumerable_test.rb new file mode 100644 index 0000000..0590846 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/enumerable_test.rb @@ -0,0 +1,61 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +Payment = Struct.new(:price) +class SummablePayment < Payment + def +(p) self.class.new(price + p.price) end +end + +class EnumerableTests < Test::Unit::TestCase + def test_group_by + names = %w(marcel sam david jeremy) + klass = Class.new + klass.send(:attr_accessor, :name) + objects = (1..50).inject([]) do |people,| + p = klass.new + p.name = names.sort_by { rand }.first + people << p + end + + objects.group_by {|object| object.name}.each do |name, group| + assert group.all? {|person| person.name == name} + end + end + + def test_sums + assert_equal 30, [5, 15, 10].sum + assert_equal 30, [5, 15, 10].sum { |i| i } + + assert_equal 'abc', %w(a b c).sum + assert_equal 'abc', %w(a b c).sum { |i| i } + + payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ] + assert_equal 30, payments.sum(&:price) + assert_equal 60, payments.sum { |p| p.price * 2 } + + payments = [ SummablePayment.new(5), SummablePayment.new(15) ] + assert_equal SummablePayment.new(20), payments.sum + assert_equal SummablePayment.new(20), payments.sum { |p| p } + end + + def test_nil_sums + assert_raise(TypeError) { [5, 15, nil].sum } + + payments = [ Payment.new(5), Payment.new(15), Payment.new(10), Payment.new(nil) ] + assert_raise(TypeError) { payments.sum(&:price) } + assert_equal 60, payments.sum { |p| p.price.to_i * 2 } + end + + def test_empty_sums + assert_equal 0, [].sum + assert_equal 0, [].sum { |i| i } + assert_equal Payment.new(0), [].sum(Payment.new(0)) + end + + def test_index_by + payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ] + assert_equal( + {5 => payments[0], 15 => payments[1], 10 => payments[2]}, + payments.index_by(&:price) + ) + end +end diff --git a/vendor/rails/activesupport/test/core_ext/exception_test.rb b/vendor/rails/activesupport/test/core_ext/exception_test.rb new file mode 100644 index 0000000..952ac04 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/exception_test.rb @@ -0,0 +1,64 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class ExceptionExtTests < Test::Unit::TestCase + + def get_exception(cls = RuntimeError, msg = nil, trace = nil) + begin raise cls, msg, (trace || caller) + rescue Object => e + return e + end + end + + def setup + Exception::TraceSubstitutions.clear + end + + def test_clean_backtrace + Exception::TraceSubstitutions << [/\s*hidden.*/, ''] + e = get_exception RuntimeError, 'RAWR', ['bhal.rb', 'rawh hid den stuff is not here', 'almost all'] + assert_kind_of Exception, e + assert_equal ['bhal.rb', 'rawh hid den stuff is not here', 'almost all'], e.clean_backtrace + end + + def test_app_backtrace + Exception::TraceSubstitutions << [/\s*hidden.*/, ''] + e = get_exception RuntimeError, 'RAWR', ['bhal.rb', ' vendor/file.rb some stuff', 'almost all'] + assert_kind_of Exception, e + assert_equal ['bhal.rb', 'almost all'], e.application_backtrace + end + + def test_app_backtrace_with_before + Exception::TraceSubstitutions << [/\s*hidden.*/, ''] + e = get_exception RuntimeError, 'RAWR', ['vendor/file.rb some stuff', 'bhal.rb', ' vendor/file.rb some stuff', 'almost all'] + assert_kind_of Exception, e + assert_equal ['vendor/file.rb some stuff', 'bhal.rb', 'almost all'], e.application_backtrace + end + + def test_framework_backtrace_with_before + Exception::TraceSubstitutions << [/\s*hidden.*/, ''] + e = get_exception RuntimeError, 'RAWR', ['vendor/file.rb some stuff', 'bhal.rb', ' vendor/file.rb some stuff', 'almost all'] + assert_kind_of Exception, e + assert_equal ['vendor/file.rb some stuff', ' vendor/file.rb some stuff'], e.framework_backtrace + end + + def test_backtrace_should_clean_paths + Exception::TraceSubstitutions << [/\s*hidden.*/, ''] + e = get_exception RuntimeError, 'RAWR', ['a/b/c/../d/../../../bhal.rb', 'rawh hid den stuff is not here', 'almost all'] + assert_kind_of Exception, e + assert_equal ['bhal.rb', 'rawh hid den stuff is not here', 'almost all'], e.clean_backtrace + end + + def test_clean_message_should_clean_paths + Exception::TraceSubstitutions << [/\s*hidden.*/, ''] + e = get_exception RuntimeError, "I dislike a/z/x/../../b/y/../c", ['a/b/c/../d/../../../bhal.rb', 'rawh hid den stuff is not here', 'almost all'] + assert_kind_of Exception, e + assert_equal "I dislike a/b/c", e.clean_message + end + + def test_app_trace_should_be_empty_when_no_app_frames + Exception::TraceSubstitutions << [/\s*hidden.*/, ''] + e = get_exception RuntimeError, 'RAWR', ['vendor/file.rb some stuff', 'generated/bhal.rb', ' vendor/file.rb some stuff', 'generated/almost all'] + assert_kind_of Exception, e + assert_equal [], e.application_backtrace + end +end diff --git a/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb b/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb new file mode 100644 index 0000000..f0a50ec --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/hash_ext_test.rb @@ -0,0 +1,431 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class HashExtTest < Test::Unit::TestCase + def setup + @strings = { 'a' => 1, 'b' => 2 } + @symbols = { :a => 1, :b => 2 } + @mixed = { :a => 1, 'b' => 2 } + end + + def test_methods + h = {} + assert_respond_to h, :symbolize_keys + assert_respond_to h, :symbolize_keys! + assert_respond_to h, :stringify_keys + assert_respond_to h, :stringify_keys! + assert_respond_to h, :to_options + assert_respond_to h, :to_options! + end + + def test_symbolize_keys + assert_equal @symbols, @symbols.symbolize_keys + assert_equal @symbols, @strings.symbolize_keys + assert_equal @symbols, @mixed.symbolize_keys + + assert_raises(NoMethodError) { { [] => 1 }.symbolize_keys } + end + + def test_symbolize_keys! + assert_equal @symbols, @symbols.dup.symbolize_keys! + assert_equal @symbols, @strings.dup.symbolize_keys! + assert_equal @symbols, @mixed.dup.symbolize_keys! + + assert_raises(NoMethodError) { { [] => 1 }.symbolize_keys } + end + + def test_stringify_keys + assert_equal @strings, @symbols.stringify_keys + assert_equal @strings, @strings.stringify_keys + assert_equal @strings, @mixed.stringify_keys + end + + def test_stringify_keys! + assert_equal @strings, @symbols.dup.stringify_keys! + assert_equal @strings, @strings.dup.stringify_keys! + assert_equal @strings, @mixed.dup.stringify_keys! + end + + def test_indifferent_assorted + @strings = @strings.with_indifferent_access + @symbols = @symbols.with_indifferent_access + @mixed = @mixed.with_indifferent_access + + assert_equal 'a', @strings.send(:convert_key, :a) + + assert_equal 1, @strings.fetch('a') + assert_equal 1, @strings.fetch(:a.to_s) + assert_equal 1, @strings.fetch(:a) + + hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed } + method_map = { :'[]' => 1, :fetch => 1, :values_at => [1], + :has_key? => true, :include? => true, :key? => true, + :member? => true } + + hashes.each do |name, hash| + method_map.sort_by { |m| m.to_s }.each do |meth, expected| + assert_equal(expected, hash.send(meth, 'a'), + "Calling #{name}.#{meth} 'a'") + assert_equal(expected, hash.send(meth, :a), + "Calling #{name}.#{meth} :a") + end + end + + assert_equal [1, 2], @strings.values_at('a', 'b') + assert_equal [1, 2], @strings.values_at(:a, :b) + assert_equal [1, 2], @symbols.values_at('a', 'b') + assert_equal [1, 2], @symbols.values_at(:a, :b) + assert_equal [1, 2], @mixed.values_at('a', 'b') + assert_equal [1, 2], @mixed.values_at(:a, :b) + end + + def test_indifferent_reading + hash = HashWithIndifferentAccess.new + hash["a"] = 1 + hash["b"] = true + hash["c"] = false + hash["d"] = nil + + assert_equal 1, hash[:a] + assert_equal true, hash[:b] + assert_equal false, hash[:c] + assert_equal nil, hash[:d] + assert_equal nil, hash[:e] + end + + def test_indifferent_reading_with_nonnil_default + hash = HashWithIndifferentAccess.new(1) + hash["a"] = 1 + hash["b"] = true + hash["c"] = false + hash["d"] = nil + + assert_equal 1, hash[:a] + assert_equal true, hash[:b] + assert_equal false, hash[:c] + assert_equal nil, hash[:d] + assert_equal 1, hash[:e] + end + + def test_indifferent_writing + hash = HashWithIndifferentAccess.new + hash[:a] = 1 + hash['b'] = 2 + hash[3] = 3 + + assert_equal hash['a'], 1 + assert_equal hash['b'], 2 + assert_equal hash[:a], 1 + assert_equal hash[:b], 2 + assert_equal hash[3], 3 + end + + def test_indifferent_update + hash = HashWithIndifferentAccess.new + hash[:a] = 'a' + hash['b'] = 'b' + + updated_with_strings = hash.update(@strings) + updated_with_symbols = hash.update(@symbols) + updated_with_mixed = hash.update(@mixed) + + assert_equal updated_with_strings[:a], 1 + assert_equal updated_with_strings['a'], 1 + assert_equal updated_with_strings['b'], 2 + + assert_equal updated_with_symbols[:a], 1 + assert_equal updated_with_symbols['b'], 2 + assert_equal updated_with_symbols[:b], 2 + + assert_equal updated_with_mixed[:a], 1 + assert_equal updated_with_mixed['b'], 2 + + assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? {|hash| hash.keys.size == 2} + end + + def test_indifferent_merging + hash = HashWithIndifferentAccess.new + hash[:a] = 'failure' + hash['b'] = 'failure' + + other = { 'a' => 1, :b => 2 } + + merged = hash.merge(other) + + assert_equal HashWithIndifferentAccess, merged.class + assert_equal 1, merged[:a] + assert_equal 2, merged['b'] + + hash.update(other) + + assert_equal 1, hash[:a] + assert_equal 2, hash['b'] + end + + def test_indifferent_deleting + get_hash = proc{ { :a => 'foo' }.with_indifferent_access } + hash = get_hash.call + assert_equal hash.delete(:a), 'foo' + assert_equal hash.delete(:a), nil + hash = get_hash.call + assert_equal hash.delete('a'), 'foo' + assert_equal hash.delete('a'), nil + end + + def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash + h = HashWithIndifferentAccess.new + h[:first] = 1 + h.stringify_keys! + assert_equal 1, h['first'] + h = HashWithIndifferentAccess.new + h['first'] = 1 + h.symbolize_keys! + assert_equal 1, h[:first] + end + + def test_assert_valid_keys + assert_nothing_raised do + { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) + { :failure => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) + end + + assert_raises(ArgumentError, "Unknown key(s): failore") do + { :failore => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) + { :failore => "stuff", :funny => "business" }.assert_valid_keys(:failure, :funny) + end + end + + def test_indifferent_subhashes + h = {'user' => {'id' => 5}}.with_indifferent_access + ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} + + h = {:user => {:id => 5}}.with_indifferent_access + ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} + end + + def test_assorted_keys_not_stringified + original = {Object.new => 2, 1 => 2, [] => true} + indiff = original.with_indifferent_access + assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!") + end + + def test_reverse_merge + assert_equal({ :a => 1, :b => 2, :c => 10 }, { :a => 1, :b => 2 }.reverse_merge({:a => "x", :b => "y", :c => 10}) ) + end + + def test_diff + assert_equal({ :a => 2 }, { :a => 2, :b => 5 }.diff({ :a => 1, :b => 5 })) + end +end + +class IWriteMyOwnXML + def to_xml(options = {}) + options[:indent] ||= 2 + xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + xml.instruct! unless options[:skip_instruct] + xml.level_one do + xml.tag!(:second_level, 'content') + end + end +end + +class HashToXmlTest < Test::Unit::TestCase + def setup + @xml_options = { :root => :person, :skip_instruct => true, :indent => 0 } + end + + def test_one_level + xml = { :name => "David", :street => "Paulina" }.to_xml(@xml_options) + assert_equal "", xml.first(8) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + end + + def test_one_level_dasherize_false + xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => false)) + assert_equal "", xml.first(8) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + end + + def test_one_level_dasherize_true + xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:dasherize => true)) + assert_equal "", xml.first(8) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + end + + def test_one_level_with_types + xml = { :name => "David", :street => "Paulina", :age => 26, :moved_on => Date.new(2005, 11, 15) }.to_xml(@xml_options) + assert_equal "", xml.first(8) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + assert xml.include?(%(26)) + assert xml.include?(%(2005-11-15)) + end + + def test_one_level_with_nils + xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options) + assert_equal "", xml.first(8) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + assert xml.include?(%()) + end + + def test_one_level_with_skipping_types + xml = { :name => "David", :street => "Paulina", :age => nil }.to_xml(@xml_options.merge(:skip_types => true)) + assert_equal "", xml.first(8) + assert xml.include?(%(Paulina)) + assert xml.include?(%(David)) + assert xml.include?(%()) + end + + def test_two_levels + xml = { :name => "David", :address => { :street => "Paulina" } }.to_xml(@xml_options) + assert_equal "", xml.first(8) + assert xml.include?(%(
      Paulina
      )) + assert xml.include?(%(David)) + end + + def test_two_levels_with_second_level_overriding_to_xml + xml = { :name => "David", :address => { :street => "Paulina" }, :child => IWriteMyOwnXML.new }.to_xml(@xml_options) + assert_equal "", xml.first(8) + assert xml.include?(%(
      Paulina
      )) + assert xml.include?(%(content)) + end + + def test_two_levels_with_array + xml = { :name => "David", :addresses => [{ :street => "Paulina" }, { :street => "Evergreen" }] }.to_xml(@xml_options) + assert_equal "", xml.first(8) + assert xml.include?(%(
      )) + assert xml.include?(%(
      Paulina
      )) + assert xml.include?(%(
      Evergreen
      )) + assert xml.include?(%(David)) + end + + def test_three_levels_with_array + xml = { :name => "David", :addresses => [{ :streets => [ { :name => "Paulina" }, { :name => "Paulina" } ] } ] }.to_xml(@xml_options) + assert xml.include?(%(
      )) + end + + def test_single_record_from_xml + topic_xml = <<-EOT + + The First Topic + David + 1 + true + 0 + 2003-07-16 + 2003-07-16T09:28:00+0000 + Have a nice day + david@loudthinking.com + + + EOT + + expected_topic_hash = { + :title => "The First Topic", + :author_name => "David", + :id => 1, + :approved => true, + :replies_count => 0, + :written_on => Date.new(2003, 7, 16), + :viewed_at => Time.utc(2003, 7, 16, 9, 28), + :content => "Have a nice day", + :author_email_address => "david@loudthinking.com", + :parent_id => nil + }.stringify_keys + + assert_equal expected_topic_hash, Hash.create_from_xml(topic_xml)["topic"] + end + + def test_multiple_records_from_xml + topics_xml = <<-EOT + + + The First Topic + David + 1 + false + 0 + 2003-07-16 + 2003-07-16T09:28:00+0000 + Have a nice day + david@loudthinking.com + + + + The Second Topic + Jason + 1 + false + 0 + 2003-07-16 + 2003-07-16T09:28:00+0000 + Have a nice day + david@loudthinking.com + + + + EOT + + expected_topic_hash = { + :title => "The First Topic", + :author_name => "David", + :id => 1, + :approved => false, + :replies_count => 0, + :written_on => Date.new(2003, 7, 16), + :viewed_at => Time.utc(2003, 7, 16, 9, 28), + :content => "Have a nice day", + :author_email_address => "david@loudthinking.com", + :parent_id => nil + }.stringify_keys + + assert_equal expected_topic_hash, Hash.create_from_xml(topics_xml)["topics"]["topic"].first + end + + def test_single_record_from_xml_with_attributes_other_than_type + topic_xml = <<-EOT + + + + + + EOT + + expected_topic_hash = { + :id => "175756086", + :owner => "55569174@N00", + :secret => "0279bf37a1", + :server => "76", + :title => "Colored Pencil PhotoBooth Fun", + :ispublic => "1", + :isfriend => "0", + :isfamily => "0", + }.stringify_keys + + assert_equal expected_topic_hash, Hash.create_from_xml(topic_xml)["rsp"]["photos"]["photo"] + end + + def test_should_use_default_value_for_unknown_key + hash_wia = HashWithIndifferentAccess.new(3) + assert_equal 3, hash_wia[:new_key] + end + + def test_should_use_default_value_if_no_key_is_supplied + hash_wia = HashWithIndifferentAccess.new(3) + assert_equal 3, hash_wia.default + end + + def test_should_nil_if_no_default_value_is_supplied + hash_wia = HashWithIndifferentAccess.new + assert_nil hash_wia.default + end + + def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access + hash = Hash.new(3) + hash_wia = hash.with_indifferent_access + assert_equal 3, hash_wia.default + end +end diff --git a/vendor/rails/activesupport/test/core_ext/integer_ext_test.rb b/vendor/rails/activesupport/test/core_ext/integer_ext_test.rb new file mode 100644 index 0000000..6d6b796 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/integer_ext_test.rb @@ -0,0 +1,37 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class IntegerExtTest < Test::Unit::TestCase + def test_even + assert [ -2, 0, 2, 4 ].all? { |i| i.even? } + assert ![ -1, 1, 3 ].all? { |i| i.even? } + + assert 22953686867719691230002707821868552601124472329079.odd? + assert !22953686867719691230002707821868552601124472329079.even? + assert 22953686867719691230002707821868552601124472329080.even? + assert !22953686867719691230002707821868552601124472329080.odd? + end + + def test_odd + assert ![ -2, 0, 2, 4 ].all? { |i| i.odd? } + assert [ -1, 1, 3 ].all? { |i| i.odd? } + assert 1000000000000000000000000000000000000000000000000000000001.odd? + end + + def test_multiple_of + [ -7, 0, 7, 14 ].each { |i| assert i.multiple_of?(7) } + [ -7, 7, 14 ].each { |i| assert ! i.multiple_of?(6) } + # test with a prime + assert !22953686867719691230002707821868552601124472329079.multiple_of?(2) + assert !22953686867719691230002707821868552601124472329079.multiple_of?(3) + assert !22953686867719691230002707821868552601124472329079.multiple_of?(5) + assert !22953686867719691230002707821868552601124472329079.multiple_of?(7) + end + + def test_ordinalize + # These tests are mostly just to ensure that the ordinalize method exists + # It's results are tested comprehensively in the inflector test cases. + assert_equal '1st', 1.ordinalize + assert_equal '8th', 8.ordinalize + 1000000000000000000000000000000000000000000000000000000000000000000000.ordinalize + end +end diff --git a/vendor/rails/activesupport/test/core_ext/kernel_test.rb b/vendor/rails/activesupport/test/core_ext/kernel_test.rb new file mode 100644 index 0000000..92d9047 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/kernel_test.rb @@ -0,0 +1,43 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class KernelTest < Test::Unit::TestCase + def test_silence_warnings + silence_warnings { assert_nil $VERBOSE } + assert_equal 1234, silence_warnings { 1234 } + end + + def test_silence_warnings_verbose_invariant + old_verbose = $VERBOSE + silence_warnings { raise } + flunk + rescue + assert_equal old_verbose, $VERBOSE + end + + + def test_enable_warnings + enable_warnings { assert_equal true, $VERBOSE } + assert_equal 1234, enable_warnings { 1234 } + end + + def test_enable_warnings_verbose_invariant + old_verbose = $VERBOSE + enable_warnings { raise } + flunk + rescue + assert_equal old_verbose, $VERBOSE + end + + + def test_silence_stderr + old_stderr_position = STDERR.tell + silence_stderr { STDERR.puts 'hello world' } + assert_equal old_stderr_position, STDERR.tell + rescue Errno::ESPIPE + # Skip if we can't STDERR.tell + end + + def test_silence_stderr_with_return_value + assert_equal 1, silence_stderr { 1 } + end +end diff --git a/vendor/rails/activesupport/test/core_ext/load_error_tests.rb b/vendor/rails/activesupport/test/core_ext/load_error_tests.rb new file mode 100644 index 0000000..34c5cb4 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/load_error_tests.rb @@ -0,0 +1,16 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class TestMissingSourceFile < Test::Unit::TestCase + def test_with_require + assert_raises(MissingSourceFile) { require 'no_this_file_don\'t_exist' } + end + def test_with_load + assert_raises(MissingSourceFile) { load 'nor_does_this_one' } + end + def test_path + begin load 'nor/this/one.rb' + rescue MissingSourceFile => e + assert_equal 'nor/this/one.rb', e.path + end + end +end diff --git a/vendor/rails/activesupport/test/core_ext/module/attr_internal_test.rb b/vendor/rails/activesupport/test/core_ext/module/attr_internal_test.rb new file mode 100644 index 0000000..25b049f --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/module/attr_internal_test.rb @@ -0,0 +1,52 @@ +require File.dirname(__FILE__) + '/../../abstract_unit' + +class AttrInternalTest < Test::Unit::TestCase + def setup + @target = Class.new + @instance = @target.new + end + + def test_attr_internal_reader + assert_nothing_raised { @target.attr_internal_reader :foo } + + assert !@instance.instance_variables.include?('@_foo') + assert_raise(NoMethodError) { @instance.foo = 1 } + + @instance.instance_variable_set('@_foo', 1) + assert_nothing_raised { assert_equal 1, @instance.foo } + end + + def test_attr_internal_writer + assert_nothing_raised { @target.attr_internal_writer :foo } + + assert !@instance.instance_variables.include?('@_foo') + assert_nothing_raised { assert_equal 1, @instance.foo = 1 } + + assert_equal 1, @instance.instance_variable_get('@_foo') + assert_raise(NoMethodError) { @instance.foo } + end + + def test_attr_internal_accessor + assert_nothing_raised { @target.attr_internal :foo } + + assert !@instance.instance_variables.include?('@_foo') + assert_nothing_raised { assert_equal 1, @instance.foo = 1 } + + assert_equal 1, @instance.instance_variable_get('@_foo') + assert_nothing_raised { assert_equal 1, @instance.foo } + end + + def test_attr_internal_naming_format + assert_equal '@_%s', @target.attr_internal_naming_format + assert_nothing_raised { @target.attr_internal_naming_format = '@abc%sdef' } + @target.attr_internal :foo + + assert !@instance.instance_variables.include?('@_foo') + assert !@instance.instance_variables.include?('@abcfoodef') + assert_nothing_raised { @instance.foo = 1 } + assert !@instance.instance_variables.include?('@_foo') + assert @instance.instance_variables.include?('@abcfoodef') + ensure + @target.attr_internal_naming_format = '@_%s' + end +end diff --git a/vendor/rails/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/vendor/rails/activesupport/test/core_ext/module/attribute_aliasing_test.rb new file mode 100644 index 0000000..66ddbfe --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/module/attribute_aliasing_test.rb @@ -0,0 +1,31 @@ +require File.dirname(__FILE__) + '/../../abstract_unit' + +module AttributeAliasing + class Content + attr_accessor :title + + def title? + !title.nil? + end + end + + class Email < Content + alias_attribute :subject, :title + end +end + +class AttributeAliasingTest < Test::Unit::TestCase + def test_attribute_alias + e = AttributeAliasing::Email.new + + assert !e.subject? + + e.title = "Upgrade computer" + assert_equal "Upgrade computer", e.subject + assert e.subject? + + e.subject = "We got a long way to go" + assert_equal "We got a long way to go", e.title + assert e.title? + end +end diff --git a/vendor/rails/activesupport/test/core_ext/module_test.rb b/vendor/rails/activesupport/test/core_ext/module_test.rb new file mode 100644 index 0000000..cdcb511 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/module_test.rb @@ -0,0 +1,178 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +module One +end + +class Ab + include One +end + +module Xy + class Bc + include One + end +end + +module Yz + module Zy + class Cd + include One + end + end +end + +class De +end + +Somewhere = Struct.new(:street, :city) + +Someone = Struct.new(:name, :place) do + delegate :street, :city, :to => :place + delegate :state, :to => :@place + delegate :upcase, :to => "place.city" +end + +class Name + delegate :upcase, :to => :@full_name + + def initialize(first, last) + @full_name = "#{first} #{last}" + end +end + +$nowhere = <<-EOF +class Name + delegate :nowhere +end +EOF + +$noplace = <<-EOF +class Name + delegate :noplace, :tos => :hollywood +end +EOF + +class ModuleTest < Test::Unit::TestCase + def test_included_in_classes + assert One.included_in_classes.include?(Ab) + assert One.included_in_classes.include?(Xy::Bc) + assert One.included_in_classes.include?(Yz::Zy::Cd) + assert !One.included_in_classes.include?(De) + end + + def test_delegation_to_methods + david = Someone.new("David", Somewhere.new("Paulina", "Chicago")) + assert_equal "Paulina", david.street + assert_equal "Chicago", david.city + end + + def test_delegation_down_hierarchy + david = Someone.new("David", Somewhere.new("Paulina", "Chicago")) + assert_equal "CHICAGO", david.upcase + end + + def test_delegation_to_instance_variable + david = Name.new("David", "Hansson") + assert_equal "DAVID HANSSON", david.upcase + end + + def test_missing_delegation_target + assert_raises(ArgumentError) { eval($nowhere) } + assert_raises(ArgumentError) { eval($noplace) } + end + + def test_parent + assert_equal Yz::Zy, Yz::Zy::Cd.parent + assert_equal Yz, Yz::Zy.parent + assert_equal Object, Yz.parent + end + + def test_parents + assert_equal [Yz::Zy, Yz, Object], Yz::Zy::Cd.parents + assert_equal [Yz, Object], Yz::Zy.parents + end + + def test_as_load_path + assert_equal 'yz/zy', Yz::Zy.as_load_path + assert_equal 'yz', Yz.as_load_path + end +end + +module BarMethodAliaser + def self.included(foo_class) + foo_class.alias_method_chain :bar, :baz + end + + def bar_with_baz + bar_without_baz << '_with_baz' + end + + def quux_with_baz! + quux_without_baz! << '_with_baz!' + end + + def quux_with_baz? + false + end +end + +class MethodAliasingTest < Test::Unit::TestCase + def setup + Object.const_set(:FooClassWithBarMethod, Class.new) + FooClassWithBarMethod.send(:define_method, 'bar', Proc.new { 'bar' }) + @instance = FooClassWithBarMethod.new + end + + def teardown + Object.send(:remove_const, :FooClassWithBarMethod) + end + + def test_alias_method_chain + assert @instance.respond_to?(:bar) + feature_aliases = [:bar_with_baz, :bar_without_baz] + + feature_aliases.each do |method| + assert !@instance.respond_to?(method) + end + + assert_equal 'bar', @instance.bar + + FooClassWithBarMethod.send(:include, BarMethodAliaser) + + feature_aliases.each do |method| + assert @instance.respond_to?(method) + end + + assert_equal 'bar_with_baz', @instance.bar + assert_equal 'bar', @instance.bar_without_baz + end + + def test_alias_method_chain_with_punctuation_method + FooClassWithBarMethod.send(:define_method, 'quux!', Proc.new { 'quux' }) + assert !@instance.respond_to?(:quux_with_baz!) + FooClassWithBarMethod.send(:include, BarMethodAliaser) + FooClassWithBarMethod.alias_method_chain :quux!, :baz + assert @instance.respond_to?(:quux_with_baz!) + + assert_equal 'quux_with_baz!', @instance.quux! + assert_equal 'quux', @instance.quux_without_baz! + end + + def test_alias_method_chain_with_same_names_between_predicates_and_bang_methods + FooClassWithBarMethod.send(:define_method, 'quux!', Proc.new { 'quux' }) + FooClassWithBarMethod.send(:define_method, 'quux?', Proc.new { true }) + assert !@instance.respond_to?(:quux_with_baz!) + assert !@instance.respond_to?(:quux_with_baz?) + + FooClassWithBarMethod.send(:include, BarMethodAliaser) + FooClassWithBarMethod.alias_method_chain :quux!, :baz + FooClassWithBarMethod.alias_method_chain :quux?, :baz + + assert @instance.respond_to?(:quux_with_baz!) + assert @instance.respond_to?(:quux_with_baz?) + assert_equal 'quux_with_baz!', @instance.quux! + assert_equal 'quux', @instance.quux_without_baz! + assert_equal false, @instance.quux? + assert_equal true, @instance.quux_without_baz? + end +end diff --git a/vendor/rails/activesupport/test/core_ext/name_error_test.rb b/vendor/rails/activesupport/test/core_ext/name_error_test.rb new file mode 100644 index 0000000..e49b88e --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/name_error_test.rb @@ -0,0 +1,27 @@ +require 'test/unit' +require File.dirname(__FILE__) + '/../../lib/active_support/core_ext/name_error' + +class NameErrorTest < Test::Unit::TestCase + + def test_name_error_should_set_missing_name + begin + SomeNameThatNobodyWillUse____Really ? 1 : 0 + flunk "?!?!" + rescue NameError => exc + assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name + assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really) + assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really") + end + end + + def test_missing_method_should_ignore_missing_name + begin + some_method_that_does_not_exist + flunk "?!?!" + rescue NameError => exc + assert_equal nil, exc.missing_name + assert ! exc.missing_name?(:Foo) + end + end + +end diff --git a/vendor/rails/activesupport/test/core_ext/numeric_ext_test.rb b/vendor/rails/activesupport/test/core_ext/numeric_ext_test.rb new file mode 100644 index 0000000..1d04cf7 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/numeric_ext_test.rb @@ -0,0 +1,57 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class NumericExtTimeTest < Test::Unit::TestCase + def setup + @now = Time.now + @seconds = { + 1.minute => 60, + 10.minutes => 600, + 1.hour + 15.minutes => 4500, + 2.days + 4.hours + 30.minutes => 189000, + 5.years + 1.month + 1.fortnight => 161589600 + } + end + + def test_units + @seconds.each do |actual, expected| + assert_equal expected, actual + end + end + + def test_intervals + @seconds.values.each do |seconds| + assert_equal seconds.since(@now), @now + seconds + assert_equal seconds.until(@now), @now - seconds + end + end + + # Test intervals based from Time.now + def test_now + @seconds.values.each do |seconds| + now = Time.now + assert seconds.ago >= now - seconds + now = Time.now + assert seconds.from_now >= now + seconds + end + end +end + +class NumericExtSizeTest < Test::Unit::TestCase + def test_unit_in_terms_of_another + relationships = { + 1024.kilobytes => 1.megabyte, + 3584.0.kilobytes => 3.5.megabytes, + 3584.0.megabytes => 3.5.gigabytes, + 1.kilobyte ** 4 => 1.terabyte, + 1024.kilobytes + 2.megabytes => 3.megabytes, + 2.gigabytes / 4 => 512.megabytes, + 256.megabytes * 20 + 5.gigabytes => 10.gigabytes, + 1.kilobyte ** 5 => 1.petabyte, + 1.kilobyte ** 6 => 1.exabyte + } + + relationships.each do |left, right| + assert_equal right, left + end + end +end diff --git a/vendor/rails/activesupport/test/core_ext/object_and_class_ext_test.rb b/vendor/rails/activesupport/test/core_ext/object_and_class_ext_test.rb new file mode 100644 index 0000000..73121ca --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -0,0 +1,151 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class ClassA; end +class ClassB < ClassA; end +class ClassC < ClassB; end +class ClassD < ClassA; end + +class ClassI; end +class ClassJ < ClassI; end + +class ClassK +end +module Nested + class ClassL < ClassK + end +end + +module Bar + def bar; end +end + +module Baz + def baz; end +end + +class Foo + include Bar +end + +class ClassExtTest < Test::Unit::TestCase + def test_methods + assert defined?(ClassB) + assert defined?(ClassC) + assert defined?(ClassD) + + ClassA.remove_subclasses + + assert !defined?(ClassB) + assert !defined?(ClassC) + assert !defined?(ClassD) + end + + def test_subclasses_of + assert_equal [ClassJ], Object.subclasses_of(ClassI) + ClassI.remove_subclasses + assert_equal [], Object.subclasses_of(ClassI) + end + + def test_subclasses_of_should_find_nested_classes + assert Object.subclasses_of(ClassK).include?(Nested::ClassL) + end + + def test_subclasses_of_should_not_return_removed_classes + # First create the removed class + old_class = Nested.send :remove_const, :ClassL + new_class = Class.new(ClassK) + Nested.const_set :ClassL, new_class + assert_equal "Nested::ClassL", new_class.name # Sanity check + + subclasses = Object.subclasses_of(ClassK) + assert subclasses.include?(new_class) + assert ! subclasses.include?(old_class) + end +end + +class ObjectTests < Test::Unit::TestCase + def test_suppress_re_raises + assert_raises(LoadError) { suppress(ArgumentError) {raise LoadError} } + end + def test_suppress_supresses + suppress(ArgumentError) { raise ArgumentError } + suppress(LoadError) { raise LoadError } + suppress(LoadError, ArgumentError) { raise LoadError } + suppress(LoadError, ArgumentError) { raise ArgumentError } + end + + def test_extended_by + foo = Foo.new + assert foo.extended_by.include?(Bar) + foo.extend(Baz) + assert ([Bar, Baz] - foo.extended_by).empty?, "Expected Bar, Baz in #{foo.extended_by.inspect}" + end + + def test_extend_with_included_modules_from + foo, object = Foo.new, Object.new + assert !object.respond_to?(:bar) + assert !object.respond_to?(:baz) + + object.extend_with_included_modules_from(foo) + assert object.respond_to?(:bar) + assert !object.respond_to?(:baz) + + foo.extend(Baz) + object.extend_with_included_modules_from(foo) + assert object.respond_to?(:bar) + assert object.respond_to?(:baz) + end + +end + +class ObjectInstanceVariableTest < Test::Unit::TestCase + def setup + @source, @dest = Object.new, Object.new + @source.instance_variable_set(:@bar, 'bar') + @source.instance_variable_set(:@baz, 'baz') + end + + def test_copy_instance_variables_from_without_explicit_excludes + assert_equal [], @dest.instance_variables + @dest.copy_instance_variables_from(@source) + + assert_equal %w(@bar @baz), @dest.instance_variables.sort + %w(@bar @baz).each do |name| + assert_equal @source.instance_variable_get(name).object_id, + @dest.instance_variable_get(name).object_id + end + end + + def test_copy_instance_variables_from_with_explicit_excludes + @dest.copy_instance_variables_from(@source, ['@baz']) + assert !@dest.instance_variables.include?('@baz') + assert_equal 'bar', @dest.instance_variable_get('@bar') + end + + def test_copy_instance_variables_automatically_excludes_protected_instance_variables + @source.instance_variable_set(:@quux, 'quux') + class << @source + def protected_instance_variables + ['@bar', :@quux] + end + end + + @dest.copy_instance_variables_from(@source) + assert !@dest.instance_variables.include?('@bar') + assert !@dest.instance_variables.include?('@quux') + assert_equal 'baz', @dest.instance_variable_get('@baz') + end + + def test_instance_values + object = Object.new + object.instance_variable_set :@a, 1 + object.instance_variable_set :@b, 2 + assert_equal({'a' => 1, 'b' => 2}, object.instance_values) + end + + def test_instance_exec_passes_arguments_to_block + block = Proc.new { |value| [self, value] } + assert_equal %w(hello goodbye), 'hello'.instance_exec('goodbye', &block) + end + +end diff --git a/vendor/rails/activesupport/test/core_ext/pathname_test.rb b/vendor/rails/activesupport/test/core_ext/pathname_test.rb new file mode 100644 index 0000000..2933e2d --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/pathname_test.rb @@ -0,0 +1,9 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class TestPathname < Test::Unit::TestCase + def test_clean_within + assert_equal "Hi", Pathname.clean_within("Hi") + assert_equal "Hi", Pathname.clean_within("Hi/a/b/../..") + assert_equal "Hello\nWorld", Pathname.clean_within("Hello/a/b/../..\na/b/../../World/c/..") + end +end diff --git a/vendor/rails/activesupport/test/core_ext/proc_test.rb b/vendor/rails/activesupport/test/core_ext/proc_test.rb new file mode 100644 index 0000000..f63cd82 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/proc_test.rb @@ -0,0 +1,11 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class ProcTests < Test::Unit::TestCase + def test_bind_returns_method_with_changed_self + block = Proc.new { self } + assert_equal self, block.call + bound_block = block.bind("hello") + assert_not_equal block, bound_block + assert_equal "hello", bound_block.call + end +end diff --git a/vendor/rails/activesupport/test/core_ext/range_ext_test.rb b/vendor/rails/activesupport/test/core_ext/range_ext_test.rb new file mode 100644 index 0000000..e8890ac --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/range_ext_test.rb @@ -0,0 +1,13 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class RangeTest < Test::Unit::TestCase + def test_to_s_from_dates + date_range = Date.new(2005, 12, 10)..Date.new(2005, 12, 12) + assert_equal "BETWEEN '2005-12-10' AND '2005-12-12'", date_range.to_s(:db) + end + + def test_to_s_from_times + date_range = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30) + assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db) + end +end diff --git a/vendor/rails/activesupport/test/core_ext/string_ext_test.rb b/vendor/rails/activesupport/test/core_ext/string_ext_test.rb new file mode 100644 index 0000000..c7c4d1a --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/string_ext_test.rb @@ -0,0 +1,106 @@ +require 'date' +require File.dirname(__FILE__) + '/../abstract_unit' + +class StringInflectionsTest < Test::Unit::TestCase + def test_pluralize + InflectorTest::SingularToPlural.each do |singular, plural| + assert_equal(plural, singular.pluralize) + end + + assert_equal("plurals", "plurals".pluralize) + end + + def test_singularize + InflectorTest::SingularToPlural.each do |singular, plural| + assert_equal(singular, plural.singularize) + end + end + + def test_camelize + InflectorTest::CamelToUnderscore.each do |camel, underscore| + assert_equal(camel, underscore.camelize) + end + end + + def test_underscore + InflectorTest::CamelToUnderscore.each do |camel, underscore| + assert_equal(underscore, camel.underscore) + end + + assert_equal "html_tidy", "HTMLTidy".underscore + assert_equal "html_tidy_generator", "HTMLTidyGenerator".underscore + end + + def test_demodulize + assert_equal "Account", Inflector.demodulize("MyApplication::Billing::Account") + end + + def test_foreign_key + InflectorTest::ClassNameToForeignKeyWithUnderscore.each do |klass, foreign_key| + assert_equal(foreign_key, klass.foreign_key) + end + + InflectorTest::ClassNameToForeignKeyWithoutUnderscore.each do |klass, foreign_key| + assert_equal(foreign_key, klass.foreign_key(false)) + end + end + + def test_tableize + InflectorTest::ClassNameToTableName.each do |class_name, table_name| + assert_equal(table_name, class_name.tableize) + end + end + + def test_classify + InflectorTest::ClassNameToTableName.each do |class_name, table_name| + assert_equal(class_name, table_name.classify) + end + end + + def test_string_to_time + assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time + assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local) + assert_equal Date.new(2005, 2, 27), "2005-02-27".to_date + end + + def test_access + s = "hello" + assert_equal "h", s.at(0) + + assert_equal "llo", s.from(2) + assert_equal "hel", s.to(2) + + assert_equal "h", s.first + assert_equal "he", s.first(2) + + assert_equal "o", s.last + assert_equal "llo", s.last(3) + + assert_equal 'x', 'x'.first + assert_equal 'x', 'x'.first(4) + + assert_equal 'x', 'x'.last + assert_equal 'x', 'x'.last(4) + end + + def test_starts_ends_with + s = "hello" + assert s.starts_with?('h') + assert s.starts_with?('hel') + assert !s.starts_with?('el') + + assert s.ends_with?('o') + assert s.ends_with?('lo') + assert !s.ends_with?('el') + end + + def test_each_char_with_utf8_string_when_kcode_is_utf8 + old_kcode, $KCODE = $KCODE, 'UTF8' + '€2.99'.each_char do |char| + assert_not_equal 1, char.length + break + end + ensure + $KCODE = old_kcode + end +end diff --git a/vendor/rails/activesupport/test/core_ext/symbol_test.rb b/vendor/rails/activesupport/test/core_ext/symbol_test.rb new file mode 100644 index 0000000..9431334 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/symbol_test.rb @@ -0,0 +1,9 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class SymbolTests < Test::Unit::TestCase + def test_to_proc + assert_equal %w(one two three), [:one, :two, :three].map(&:to_s) + assert_equal(%w(one two three), + {1 => "one", 2 => "two", 3 => "three"}.sort_by(&:first).map(&:last)) + end +end diff --git a/vendor/rails/activesupport/test/core_ext/time_ext_test.rb b/vendor/rails/activesupport/test/core_ext/time_ext_test.rb new file mode 100644 index 0000000..ab76348 --- /dev/null +++ b/vendor/rails/activesupport/test/core_ext/time_ext_test.rb @@ -0,0 +1,207 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class TimeExtCalculationsTest < Test::Unit::TestCase + def test_seconds_since_midnight + assert_equal 1,Time.local(2005,1,1,0,0,1).seconds_since_midnight + assert_equal 60,Time.local(2005,1,1,0,1,0).seconds_since_midnight + assert_equal 3660,Time.local(2005,1,1,1,1,0).seconds_since_midnight + assert_equal 86399,Time.local(2005,1,1,23,59,59).seconds_since_midnight + assert_equal 60.00001,Time.local(2005,1,1,0,1,0,10).seconds_since_midnight + end + + def test_begining_of_week + assert_equal Time.local(2005,1,31), Time.local(2005,2,4,10,10,10).beginning_of_week + assert_equal Time.local(2005,11,28), Time.local(2005,11,28,0,0,0).beginning_of_week #monday + assert_equal Time.local(2005,11,28), Time.local(2005,11,29,0,0,0).beginning_of_week #tuesday + assert_equal Time.local(2005,11,28), Time.local(2005,11,30,0,0,0).beginning_of_week #wednesday + assert_equal Time.local(2005,11,28), Time.local(2005,12,01,0,0,0).beginning_of_week #thursday + assert_equal Time.local(2005,11,28), Time.local(2005,12,02,0,0,0).beginning_of_week #friday + assert_equal Time.local(2005,11,28), Time.local(2005,12,03,0,0,0).beginning_of_week #saturday + assert_equal Time.local(2005,11,28), Time.local(2005,12,04,0,0,0).beginning_of_week #sunday + end + + def test_beginning_of_day + assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day + end + + def test_beginning_of_month + assert_equal Time.local(2005,2,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_month + end + + def test_beginning_of_quarter + assert_equal Time.local(2005,1,1,0,0,0), Time.local(2005,2,15,10,10,10).beginning_of_quarter + assert_equal Time.local(2005,1,1,0,0,0), Time.local(2005,1,1,0,0,0).beginning_of_quarter + assert_equal Time.local(2005,10,1,0,0,0), Time.local(2005,12,31,10,10,10).beginning_of_quarter + assert_equal Time.local(2005,4,1,0,0,0), Time.local(2005,6,30,23,59,59).beginning_of_quarter + end + + def test_end_of_month + assert_equal Time.local(2005,3,31,0,0,0), Time.local(2005,3,20,10,10,10).end_of_month + assert_equal Time.local(2005,2,28,0,0,0), Time.local(2005,2,20,10,10,10).end_of_month + assert_equal Time.local(2005,4,30,0,0,0), Time.local(2005,4,20,10,10,10).end_of_month + + end + + def test_beginning_of_year + assert_equal Time.local(2005,1,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_year + end + + def test_months_ago + assert_equal Time.local(2005,5,5,10), Time.local(2005,6,5,10,0,0).months_ago(1) + assert_equal Time.local(2004,11,5,10), Time.local(2005,6,5,10,0,0).months_ago(7) + assert_equal Time.local(2004,12,5,10), Time.local(2005,6,5,10,0,0).months_ago(6) + assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).months_ago(12) + assert_equal Time.local(2003,6,5,10), Time.local(2005,6,5,10,0,0).months_ago(24) + end + + def test_months_since + assert_equal Time.local(2005,7,5,10), Time.local(2005,6,5,10,0,0).months_since(1) + assert_equal Time.local(2006,1,5,10), Time.local(2005,12,5,10,0,0).months_since(1) + assert_equal Time.local(2005,12,5,10), Time.local(2005,6,5,10,0,0).months_since(6) + assert_equal Time.local(2006,6,5,10), Time.local(2005,12,5,10,0,0).months_since(6) + assert_equal Time.local(2006,1,5,10), Time.local(2005,6,5,10,0,0).months_since(7) + assert_equal Time.local(2006,6,5,10), Time.local(2005,6,5,10,0,0).months_since(12) + assert_equal Time.local(2007,6,5,10), Time.local(2005,6,5,10,0,0).months_since(24) + end + + def test_years_ago + assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).years_ago(1) + assert_equal Time.local(1998,6,5,10), Time.local(2005,6,5,10,0,0).years_ago(7) + end + + def test_years_since + assert_equal Time.local(2006,6,5,10), Time.local(2005,6,5,10,0,0).years_since(1) + assert_equal Time.local(2012,6,5,10), Time.local(2005,6,5,10,0,0).years_since(7) + # Failure because of size limitations of numeric? + # assert_equal Time.local(2182,6,5,10), Time.local(2005,6,5,10,0,0).years_since(177) + end + + def test_last_year + assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year + end + + + def test_ago + assert_equal Time.local(2005,2,22,10,10,9), Time.local(2005,2,22,10,10,10).ago(1) + assert_equal Time.local(2005,2,22,9,10,10), Time.local(2005,2,22,10,10,10).ago(3600) + assert_equal Time.local(2005,2,20,10,10,10), Time.local(2005,2,22,10,10,10).ago(86400*2) + assert_equal Time.local(2005,2,20,9,9,45), Time.local(2005,2,22,10,10,10).ago(86400*2 + 3600 + 25) + end + + def test_since + assert_equal Time.local(2005,2,22,10,10,11), Time.local(2005,2,22,10,10,10).since(1) + assert_equal Time.local(2005,2,22,11,10,10), Time.local(2005,2,22,10,10,10).since(3600) + assert_equal Time.local(2005,2,24,10,10,10), Time.local(2005,2,22,10,10,10).since(86400*2) + assert_equal Time.local(2005,2,24,11,10,35), Time.local(2005,2,22,10,10,10).since(86400*2 + 3600 + 25) + end + + def test_yesterday + assert_equal Time.local(2005,2,21,10,10,10), Time.local(2005,2,22,10,10,10).yesterday + assert_equal Time.local(2005,2,28,10,10,10), Time.local(2005,3,2,10,10,10).yesterday.yesterday + end + + def test_tomorrow + assert_equal Time.local(2005,2,23,10,10,10), Time.local(2005,2,22,10,10,10).tomorrow + assert_equal Time.local(2005,3,2,10,10,10), Time.local(2005,2,28,10,10,10).tomorrow.tomorrow + end + + def test_change + assert_equal Time.local(2006,2,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:year => 2006) + assert_equal Time.local(2005,6,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:month => 6) + assert_equal Time.local(2012,9,22,15,15,10), Time.local(2005,2,22,15,15,10).change(:year => 2012, :month => 9) + assert_equal Time.local(2005,2,22,16), Time.local(2005,2,22,15,15,10).change(:hour => 16) + assert_equal Time.local(2005,2,22,16,45), Time.local(2005,2,22,15,15,10).change(:hour => 16, :min => 45) + assert_equal Time.local(2005,2,22,15,45), Time.local(2005,2,22,15,15,10).change(:min => 45) + + assert_equal Time.local(2005,1,2, 5, 0, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:hour => 5) + assert_equal Time.local(2005,1,2,11, 6, 0, 0), Time.local(2005,1,2,11,22,33,44).change(:min => 6) + assert_equal Time.local(2005,1,2,11,22, 7, 0), Time.local(2005,1,2,11,22,33,44).change(:sec => 7) + assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8) + end + + def test_utc_change + assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:year => 2006) + assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:month => 6) + assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).change(:year => 2012, :month => 9) + assert_equal Time.utc(2005,2,22,16), Time.utc(2005,2,22,15,15,10).change(:hour => 16) + assert_equal Time.utc(2005,2,22,16,45), Time.utc(2005,2,22,15,15,10).change(:hour => 16, :min => 45) + assert_equal Time.utc(2005,2,22,15,45), Time.utc(2005,2,22,15,15,10).change(:min => 45) + end + + def test_plus + assert_equal Time.local(2006,2,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 1) + assert_equal Time.local(2005,6,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:months => 4) + assert_equal Time.local(2012,9,28,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 7) + assert_equal Time.local(2013,10,3,15,15,10), Time.local(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5) + end + + def test_utc_plus + assert_equal Time.utc(2006,2,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 1) + assert_equal Time.utc(2005,6,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:months => 4) + assert_equal Time.utc(2012,9,22,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 7) + assert_equal Time.utc(2013,10,3,15,15,10), Time.utc(2005,2,22,15,15,10).advance(:years => 7, :months => 19, :days => 11) + end + + def test_next_week + assert_equal Time.local(2005,2,28), Time.local(2005,2,22,15,15,10).next_week + assert_equal Time.local(2005,2,29), Time.local(2005,2,22,15,15,10).next_week(:tuesday) + assert_equal Time.local(2005,3,4), Time.local(2005,2,22,15,15,10).next_week(:friday) + end + + def test_to_s + time = Time.local(2005, 2, 21, 17, 44, 30) + assert_equal "2005-02-21 17:44:30", time.to_s(:db) + assert_equal "21 Feb 17:44", time.to_s(:short) + assert_equal "February 21, 2005 17:44", time.to_s(:long) + + time = Time.utc(2005, 2, 21, 17, 44, 30) + assert_equal "Mon, 21 Feb 2005 17:44:30 +0000", time.to_s(:rfc822) + end + + def test_to_date + assert_equal Date.new(2005, 2, 21), Time.local(2005, 2, 21, 17, 44, 30).to_date + end + + def test_to_time + assert_equal Time.local(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30).to_time + end + + # NOTE: this test seems to fail (changeset 1958) only on certain platforms, + # like OSX, and FreeBSD 5.4. + def test_fp_inaccuracy_ticket_1836 + midnight = Time.local(2005, 2, 21, 0, 0, 0) + assert_equal midnight.midnight, (midnight + 1.hour + 0.000001).midnight + end + + def test_days_in_month + assert_equal 31, Time.days_in_month(1, 2005) + + assert_equal 28, Time.days_in_month(2, 2005) + assert_equal 29, Time.days_in_month(2, 2004) + assert_equal 29, Time.days_in_month(2, 2000) + assert_equal 28, Time.days_in_month(2, 1900) + + assert_equal 31, Time.days_in_month(3, 2005) + assert_equal 30, Time.days_in_month(4, 2005) + assert_equal 31, Time.days_in_month(5, 2005) + assert_equal 30, Time.days_in_month(6, 2005) + assert_equal 31, Time.days_in_month(7, 2005) + assert_equal 31, Time.days_in_month(8, 2005) + assert_equal 30, Time.days_in_month(9, 2005) + assert_equal 31, Time.days_in_month(10, 2005) + assert_equal 30, Time.days_in_month(11, 2005) + assert_equal 31, Time.days_in_month(12, 2005) + end + + def test_next_month_on_31st + assert_equal Time.local(2005, 9, 30), Time.local(2005, 8, 31).next_month + end + + def test_last_month_on_31st + assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month + end + + def test_xmlschema_is_available + assert_nothing_raised { Time.now.xmlschema } + end +end diff --git a/vendor/rails/activesupport/test/dependencies/check_warnings.rb b/vendor/rails/activesupport/test/dependencies/check_warnings.rb new file mode 100644 index 0000000..03c3dca --- /dev/null +++ b/vendor/rails/activesupport/test/dependencies/check_warnings.rb @@ -0,0 +1,2 @@ +$check_warnings_load_count += 1 +$checked_verbose = $VERBOSE diff --git a/vendor/rails/activesupport/test/dependencies/mutual_one.rb b/vendor/rails/activesupport/test/dependencies/mutual_one.rb new file mode 100644 index 0000000..576eb31 --- /dev/null +++ b/vendor/rails/activesupport/test/dependencies/mutual_one.rb @@ -0,0 +1,4 @@ +$mutual_dependencies_count += 1 +require_dependency 'mutual_two' +require_dependency 'mutual_two.rb' +require_dependency 'mutual_two' diff --git a/vendor/rails/activesupport/test/dependencies/mutual_two.rb b/vendor/rails/activesupport/test/dependencies/mutual_two.rb new file mode 100644 index 0000000..fdbc2dc --- /dev/null +++ b/vendor/rails/activesupport/test/dependencies/mutual_two.rb @@ -0,0 +1,4 @@ +$mutual_dependencies_count += 1 +require_dependency 'mutual_one.rb' +require_dependency 'mutual_one' +require_dependency 'mutual_one.rb' diff --git a/vendor/rails/activesupport/test/dependencies/raises_exception.rb b/vendor/rails/activesupport/test/dependencies/raises_exception.rb new file mode 100644 index 0000000..69750ee --- /dev/null +++ b/vendor/rails/activesupport/test/dependencies/raises_exception.rb @@ -0,0 +1,3 @@ +$raises_exception_load_count += 1 +raise 'Loading me failed, so do not add to loaded or history.' +$raises_exception_load_count += 1 diff --git a/vendor/rails/activesupport/test/dependencies/service_one.rb b/vendor/rails/activesupport/test/dependencies/service_one.rb new file mode 100644 index 0000000..f43bfea --- /dev/null +++ b/vendor/rails/activesupport/test/dependencies/service_one.rb @@ -0,0 +1,5 @@ +$loaded_service_one ||= 0 +$loaded_service_one += 1 + +class ServiceOne +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/dependencies/service_two.rb b/vendor/rails/activesupport/test/dependencies/service_two.rb new file mode 100644 index 0000000..5205a78 --- /dev/null +++ b/vendor/rails/activesupport/test/dependencies/service_two.rb @@ -0,0 +1,2 @@ +class ServiceTwo +end \ No newline at end of file diff --git a/vendor/rails/activesupport/test/dependencies_test.rb b/vendor/rails/activesupport/test/dependencies_test.rb new file mode 100644 index 0000000..f19542b --- /dev/null +++ b/vendor/rails/activesupport/test/dependencies_test.rb @@ -0,0 +1,185 @@ +require File.dirname(__FILE__) + '/abstract_unit' +#require 'dependencies' + +class DependenciesTest < Test::Unit::TestCase + def teardown + Dependencies.clear + end + + def with_loading(from_dir = nil) + prior_path = $LOAD_PATH.clone + $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/#{from_dir}" if from_dir + old_mechanism, Dependencies.mechanism = Dependencies.mechanism, :load + yield + ensure + $LOAD_PATH.clear + $LOAD_PATH.concat prior_path + Dependencies.mechanism = old_mechanism + end + + def test_tracking_loaded_files + require_dependency(File.dirname(__FILE__) + "/dependencies/service_one") + require_dependency(File.dirname(__FILE__) + "/dependencies/service_two") + assert_equal 2, Dependencies.loaded.size + end + + def test_tracking_identical_loaded_files + require_dependency(File.dirname(__FILE__) + "/dependencies/service_one") + require_dependency(File.dirname(__FILE__) + "/dependencies/service_one") + assert_equal 1, Dependencies.loaded.size + end + + def test_missing_dependency_raises_missing_source_file + assert_raises(MissingSourceFile) { require_dependency("missing_service") } + end + + def test_missing_association_raises_nothing + assert_nothing_raised { require_association("missing_model") } + end + + def test_dependency_which_raises_exception_isnt_added_to_loaded_set + with_loading do + filename = "#{File.dirname(__FILE__)}/dependencies/raises_exception" + $raises_exception_load_count = 0 + + 5.times do |count| + assert_raises(RuntimeError) { require_dependency filename } + assert_equal count + 1, $raises_exception_load_count + + assert !Dependencies.loaded.include?(filename) + assert !Dependencies.history.include?(filename) + end + end + end + + def test_warnings_should_be_enabled_on_first_load + with_loading do + old_warnings, Dependencies.warnings_on_first_load = Dependencies.warnings_on_first_load, true + + filename = "#{File.dirname(__FILE__)}/dependencies/check_warnings" + $check_warnings_load_count = 0 + + assert !Dependencies.loaded.include?(filename) + assert !Dependencies.history.include?(filename) + + silence_warnings { require_dependency filename } + assert_equal 1, $check_warnings_load_count + assert_equal true, $checked_verbose, 'On first load warnings should be enabled.' + + assert Dependencies.loaded.include?(filename) + Dependencies.clear + assert !Dependencies.loaded.include?(filename) + assert Dependencies.history.include?(filename) + + silence_warnings { require_dependency filename } + assert_equal 2, $check_warnings_load_count + assert_equal nil, $checked_verbose, 'After first load warnings should be left alone.' + + assert Dependencies.loaded.include?(filename) + Dependencies.clear + assert !Dependencies.loaded.include?(filename) + assert Dependencies.history.include?(filename) + + enable_warnings { require_dependency filename } + assert_equal 3, $check_warnings_load_count + assert_equal true, $checked_verbose, 'After first load warnings should be left alone.' + + assert Dependencies.loaded.include?(filename) + end + end + + def test_mutual_dependencies_dont_infinite_loop + with_loading 'dependencies' do + $mutual_dependencies_count = 0 + assert_nothing_raised { require_dependency 'mutual_one' } + assert_equal 2, $mutual_dependencies_count + + Dependencies.clear + + $mutual_dependencies_count = 0 + assert_nothing_raised { require_dependency 'mutual_two' } + assert_equal 2, $mutual_dependencies_count + end + end + + def test_as_load_path + assert_equal '', DependenciesTest.as_load_path + end + + def test_module_loading + with_loading 'autoloading_fixtures' do + assert_kind_of Module, A + assert_kind_of Class, A::B + assert_kind_of Class, A::C::D + assert_kind_of Class, A::C::E::F + end + end + + def test_non_existing_const_raises_name_error + with_loading 'autoloading_fixtures' do + assert_raises(NameError) { DoesNotExist } + assert_raises(NameError) { NoModule::DoesNotExist } + assert_raises(NameError) { A::DoesNotExist } + assert_raises(NameError) { A::B::DoesNotExist } + end + end + + def test_directories_should_manifest_as_modules + with_loading 'autoloading_fixtures' do + assert_kind_of Module, ModuleFolder + Object.send :remove_const, :ModuleFolder + end + end + + def test_nested_class_access + with_loading 'autoloading_fixtures' do + assert_kind_of Class, ModuleFolder::NestedClass + Object.send :remove_const, :ModuleFolder + end + end + + def test_nested_class_can_access_sibling + with_loading 'autoloading_fixtures' do + sibling = ModuleFolder::NestedClass.class_eval "NestedSibling" + assert defined?(ModuleFolder::NestedSibling) + assert_equal ModuleFolder::NestedSibling, sibling + Object.send :remove_const, :ModuleFolder + end + end + + def failing_test_access_thru_and_upwards_fails + with_loading 'autoloading_fixtures' do + assert ! defined?(ModuleFolder) + assert_raises(NameError) { ModuleFolder::Object } + assert_raises(NameError) { ModuleFolder::NestedClass::Object } + Object.send :remove_const, :ModuleFolder + end + end + + def test_non_existing_const_raises_name_error_with_fully_qualified_name + with_loading 'autoloading_fixtures' do + begin + A::DoesNotExist + flunk "No raise!!" + rescue NameError => e + assert_equal "uninitialized constant A::DoesNotExist", e.message + end + begin + A::B::DoesNotExist + flunk "No raise!!" + rescue NameError => e + assert_equal "uninitialized constant A::B::DoesNotExist", e.message + end + end + end + + def test_smart_name_error_strings + begin + Object.module_eval "ImaginaryObject" + flunk "No raise!!" + rescue NameError => e + assert e.message.include?("uninitialized constant ImaginaryObject") + end + end + +end diff --git a/vendor/rails/activesupport/test/deprecation_test.rb b/vendor/rails/activesupport/test/deprecation_test.rb new file mode 100644 index 0000000..472fdec --- /dev/null +++ b/vendor/rails/activesupport/test/deprecation_test.rb @@ -0,0 +1,74 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class Deprecatee + def initialize + @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request) + @_request = 'there we go' + end + def request; @_request end + def old_request; @request end + + def partially(foo = nil) + ActiveSupport::Deprecation.warn 'calling with foo=nil is out' if foo.nil? + end + + def not() 2 end + def none() 1 end + def one(a) a end + def multi(a,b,c) [a,b,c] end + deprecate :none, :one, :multi +end + + +class DeprecationTest < Test::Unit::TestCase + def setup + # Track the last warning. + @old_behavior = ActiveSupport::Deprecation.behavior + @last_message = nil + ActiveSupport::Deprecation.behavior = Proc.new { |message| @last_message = message } + + @dtc = Deprecatee.new + end + + def teardown + ActiveSupport::Deprecation.behavior = @old_behavior + end + + def test_inline_deprecation_warning + assert_deprecated(/foo=nil/) do + @dtc.partially + end + end + + def test_undeprecated + assert_not_deprecated do + assert_equal 2, @dtc.not + end + end + + def test_deprecate_class_method + assert_deprecated(/none is deprecated/) do + assert_equal 1, @dtc.none + end + + assert_deprecated(/one is deprecated/) do + assert_equal 1, @dtc.one(1) + end + + assert_deprecated(/multi is deprecated/) do + assert_equal [1,2,3], @dtc.multi(1,2,3) + end + end + + def test_nil_behavior_is_ignored + ActiveSupport::Deprecation.behavior = nil + assert_deprecated(/foo=nil/) { @dtc.partially } + end + + def test_deprecated_instance_variable_proxy + assert_not_deprecated { @dtc.request.size } + + assert_deprecated('@request.size') { assert_equal @dtc.request.size, @dtc.old_request.size } + assert_deprecated('@request.to_s') { assert_equal @dtc.request.to_s, @dtc.old_request.to_s } + end +end diff --git a/vendor/rails/activesupport/test/inflector_test.rb b/vendor/rails/activesupport/test/inflector_test.rb new file mode 100644 index 0000000..f8a665c --- /dev/null +++ b/vendor/rails/activesupport/test/inflector_test.rb @@ -0,0 +1,329 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +module Ace + module Base + class Case + end + end +end + +class InflectorTest < Test::Unit::TestCase + SingularToPlural = { + "search" => "searches", + "switch" => "switches", + "fix" => "fixes", + "box" => "boxes", + "process" => "processes", + "address" => "addresses", + "case" => "cases", + "stack" => "stacks", + "wish" => "wishes", + "fish" => "fish", + + "category" => "categories", + "query" => "queries", + "ability" => "abilities", + "agency" => "agencies", + "movie" => "movies", + + "archive" => "archives", + + "index" => "indices", + + "wife" => "wives", + "safe" => "saves", + "half" => "halves", + + "move" => "moves", + + "salesperson" => "salespeople", + "person" => "people", + + "spokesman" => "spokesmen", + "man" => "men", + "woman" => "women", + + "basis" => "bases", + "diagnosis" => "diagnoses", + + "datum" => "data", + "medium" => "media", + "analysis" => "analyses", + + "node_child" => "node_children", + "child" => "children", + + "experience" => "experiences", + "day" => "days", + + "comment" => "comments", + "foobar" => "foobars", + "newsletter" => "newsletters", + + "old_news" => "old_news", + "news" => "news", + + "series" => "series", + "species" => "species", + + "quiz" => "quizzes", + + "perspective" => "perspectives", + + "ox" => "oxen", + "photo" => "photos", + "buffalo" => "buffaloes", + "tomato" => "tomatoes", + "dwarf" => "dwarves", + "elf" => "elves", + "information" => "information", + "equipment" => "equipment", + "bus" => "buses", + "status" => "statuses", + "status_code" => "status_codes", + "mouse" => "mice", + + "louse" => "lice", + "house" => "houses", + "octopus" => "octopi", + "virus" => "viri", + "alias" => "aliases", + "portfolio" => "portfolios", + + "vertex" => "vertices", + "matrix" => "matrices", + + "axis" => "axes", + "testis" => "testes", + "crisis" => "crises", + + "rice" => "rice", + "shoe" => "shoes", + + "horse" => "horses", + "prize" => "prizes", + "edge" => "edges" + } + + CamelToUnderscore = { + "Product" => "product", + "SpecialGuest" => "special_guest", + "ApplicationController" => "application_controller", + "Area51Controller" => "area51_controller" + } + + UnderscoreToLowerCamel = { + "product" => "product", + "special_guest" => "specialGuest", + "application_controller" => "applicationController", + "area51_controller" => "area51Controller" + } + + CamelToUnderscoreWithoutReverse = { + "HTMLTidy" => "html_tidy", + "HTMLTidyGenerator" => "html_tidy_generator", + "FreeBSD" => "free_bsd", + "HTML" => "html", + } + + CamelWithModuleToUnderscoreWithSlash = { + "Admin::Product" => "admin/product", + "Users::Commission::Department" => "users/commission/department", + "UsersSection::CommissionDepartment" => "users_section/commission_department", + } + + ClassNameToForeignKeyWithUnderscore = { + "Person" => "person_id", + "MyApplication::Billing::Account" => "account_id" + } + + ClassNameToForeignKeyWithoutUnderscore = { + "Person" => "personid", + "MyApplication::Billing::Account" => "accountid" + } + + ClassNameToTableName = { + "PrimarySpokesman" => "primary_spokesmen", + "NodeChild" => "node_children" + } + + UnderscoreToHuman = { + "employee_salary" => "Employee salary", + "employee_id" => "Employee", + "underground" => "Underground" + } + + MixtureToTitleCase = { + 'active_record' => 'Active Record', + 'ActiveRecord' => 'Active Record', + 'action web service' => 'Action Web Service', + 'Action Web Service' => 'Action Web Service', + 'Action web service' => 'Action Web Service', + 'actionwebservice' => 'Actionwebservice', + 'Actionwebservice' => 'Actionwebservice' + } + + OrdinalNumbers = { + "0" => "0th", + "1" => "1st", + "2" => "2nd", + "3" => "3rd", + "4" => "4th", + "5" => "5th", + "6" => "6th", + "7" => "7th", + "8" => "8th", + "9" => "9th", + "10" => "10th", + "11" => "11th", + "12" => "12th", + "13" => "13th", + "14" => "14th", + "20" => "20th", + "21" => "21st", + "22" => "22nd", + "23" => "23rd", + "24" => "24th", + "100" => "100th", + "101" => "101st", + "102" => "102nd", + "103" => "103rd", + "104" => "104th", + "110" => "110th", + "1000" => "1000th", + "1001" => "1001st" + } + + UnderscoresToDashes = { + "street" => "street", + "street_address" => "street-address", + "person_street_address" => "person-street-address" + } + + def test_pluralize_plurals + assert_equal "plurals", Inflector.pluralize("plurals") + assert_equal "Plurals", Inflector.pluralize("Plurals") + end + + SingularToPlural.each do |singular, plural| + define_method "test_pluralize_#{singular}" do + assert_equal(plural, Inflector.pluralize(singular)) + assert_equal(plural.capitalize, Inflector.pluralize(singular.capitalize)) + end + end + + SingularToPlural.each do |singular, plural| + define_method "test_singularize_#{plural}" do + assert_equal(singular, Inflector.singularize(plural)) + assert_equal(singular.capitalize, Inflector.singularize(plural.capitalize)) + end + end + + MixtureToTitleCase.each do |before, title_cased| + define_method 'test_titlecase' do + assert_equal(title_cased, Inflector.titleize(before)) + end + end + + def test_camelize + CamelToUnderscore.each do |camel, underscore| + assert_equal(camel, Inflector.camelize(underscore)) + end + end + + def test_underscore + CamelToUnderscore.each do |camel, underscore| + assert_equal(underscore, Inflector.underscore(camel)) + end + CamelToUnderscoreWithoutReverse.each do |camel, underscore| + assert_equal(underscore, Inflector.underscore(camel)) + end + end + + def test_camelize_with_module + CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| + assert_equal(camel, Inflector.camelize(underscore)) + end + end + + def test_underscore_with_slashes + CamelWithModuleToUnderscoreWithSlash.each do |camel, underscore| + assert_equal(underscore, Inflector.underscore(camel)) + end + end + + def test_demodulize + assert_equal "Account", Inflector.demodulize("MyApplication::Billing::Account") + end + + def test_foreign_key + ClassNameToForeignKeyWithUnderscore.each do |klass, foreign_key| + assert_equal(foreign_key, Inflector.foreign_key(klass)) + end + + ClassNameToForeignKeyWithoutUnderscore.each do |klass, foreign_key| + assert_equal(foreign_key, Inflector.foreign_key(klass, false)) + end + end + + def test_tableize + ClassNameToTableName.each do |class_name, table_name| + assert_equal(table_name, Inflector.tableize(class_name)) + end + end + + def test_classify + ClassNameToTableName.each do |class_name, table_name| + assert_equal(class_name, Inflector.classify(table_name)) + end + end + + def test_classify_with_symbol + assert_nothing_raised do + assert_equal 'FooBar', Inflector.classify(:foo_bar) + end + end + + def test_humanize + UnderscoreToHuman.each do |underscore, human| + assert_equal(human, Inflector.humanize(underscore)) + end + end + + def test_constantize + assert_equal Ace::Base::Case, Inflector.constantize("Ace::Base::Case") + assert_equal Ace::Base::Case, Inflector.constantize("::Ace::Base::Case") + assert_equal InflectorTest, Inflector.constantize("InflectorTest") + assert_equal InflectorTest, Inflector.constantize("::InflectorTest") + assert_raises(NameError) { Inflector.constantize("UnknownClass") } + assert_raises(NameError) { Inflector.constantize("An invalid string") } + end + + def test_constantize_doesnt_look_in_parent + assert_raises(NameError) { Inflector.constantize("Ace::Base::InflectorTest") } + end + + def test_ordinal + OrdinalNumbers.each do |number, ordinalized| + assert_equal(ordinalized, Inflector.ordinalize(number)) + end + end + + def test_dasherize + UnderscoresToDashes.each do |underscored, dasherized| + assert_equal(dasherized, Inflector.dasherize(underscored)) + end + end + + def test_underscore_as_reverse_of_dasherize + UnderscoresToDashes.each do |underscored, dasherized| + assert_equal(underscored, Inflector.underscore(Inflector.dasherize(underscored))) + end + end + + def test_underscore_to_lower_camel + UnderscoreToLowerCamel.each do |underscored, lower_camel| + assert_equal(lower_camel, Inflector.camelize(underscored, false)) + end + end +end diff --git a/vendor/rails/activesupport/test/json.rb b/vendor/rails/activesupport/test/json.rb new file mode 100644 index 0000000..d9c3ceb --- /dev/null +++ b/vendor/rails/activesupport/test/json.rb @@ -0,0 +1,55 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class Foo + def initialize(a, b) + @a, @b = a, b + end +end + +class TestJSONEmitters < Test::Unit::TestCase + TrueTests = [[ true, %(true) ]] + FalseTests = [[ false, %(false) ]] + NilTests = [[ nil, %(null) ]] + NumericTests = [[ 1, %(1) ], + [ 2.5, %(2.5) ]] + + StringTests = [[ 'this is the string', %("this is the string") ], + [ 'a "string" with quotes', %("a \\"string\\" with quotes") ]] + + ArrayTests = [[ ['a', 'b', 'c'], %([\"a\", \"b\", \"c\"]) ], + [ [1, 'a', :b, nil, false], %([1, \"a\", \"b\", null, false]) ]] + + HashTests = [[ {:a => :b, :c => :d}, %({\"c\": \"d\", \"a\": \"b\"}) ]] + + SymbolTests = [[ :a, %("a") ], + [ :this, %("this") ], + [ :"a b", %("a b") ]] + + ObjectTests = [[ Foo.new(1, 2), %({\"a\": 1, \"b\": 2}) ]] + + VariableTests = [[ ActiveSupport::JSON::Variable.new('foo'), 'foo'], + [ ActiveSupport::JSON::Variable.new('alert("foo")'), 'alert("foo")']] + RegexpTests = [[ /^a/, '/^a/' ], /^\w{1,2}[a-z]+/ix, '/^\\w{1,2}[a-z]+/ix'] + + constants.grep(/Tests$/).each do |class_tests| + define_method("test_#{class_tests[0..-6].downcase}") do + self.class.const_get(class_tests).each do |pair| + assert_equal pair.last, pair.first.to_json + end + end + end + + def test_utf8_string_encoded_properly_when_kcode_is_utf8 + old_kcode, $KCODE = $KCODE, 'UTF8' + assert_equal '"\\u20ac2.99"', '€2.99'.to_json + assert_equal '"\\u270e\\u263a"', '✎âş'.to_json + ensure + $KCODE = old_kcode + end + + def test_exception_raised_when_encoding_circular_reference + a = [1] + a << a + assert_raises(ActiveSupport::JSON::CircularReferenceError) { a.to_json } + end +end diff --git a/vendor/rails/activesupport/test/option_merger_test.rb b/vendor/rails/activesupport/test/option_merger_test.rb new file mode 100644 index 0000000..db32d4f --- /dev/null +++ b/vendor/rails/activesupport/test/option_merger_test.rb @@ -0,0 +1,45 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class OptionMergerTest < Test::Unit::TestCase + def setup + @options = {:hello => 'world'} + end + + def test_method_with_options_merges_options_when_options_are_present + local_options = {:cool => true} + + with_options(@options) do |o| + assert_equal local_options, method_with_options(local_options) + assert_equal @options.merge(local_options), + o.method_with_options(local_options) + end + end + + def test_method_with_options_appends_options_when_options_are_missing + with_options(@options) do |o| + assert_equal Hash.new, method_with_options + assert_equal @options, o.method_with_options + end + end + + def test_method_with_options_allows_to_overwrite_options + local_options = {:hello => 'moon'} + assert_equal @options.keys, local_options.keys + + with_options(@options) do |o| + assert_equal local_options, method_with_options(local_options) + assert_equal @options.merge(local_options), + o.method_with_options(local_options) + assert_equal local_options, o.method_with_options(local_options) + end + with_options(local_options) do |o| + assert_equal local_options.merge(@options), + o.method_with_options(@options) + end + end + + private + def method_with_options(options = {}) + options + end +end diff --git a/vendor/rails/activesupport/test/ordered_options_test.rb b/vendor/rails/activesupport/test/ordered_options_test.rb new file mode 100644 index 0000000..903c730 --- /dev/null +++ b/vendor/rails/activesupport/test/ordered_options_test.rb @@ -0,0 +1,80 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class OrderedHashTest < Test::Unit::TestCase + def setup + @keys = %w( blue green red pink orange ) + @values = %w( 000099 009900 aa0000 cc0066 cc6633 ) + @ordered_hash = ActiveSupport::OrderedHash.new(@keys.zip(@values)) + end + + def test_order + assert_equal @keys, @ordered_hash.keys + assert_equal @values, @ordered_hash.values + end + + def test_access + assert @keys.zip(@values).all? { |k, v| @ordered_hash[k] == v } + end + + def test_assignment + key, value = 'purple', '5422a8' + + @ordered_hash[key] = value + assert_equal @keys.length + 1, @ordered_hash.length + assert_equal key, @ordered_hash.keys.last + assert_equal value, @ordered_hash.values.last + assert_equal value, @ordered_hash[key] + end +end + +class OrderedOptionsTest < Test::Unit::TestCase + def test_usage + a = OrderedOptions.new + + assert_nil a[:not_set] + + a[:allow_concurreny] = true + assert_equal 1, a.size + assert a[:allow_concurreny] + + a[:allow_concurreny] = false + assert_equal 1, a.size + assert !a[:allow_concurreny] + + a["else_where"] = 56 + assert_equal 2, a.size + assert_equal 56, a[:else_where] + end + + def test_looping + a = OrderedOptions.new + + a[:allow_concurreny] = true + a["else_where"] = 56 + + test = [[:allow_concurreny, true], [:else_where, 56]] + + a.each_with_index do |(key, value), index| + assert_equal test[index].first, key + assert_equal test[index].last, value + end + end + + def test_method_access + a = OrderedOptions.new + + assert_nil a.not_set + + a.allow_concurreny = true + assert_equal 1, a.size + assert a.allow_concurreny + + a.allow_concurreny = false + assert_equal 1, a.size + assert !a.allow_concurreny + + a.else_where = 56 + assert_equal 2, a.size + assert_equal 56, a.else_where + end +end diff --git a/vendor/rails/activesupport/test/reloadable_test.rb b/vendor/rails/activesupport/test/reloadable_test.rb new file mode 100644 index 0000000..c330394 --- /dev/null +++ b/vendor/rails/activesupport/test/reloadable_test.rb @@ -0,0 +1,81 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +module ReloadableTestSandbox + + class AReloadableClass + include Reloadable + end + class AReloadableClassWithSubclasses + include Reloadable + end + class AReloadableSubclass < AReloadableClassWithSubclasses + end + class ANonReloadableSubclass < AReloadableClassWithSubclasses + def self.reloadable? + false + end + end + class AClassWhichDefinesItsOwnReloadable + def self.reloadable? + 10 + end + include Reloadable + end + + class SubclassesReloadable + include Reloadable::Subclasses + end + class ASubclassOfSubclassesReloadable < SubclassesReloadable + end + + class AnOnlySubclassReloadableClassSubclassingAReloadableClass + include Reloadable::Subclasses + end + + class ASubclassofAOnlySubclassReloadableClassWhichWasSubclassingAReloadableClass < AnOnlySubclassReloadableClassSubclassingAReloadableClass + end +end + +class ReloadableTest < Test::Unit::TestCase + def test_classes_receive_reloadable + assert ReloadableTestSandbox::AReloadableClass.respond_to?(:reloadable?) + end + def test_classes_inherit_reloadable + assert ReloadableTestSandbox::AReloadableSubclass.respond_to?(:reloadable?) + end + def test_reloadable_is_not_overwritten_if_present + assert_equal 10, ReloadableTestSandbox::AClassWhichDefinesItsOwnReloadable.reloadable? + end + + def test_only_subclass_reloadable + assert ! ReloadableTestSandbox::SubclassesReloadable.reloadable? + assert ReloadableTestSandbox::ASubclassOfSubclassesReloadable.reloadable? + end + + def test_inside_hierarchy_only_subclass_reloadable + assert ! ReloadableTestSandbox::AnOnlySubclassReloadableClassSubclassingAReloadableClass.reloadable? + assert ReloadableTestSandbox::ASubclassofAOnlySubclassReloadableClassWhichWasSubclassingAReloadableClass.reloadable? + end + + def test_removable_classes + reloadables = %w( + AReloadableClass + AReloadableClassWithSubclasses + AReloadableSubclass + AClassWhichDefinesItsOwnReloadable + ASubclassOfSubclassesReloadable + ) + non_reloadables = %w( + ANonReloadableSubclass + SubclassesReloadable + ) + + results = Reloadable.reloadable_classes + reloadables.each do |name| + assert results.include?(ReloadableTestSandbox.const_get(name)), "Expected #{name} to be reloadable" + end + non_reloadables.each do |name| + assert ! results.include?(ReloadableTestSandbox.const_get(name)), "Expected #{name} NOT to be reloadable" + end + end +end diff --git a/vendor/rails/activesupport/test/time_zone_test.rb b/vendor/rails/activesupport/test/time_zone_test.rb new file mode 100644 index 0000000..3cdf558 --- /dev/null +++ b/vendor/rails/activesupport/test/time_zone_test.rb @@ -0,0 +1,91 @@ +require File.dirname(__FILE__) + '/abstract_unit' + +class TimeZoneTest < Test::Unit::TestCase + class MockTime + def self.now + Time.utc( 2004, 7, 25, 14, 49, 00 ) + end + + def self.local(*args) + Time.utc(*args) + end + end + + TimeZone::Time = MockTime + + def test_formatted_offset_positive + zone = TimeZone.create( "Test", 4200 ) + assert_equal "+01:10", zone.formatted_offset + end + + def test_formatted_offset_negative + zone = TimeZone.create( "Test", -4200 ) + assert_equal "-01:10", zone.formatted_offset + end + + def test_now + zone = TimeZone.create( "Test", 4200 ) + assert_equal Time.local(2004,7,25,15,59,00).to_a[0,6], zone.now.to_a[0,6] + end + + def test_today + zone = TimeZone.create( "Test", 43200 ) + assert_equal Date.new(2004,7,26), zone.today + end + + def test_adjust_negative + zone = TimeZone.create( "Test", -4200 ) + assert_equal Time.utc(2004,7,24,23,55,0), zone.adjust(Time.utc(2004,7,25,1,5,0)) + end + + def test_adjust_positive + zone = TimeZone.create( "Test", 4200 ) + assert_equal Time.utc(2004,7,26,1,5,0), zone.adjust(Time.utc(2004,7,25,23,55,0)) + end + + def test_unadjust + zone = TimeZone.create( "Test", 4200 ) + expect = Time.utc(2004,7,24,23,55,0).to_a[0,6] + actual = zone.unadjust(Time.utc(2004,7,25,1,5,0)).to_a[0,6] + assert_equal expect, actual + end + + def test_zone_compare + zone1 = TimeZone.create( "Test1", 4200 ) + zone2 = TimeZone.create( "Test1", 5600 ) + assert zone1 < zone2 + assert zone2 > zone1 + + zone1 = TimeZone.create( "Able", 10000 ) + zone2 = TimeZone.create( "Zone", 10000 ) + assert zone1 < zone2 + assert zone2 > zone1 + + zone1 = TimeZone.create( "Able", 10000 ) + assert zone1 == zone1 + end + + def test_to_s + zone = TimeZone.create( "Test", 4200 ) + assert_equal "(GMT+01:10) Test", zone.to_s + end + + def test_all_sorted + all = TimeZone.all + 1.upto( all.length-1 ) do |i| + assert all[i-1] < all[i] + end + end + + def test_index + assert_nil TimeZone["bogus"] + assert_not_nil TimeZone["Central Time (US & Canada)"] + end + + def test_new + a = TimeZone.new("Berlin") + b = TimeZone.new("Berlin") + assert_same a, b + assert_nil TimeZone.new("bogus") + end +end diff --git a/vendor/rails/activesupport/test/whiny_nil_test.rb b/vendor/rails/activesupport/test/whiny_nil_test.rb new file mode 100644 index 0000000..4483134 --- /dev/null +++ b/vendor/rails/activesupport/test/whiny_nil_test.rb @@ -0,0 +1,38 @@ +# Stub to enable testing without Active Record +module ActiveRecord + class Base + def save! + end + end +end + +require File.dirname(__FILE__) + '/abstract_unit' +require 'active_support/whiny_nil' + +class WhinyNilTest < Test::Unit::TestCase + def test_unchanged + nil.method_thats_not_in_whiners + rescue NoMethodError => nme + assert_match(/nil.method_thats_not_in_whiners/, nme.message) + end + + def test_active_record + nil.save! + rescue NoMethodError => nme + assert(!(nme.message =~ /nil:NilClass/)) + assert_match(/nil\.save!/, nme.message) + end + + def test_array + nil.each + rescue NoMethodError => nme + assert(!(nme.message =~ /nil:NilClass/)) + assert_match(/nil\.each/, nme.message) + end + + def test_id + nil.id + rescue RuntimeError => nme + assert(!(nme.message =~ /nil:NilClass/)) + end +end diff --git a/vendor/rails/railties/CHANGELOG b/vendor/rails/railties/CHANGELOG new file mode 100644 index 0000000..50daa2f --- /dev/null +++ b/vendor/rails/railties/CHANGELOG @@ -0,0 +1,1169 @@ +*SVN* + +* Fix Dispatcher.reset_application! so that AR subclasses are removed and Observers re-initialized *after* Reloadable classes are removed. Closes #5743. [Rick Olson] + +* Clarify usage of script/plugin source. Closes #5344. [james.adam@gmail.com] + +* Add Dispatcher.to_prepare and config.to_prepare to provide a pre-request hook. [Nicholas Seckar] + +* Tweak the Rails load order so observers are loaded after plugins, and reloaded in development mode. Closed #5279. [Rick Olson] + +* Added that you can change the web server port in config/lighttpd.conf from script/server --port/-p #5465 [mats@imediatec.co.uk] + +* script/performance/profiler compatibility with the new ruby-prof, including an option to choose the results printer. #5679 [shugo@ruby-lang.org] + +* Fixed the failsafe response so it uses either the current recognized controller or ApplicationController. [Rick Olson] + +* Make sure script/reaper only reaps dispatcher pids by default, and not the spawner's pid. [Jamis Buck] + +* Fix script/plugin about so it uses about.yml and not meta.yml. [James Adam] + +* Dispatcher processes rescued actions with the same controller that processed the request. #4625 [sd@notso.net] + +* rails -d frontbase to create a new project with a frontbase database.yml. #4945 [mlaster@metavillage.com] + +* Ensure the logger is initialized. #5629 [mike@clarkware.com] + +* Added Mongrel-spawning capabilities to script/process/spawner. Mongrel will be the default choice if installed, otherwise FCGI is tried [DHH]. Examples: + + spawner # starts instances on 8000, 8001, and 8002 using Mongrel if available + spawner fcgi # starts instances on 8000, 8001, and 8002 using FCGI + spawner mongrel -i 5 # starts instances on 8000, 8001, 8002, 8003, and 8004 using Mongrel + spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109 using Mongrel if available + spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to 9102 and attempts start them every 5 seconds + + Also note that script/process/reaper is Mongrel capable. So the combination of spawner and reaper is a built-in alternative to something like mongrel_cluster. + +* Update scaffolding functional tests to use :id => people(:first) instead of :id => 1. #5612 [evan@protest.net] + +* db:test:clone should remove existing tables before reloading the schema. #5607 [sveit@tradeharbor.com] + +* Fixed migration generation for class names like ACLController #5197 [brad@madriska.com] + +* Added show_source_list and show_call_stack to breakpoints to make it easier to get context #5476 [takiuchi@drecom.co.jp]. Examples: + + irb(#):002:0> show_source_list + 0001 class TopController < ApplicationController + 0002 def show + 0003-> breakpoint + 0004 end + 0005 + 0006 def index + 0007 end + 0008 + => "/path/to/rails/root/app/controllers/top_controller.rb" + + irb(#):004:0> show_call_stack 3 + vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint' + vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint' + app/controllers/top_controller.rb:3:in `show' + => "/path/to/rails/root/app/controllers/top_controller.rb:3" + +* Generate scaffold layout in subdirectory appropriate to its module nesting. #5511 [nils@alumni.rice.edu] + +* Mongrel: script/server tails the rails log like it does with lighttpd. Prefer mongrel over lighttpd. #5541 [mike@clarkware.com] + +* Don't assume Active Record is available. #5497 [bob@sporkmonger.com] + +* Mongrel: script/server works on Win32. #5499 [jeremydurham@gmail.com] + +* Remove opts.on { |options[:option_name] } style hash assignment. Closes #4440. [nicksieger@gmail.com] + +* Mongrel support for script/server. #5475 [jeremydurham@gmail.com] + +* Fix script/plugin so it doesn't barf on invalid URLs [Rick] + +* Fix plugin install bug at dir with space. (closes #5359) [Yoshimasa NIWA] + +* Fix bug with 'script/plugin install' so it reports unknown plugin names correctly. [Rick] + +* Added uninstall.rb hook to plugin handling, such that plugins have a way of removing assets and other artifacts on removal #5003 [takiuchi@drecom.co.jp] + +* Create temporary dirs relative to RAILS_ROOT when running script/server #5014 [elliot@townx.org] + +* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. [Jamis Buck] + +* Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson] + +* Added lib/ to the directories that will get application docs generated [DHH] + +* Add observer generator. Closes #5167. [francois.beausoleil@gmail.com] + +* Session migration generator obeys pluralize_table_names. #5145 [james.adam@gmail.com] + +* rake test:recent understands subdirectories. #2925 [jerrett@bravenet.com] + +* The app generator detects the XAMPP package's MySQL socket location. #3832 [elliot@townx.org] + +* The app generator sets a session key in application.rb so apps running on the same host may distinguish their cookies. #2967 [rcoder, rails-bug@owl.me.uk] + +* Distinguish the spawners for different processes [DHH] + +* Added -n/--process to script/process/spawner name the process pid (default is dispatch) [DHH] + +* Namespaced OrderedHash so the Rails implementation does not clash with any others. (fixes #4911) [Julian Tarkhanov] + +* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.] + +* Added script/process/inspector to do simple process status information on Rails dispatchers keeping pid files in tmp/pids [DHH] + +* Added pid file usage to script/process/spawner and script/process/reaper along with a directive in default config/lighttpd.conf file to record the pid. They will all save their pid file in tmp/pids [DHH] + + +*1.1.2* (April 9th, 2005) + +* Mention in docs that config.frameworks doesn't work when getting Rails via Gems. Closes #4857. [Alisdair McDiarmid] + +* Change the scaffolding layout to use yield rather than @content_for_layout. [Marcel Molina Jr.] + +* Added rake rails:update:configs to update config/boot.rb from the latest (also included in rake rails:update) [DHH] + +* Fixed that boot.rb would set RAILS_GEM_VERSION twice, not respect an uncommented RAILS_GEM_VERSION line, and not use require_gem [DHH] + + +*1.1.1* (April 6th, 2006) + +* Enhances plugin#discover allowing it to discover svn:// like URIs (closes #4565) [ruben.nine@gmail.com] + +* Update to Prototype 1.5.0_rc0 [Sam Stephenson] + +* Fixed that the -r/--ruby path option of the rails command was not being respected #4549 [ryan.raaum@gmail.com] + +* Added that Dispatcher exceptions should not be shown to the user unless a default log has not been configured. Instead show public/500.html [DHH] + +* Fixed that rake clone_structure_to_test should quit on pgsql if the dump is unsuccesful #4585 [augustz@augustz.com] + +* Fixed that rails --version should have the return code of 0 (success) #4560 [blair@orcaware.com] + +* Install alias so Rails::InfoController is accessible at /rails_info. Closes #4546. [Nicholas Seckar] + +* Fixed that spawner should daemonize if running in repeat mode [DHH] + +* Added TAG option for rake rails:freeze:edge, so you can say rake rails:freeze:edge TAG=rel_1-1-0 to lock to the 1.1.0 release [DHH] + +* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us [Sam Stephenson, Thomas Fuchs] + +* Use --simple-prompt instead of --prompt-mode simple for console compatibility with Windows/Ruby 1.8.2 #4532 [starr@starrnhorne.com] + +* Make Rails::VERSION implicitly loadable #4491. [Nicholas Seckar] + +* Fixed rake rails:freeze:gems #4518 [benji@silverinsanity.com] + +* Added -f/--freeze option to rails command for freezing the application to the Rails version it was generated with [DHH] + +* Added gem binding of apps generated through the rails command to the gems of they were generated with [Nicholas Seckar] + +* Added expiration settings for JavaScript, CSS, HTML, and images to default lighttpd.conf [DHH] + +* Added gzip compression for JavaScript, CSS, and HTML to default lighttpd.conf [DHH] + +* Avoid passing escapeHTML non-string in Rails' info controller [Nicholas Seckar] + + +*1.1.0* (March 27th, 2006) + +* Allow db:fixtures:load to load a subset of the applications fixtures. [Chad Fowler] + + ex. + + rake db:fixtures:load FIXTURES=customers,plans + +* Update to Prototype 1.5.0_pre1 [Sam Stephenson] + +* Update to script.aculo.us 1.6 [Thomas Fuchs] + +* Add an integration_test generator [Jamis Buck] + +* Make all ActionView helpers available in the console from the helper method for debugging purposes. n.b.: Only an 80% solution. Some stuff won't work, most will. [Marcel Molina Jr.] + + ex. + + >> puts helper.options_for_select([%w(a 1), %w(b 2), %w(c 3)]) + + + + => nil + +* Replaced old session rake tasks with db:sessions:create to generate a migration, and db:sessions:clear to remove sessions. [Rick Olson] + +* Reject Ruby 1.8.3 when loading Rails; extract version checking code. [Chad Fowler] + +* Remove explicit loading of RailsInfo and RailsInfoController. [Nicholas Seckar] + +* Move RailsInfo and RailsInfoController to Rails::Info and Rails::InfoController. [Nicholas Seckar] + +* Extend load path with Railties' builtin directory to make adding support code easy. [Nicholas Seckar] + +* Fix the rails_info controller by explicitly loading it, and marking it as not reloadable. [Nicholas Seckar] + +* Fixed rails:freeze:gems for Windows #3274 [paul@paulbutcher.com] + +* Added 'port open?' check to the spawner when running in repeat mode so we don't needlessly boot the dispatcher if the port is already in use anyway #4089 [guy.naor@famundo.com] + +* Add verification to generated scaffolds, don't allow get for unsafe actions [Michael Koziarski] + +* Don't replace application.js in public/javascripts if it already exists [Cody Fauser] + +* Change test:uncommitted to delay execution of `svn status` by using internal Rake API's. [Nicholas Seckar] + +* Use require_library_or_gem to load rake in commands/server.rb. Closes #4205. [rob.rasmussen@gmail.com] + +* Use the Rake API instead of shelling out to create the tmp directory in commands/server.rb. [Chad Fowler] + +* Added a backtrace to the evil WSOD (White Screen of Death). Closes #4073. TODO: Clearer exceptions [Rick Olson] + +* Added tracking of database and framework versions in script/about #4088 [charles.gerungan@gmail.com/Rick Olson] + +* Added public/javascripts/application.js as a sample since it'll automatically be included in javascript_include_tag :defaults [DHH] + +* Added socket cleanup for lighttpd, both before and after [DHH] + +* Added automatic creation of tmp/ when running script/server [DHH] + +* Added silence_stream that'll work on both STDERR or STDOUT or any other stream and deprecated silence_stderr in the process [DHH] + +* Added reload! method to script/console to reload all models and others that include Reloadable without quitting the console #4056 [esad@esse.at] + +* Added that rake rails:freeze:edge will now just export all the contents of the frameworks instead of just lib, so stuff like rails:update:scripts, rails:update:javascripts, and script/server on lighttpd still just works #4047 [DHH] + +* Added fix for upload problems with lighttpd from Safari/IE to config/lighttpd.conf #3999 [thijs@fngtps.com] + +* Added test:uncommitted to test changes since last checkin to Subversion #4035 [technomancy@gmail.com] + +* Help script/about print the correct svn revision when in a non-English locale. #4026 [babie7a0@ybb.ne.jp] + +* Add 'app' accessor to script/console as an instance of Integration::Session [Jamis Buck] + +* Generator::Base#usage takes an optional message argument which defaults to Generator::Base#usage_message. [Jeremy Kemper] + +* Remove the extraneous AR::Base.threaded_connections setting from the webrick server. [Jeremy Kemper] + +* Add integration test support to app generation and testing [Jamis Buck] + +* Added namespaces to all tasks, so for example load_fixtures is now db:fixtures:load. All the old task names are still valid, they just point to the new namespaced names. "rake -T" will only show the namespaced ones, though [DHH] + +* CHANGED DEFAULT: ActiveRecord::Base.schema_format is now :ruby by default instead of :sql. This means that we'll assume you want to live in the world of db/schema.rb where the grass is green and the girls are pretty. If your schema contains un-dumpable elements, such as constraints or database-specific column types, you just got an invitation to either 1) patch the dumper to include foreign key support, 2) stop being db specific, or 3) just change the default in config/environment.rb to config.active_record.schema_format = :sql -- we even include an example for that on new Rails skeletons now. Brought to you by the federation of opinionated framework builders! [DHH] + +* Added -r/--repeat option to script/process/spawner that offers the same loop protection as the spinner did. This deprecates the script/process/spinner, so it's no longer included in the default Rails skeleton, but still available for backwards compatibility #3461 [ror@andreas-s.net] + +* Added collision option to template generation in generators #3329 [anna@wota.jp]. Examples: + + m.template "stuff.config" , "config/stuff.config" , :collision => :skip + m.template "auto-stamping", "config/generator.log", :collision => :force + +* Added more information to script/plugin's doings to ease debugging #3755 [Rick Olson] + +* Changed the default configuration for lighttpd to use tmp/sockets instead of log/ for the FastCGI sockets [DHH] + +* Added a default configuration of the FileStore for fragment caching if tmp/cache is available, which makes action/fragment caching ready to use out of the box with no additional configuration [DHH] + +* Changed the default session configuration to place sessions in tmp/sessions, if that directory is available, instead of /tmp (this essentially means a goodbye to 9/10 White Screen of Death errors and should have web hosting firms around the world cheering) [DHH] + +* Added tmp/sessions, tmp/cache, and tmp/sockets as default directories in the Rails skeleton [DHH] + +* Added that script/generate model will now automatically create a migration file for the model created. This can be turned off by calling the generator with --skip-migration [DHH] + +* Added -d/--database option to the rails command, so you can do "rails --database=sqlite2 myapp" to start a new application preconfigured to use SQLite2 as the database. Removed the configuration examples from SQLite and PostgreSQL from the default MySQL configuration [DHH] + +* Allow script/server -c /path/to/lighttpd.conf [Jeremy Kemper] + +* Remove hardcoded path to reaper script in script/server [Jeremy Kemper] + +* Update script.aculo.us to V1.5.3 [Thomas Fuchs] + +* Added SIGTRAP signal handler to RailsFCGIHandler that'll force the process into a breakpoint after the next request. This breakpoint can then be caught with script/breakpointer and give you access to the Ruby image inside that process. Useful for debugging memory leaks among other things [DHH] + +* Changed default lighttpd.conf to use CWD from lighttpd 1.4.10 that allows the same configuration to be used for both detach and not. Also ensured that auto-repeaping of FCGIs only happens when lighttpd is not detached. [DHH] + +* Added Configuration#after_initialize for registering a block which gets called after the framework is fully initialized. Useful for things like per-environment configuration of plugins. [Michael Koziarski] + +* Added check for RAILS_FRAMEWORK_ROOT constant that allows the Rails framework to be found in a different place than vendor/rails. Should be set in boot.rb. [DHH] + +* Fixed that static requests could unlock the mutex guarding dynamic requests in the WEBrick servlet #3433 [tom@craz8.com] + +* Fixed documentation tasks to work with Rake 0.7.0 #3563 [kazuhiko@fdiary.net] + +* Update to Prototype 1.5.0_pre0 [Sam Stephenson] + +* Sort the list of plugins so we load in a consistent order [Rick Olson] + +* Show usage when script/plugin is called without arguments [tom@craz8.com] + +* Corrected problems with plugin loader where plugins set 'name' incorrectly #3297 [anna@wota.jp] + +* Make migration generator only report on exact duplicate names, not partial dupliate names. #3442 [jeremy@planetargon.com Marcel Molina Jr.] + +* Fix typo in mailer generator USAGE. #3458 [chriztian.steinmeier@gmail.com] + +* Ignore version mismatch between pg_dump and the database server. #3457 [simon.stapleton@gmail.com] + +* Reap FCGI processes after lighttpd exits. [Sam Stephenson] + +* Honor ActiveRecord::Base.pluralize_table_names when creating and destroying session store table. #3204. [rails@bencurtis.com, Marcel Molina Jr.] + +*1.0.0* (December 13th, 2005) + +* Update instructions on how to find and install generators. #3172. [Chad Fowler] + +* Generator looks in vendor/generators also. [Chad Fowler] + +* Generator copies files in binary mode. #3156 [minimudboy@gmail.com] + +* Add builtin/ to the gemspec. Closes #3047. [Nicholas Seckar, Sam Stephenson] + +* Add install.rb file to plugin generation which is loaded, if it exists, when you install a plugin. [Marcel Molina Jr.] + +* Run initialize_logger in script/lighttpd to ensure the log file exists before tailing it. [Sam Stephenson] + +* Make load_fixtures include csv fixtures. #3053. [me@mdaines.com] + +* Fix freeze_gems so that the latest rails version is dumped by default. [Nicholas Seckar] + +* script/plugin: handle root paths and plugin names which contain spaces. #2995 [justin@aspect.net] + +* Model generator: correct relative path to test_helper in unit test. [Jeremy Kemper] + +* Make the db_schema_dump task honor the SCHEMA environment variable if present the way db_schema_import does. #2931. [Blair Zajac] + +* Have the lighttpd server script report the actual ip to which the server is bound. #2903. [Adam] + +* Add plugin library directories to the load path after the lib directory so that libraries in the lib directory get precedence. #2910. [james.adam@gmail.com] + +* Make help for the console command more explicit about how to specify the desired environment in which to run the console. #2911. [anonymous] + +* PostgreSQL: the purge_test_database Rake task shouldn't explicitly specify the template0 template when creating a fresh test database. #2964 [dreamer3@gmail.com] + +* Introducing the session_migration generator. Creates an add_session_table migration. Allows generator to specify migrations directory. #2958, #2960 [Rick Olson] + +* script/console uses RAILS_ENV environment variable if present. #2932 [Blair Zajac + +* Windows: eliminate the socket option in database.yml. #2924 [Wayne Vucenic ] + +* Eliminate nil from newly generated logfiles. #2927 [Blair Zajac ] + +* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.] + +* Eliminate Subversion dependencies in scripts/plugin. Correct install options. Introduce --force option to reinstall a plugin. Remove useless --long option for list. Use --quiet to quiet the download output and --revision to update to a specific svn revision. #2842 [Chad Fowler, Rick Olson] + +* SQLite: the clone_structure_to_test and purge_test_database Rake tasks should always use the test environment. #2846 [Rick Olson] + +* Make sure that legacy db tasks also reference :database for SQLite #2830 [kazuhiko@fdiary.net] + +* Pass __FILE__ when evaluating plugins' init.rb. #2817 [james.adam@gmail.com] + +* Better svn status matching for generators. #2814 [François Beausoleil , Blair Zajac ] + +* Don't reload routes until plugins have been loaded so they have a chance to extend the routing capabilities [DHH] + +* Don't detach or fork for script/server tailing [Nicholas Seckar] + +* Changed all script/* to use #!/usr/bin/env ruby instead of hard-coded Ruby path. public/dispatcher.* still uses the hard-coded path for compatibility with web servers that don't have Ruby in path [DHH] + +* Force RAILS_ENV to be "test" when running tests, so that ENV["RAILS_ENV"] = "production" in config/environment.rb doesn't wreck havok [DHH] #2660 + +* Correct versioning in :freeze_gems Rake task. #2778 [jakob@mentalized.net, Jeremy Kemper] + +* Added an omnipresent RailsInfoController with a properties action that delivers an HTML rendering of Rails::Info (but only when local_request? is true). Added a new default index.html which fetches this with Ajax. [Sam Stephenson] + + +*0.14.3 (RC4)* (November 7th, 2005) + +* Add 'add_new_scripts' rake task for adding new rails scripts to script/* [Jamis Buck] + +* Remove bogus hyphen from script/process/reaper calls to 'ps'. #2767 [anonymous] + +* Copy lighttpd.conf when it is first needed, instead of on app creation [Jamis Buck] + +* Use require_library_or_gem 'fcgi' in script/server [Sam Stephenson] + +* Added default lighttpd config in config/lighttpd.conf and added a default runner for lighttpd in script/server (works like script/server, but using lighttpd and FastCGI). It will use lighttpd if available, otherwise WEBrick. You can force either or using 'script/server lighttpd' or 'script/server webrick' [DHH] + +* New configuration option config.plugin_paths which may be a single path like the default 'vendor/plugins' or an array of paths: ['vendor/plugins', 'lib/plugins']. [Jeremy Kemper] + +* Plugins are discovered in nested paths, so you can organize your plugins directory as you like. [Jeremy Kemper] + +* Refactor load_plugin from load_plugins. #2757 [alex.r.moon@gmail.com] + +* Make use of silence_stderr in script/lighttpd, script/plugin, and Rails::Info [Sam Stephenson] + +* Enable HTTP installation of plugins when svn isn't avaialable. Closes #2661. [Chad Fowler] + +* Added script/about to display formatted Rails::Info output [Sam Stephenson] + +* Added Rails::Info to catalog assorted information about a Rails application's environment [Sam Stephenson] + +* Tail the logfile when running script/server lighttpd in the foreground [Sam Stephenson] + +* Try to guess the port number from config/lighttpd.conf in script/server lighttpd [Sam Stephenson] + +* Don't reap spawn-fcgi. #2727 [matthew@walker.wattle.id.au] + +* Reaper knows how to find processes even if the dispatch path is very long. #2711 [matthew@walker.wattle.id.au] + +* Make fcgi handler respond to TERM signals with an explicit exit [Jamis Buck] + +* Added demonstration of fixture use to the test case generated by the model generator [DHH] + +* If specified, pass PostgreSQL client character encoding to createdb. #2703 [Kazuhiko ] + +* Catch CGI multipart parse errors. Wrap dispatcher internals in a failsafe response handler. [Jeremy Kemper] + +* The freeze_gems Rake task accepts the VERSION environment variable to decide which version of Rails to pull into vendor/rails. [Chad Fowler, Jeremy Kemper] + +* Removed script.aculo.us.js, builder.js and slider.js (preperation for move of scriptaculous extensions to plugins, core scriptaculous will remain in Railties) [Thomas Fuchs] + +* The freeze_edge Rake task does smarter svn detection and can export a specific revision by passing the REVISION environment variable. For example: rake freeze_edge REVISION=1234. #2663 [Rick Olson] + +* Comment database.yml and include PostgreSQL and SQLite examples. [Jeremy Kemper] + +* Improve script/plugin on Windows. #2646 [Chad Fowler] + +* The *_plugindoc Rake tasks look deeper into the plugins' lib directories. #2652 [bellis@deepthought.org] + +* The PostgreSQL :db_structure_dump Rake task limits its dump to the schema search path in database.yml. [Anatol Pomozov ] + +* Add task to generate rdoc for all installed plugins. [Marcel Molina] + +* Update script.aculo.us to V1.5_rc4 [Thomas Fuchs] + +* Add default Mac + DarwinPorts MySQL socket locations to the app generator. [Jeremy Kemper] + +* Migrations may be destroyed: script/destroy migration foo. #2635 [Charles M. Gerungan , Jamis Buck, Jeremy Kemper] + +* Added that plugins can carry generators and that generator stub files can be created along with new plugins using script/generate plugin --with-generator [DHH] + +* Removed app/apis as a default empty dir since its automatically created when using script/generate web_service [DHH] + +* Added script/plugin to manage plugins (install, remove, list, etc) [Ryan Tomayko] + +* Added test_plugins task: Run the plugin tests in vendor/plugins/**/test (or specify with PLUGIN=name) [DHH] + +* Added plugin generator to create a stub structure for a new plugin in vendor/plugins (see "script/generate plugin" for help) [DHH] + +* Fixed scaffold generator when started with only 1 parameter #2609 [self@mattmower.com] + +* rake should run functional tests even if the unit tests have failures [Jim Weirich] + +* Back off cleanpath to be symlink friendly. Closes #2533 [Nicholas Seckar] + +* Load rake task files in alphabetical order so you can build dependencies and count on them #2554 [Blair Zajac] + + +*0.14.2 (RC3)* (October 26th, 2005) + +* Constants set in the development/test/production environment file are set in Object + +* Scaffold generator pays attention to the controller name. #2562 [self@mattmower.com] + +* Include tasks from vendor/plugins/*/tasks in the Rakefile #2545 [Rick Olson] + + +*0.14.1 (RC2)* (October 19th, 2005) + +* Don't clean RAILS_ROOT on windows + +* Remove trailing '/' from RAILS_ROOT [Nicholas Seckar] + +* Upgraded to Active Record 1.12.1 and Action Pack 1.10.1 + + +*0.14.0 (RC1)* (October 16th, 2005) + +* Moved generator folder from RAILS_ROOT/generators to RAILS_ROOT/lib/generators [Tobias Luetke] + +* Fix rake dev and related commands [Nicholas Seckar] + +* The rails command tries to deduce your MySQL socket by running `mysql_config +--socket`. If it fails, default to /path/to/your/mysql.sock + +* Made the rails command use the application name for database names in the tailored database.yml file. Example: "rails ~/projects/blog" will use "blog_development" instead of "rails_development". [Florian Weber] + +* Added Rails framework freezing tasks: freeze_gems (freeze to current gems), freeze_edge (freeze to Rails SVN trunk), unfreeze_rails (float with newest gems on system) + +* Added update_javascripts task which will fetch all the latest js files from your current rails install. Use after updating rails. [Tobias Luetke] + +* Added cleaning of RAILS_ROOT to useless elements such as '../non-dot-dot/'. Provides cleaner backtraces and error messages. [Nicholas Seckar] + +* Made the instantiated/transactional fixtures settings be controlled through Rails::Initializer. Transactional and non-instantiated fixtures are default from now on. [Florian Weber] + +* Support using different database adapters for development and test with ActiveRecord::Base.schema_format = :ruby [Sam Stephenson] + +* Make webrick work with session(:off) + +* Add --version, -v option to the Rails command. Closes #1840. [stancell] + +* Update Prototype to V1.4.0_pre11, script.aculo.us to V1.5_rc3 [2504] and fix the rails generator to include the new .js files [Thomas Fuchs] + +* Make the generator skip a file if it already exists and is identical to the new file. + +* Add experimental plugin support #2335 + +* Made Rakefile aware of new .js files in script.aculo.us [Thomas Fuchs] + +* Make table_name and controller_name in generators honor AR::Base.pluralize_table_names. #1216 #2213 [kazuhiko@fdiary.net] + +* Clearly label functional and unit tests in rake stats output. #2297 [lasse.koskela@gmail.com] + +* Make the migration generator only check files ending in *.rb when calculating the next file name #2317 [Chad Fowler] + +* Added prevention of duplicate migrations from the generator #2240 [fbeausoleil@ftml.net] + +* Add db_schema_dump and db_schema_import rake tasks to work with the new ActiveRecord::SchemaDumper (for dumping a schema to and reading a schema from a ruby file). + +* Reformed all the config/environments/* files to conform to the new Rails::Configuration approach. Fully backwards compatible. + +* Added create_sessions_table, drop_sessions_table, and purge_sessions_table as rake tasks for databases that supports migrations (MySQL, PostgreSQL, SQLite) to get a table for use with CGI::Session::ActiveRecordStore + +* Added dump of schema version to the db_structure_dump task for databases that support migrations #1835 [Rick Olson] + +* Fixed script/profiler for Ruby 1.8.2 #1863 [Rick Olson] + +* Fixed clone_structure_to_test task for SQLite #1864 [jon@burningbush.us] + +* Added -m/--mime-types option to the WEBrick server, so you can specify a Apache-style mime.types file to load #2059 [ask@develooper.com] + +* Added -c/--svn option to the generator that'll add new files and remove destroyed files using svn add/revert/remove as appropriate #2064 [kevin.clark@gmail.com] + +* Added -c/--charset option to WEBrick server, so you can specify a default charset (which without changes is UTF-8) #2084 [wejn@box.cz] + +* Make the default stats task extendable by modifying the STATS_DIRECTORIES constant + +* Allow the selected environment to define RAILS_DEFAULT_LOGGER, and have Rails::Initializer use it if it exists. + +* Moved all the shared tasks from Rakefile into Rails, so that the Rakefile is empty and doesn't require updating. + +* Added Rails::Initializer and Rails::Configuration to abstract all of the common setup out of config/environment.rb (uses config/boot.rb to bootstrap the initializer and paths) + +* Fixed the scaffold generator to fail right away if the database isn't accessible instead of in mid-air #1169 [Chad Fowler] + +* Corrected project-local generator location in scripts.rb #2010 [Michael Schuerig] + +* Don't require the environment just to clear the logs #2093 [Scott Barron] + +* Make the default rakefile read *.rake files from config/tasks (for easy extension of the rakefile by e.g. generators) + +* Only load breakpoint in development mode and when BREAKPOINT_SERVER_PORT is defined. + +* Allow the --toggle-spin switch on process/reaper to be negated + +* Replace render_partial with render :partial in scaffold generator [Nicholas Seckar] + +* Added -w flag to ps in process/reaper #1934 [Scott Barron] + +* Allow ERb in the database.yml file (just like with fixtures), so you can pull out the database configuration in environment variables #1822 [Duane Johnson] + +* Added convenience controls for FCGI processes (especially when managed remotely): spinner, spawner, and reaper. They reside in script/process. More details can be had by calling them with -h/--help. + +* Added load_fixtures task to the Rakefile, which will load all the fixtures into the database for the current environment #1791 [Marcel Molina] + +* Added an empty robots.txt to public/, so that web servers asking for it won't trigger a dynamic call, like favicon.ico #1738 [michael@schubert] + +* Dropped the 'immediate close-down' of FCGI processes since it didn't work consistently and produced bad responses when it didn't. So now a TERM ensures exit after the next request (just as if the process is handling a request when it receives the signal). This means that you'll have to 'nudge' all FCGI processes with a request in order to ensure that they have all reloaded. This can be done by something like ./script/process/repear --nudge 'http://www.myapp.com' --instances 10, which will load the myapp site 10 times (and thus hit all of the 10 FCGI processes once, enough to shut down). + + +*0.13.1* (11 July, 2005) + +* Look for app-specific generators in RAILS_ROOT/generators rather than the clunky old RAILS_ROOT/script/generators. Nobody really uses this feature except for the unit tests, so it's a negligible-impact change. If you want to work with third-party generators, drop them in ~/.rails/generators or simply install gems. + +* Fixed that each request with the WEBrick adapter would open a new database connection #1685 [Sam Stephenson] + +* Added support for SQL Server in the database rake tasks #1652 [ken.barker@gmail.com] Note: osql and scptxfr may need to be installed on your development environment. This involves getting the .exes and a .rll (scptxfr) from a production SQL Server (not developer level SQL Server). Add their location to your Environment PATH and you are all set. + +* Added a VERSION parameter to the migrate task that allows you to do "rake migrate VERSION=34" to migrate to the 34th version traveling up or down depending on the current version + +* Extend Ruby version check to include RUBY_RELEASE_DATE >= '2005-12-25', the final Ruby 1.8.2 release #1674 [court3nay@gmail.com] + +* Improved documentation for environment config files #1625 [court3nay@gmail.com] + + +*0.13.0* (6 July, 2005) + +* Changed the default logging level in config/environment.rb to INFO for production (so SQL statements won't be logged) + +* Added migration generator: ./script/generate migration add_system_settings + +* Added "migrate" as rake task to execute all the pending migrations from db/migrate + +* Fixed that model generator would make fixtures plural, even if ActiveRecord::Base.pluralize_table_names was false #1185 [Marcel Molina] + +* Added a DOCTYPE of HTML transitional to the HTML files generated by Rails #1124 [Michael Koziarski] + +* SIGTERM also gracefully exits dispatch.fcgi. Ignore SIGUSR1 on Windows. + +* Add the option to manually manage garbage collection in the FastCGI dispatcher. Set the number of requests between GC runs in your public/dispatch.fcgi [skaes@web.de] + +* Allow dynamic application reloading for dispatch.fcgi processes by sending a SIGHUP. If the process is currently handling a request, the request will be allowed to complete first. This allows production fcgi's to be reloaded without having to restart them. + +* RailsFCGIHandler (dispatch.fcgi) no longer tries to explicitly flush $stdout (CgiProcess#out always calls flush) + +* Fixed rakefile actions against PostgreSQL when the password is all numeric #1462 [michael@schubert.cx] + +* ActionMailer::Base subclasses are reloaded with the other rails components #1262 + +* Made the WEBrick adapter not use a mutex around action performance if ActionController::Base.allow_concurrency is true (default is false) + +* Fixed that mailer generator generated fixtures/plural while units expected fixtures/singular #1457 [Scott Barron] + +* Added a 'whiny nil' that's aim 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. Only active in test and development environments by default #1209 [Michael Koziarski] + +* Fixed the test_helper.rb to be safe for requiring controllers from multiple spots, like app/controllers/article_controller.rb and app/controllers/admin/article_controller.rb, without reloading the environment twice #1390 [Nicholas Seckar] + +* Fixed Webrick to escape + characters in URL's the same way that lighttpd and apache do #1397 [Nicholas Seckar] + +* Added -e/--environment option to script/runner #1408 [fbeausoleil@ftml.net] + +* Modernize the scaffold generator to use the simplified render and test methods and to change style from @params["id"] to params[:id]. #1367 + +* Added graceful exit from pressing CTRL-C during the run of the rails command #1150 [Caleb Tennis] + +* Allow graceful exits for dispatch.fcgi processes by sending a SIGUSR1. If the process is currently handling a request, the request will be allowed to complete and then will terminate itself. If a request is not being handled, the process is terminated immediately (via #exit). This basically works like restart graceful on Apache. [Jamis Buck] + +* Made dispatch.fcgi more robust by catching fluke errors and retrying unless its a permanent condition. [Jamis Buck] + +* Added console --profile for profiling an IRB session #1154 [Jeremy Kemper] + +* Changed console_sandbox into console --sandbox #1154 [Jeremy Kemper] + + +*0.12.1* (20th April, 2005) + +* Upgraded to Active Record 1.10.1, Action Pack 1.8.1, Action Mailer 0.9.1, Action Web Service 0.7.1 + + +*0.12.0* (19th April, 2005) + +* Fixed that purge_test_database would use database settings from the development environment when recreating the test database #1122 [rails@cogentdude.com] + +* Added script/benchmarker to easily benchmark one or more statement a number of times from within the environment. Examples: + + # runs the one statement 10 times + script/benchmarker 10 'Person.expensive_method(10)' + + # pits the two statements against each other with 50 runs each + script/benchmarker 50 'Person.expensive_method(10)' 'Person.cheap_method(10)' + +* Added script/profiler to easily profile a single statement from within the environment. Examples: + + script/profiler 'Person.expensive_method(10)' + script/profiler 'Person.expensive_method(10)' 10 # runs the statement 10 times + +* Added Rake target clear_logs that'll truncate all the *.log files in log/ to zero #1079 [Lucas Carlson] + +* Added lazy typing for generate, such that ./script/generate cn == ./script/generate controller and the likes #1051 [k@v2studio.com] + +* Fixed that ownership is brought over in pg_dump during tests for PostgreSQL #1060 [pburleson@gmail.com] + +* Upgraded to Active Record 1.10.0, Action Pack 1.8.0, Action Mailer 0.9.0, Action Web Service 0.7.0, Active Support 1.0.4 + + +*0.11.1* (27th March, 2005) + +* Fixed the dispatch.fcgi use of a logger + +* Upgraded to Active Record 1.9.1, Action Pack 1.7.0, Action Mailer 0.8.1, Action Web Service 0.6.2, Active Support 1.0.3 + + +*0.11.0* (22th March, 2005) + +* Removed SCRIPT_NAME from the WEBrick environment to prevent conflicts with PATH_INFO #896 [Nicholas Seckar] + +* Removed ?$1 from the dispatch.f/cgi redirect line to get rid of 'complete/path/from/request.html' => nil being in the @params now that the ENV["REQUEST_URI"] is used to determine the path #895 [dblack/Nicholas Seckar] + +* Added additional error handling to the FastCGI dispatcher to catch even errors taking down the entire process + +* Improved the generated scaffold code a lot to take advantage of recent Rails developments #882 [Tobias Luetke] + +* Combined the script/environment.rb used for gems and regular files version. If vendor/rails/* has all the frameworks, then files version is used, otherwise gems #878 [Nicholas Seckar] + +* Changed .htaccess to allow dispatch.* to be called from a sub-directory as part of the push with Action Pack to make Rails work on non-vhost setups #826 [Nicholas Seckar/Tobias Luetke] + +* Added script/runner which can be used to run code inside the environment by eval'ing the first parameter. Examples: + + ./script/runner 'ReminderService.deliver' + ./script/runner 'Mailer.receive(STDIN.read)' + + This makes it easier to do CRON and postfix scripts without actually making a script just to trigger 1 line of code. + +* Fixed webrick_server cookie handling to allow multiple cookes to be set at once #800, #813 [dave@cherryville.org] + +* Fixed the Rakefile's interaction with postgresql to: + + 1. Use PGPASSWORD and PGHOST in the environment to fix prompting for + passwords when connecting to a remote db and local socket connections. + 2. Add a '-x' flag to pg_dump which stops it dumping privileges #807 [rasputnik] + 3. Quote the user name and use template0 when dumping so the functions doesn't get dumped too #855 [pburleson] + 4. Use the port if available #875 [madrobby] + +* Upgraded to Active Record 1.9.0, Action Pack 1.6.0, Action Mailer 0.8.0, Action Web Service 0.6.1, Active Support 1.0.2 + + +*0.10.1* (7th March, 2005) + +* Fixed rake stats to ignore editor backup files like model.rb~ #791 [skanthak] + +* Added exception shallowing if the DRb server can't be started (not worth making a fuss about to distract new users) #779 [Tobias Luetke] + +* Added an empty favicon.ico file to the public directory of new applications (so the logs are not spammed by its absence) + +* Fixed that scaffold generator new template should use local variable instead of instance variable #778 [Dan Peterson] + +* Allow unit tests to run on a remote server for PostgreSQL #781 [adamm@galacticasoftware.com] + +* Added web_service generator (run ./script/generate web_service for help) #776 [Leon Bredt] + +* Added app/apis and components to code statistics report #729 [Scott Barron] + +* Fixed WEBrick server to use ABSOLUTE_RAILS_ROOT instead of working_directory #687 [Nicholas Seckar] + +* Fixed rails_generator to be usable without RubyGems #686 [Cristi BALAN] + +* Fixed -h/--help for generate and destroy generators #331 + +* Added begin/rescue around the FCGI dispatcher so no uncaught exceptions can bubble up to kill the process (logs to log/fastcgi.crash.log) + +* Fixed that association#count would produce invalid sql when called sequentialy #659 [kanis@comcard.de] + +* Fixed test/mocks/testing to the correct test/mocks/test #740 + +* Added early failure if the Ruby version isn't 1.8.2 or above #735 + +* Removed the obsolete -i/--index option from the WEBrick servlet #743 + +* Upgraded to Active Record 1.8.0, Action Pack 1.5.1, Action Mailer 0.7.1, Action Web Service 0.6.0, Active Support 1.0.1 + + +*0.10.0* (24th February, 2005) + +* Changed default IP binding for WEBrick from 127.0.0.1 to 0.0.0.0 so that the server is accessible both locally and remotely #696 [Marcel] + +* Fixed that script/server -d was broken so daemon mode couldn't be used #687 [Nicholas Seckar] + +* Upgraded to breakpoint 92 which fixes: + + * overload IRB.parse_opts(), fixes #443 + => breakpoints in tests work even when running them via rake + * untaint handlers, might fix an issue discussed on the Rails ML + * added verbose mode to breakpoint_client + * less noise caused by breakpoint_client by default + * ignored TerminateLineInput exception in signal handler + => quiet exit on Ctrl-C + +* Added support for independent components residing in /components. Example: + + Controller: components/list/items_controller.rb + (holds a List::ItemsController class with uses_component_template_root called) + + Model : components/list/item.rb + (namespace is still shared, so an Item model in app/models will take precedence) + + Views : components/list/items/show.rhtml + + +* Added --sandbox option to script/console that'll roll back all changes made to the database when you quit #672 [Jeremy Kemper] + +* Added 'recent' as a rake target that'll run tests for files that changed in the last 10 minutes #612 [Jeremy Kemper] + +* Changed script/console to default to development environment and drop --no-inspect #650 [Jeremy Kemper] + +* Added that the 'fixture :posts' syntax can be used for has_and_belongs_to_many fixtures where a model doesn't exist #572 [Jeremy Kemper] + +* Added that running test_units and test_functional now performs the clone_structure_to_test as well #566 [rasputnik] + +* Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 [Jeremy Kemper] + +* Added Action Web Service as a new add-on framework for Action Pack [Leon Bredt] + +* Added Active Support as an independent utility and standard library extension bundle + +* Upgraded to Active Record 1.7.0, Action Pack 1.5.0, Action Mailer 0.7.0 + + +*0.9.5* (January 25th, 2005) + +* Fixed dependency reloading by switching to a remove_const approach where all Active Records, Active Record Observers, and Action Controllers are reloading by undefining their classes. This enables you to remove methods in all three types and see the change reflected immediately and it fixes #539. This also means that only those three types of classes will benefit from the const_missing and reloading approach. If you want other classes (like some in lib/) to reload, you must use require_dependency to do it. + +* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 [Florian Gross] + +* Fixed skeleton Rakefile to work with sqlite3 out of the box #521 [rasputnik] + +* Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 [brandt@kurowski.net] + +* Fixed handling of syntax errors in models that had already been succesfully required once in the current interpreter + +* Fixed that models that weren't referenced in associations weren't being reloaded in the development mode by reinstating the reload + +* Fixed that generate scaffold would produce bad functional tests + +* Fixed that FCGI can also display SyntaxErrors + +* Upgraded to Active Record 1.6.0, Action Pack 1.4.0 + + +*0.9.4.1* (January 18th, 2005) + +* Added 5-second timeout to WordNet alternatives on creating reserved-word models #501 [Marcel Molina] + +* Fixed binding of caller #496 [Alexey] + +* Upgraded to Active Record 1.5.1, Action Pack 1.3.1, Action Mailer 0.6.1 + + +*0.9.4* (January 17th, 2005) + +* Added that ApplicationController will catch a ControllerNotFound exception if someone attempts to access a url pointing to an unexisting controller [Tobias Luetke] + +* Flipped code-to-test ratio around to be more readable #468 [Scott Baron] + +* Fixed log file permissions to be 666 instead of 777 (so they're not executable) #471 [Lucas Carlson] + +* Fixed that auto reloading would some times not work or would reload the models twice #475 [Tobias Luetke] + +* Added rewrite rules to deal with caching to public/.htaccess + +* Added the option to specify a controller name to "generate scaffold" and made the default controller name the plural form of the model. + +* Added that rake clone_structure_to_test, db_structure_dump, and purge_test_database tasks now pick up the source database to use from + RAILS_ENV instead of just forcing development #424 [Tobias Luetke] + +* Fixed script/console to work with Windows (that requires the use of irb.bat) #418 [octopod] + +* Fixed WEBrick servlet slowdown over time by restricting the load path reloading to mod_ruby + +* Removed Fancy Indexing as a default option on the WEBrick servlet as it made it harder to use various caching schemes + +* Upgraded to Active Record 1.5, Action Pack 1.3, Action Mailer 0.6 + + +*0.9.3* (January 4th, 2005) + +* Added support for SQLite in the auto-dumping/importing of schemas for development -> test #416 + +* Added automated rewriting of the shebang lines on installs through the gem rails command #379 [Manfred Stienstra] + +* Added ActionMailer::Base.deliver_method = :test to the test environment so that mail objects are available in ActionMailer::Base.deliveries + for functional testing. + +* Added protection for creating a model through the generators with a name of an existing class, like Thread or Date. + It'll even offer you a synonym using wordnet.princeton.edu as a look-up. No, I'm not kidding :) [Florian Gross] + +* Fixed dependency management to happen in a unified fashion for Active Record and Action Pack using the new Dependencies module. This means that + the environment options needs to change from: + + Before in development.rb: + ActionController::Base.reload_dependencies = true   + ActiveRecord::Base.reload_associations     = true + + Now in development.rb: + Dependencies.mechanism = :load + + Before in production.rb and test.rb: + ActionController::Base.reload_dependencies = false + ActiveRecord::Base.reload_associations     = false + + Now in production.rb and test.rb: + Dependencies.mechanism = :require + +* Fixed problems with dependency caching and controller hierarchies on Ruby 1.8.2 in development mode #351 + +* Fixed that generated action_mailers doesnt need to require the action_mailer since thats already done in the environment #382 [Lucas Carlson] + +* Upgraded to Action Pack 1.2.0 and Active Record 1.4.0 + + +*0.9.2* + +* Fixed CTRL-C exists from the Breakpointer to be a clean affair without error dumping [Kent Sibilev] + +* Fixed "rake stats" to work with sub-directories in models and controllers and to report the code to test ration [Scott Baron] + +* Added that Active Record associations are now reloaded instead of cleared to work with the new const_missing hook in Active Record. + +* Added graceful handling of an inaccessible log file by redirecting output to STDERR with a warning #330 [rainmkr] + +* Added support for a -h/--help parameter in the generator #331 [Ulysses] + +* Fixed that File.expand_path in config/environment.rb would fail when dealing with symlinked public directories [mjobin] + +* Upgraded to Action Pack 1.1.0 and Active Record 1.3.0 + + +*0.9.1* + +* Upgraded to Action Pack 1.0.1 for important bug fix + +* Updated gem dependencies + + +*0.9.0* + +* Renamed public/dispatch.servlet to script/server -- it wasn't really dispatching anyway as its delegating calls to public/dispatch.rb + +* Renamed AbstractApplicationController and abstract_application.rb to ApplicationController and application.rb, so that it will be possible + for the framework to automatically pick up on app/views/layouts/application.rhtml and app/helpers/application.rb + +* Added script/console that makes it even easier to start an IRB session for interacting with the domain model. Run with no-args to + see help. + +* Added breakpoint support through the script/breakpointer client. This means that you can break out of execution at any point in + the code, investigate and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find_all + breakpoint "Breaking out from the list" + end + end + + So the controller will accept the action, run the first line, then present you with a IRB prompt in the breakpointer window. + Here you can do things like: + + Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a breakpoint" + => "hello from a breakpoint" + + ...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + + Finally, when you're ready to resume execution, you press CTRL-D + +* Changed environments to be configurable through an environment variable. By default, the environment is "development", but you + can change that and set your own by configuring the Apache vhost with a string like (mod_env must be available on the server): + + SetEnv RAILS_ENV production + + ...if you're using WEBrick, you can pick the environment to use with the command-line parameters -e/--environment, like this: + + ruby public/dispatcher.servlet -e production + +* Added a new default environment called "development", which leaves the production environment to be tuned exclusively for that. + +* Added a start_server in the root of the Rails application to make it even easier to get started + +* Fixed public/.htaccess to use RewriteBase and share the same rewrite rules for all the dispatch methods + +* Fixed webrick_server to handle requests in a serialized manner (the Rails reloading infrastructure is not thread-safe) + +* Added support for controllers in directories. So you can have: + + app/controllers/account_controller.rb # URL: /account/ + app/controllers/admin/account_controller.rb # URL: /admin/account/ + + NOTE: You need to update your public/.htaccess with the new rules to pick it up + +* Added reloading for associations and dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use + those environments for development. This is turned on by default, but can be turned off with + ActiveRecord::Base.reload_associations = false and ActionController::Base.reload_dependencies = false in production environments. + +* Added support for sub-directories in app/models. So now you can have something like Basecamp with: + + app/models/accounting + app/models/project + app/models/participants + app/models/settings + + It's poor man's namespacing, but only for file-system organization. You still require files just like before. + Nothing changes inside the files themselves. + + +* Fixed a few references in the tests generated by new_mailer [Jeremy Kemper] + +* Added support for mocks in testing with test/mocks + +* Cleaned up the environments a bit and added global constant RAILS_ROOT + + +*0.8.5* (9) + +* Made dev-util available to all tests, so you can insert breakpoints in any test case to get an IRB prompt at that point [Jeremy Kemper]: + + def test_complex_stuff + @david.projects << @new_project + breakpoint "Let's have a closer look at @david" + end + + You need to install dev-utils yourself for this to work ("gem install dev-util"). + +* Added shared generator behavior so future upgrades should be possible without manually copying over files [Jeremy Kemper] + +* Added the new helper style to both controller and helper templates [Jeremy Kemper] + +* Added new_crud generator for creating a model and controller at the same time with explicit scaffolding [Jeremy Kemper] + +* Added configuration of Test::Unit::TestCase.fixture_path to test_helper to concide with the new AR fixtures style + +* Fixed that new_model was generating singular table/fixture names + +* Upgraded to Action Mailer 0.4.0 + +* Upgraded to Action Pack 0.9.5 + +* Upgraded to Active Record 1.1.0 + + +*0.8.0 (15)* + +* Removed custom_table_name option for new_model now that the Inflector is as powerful as it is + +* Changed the default rake action to just do testing and separate API generation and coding statistics into a "doc" task. + +* Fixed WEBrick dispatcher to handle missing slashes in the URLs gracefully [alexey] + +* Added user option for all postgresql tool calls in the rakefile [elvstone] + +* Fixed problem with running "ruby public/dispatch.servlet" instead of "cd public; ruby dispatch.servlet" [alexey] + +* Fixed WEBrick server so that it no longer hardcodes the ruby interpreter used to "ruby" but will get the one used based + on the Ruby runtime configuration. [Marcel Molina Jr.] + +* Fixed Dispatcher so it'll route requests to magic_beans to MagicBeansController/magic_beans_controller.rb [Caio Chassot] + +* "new_controller MagicBeans" and "new_model SubscriptionPayments" will now both behave properly as they use the new Inflector. + +* Fixed problem with MySQL foreign key constraint checks in Rake :clone_production_structure_to_test target [Andreas Schwarz] + +* Changed WEBrick server to by default be auto-reloading, which is slower but makes source changes instant. + Class compilation cache can be turned on with "-c" or "--cache-classes". + +* Added "-b/--binding" option to WEBrick dispatcher to bind the server to a specific IP address (default: 127.0.0.1) [Kevin Temp] + +* dispatch.fcgi now DOESN'T set FCGI_PURE_RUBY as it was slowing things down for now reason [Andreas Schwarz] + +* Added new_mailer generator to work with Action Mailer + +* Included new framework: Action Mailer 0.3 + +* Upgraded to Action Pack 0.9.0 + +* Upgraded to Active Record 1.0.0 + + +*0.7.0* + +* Added an optional second argument to the new_model script that allows the programmer to specify the table name, + which will used to generate a custom table_name method in the model and will also be used in the creation of fixtures. + [Kevin Radloff] + +* script/new_model now turns AccountHolder into account_holder instead of accountholder [Kevin Radloff] + +* Fixed the faulty handleing of static files with WEBrick [Andreas Schwarz] + +* Unified function_test_helper and unit_test_helper into test_helper + +* Fixed bug with the automated production => test database dropping on PostgreSQL [dhawkins] + +* create_fixtures in both the functional and unit test helper now turns off the log during fixture generation + and can generate more than one fixture at a time. Which makes it possible for assignments like: + + @people, @projects, @project_access, @companies, @accounts = + create_fixtures "people", "projects", "project_access", "companies", "accounts" + +* Upgraded to Action Pack 0.8.5 (locally-scoped variables, partials, advanced send_file) + +* Upgraded to Active Record 0.9.5 (better table_name guessing, cloning, find_all_in_collection) + + +*0.6.5* + +* No longer specifies a template for rdoc, so it'll use whatever is default (you can change it in the rakefile) + +* The new_model generator will now use the same rules for plural wordings as Active Record + (so Category will give categories, not categorys) [Kevin Radloff] + +* dispatch.fcgi now sets FCGI_PURE_RUBY to true to ensure that it's the Ruby version that's loaded [danp] + +* Made the GEM work with Windows + +* Fixed bug where mod_ruby would "forget" the load paths added when switching between controllers + +* PostgreSQL are now supported for the automated production => test database dropping [Kevin Radloff] + +* Errors thrown by the dispatcher are now properly handled in FCGI. + +* Upgraded to Action Pack 0.8.0 (lots and lots and lots of fixes) + +* Upgraded to Active Record 0.9.4 (a bunch of fixes) + + +*0.6.0* + +* Added AbstractionApplicationController as a superclass for all controllers generated. This class can be used + to carry filters and methods that are to be shared by all. It has an accompanying ApplicationHelper that all + controllers will also automatically have available. + +* Added environments that can be included from any script to get the full Active Record and Action Controller + context running. This can be used by maintenance scripts or to interact with the model through IRB. Example: + + require 'config/environments/production' + + for account in Account.find_all + account.recalculate_interests + end + + A short migration script for an account model that had it's interest calculation strategy changed. + +* Accessing the index of a controller with "/weblog" will now redirect to "/weblog/" (only on Apache, not WEBrick) + +* Simplified the default Apache config so even remote requests are served off CGI as a default. + You'll now have to do something specific to activate mod_ruby and FCGI (like using the force urls). + This should make it easier for new comers that start on an external server. + +* Added more of the necessary Apache options to .htaccess to make it easier to setup + +* Upgraded to Action Pack 0.7.9 (lots of fixes) + +* Upgraded to Active Record 0.9.3 (lots of fixes) + + +*0.5.7* + +* Fixed bug in the WEBrick dispatcher that prevented it from getting parameters from the URL + (through GET requests or otherwise) + +* Added lib in root as a place to store app specific libraries + +* Added lib and vendor to load_path, so anything store within can be loaded directly. + Hence lib/redcloth.rb can be loaded with require "redcloth" + +* Upgraded to Action Pack 0.7.8 (lots of fixes) + +* Upgraded to Active Record 0.9.2 (minor upgrade) + + +*0.5.6* + +* Upgraded to Action Pack 0.7.7 (multipart form fix) + +* Updated the generated template stubs to valid XHTML files + +* Ensure that controllers generated are capitalized, so "new_controller TodoLists" + gives the same as "new_controller Todolists" and "new_controller todolists". + + +*0.5.5* + +* Works on Windows out of the box! (Dropped symlinks) + +* Added webrick dispatcher: Try "ruby public/dispatch.servlet --help" [Florian Gross] + +* Report errors about initialization to browser (instead of attempting to use uninitialized logger) + +* Upgraded to Action Pack 0.7.6 + +* Upgraded to Active Record 0.9.1 + +* Added distinct 500.html instead of reusing 404.html + +* Added MIT license + + +*0.5.0* + +* First public release diff --git a/vendor/rails/railties/MIT-LICENSE b/vendor/rails/railties/MIT-LICENSE new file mode 100644 index 0000000..5919c28 --- /dev/null +++ b/vendor/rails/railties/MIT-LICENSE @@ -0,0 +1,20 @@ +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. \ No newline at end of file diff --git a/vendor/rails/railties/README b/vendor/rails/railties/README new file mode 100644 index 0000000..7d8965e --- /dev/null +++ b/vendor/rails/railties/README @@ -0,0 +1,183 @@ +== Welcome to Rails + +Rails is a web-application and persistence framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-built data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting started + +1. Start the web server: ruby script/server (run with --help for options) +2. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" +3. Follow the guidelines to start developing your application + + +== Web servers + +Rails uses the built-in web server in Ruby called WEBrick by default, so you don't +have to install or configure anything to play around. + +If you have lighttpd installed, though, it'll be used instead when running script/server. +It's considerably faster than WEBrick and suited for production use, but requires additional +installation and currently only works well on OS X/Unix (Windows users are encouraged +to start with WEBrick). We recommend version 1.4.11 and higher. You can download it from +http://www.lighttpd.net. + +If you want something that's halfway between WEBrick and lighttpd, we heartily recommend +Mongrel. It's a Ruby-based web server with a C-component (so it requires compilation) that +also works very well with Windows. See more at http://mongrel.rubyforge.org/. + +But of course its also possible to run Rails with the premiere open source web server Apache. +To get decent performance, though, you'll need to install FastCGI. For Apache 1.3, you want +to use mod_fastcgi. For Apache 2.0+, you want to use mod_fcgid. + +See http://wiki.rubyonrails.com/rails/pages/FastCGI for more information on FastCGI. + +== Example for Apache conf + + + ServerName rails + DocumentRoot /path/application/public/ + ErrorLog /path/application/log/server.log + + + Options ExecCGI FollowSymLinks + AllowOverride all + Allow from all + Order allow,deny + + + +NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI +should be on and ".cgi" should respond. All requests from 127.0.0.1 go +through CGI, so no Apache restart is necessary for changes. All other requests +go through FCGI (or mod_ruby), which requires a restart to show changes. + + +== Debugging Rails + +Have "tail -f" commands running on both the server.log, production.log, and +test.log files. Rails will automatically display debugging and runtime +information to these files. Debugging info will also be shown in the browser +on requests from 127.0.0.1. + + +== Breakpoints + +Breakpoint support is available through the script/breakpointer client. This +means that you can break out of execution at any point in the code, investigate +and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find_all + breakpoint "Breaking out from the list" + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the breakpointer window. Here you can do things like: + +Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a breakpoint" + => "hello from a breakpoint" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you press CTRL-D + + +== Console + +You can interact with the domain model by starting the console through script/console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like script/console production. + +To reload your controllers and models after launching the console run reload! + + + +== Description of contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblog_controller.rb for + automated URL mapping. All controllers should descend from + ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblog/index.rhtml for the WeblogController#index action. All views use eRuby + syntax. This directory can also be used to keep stylesheets, images, and so on + that can be symlinked to public. + +app/helpers + Holds view helpers that should be named like weblog_helper.rb. + +app/apis + Holds API classes for web services. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +components + Self-contained mini-applications that can bundle together controllers, models, and views. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + This directory is in the load path. diff --git a/vendor/rails/railties/Rakefile b/vendor/rails/railties/Rakefile new file mode 100644 index 0000000..f85f3e2 --- /dev/null +++ b/vendor/rails/railties/Rakefile @@ -0,0 +1,320 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/gempackagetask' +require 'rake/contrib/rubyforgepublisher' + +require 'date' +require 'rbconfig' + +require File.join(File.dirname(__FILE__), 'lib/rails', 'version') + +PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' +PKG_NAME = 'rails' +PKG_VERSION = Rails::VERSION::STRING + PKG_BUILD +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" +PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}" + +RELEASE_NAME = "REL #{PKG_VERSION}" + +RUBY_FORGE_PROJECT = "rails" +RUBY_FORGE_USER = "webster132" + + +# Rake::TestTask.new("test") do |t| +# t.libs << 'test' +# t.pattern = 'test/*_test.rb' +# t.verbose = true +# end + + +BASE_DIRS = %w( + app config/environments components db doc log lib lib/tasks public script script/performance script/process test vendor vendor/plugins + tmp/sessions tmp/cache tmp/sockets tmp/pids +) + +APP_DIRS = %w( models controllers helpers views views/layouts ) +PUBLIC_DIRS = %w( images javascripts stylesheets ) +TEST_DIRS = %w( fixtures unit functional mocks mocks/development mocks/test ) + +LOG_FILES = %w( server.log development.log test.log production.log ) +HTML_FILES = %w( 404.html 500.html index.html robots.txt favicon.ico images/rails.png + javascripts/prototype.js javascripts/application.js + javascripts/effects.js javascripts/dragdrop.js javascripts/controls.js ) +BIN_FILES = %w( about breakpointer console destroy generate performance/benchmarker performance/profiler process/reaper process/spawner process/inspector runner server plugin ) + +VENDOR_LIBS = %w( actionpack activerecord actionmailer activesupport actionwebservice railties ) + + +desc "Generates a fresh Rails package with documentation" +task :fresh_rails => [ :clean, :make_dir_structure, :initialize_file_stubs, :copy_vendor_libraries, :copy_ties_content, :generate_documentation ] + +desc "Generates a fresh Rails package using GEMs with documentation" +task :fresh_gem_rails => [ :clean, :make_dir_structure, :initialize_file_stubs, :copy_ties_content, :copy_gem_environment ] + +desc "Generates a fresh Rails package without documentation (faster)" +task :fresh_rails_without_docs => [ :clean, :make_dir_structure, :initialize_file_stubs, :copy_vendor_libraries, :copy_ties_content ] + +desc "Generates a fresh Rails package without documentation (faster)" +task :fresh_rails_without_docs_using_links => [ :clean, :make_dir_structure, :initialize_file_stubs, :link_vendor_libraries, :copy_ties_content ] + +desc "Generates minimal Rails package using symlinks" +task :dev => [ :clean, :make_dir_structure, :initialize_file_stubs, :link_vendor_libraries, :copy_ties_content ] + +desc "Packages the fresh Rails package with documentation" +task :package => [ :clean, :fresh_rails ] do + system %{cd ..; tar -czvf #{PKG_NAME}-#{PKG_VERSION}.tgz #{PKG_NAME}} + system %{cd ..; zip -r #{PKG_NAME}-#{PKG_VERSION}.zip #{PKG_NAME}} +end + +task :clean do + rm_rf PKG_DESTINATION +end + +# Get external spinoffs ------------------------------------------------------------------- + +desc "Updates railties to the latest version of the javascript spinoffs" +task :update_js do + for js in %w( prototype controls dragdrop effects ) + rm "html/javascripts/#{js}.js" + cp "./../actionpack/lib/action_view/helpers/javascripts/#{js}.js", "html/javascripts" + end +end + +# Make directory structure ---------------------------------------------------------------- + +def make_dest_dirs(dirs, path = '.') + mkdir_p dirs.map { |dir| File.join(PKG_DESTINATION, path.to_s, dir) } +end + +desc "Make the directory structure for the new Rails application" +task :make_dir_structure => [ :make_base_dirs, :make_app_dirs, :make_public_dirs, :make_test_dirs ] + +task(:make_base_dirs) { make_dest_dirs BASE_DIRS } +task(:make_app_dirs) { make_dest_dirs APP_DIRS, 'app' } +task(:make_public_dirs) { make_dest_dirs PUBLIC_DIRS, 'public' } +task(:make_test_dirs) { make_dest_dirs TEST_DIRS, 'test' } + + +# Initialize file stubs ------------------------------------------------------------------- + +desc "Initialize empty file stubs (such as for logging)" +task :initialize_file_stubs => [ :initialize_log_files ] + +task :initialize_log_files do + log_dir = File.join(PKG_DESTINATION, 'log') + chmod 0777, log_dir + LOG_FILES.each do |log_file| + log_path = File.join(log_dir, log_file) + touch log_path + chmod 0666, log_path + end +end + + +# Copy Vendors ---------------------------------------------------------------------------- + +desc "Copy in all the Rails packages to vendor" +task :copy_vendor_libraries do + mkdir File.join(PKG_DESTINATION, 'vendor', 'rails') + VENDOR_LIBS.each { |dir| cp_r File.join('..', dir), File.join(PKG_DESTINATION, 'vendor', 'rails', dir) } + FileUtils.rm_r(Dir.glob(File.join(PKG_DESTINATION, 'vendor', 'rails', "**", ".svn"))) +end + +desc "Link in all the Rails packages to vendor" +task :link_vendor_libraries do + mkdir File.join(PKG_DESTINATION, 'vendor', 'rails') + VENDOR_LIBS.each { |dir| ln_s File.join('..', '..', '..', dir), File.join(PKG_DESTINATION, 'vendor', 'rails', dir) } +end + + +# Copy Ties Content ----------------------------------------------------------------------- + +# :link_apache_config +desc "Make copies of all the default content of ties" +task :copy_ties_content => [ + :copy_rootfiles, :copy_dispatches, :copy_html_files, :copy_application, + :copy_configs, :copy_binfiles, :copy_test_helpers, :copy_app_doc_readme ] + +task :copy_dispatches do + copy_with_rewritten_ruby_path("dispatches/dispatch.rb", "#{PKG_DESTINATION}/public/dispatch.rb") + chmod 0755, "#{PKG_DESTINATION}/public/dispatch.rb" + + copy_with_rewritten_ruby_path("dispatches/dispatch.rb", "#{PKG_DESTINATION}/public/dispatch.cgi") + chmod 0755, "#{PKG_DESTINATION}/public/dispatch.cgi" + + copy_with_rewritten_ruby_path("dispatches/dispatch.fcgi", "#{PKG_DESTINATION}/public/dispatch.fcgi") + chmod 0755, "#{PKG_DESTINATION}/public/dispatch.fcgi" + + # copy_with_rewritten_ruby_path("dispatches/gateway.cgi", "#{PKG_DESTINATION}/public/gateway.cgi") + # chmod 0755, "#{PKG_DESTINATION}/public/gateway.cgi" +end + +task :copy_html_files do + HTML_FILES.each { |file| cp File.join('html', file), File.join(PKG_DESTINATION, 'public', file) } +end + +task :copy_application do + cp "helpers/application.rb", "#{PKG_DESTINATION}/app/controllers/application.rb" + cp "helpers/application_helper.rb", "#{PKG_DESTINATION}/app/helpers/application_helper.rb" +end + +task :copy_configs do + app_name = "rails" + socket = nil + require 'erb' + File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/mysql.yml"), nil, '-').result(binding)} + + cp "configs/routes.rb", "#{PKG_DESTINATION}/config/routes.rb" + + cp "configs/apache.conf", "#{PKG_DESTINATION}/public/.htaccess" + + cp "environments/boot.rb", "#{PKG_DESTINATION}/config/boot.rb" + cp "environments/environment.rb", "#{PKG_DESTINATION}/config/environment.rb" + cp "environments/production.rb", "#{PKG_DESTINATION}/config/environments/production.rb" + cp "environments/development.rb", "#{PKG_DESTINATION}/config/environments/development.rb" + cp "environments/test.rb", "#{PKG_DESTINATION}/config/environments/test.rb" +end + +task :copy_binfiles do + BIN_FILES.each do |file| + dest_file = File.join(PKG_DESTINATION, 'script', file) + copy_with_rewritten_ruby_path(File.join('bin', file), dest_file) + chmod 0755, dest_file + end +end + +task :copy_rootfiles do + cp "fresh_rakefile", "#{PKG_DESTINATION}/Rakefile" + cp "README", "#{PKG_DESTINATION}/README" + cp "CHANGELOG", "#{PKG_DESTINATION}/CHANGELOG" +end + +task :copy_test_helpers do + cp "helpers/test_helper.rb", "#{PKG_DESTINATION}/test/test_helper.rb" +end + +task :copy_app_doc_readme do + cp "doc/README_FOR_APP", "#{PKG_DESTINATION}/doc/README_FOR_APP" +end + +task :link_apache_config do + chdir(File.join(PKG_DESTINATION, 'config')) { + ln_s "../public/.htaccess", "apache.conf" + } +end + +def copy_with_rewritten_ruby_path(src_file, dest_file) + ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) + + File.open(dest_file, 'w') do |df| + File.open(src_file) do |sf| + line = sf.gets + if (line =~ /#!.+ruby\s*/) != nil + df.puts("#!#{ruby}") + else + df.puts(line) + end + df.write(sf.read) + end + end +end + + +# Generate documentation ------------------------------------------------------------------ + +desc "Generate documentation for the framework and for the empty application" +task :generate_documentation => [ :generate_app_doc, :generate_rails_framework_doc ] + +task :generate_rails_framework_doc do + system %{cd #{PKG_DESTINATION}; rake apidoc} +end + +task :generate_app_doc do + File.cp "doc/README_FOR_APP", "#{PKG_DESTINATION}/doc/README_FOR_APP" + system %{cd #{PKG_DESTINATION}; rake appdoc} +end + +Rake::RDocTask.new { |rdoc| + rdoc.rdoc_dir = 'doc' + rdoc.title = "Railties -- Gluing the Engine to the Rails" + rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.rdoc_files.include('README', 'CHANGELOG') + rdoc.rdoc_files.include('lib/*.rb') + rdoc.rdoc_files.include('lib/rails_generator/*.rb') + rdoc.rdoc_files.include('lib/commands/**/*.rb') +} + +# Generate GEM ---------------------------------------------------------------------------- + +task :copy_gem_environment do + cp "environments/environment.rb", "#{PKG_DESTINATION}/config/environment.rb" + chmod 0755, dest_file +end + + +PKG_FILES = FileList[ + '[a-zA-Z]*', + 'bin/**/*', + 'builtin/**/*', + 'configs/**/*', + 'doc/**/*', + 'dispatches/**/*', + 'environments/**/*', + 'helpers/**/*', + 'generators/**/*', + 'html/**/*', + 'lib/**/*' +] + +spec = Gem::Specification.new do |s| + s.name = 'rails' + s.version = PKG_VERSION + s.summary = "Web-application framework with template engine, control-flow layer, and ORM." + s.description = <<-EOF + Rails is a framework for building web-application using CGI, FCGI, mod_ruby, or WEBrick + on top of either MySQL, PostgreSQL, SQLite, DB2, SQL Server, or Oracle with eRuby- or Builder-based templates. + EOF + + s.add_dependency('rake', '>= 0.7.1') + s.add_dependency('activesupport', '= 1.3.1' + PKG_BUILD) + s.add_dependency('activerecord', '= 1.14.2' + PKG_BUILD) + s.add_dependency('actionpack', '= 1.12.1' + PKG_BUILD) + s.add_dependency('actionmailer', '= 1.2.1' + PKG_BUILD) + s.add_dependency('actionwebservice', '= 1.1.2' + PKG_BUILD) + + s.rdoc_options << '--exclude' << '.' + s.has_rdoc = false + + s.files = PKG_FILES.to_a.delete_if {|f| f.include?('.svn')} + s.require_path = 'lib' + + s.bindir = "bin" # Use these for applications. + s.executables = ["rails"] + s.default_executable = "rails" + + s.author = "David Heinemeier Hansson" + s.email = "david@loudthinking.com" + s.homepage = "http://www.rubyonrails.org" + s.rubyforge_project = "rails" +end + +Rake::GemPackageTask.new(spec) do |pkg| +end + + +# Publishing ------------------------------------------------------- +desc "Publish the API documentation" +task :pgem => [:gem] do + Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload + `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'` +end + +desc "Publish the release files to RubyForge." +task :release => [ :gem ] do + `rubyforge login` + release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.gem" + puts release_command + system(release_command) +end diff --git a/vendor/rails/railties/bin/about b/vendor/rails/railties/bin/about new file mode 100644 index 0000000..7b07d46 --- /dev/null +++ b/vendor/rails/railties/bin/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' \ No newline at end of file diff --git a/vendor/rails/railties/bin/breakpointer b/vendor/rails/railties/bin/breakpointer new file mode 100644 index 0000000..64af76e --- /dev/null +++ b/vendor/rails/railties/bin/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/vendor/rails/railties/bin/console b/vendor/rails/railties/bin/console new file mode 100644 index 0000000..42f28f7 --- /dev/null +++ b/vendor/rails/railties/bin/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/vendor/rails/railties/bin/destroy b/vendor/rails/railties/bin/destroy new file mode 100644 index 0000000..fa0e6fc --- /dev/null +++ b/vendor/rails/railties/bin/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/vendor/rails/railties/bin/generate b/vendor/rails/railties/bin/generate new file mode 100644 index 0000000..ef976e0 --- /dev/null +++ b/vendor/rails/railties/bin/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/vendor/rails/railties/bin/performance/benchmarker b/vendor/rails/railties/bin/performance/benchmarker new file mode 100644 index 0000000..c842d35 --- /dev/null +++ b/vendor/rails/railties/bin/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/vendor/rails/railties/bin/performance/profiler b/vendor/rails/railties/bin/performance/profiler new file mode 100644 index 0000000..d855ac8 --- /dev/null +++ b/vendor/rails/railties/bin/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/vendor/rails/railties/bin/plugin b/vendor/rails/railties/bin/plugin new file mode 100644 index 0000000..26ca64c --- /dev/null +++ b/vendor/rails/railties/bin/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' \ No newline at end of file diff --git a/vendor/rails/railties/bin/process/inspector b/vendor/rails/railties/bin/process/inspector new file mode 100644 index 0000000..bf25ad8 --- /dev/null +++ b/vendor/rails/railties/bin/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/vendor/rails/railties/bin/process/reaper b/vendor/rails/railties/bin/process/reaper new file mode 100644 index 0000000..c77f045 --- /dev/null +++ b/vendor/rails/railties/bin/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/vendor/rails/railties/bin/process/spawner b/vendor/rails/railties/bin/process/spawner new file mode 100644 index 0000000..7118f39 --- /dev/null +++ b/vendor/rails/railties/bin/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/vendor/rails/railties/bin/rails b/vendor/rails/railties/bin/rails new file mode 100755 index 0000000..ae0cc8a --- /dev/null +++ b/vendor/rails/railties/bin/rails @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/../lib/ruby_version_check' +Signal.trap("INT") { puts; exit } + +require File.dirname(__FILE__) + '/../lib/rails/version' +if %w(--version -v).include? ARGV.first + puts "Rails #{Rails::VERSION::STRING}" + exit(0) +end + +freeze = ARGV.any? { |option| %w(--freeze -f).include?(option) } +app_path = ARGV.first + +require File.dirname(__FILE__) + '/../lib/rails_generator' + +require 'rails_generator/scripts/generate' +Rails::Generator::Base.use_application_sources! +Rails::Generator::Scripts::Generate.new.run(ARGV, :generator => 'app') + +Dir.chdir(app_path) { `rake rails:freeze:gems`; puts "froze" } if freeze \ No newline at end of file diff --git a/vendor/rails/railties/bin/runner b/vendor/rails/railties/bin/runner new file mode 100644 index 0000000..ccc30f9 --- /dev/null +++ b/vendor/rails/railties/bin/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/vendor/rails/railties/bin/server b/vendor/rails/railties/bin/server new file mode 100644 index 0000000..dfabcb8 --- /dev/null +++ b/vendor/rails/railties/bin/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/vendor/rails/railties/builtin/rails_info/rails/info.rb b/vendor/rails/railties/builtin/rails_info/rails/info.rb new file mode 100644 index 0000000..89c2ce6 --- /dev/null +++ b/vendor/rails/railties/builtin/rails_info/rails/info.rb @@ -0,0 +1,123 @@ +module Rails + module Info + mattr_accessor :properties + class << (@@properties = []) + def names + map {|(name, )| name} + end + + def value_for(property_name) + find {|(name, )| name == property_name}.last rescue nil + end + end + + class << self #:nodoc: + def property(name, value = nil) + value ||= yield + properties << [name, value] if value + rescue Exception + end + + def components + %w( active_record action_pack action_web_service action_mailer active_support ) + end + + def component_version(component) + require "#{component}/version" + "#{component.classify}::VERSION::STRING".constantize + end + + def edge_rails_revision(info = svn_info) + info[/^Revision: (\d+)/, 1] || freeze_edge_version + end + + def freeze_edge_version + if File.exists?(rails_vendor_root) + begin + Dir[File.join(rails_vendor_root, 'REVISION_*')].first.scan(/_(\d+)$/).first.first + rescue + Dir[File.join(rails_vendor_root, 'TAG_*')].first.scan(/_(.+)$/).first.first rescue 'unknown' + end + end + end + + def to_s + column_width = properties.names.map {|name| name.length}.max + ["About your application's environment", *properties.map do |property| + "%-#{column_width}s %s" % property + end] * "\n" + end + + alias inspect to_s + + def to_html + returning table = '' do + properties.each do |(name, value)| + table << %() + table << %() + end + table << '
      #{CGI.escapeHTML(name.to_s)}#{CGI.escapeHTML(value.to_s)}
      ' + end + end + + protected + def rails_vendor_root + @rails_vendor_root ||= "#{RAILS_ROOT}/vendor/rails" + end + + def svn_info + env_lang, ENV['LC_ALL'] = ENV['LC_ALL'], 'C' + Dir.chdir(rails_vendor_root) do + silence_stderr { `svn info` } + end + ensure + ENV['LC_ALL'] = env_lang + end + end + + # The Ruby version and platform, e.g. "1.8.2 (powerpc-darwin8.2.0)". + property 'Ruby version', "#{RUBY_VERSION} (#{RUBY_PLATFORM})" + + # The RubyGems version, if it's installed. + property 'RubyGems version' do + Gem::RubyGemsVersion + end + + # The Rails version. + property 'Rails version' do + Rails::VERSION::STRING + end + + # Versions of each Rails component (Active Record, Action Pack, + # Action Web Service, Action Mailer, and Active Support). + components.each do |component| + property "#{component.titlecase} version" do + component_version(component) + end + end + + # The Rails SVN revision, if it's checked out into vendor/rails. + property 'Edge Rails revision' do + edge_rails_revision + end + + # The application's location on the filesystem. + property 'Application root' do + File.expand_path(RAILS_ROOT) + end + + # The current Rails environment (development, test, or production). + property 'Environment' do + RAILS_ENV + end + + # The name of the database adapter for the current environment. + property 'Database adapter' do + ActiveRecord::Base.configurations[RAILS_ENV]['adapter'] + end + + property 'Database schema version' do + ActiveRecord::Migrator.current_version rescue nil + end + end +end diff --git a/vendor/rails/railties/builtin/rails_info/rails/info_controller.rb b/vendor/rails/railties/builtin/rails_info/rails/info_controller.rb new file mode 100644 index 0000000..39f8b1f --- /dev/null +++ b/vendor/rails/railties/builtin/rails_info/rails/info_controller.rb @@ -0,0 +1,9 @@ +class Rails::InfoController < ActionController::Base + def properties + if local_request? + render :inline => Rails::Info.to_html + else + render :text => '

      For security purposes, this information is only available to local requests.

      ', :status => 500 + end + end +end diff --git a/vendor/rails/railties/builtin/rails_info/rails/info_helper.rb b/vendor/rails/railties/builtin/rails_info/rails/info_helper.rb new file mode 100644 index 0000000..e5605a8 --- /dev/null +++ b/vendor/rails/railties/builtin/rails_info/rails/info_helper.rb @@ -0,0 +1,2 @@ +module Rails::InfoHelper +end diff --git a/vendor/rails/railties/builtin/rails_info/rails_info_controller.rb b/vendor/rails/railties/builtin/rails_info/rails_info_controller.rb new file mode 100644 index 0000000..2009eb3 --- /dev/null +++ b/vendor/rails/railties/builtin/rails_info/rails_info_controller.rb @@ -0,0 +1,2 @@ +# Alias to ensure old public.html still works. +RailsInfoController = Rails::InfoController diff --git a/vendor/rails/railties/configs/apache.conf b/vendor/rails/railties/configs/apache.conf new file mode 100755 index 0000000..d3c9983 --- /dev/null +++ b/vendor/rails/railties/configs/apache.conf @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

      Application error

      Rails application failed to start properly" \ No newline at end of file diff --git a/vendor/rails/railties/configs/databases/frontbase.yml b/vendor/rails/railties/configs/databases/frontbase.yml new file mode 100644 index 0000000..2eed313 --- /dev/null +++ b/vendor/rails/railties/configs/databases/frontbase.yml @@ -0,0 +1,28 @@ +# FrontBase versions 4.x +# +# Get the bindings: +# gem install ruby-frontbase + +development: + adapter: frontbase + host: localhost + database: <%= app_name %>_development + username: <%= app_name %> + password: '' + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: frontbase + host: localhost + database: <%= app_name %>_test + username: <%= app_name %> + password: '' + +production: + adapter: frontbase + host: localhost + database: <%= app_name %>_production + username: <%= app_name %> + password: '' diff --git a/vendor/rails/railties/configs/databases/mysql.yml b/vendor/rails/railties/configs/databases/mysql.yml new file mode 100644 index 0000000..b7e01da --- /dev/null +++ b/vendor/rails/railties/configs/databases/mysql.yml @@ -0,0 +1,48 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# gem install mysql +# Choose the win32 build. +# Install MySQL and put its /bin directory on your path. +# +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html +development: + adapter: mysql + database: <%= app_name %>_development + username: root + password: +<% if socket -%> + socket: <%= socket %> +<% else -%> + host: localhost +<% end -%> + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: <%= app_name %>_test + username: root + password: +<% if socket -%> + socket: <%= socket %> +<% else -%> + host: localhost +<% end -%> + +production: + adapter: mysql + database: <%= app_name %>_production + username: root + password: +<% if socket -%> + socket: <%= socket %> +<% else -%> + host: localhost +<% end -%> \ No newline at end of file diff --git a/vendor/rails/railties/configs/databases/oracle.yml b/vendor/rails/railties/configs/databases/oracle.yml new file mode 100644 index 0000000..3c3c8f8 --- /dev/null +++ b/vendor/rails/railties/configs/databases/oracle.yml @@ -0,0 +1,30 @@ +# Oracle/OCI 8i, 9, 10g +# +# Requires Ruby/OCI8: +# http://rubyforge.org/projects/ruby-oci8/ +# +# Specify your database using any valid connection syntax, such as a +# tnsnames.ora service name, or a sql connect url string of the form: +# +# //host:[port][/service name] + +development: + adapter: oracle + database: <%= app_name %>_development + username: <%= app_name %> + password: + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: oracle + database: <%= app_name %>_test + username: <%= app_name %> + password: + +production: + adapter: oracle + database: <%= app_name %>_production + username: <%= app_name %> + password: diff --git a/vendor/rails/railties/configs/databases/postgresql.yml b/vendor/rails/railties/configs/databases/postgresql.yml new file mode 100644 index 0000000..3c146c1 --- /dev/null +++ b/vendor/rails/railties/configs/databases/postgresql.yml @@ -0,0 +1,44 @@ +# PostgreSQL versions 7.4 - 8.1 +# +# Get the C bindings: +# gem install postgres +# or use the pure-Ruby bindings on Windows: +# gem install postgres-pr +development: + adapter: postgresql + database: <%= app_name %>_development + username: <%= app_name %> + password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Character set encoding. The server defaults to sql_ascii. + #encoding: UTF8 + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # info, notice, warning, error, log, fatal, or panic + # The server defaults to notice. + #min_messages: warning + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: postgresql + database: <%= app_name %>_test + username: <%= app_name %> + password: + +production: + adapter: postgresql + database: <%= app_name %>_production + username: <%= app_name %> + password: diff --git a/vendor/rails/railties/configs/databases/sqlite2.yml b/vendor/rails/railties/configs/databases/sqlite2.yml new file mode 100644 index 0000000..26d3957 --- /dev/null +++ b/vendor/rails/railties/configs/databases/sqlite2.yml @@ -0,0 +1,16 @@ +# SQLite version 2.x +# gem install sqlite-ruby +development: + adapter: sqlite + database: db/development.sqlite2 + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: sqlite + database: db/test.sqlite2 + +production: + adapter: sqlite + database: db/production.sqlite2 diff --git a/vendor/rails/railties/configs/databases/sqlite3.yml b/vendor/rails/railties/configs/databases/sqlite3.yml new file mode 100644 index 0000000..6f8cbaf --- /dev/null +++ b/vendor/rails/railties/configs/databases/sqlite3.yml @@ -0,0 +1,16 @@ +# SQLite version 3.x +# gem install sqlite3-ruby +development: + adapter: sqlite3 + database: db/development.sqlite3 + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: sqlite3 + database: db/test.sqlite3 + +production: + adapter: sqlite3 + database: db/production.sqlite3 diff --git a/vendor/rails/railties/configs/empty.log b/vendor/rails/railties/configs/empty.log new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/railties/configs/lighttpd.conf b/vendor/rails/railties/configs/lighttpd.conf new file mode 100644 index 0000000..ed68d71 --- /dev/null +++ b/vendor/rails/railties/configs/lighttpd.conf @@ -0,0 +1,54 @@ +# Default configuration file for the lighttpd web server +# Start using ./script/server lighttpd + +server.bind = "0.0.0.0" +server.port = 3000 + +server.modules = ( "mod_rewrite", "mod_accesslog", "mod_fastcgi", "mod_compress", "mod_expire" ) + +server.error-handler-404 = "/dispatch.fcgi" +server.pid-file = CWD + "/tmp/pids/lighttpd.pid" +server.document-root = CWD + "/public/" + +server.errorlog = CWD + "/log/lighttpd.error.log" +accesslog.filename = CWD + "/log/lighttpd.access.log" + +url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" ) + +compress.filetype = ( "text/plain", "text/html", "text/css", "text/javascript" ) +compress.cache-dir = CWD + "/tmp/cache" + +expire.url = ( "/favicon.ico" => "access 3 days", + "/images/" => "access 3 days", + "/stylesheets/" => "access 3 days", + "/javascripts/" => "access 3 days" ) + + +# Change *-procs to 2 if you need to use Upload Progress or other tasks that +# *need* to execute a second request while the first is still pending. +fastcgi.server = ( ".fcgi" => ( "localhost" => ( + "min-procs" => 1, + "max-procs" => 1, + "socket" => CWD + "/tmp/sockets/fcgi.socket", + "bin-path" => CWD + "/public/dispatch.fcgi", + "bin-environment" => ( "RAILS_ENV" => "development" ) +) ) ) + +mimetype.assign = ( + ".css" => "text/css", + ".gif" => "image/gif", + ".htm" => "text/html", + ".html" => "text/html", + ".jpeg" => "image/jpeg", + ".jpg" => "image/jpeg", + ".js" => "text/javascript", + ".png" => "image/png", + ".swf" => "application/x-shockwave-flash", + ".txt" => "text/plain" +) + +# Making sure file uploads above 64k always work when using IE or Safari +# For more information, see http://trac.lighttpd.net/trac/ticket/360 +$HTTP["useragent"] =~ "^(.*MSIE.*)|(.*AppleWebKit.*)$" { + server.max-keep-alive-requests = 0 +} diff --git a/vendor/rails/railties/configs/routes.rb b/vendor/rails/railties/configs/routes.rb new file mode 100644 index 0000000..27fcae8 --- /dev/null +++ b/vendor/rails/railties/configs/routes.rb @@ -0,0 +1,22 @@ +ActionController::Routing::Routes.draw do |map| + # The priority is based upon order of creation: first created -> highest priority. + + # Sample of regular route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' + # This route can be invoked with purchase_url(:id => product.id) + + # You can have the root of your site routed by hooking up '' + # -- just remember to delete public/index.html. + # map.connect '', :controller => "welcome" + + # Allow downloading Web Service WSDL as a file with an extension + # instead of a file named 'wsdl' + map.connect ':controller/service.wsdl', :action => 'wsdl' + + # Install the default route as the lowest priority. + map.connect ':controller/:action/:id' +end diff --git a/vendor/rails/railties/dispatches/dispatch.fcgi b/vendor/rails/railties/dispatches/dispatch.fcgi new file mode 100755 index 0000000..65188f3 --- /dev/null +++ b/vendor/rails/railties/dispatches/dispatch.fcgi @@ -0,0 +1,24 @@ +#!/usr/local/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/vendor/rails/railties/dispatches/dispatch.rb b/vendor/rails/railties/dispatches/dispatch.rb new file mode 100755 index 0000000..9b5ae76 --- /dev/null +++ b/vendor/rails/railties/dispatches/dispatch.rb @@ -0,0 +1,10 @@ +#!/usr/local/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/vendor/rails/railties/dispatches/gateway.cgi b/vendor/rails/railties/dispatches/gateway.cgi new file mode 100644 index 0000000..d21bf09 --- /dev/null +++ b/vendor/rails/railties/dispatches/gateway.cgi @@ -0,0 +1,97 @@ +#!/usr/local/bin/ruby + +require 'drb' + +# This file includes an experimental gateway CGI implementation. It will work +# only on platforms which support both fork and sockets. +# +# To enable it edit public/.htaccess and replace dispatch.cgi with gateway.cgi. +# +# Next, create the directory log/drb_gateway and grant the apache user rw access +# to said directory. +# +# On the next request to your server, the gateway tracker should start up, along +# with a few listener processes. This setup should provide you with much better +# speeds than dispatch.cgi. +# +# Keep in mind that the first request made to the server will be slow, as the +# tracker and listeners will have to load. Also, the tracker and listeners will +# shutdown after a period if inactivity. You can set this value below -- the +# default is 90 seconds. + +TrackerSocket = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway/tracker.sock')) +DieAfter = 90 # Seconds +Listeners = 3 + +def message(s) + $stderr.puts "gateway.cgi: #{s}" if ENV && ENV["DEBUG_GATEWAY"] +end + +def listener_socket(number) + File.expand_path(File.join(File.dirname(__FILE__), "../log/drb_gateway/listener_#{number}.sock")) +end + +unless File.exists? TrackerSocket + message "Starting tracker and #{Listeners} listeners" + fork do + Process.setsid + STDIN.reopen "/dev/null" + STDOUT.reopen "/dev/null", "a" + + root = File.expand_path(File.dirname(__FILE__) + '/..') + + message "starting tracker" + fork do + ARGV.clear + ARGV << TrackerSocket << Listeners.to_s << DieAfter.to_s + load File.join(root, 'script', 'tracker') + end + + message "starting listeners" + require File.join(root, 'config/environment.rb') + Listeners.times do |number| + fork do + ARGV.clear + ARGV << listener_socket(number) << DieAfter.to_s + load File.join(root, 'script', 'listener') + end + end + end + + message "waiting for tracker and listener to arise..." + ready = false + 10.times do + sleep 0.5 + break if (ready = File.exists?(TrackerSocket) && File.exists?(listener_socket(0))) + end + + if ready + message "tracker and listener are ready" + else + message "Waited 5 seconds, listener and tracker not ready... dropping request" + Kernel.exit 1 + end +end + +DRb.start_service + +message "connecting to tracker" +tracker = DRbObject.new_with_uri("drbunix:#{TrackerSocket}") + +input = $stdin.read +$stdin.close + +env = ENV.inspect + +output = nil +tracker.with_listener do |number| + message "connecting to listener #{number}" + socket = listener_socket(number) + listener = DRbObject.new_with_uri("drbunix:#{socket}") + output = listener.process(env, input) + message "listener #{number} has finished, writing output" +end + +$stdout.write output +$stdout.flush +$stdout.close \ No newline at end of file diff --git a/vendor/rails/railties/doc/README_FOR_APP b/vendor/rails/railties/doc/README_FOR_APP new file mode 100644 index 0000000..ac6c149 --- /dev/null +++ b/vendor/rails/railties/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. \ No newline at end of file diff --git a/vendor/rails/railties/environments/boot.rb b/vendor/rails/railties/environments/boot.rb new file mode 100644 index 0000000..9a094cb --- /dev/null +++ b/vendor/rails/railties/environments/boot.rb @@ -0,0 +1,44 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + + unless RUBY_PLATFORM =~ /mswin32/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath(true).to_s + end + + RAILS_ROOT = root_path +end + +unless defined?(Rails::Initializer) + if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + else + require 'rubygems' + + environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join + environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/ + rails_gem_version = $1 + + if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version + rails_gem = Gem.cache.search('rails', "=#{version}").first + + if rails_gem + require_gem "rails", "=#{version}" + require rails_gem.full_gem_path + '/lib/initializer' + else + STDERR.puts %(Cannot find gem for Rails =#{version}: + Install the missing gem with 'gem install -v=#{version} rails', or + change environment.rb to define RAILS_GEM_VERSION with your desired version. + ) + exit 1 + end + else + require_gem "rails" + require 'initializer' + end + end + + Rails::Initializer.run(:set_load_path) +end \ No newline at end of file diff --git a/vendor/rails/railties/environments/development.rb b/vendor/rails/railties/environments/development.rb new file mode 100644 index 0000000..0589aa9 --- /dev/null +++ b/vendor/rails/railties/environments/development.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false +config.action_view.debug_rjs = true + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/vendor/rails/railties/environments/environment.rb b/vendor/rails/railties/environments/environment.rb new file mode 100644 index 0000000..ae63a4a --- /dev/null +++ b/vendor/rails/railties/environments/environment.rb @@ -0,0 +1,53 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present +<%= '# ' if freeze %>RAILS_GEM_VERSION = '<%= Rails::VERSION::STRING %>' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence those specified here + + # Skip frameworks you're not going to use (only works if using vendor/rails) + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # See Rails::Configuration for more options +end + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# Include your application configuration below \ No newline at end of file diff --git a/vendor/rails/railties/environments/production.rb b/vendor/rails/railties/environments/production.rb new file mode 100644 index 0000000..5a4e2b1 --- /dev/null +++ b/vendor/rails/railties/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors if you bad email addresses should just be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/vendor/rails/railties/environments/test.rb b/vendor/rails/railties/environments/test.rb new file mode 100644 index 0000000..f0689b9 --- /dev/null +++ b/vendor/rails/railties/environments/test.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Tell ActionMailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test \ No newline at end of file diff --git a/vendor/rails/railties/fresh_rakefile b/vendor/rails/railties/fresh_rakefile new file mode 100755 index 0000000..3bb0e85 --- /dev/null +++ b/vendor/rails/railties/fresh_rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/vendor/rails/railties/helpers/application.rb b/vendor/rails/railties/helpers/application.rb new file mode 100644 index 0000000..3bef7f5 --- /dev/null +++ b/vendor/rails/railties/helpers/application.rb @@ -0,0 +1,7 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + # Pick a unique cookie name to distinguish our session data from others' + session :session_key => '_<%= app_name %>_session_id' +end diff --git a/vendor/rails/railties/helpers/application_helper.rb b/vendor/rails/railties/helpers/application_helper.rb new file mode 100644 index 0000000..22a7940 --- /dev/null +++ b/vendor/rails/railties/helpers/application_helper.rb @@ -0,0 +1,3 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end diff --git a/vendor/rails/railties/helpers/test_helper.rb b/vendor/rails/railties/helpers/test_helper.rb new file mode 100644 index 0000000..a299c7f --- /dev/null +++ b/vendor/rails/railties/helpers/test_helper.rb @@ -0,0 +1,28 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... +end diff --git a/vendor/rails/railties/html/404.html b/vendor/rails/railties/html/404.html new file mode 100644 index 0000000..0e18456 --- /dev/null +++ b/vendor/rails/railties/html/404.html @@ -0,0 +1,8 @@ + + + +

      File not found

      +

      Change this error message for pages not found in public/404.html

      + + \ No newline at end of file diff --git a/vendor/rails/railties/html/500.html b/vendor/rails/railties/html/500.html new file mode 100644 index 0000000..ab95f74 --- /dev/null +++ b/vendor/rails/railties/html/500.html @@ -0,0 +1,8 @@ + + + +

      Application error

      +

      Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html

      + + \ No newline at end of file diff --git a/vendor/rails/railties/html/favicon.ico b/vendor/rails/railties/html/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rails/railties/html/images/rails.png b/vendor/rails/railties/html/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/vendor/rails/railties/html/images/rails.png differ diff --git a/vendor/rails/railties/html/index.html b/vendor/rails/railties/html/index.html new file mode 100644 index 0000000..a2daab7 --- /dev/null +++ b/vendor/rails/railties/html/index.html @@ -0,0 +1,277 @@ + + + + + Ruby on Rails: Welcome aboard + + + + + + +
      + + +
      + + + + +
      +

      Getting started

      +

      Here’s how to get rolling:

      + +
        +
      1. +

        Create your databases and edit config/database.yml

        +

        Rails needs to know your login and password.

        +
      2. + +
      3. +

        Use script/generate to create your models and controllers

        +

        To see all available options, run it without parameters.

        +
      4. + +
      5. +

        Set up a default route and remove or rename this file

        +

        Routes are setup in config/routes.rb.

        +
      6. +
      +
      +
      + + +
      + + \ No newline at end of file diff --git a/vendor/rails/railties/html/javascripts/application.js b/vendor/rails/railties/html/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/vendor/rails/railties/html/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/vendor/rails/railties/html/javascripts/controls.js b/vendor/rails/railties/html/javascripts/controls.js new file mode 100644 index 0000000..de0261e --- /dev/null +++ b/vendor/rails/railties/html/javascripts/controls.js @@ -0,0 +1,815 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// See scriptaculous.js for full license. + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if (this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if (typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.firstChild); + + if(this.update.firstChild && this.update.firstChild.childNodes) { + this.entryCount = + this.update.firstChild.childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
    • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
    • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
    • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
    • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
        " + ret.join('') + "
      "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
      /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
      /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

      /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = "value"; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/vendor/rails/railties/html/javascripts/dragdrop.js b/vendor/rails/railties/html/javascripts/dragdrop.js new file mode 100644 index 0000000..a01b7be --- /dev/null +++ b/vendor/rails/railties/html/javascripts/dragdrop.js @@ -0,0 +1,913 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// See scriptaculous.js for full license. + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) + this.last_active.onDrop(element, this.last_active.element, event); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable.prototype = { + initialize: function(element) { + var options = Object.extend({ + handle: false, + starteffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); + }, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur}); + }, + endeffect: function(element) { + new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } + }, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) { + var h = Element.childrenWithClassName(this.element, options.handle, true); + if(h.length>0) this.handle = h[0]; + } + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) + options.scroll = $(options.scroll); + + Element.makePositioned(this.element); // fix IE + + this.delta = this.currentDelta(); + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + if(this.element._revert) { + this.element._revert.cancel(); + this.element._revert = null; + } + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + Position.prepare(); + Droppables.show(pointer, this.element); + Draggables.notify('onDrag', this, event); + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft; + p[1] += this.options.scroll.scrollTop; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1]); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +var Sortable = { + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: /^[^_]*_(.*)$/, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + //greedy: !options.dropOnEmpty + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.childrenWithClassName(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.addClassName(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.left = offsets[0] + 'px'; + Sortable._marker.style.top = offsets[1] + 'px'; + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; + else + Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + + Element.show(Sortable._marker); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: new Array, + position: parent.children.length, + container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + /* Finds the first element of the given tag type within a parent element. + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ + _findChildrenElement: function (element, containerTag) { + if (element && element.hasChildNodes) + for (var i = 0; i < element.childNodes.length; ++i) + if (element.childNodes[i].tagName == containerTag) + return element.childNodes[i]; + + return null; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: new Array, + container: element, + position: 0 + } + + return Sortable._tree (element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +/* Returns true if child is contained within element */ +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + + if (child.parentNode == element) return true; + + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + if (type == 'vertical' || type == 'height') + return element.offsetHeight; + else + return element.offsetWidth; +} \ No newline at end of file diff --git a/vendor/rails/railties/html/javascripts/effects.js b/vendor/rails/railties/html/javascripts/effects.js new file mode 100644 index 0000000..9274005 --- /dev/null +++ b/vendor/rails/railties/html/javascripts/effects.js @@ -0,0 +1,958 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : null }); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className, findFirst) { + var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); + var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { + return (c.className && c.className.match(classNameRegExp)); + }); + if(!results) results = []; + return results; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: this.options.x * position + this.originalLeft + 'px', + top: this.options.y * position + this.originalTop + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = width + 'px'; + if(this.options.scaleY) d.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide(); + effect.element.setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from); + effect.element.show(); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + effect.effects[0].element.setStyle({position: 'absolute'}); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned(); + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.undoPositioned(); + effect.element.setStyle({opacity: oldOpacity}); + } + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned(); + effect.element.setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + element.cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + // IE will crash if child is undoPositioned first + if(/MSIE/.test(navigator.userAgent)){ + effect.element.undoPositioned(); + effect.element.firstChild.undoPositioned(); + }else{ + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + } + effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + element.cleanWhitespace(); + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + effect.element.setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + effect.element.hide(effect.element); + effect.element.undoClipping(effect.element); } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide(); + effect.element.makeClipping(); + effect.element.makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}); + effect.effects[0].element.show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned(); + effect.effects[0].element.makeClipping(); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); } + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/vendor/rails/railties/html/javascripts/prototype.js b/vendor/rails/railties/html/javascripts/prototype.js new file mode 100644 index 0000000..5ba3a30 --- /dev/null +++ b/vendor/rails/railties/html/javascripts/prototype.js @@ -0,0 +1,2012 @@ +/* Prototype JavaScript framework, version 1.5.0_rc0 + * (c) 2005 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0_rc0', + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += (replacement(match) || '').toString(); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + (object[match[3]] || '').toString(); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + /* Simulate other verbs over post */ + if (this.options.method != 'get' && this.options.method != 'post') { + parameters += (parameters.length > 0 ? '&' : '') + '_method=' + this.options.method + this.options.method = 'post' + } + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version, + 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', this.options.contentType); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval('(' + this.header('X-JSON') + ')'); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $() { + var results = [], element; + for (var i = 0; i < arguments.length; i++) { + element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + results.push(Element.extend(element)); + } + return results.length < 2 ? results[0] : results; +} + +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(Element.extend(child)); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element) return; + if (_nativeExtensions) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Element.Methods, cache = Element.extend.cache; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +} + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +} + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + replace: function(element, html) { + element = $(element); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + childOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +} + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + var HTMLElement = {} + HTMLElement.prototype = document.createElement('div').__proto__; +} + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + if(typeof HTMLElement != 'undefined') { + var methods = Element.Methods, cache = Element.extend.cache; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + HTMLElement.prototype[property] = cache.findOrStore(value); + } + _nativeExtensions = true; + } +} + +Element.addMethods(); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toLowerCase(); + if (tagName == 'tbody' || tagName == 'tr') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
      '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.id == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0; i < clause.length; i++) + conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push(value + ' != null'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0; i < scope.length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +function $$() { + return $A(arguments).map(function(expression) { + return expression.strip().split(/\s+/).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.map(selector.findElements.bind(selector)).flatten(); + }); + }).flatten(); +} +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (var tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value || opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = []; + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) + value.push(opt.value || opt.text); + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} \ No newline at end of file diff --git a/vendor/rails/railties/html/robots.txt b/vendor/rails/railties/html/robots.txt new file mode 100644 index 0000000..4ab9e89 --- /dev/null +++ b/vendor/rails/railties/html/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/vendor/rails/railties/lib/binding_of_caller.rb b/vendor/rails/railties/lib/binding_of_caller.rb new file mode 100644 index 0000000..c1f2cc7 --- /dev/null +++ b/vendor/rails/railties/lib/binding_of_caller.rb @@ -0,0 +1,85 @@ +begin + require 'simplecc' +rescue LoadError + # to satisfy rdoc + class Continuation #:nodoc: + 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 diff --git a/vendor/rails/railties/lib/breakpoint.rb b/vendor/rails/railties/lib/breakpoint.rb new file mode 100644 index 0000000..60f7fdb --- /dev/null +++ b/vendor/rails/railties/lib/breakpoint.rb @@ -0,0 +1,547 @@ +# 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 '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(#):001:0> local_variables + # => ["name", "age", "_", "__"] + # irb(#):002:0> [name, age] + # => ["Random Person", 23] + # irb(#):003:0> [@name, @age] + # => ["Random Person", 23] + # irb(#):004:0> self + # => # + # irb(#):005:0> @age += 1; self + # => # + # irb(#):006:0> exit + # Executing break point "Person#name" at file.rb:9 in `name' + # irb(#):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 + # Proxy to a Breakpoint client. Lets you directly execute code + # in the context of the client. + class Client + 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 + + # Prints the source code surrounding the location where the + # breakpoint was issued. + def show_source_list(context = 5) + start_line, break_line, result = source_lines(context, true) + offset = [(break_line + context).to_s.length, 4].max + result.each_with_index do |line, i| + mark = (start_line + i == break_line ? '->' : ' ') + client.puts("%0#{offset}d%s#{line}" % [start_line + i, mark]) + end + Pathname.new(@__bp_file).cleanpath.to_s + end + + # Prints the call stack. + def show_call_stack(depth = 10) + base = Pathname.new(RAILS_ROOT).cleanpath.to_s + caller[1..depth].each do |line| + line.sub!(/^[^:]*/) do |path| + Pathname.new(path).cleanpath.to_s + end + client.puts(line.index(base) == 0 ? line[(base.length + 1)..-1] : line) + end + "#{Pathname.new(@__bp_file).cleanpath.to_s}:#{@__bp_line}" + 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 + 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 diff --git a/vendor/rails/railties/lib/breakpoint_client.rb b/vendor/rails/railties/lib/breakpoint_client.rb new file mode 100644 index 0000000..9273321 --- /dev/null +++ b/vendor/rails/railties/lib/breakpoint_client.rb @@ -0,0 +1,196 @@ +require 'breakpoint' +require 'optparse' +require 'timeout' + +Options = { + :ClientURI => nil, + :ServerURI => "druby://localhost:42531", + :RetryDelay => 2, + :Permanent => true, + :Verbose => false +} + +ARGV.options do |opts| + script_name = File.basename($0) + opts.banner = [ + "Usage: ruby #{script_name} [Options] [server uri]", + "", + "This tool lets you connect to a breakpoint service ", + "which was started via Breakpoint.activate_drb.", + "", + "The server uri defaults to druby://localhost:42531" + ].join("\n") + + opts.separator "" + + opts.on("-c", "--client-uri=uri", + "Run the client on the specified uri.", + "This can be used to specify the port", + "that the client uses to allow for back", + "connections from the server.", + "Default: Find a good URI automatically.", + "Example: -c druby://localhost:12345" + ) { |v| Options[:ClientURI] = v } + + opts.on("-s", "--server-uri=uri", + "Connect to the server specified at the", + "specified uri.", + "Default: druby://localhost:42531" + ) { |v| Options[:ServerURI] = v } + + opts.on("-R", "--retry-delay=delay", Integer, + "Automatically try to reconnect to the", + "server after delay seconds when the", + "connection failed or timed out.", + "A value of 0 disables automatical", + "reconnecting completely.", + "Default: 10" + ) { |v| Options[:RetryDelay] = v } + + opts.on("-P", "--[no-]permanent", + "Run the breakpoint client in permanent mode.", + "This means that the client will keep continue", + "running even after the server has closed the", + "connection. Useful for example in Rails." + ) { |v| Options[:Permanent] = v } + + opts.on("-V", "--[no-]verbose", + "Run the breakpoint client in verbose mode.", + "Will produce more messages, for example between", + "individual breakpoints. This might help in seeing", + "that the breakpoint client is still alive, but adds", + "quite a bit of clutter." + ) { |v| Options[:Verbose] = v } + + opts.separator "" + + opts.on("-h", "--help", + "Show this help message." + ) { puts opts; exit } + opts.on("-v", "--version", + "Display the version information." + ) do + id = %q$Id: breakpoint_client.rb 91 2005-02-04 22:34:08Z flgr $ + puts id.sub("Id: ", "") + puts "(Breakpoint::Version = #{Breakpoint::Version})" + exit + end + + opts.parse! +end + +Options[:ServerURI] = ARGV[0] if ARGV[0] + +module Handlers #:nodoc: + extend self + + def breakpoint_handler(workspace, message) + puts message + IRB.start(nil, nil, workspace) + + puts "" + if Options[:Verbose] then + puts "Resumed execution. Waiting for next breakpoint...", "" + end + end + + def eval_handler(code) + result = eval(code, TOPLEVEL_BINDING) + if result then + DRbObject.new(result) + else + result + end + end + + def collision_handler() + msg = [ + " *** Breakpoint service collision ***", + " Another Breakpoint service tried to use the", + " port already occupied by this one. It will", + " keep waiting until this Breakpoint service", + " is shut down.", + " ", + " If you are using the Breakpoint library for", + " debugging a Rails or other CGI application", + " this likely means that this Breakpoint", + " session belongs to an earlier, outdated", + " request and should be shut down via 'exit'." + ].join("\n") + + if RUBY_PLATFORM["win"] then + # This sucks. Sorry, I'm not doing this because + # I like funky message boxes -- I need to do this + # because on Windows I have no way of displaying + # my notification via puts() when gets() is still + # being performed on STDIN. I have not found a + # better solution. + begin + require 'tk' + root = TkRoot.new { withdraw } + Tk.messageBox('message' => msg, 'type' => 'ok') + root.destroy + rescue Exception + puts "", msg, "" + end + else + puts "", msg, "" + end + end +end + +# Used for checking whether we are currently in the reconnecting loop. +reconnecting = false + +loop do + DRb.start_service(Options[:ClientURI]) + + begin + service = DRbObject.new(nil, Options[:ServerURI]) + + begin + ehandler = Handlers.method(:eval_handler) + chandler = Handlers.method(:collision_handler) + handler = Handlers.method(:breakpoint_handler) + service.eval_handler = ehandler + service.collision_handler = chandler + service.handler = handler + + reconnecting = false + if Options[:Verbose] then + puts "Connection established. Waiting for breakpoint...", "" + end + + loop do + begin + service.ping + rescue DRb::DRbConnError => error + puts "Server exited. Closing connection...", "" + exit! unless Options[:Permanent] + break + end + + sleep(0.5) + end + ensure + service.eval_handler = nil + service.collision_handler = nil + service.handler = nil + end + rescue Exception => error + if Options[:RetryDelay] > 0 then + if not reconnecting then + reconnecting = true + puts "No connection to breakpoint service at #{Options[:ServerURI]} " + + "(#{error.class})" + puts error.backtrace if $DEBUG + puts "Tries to connect will be made every #{Options[:RetryDelay]} seconds..." + end + + sleep Options[:RetryDelay] + retry + else + raise + end + end +end diff --git a/vendor/rails/railties/lib/code_statistics.rb b/vendor/rails/railties/lib/code_statistics.rb new file mode 100644 index 0000000..e99d876 --- /dev/null +++ b/vendor/rails/railties/lib/code_statistics.rb @@ -0,0 +1,107 @@ +class CodeStatistics #:nodoc: + + TEST_TYPES = %w(Units Functionals Unit\ tests Functional\ tests Integration\ tests) + + def initialize(*pairs) + @pairs = pairs + @statistics = calculate_statistics + @total = calculate_total if pairs.length > 1 + end + + def to_s + print_header + @pairs.each { |pair| print_line(pair.first, @statistics[pair.first]) } + print_splitter + + if @total + print_line("Total", @total) + print_splitter + end + + print_code_test_stats + end + + private + def calculate_statistics + @pairs.inject({}) { |stats, pair| stats[pair.first] = calculate_directory_statistics(pair.last); stats } + end + + def calculate_directory_statistics(directory, pattern = /.*\.rb$/) + stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 } + + Dir.foreach(directory) do |file_name| + if File.stat(directory + "/" + file_name).directory? and (/^\./ !~ file_name) + newstats = calculate_directory_statistics(directory + "/" + file_name, pattern) + stats.each { |k, v| stats[k] += newstats[k] } + end + + next unless file_name =~ pattern + + f = File.open(directory + "/" + file_name) + + while line = f.gets + stats["lines"] += 1 + stats["classes"] += 1 if line =~ /class [A-Z]/ + stats["methods"] += 1 if line =~ /def [a-z]/ + stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/ + end + end + + stats + end + + def calculate_total + total = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 } + @statistics.each_value { |pair| pair.each { |k, v| total[k] += v } } + total + end + + def calculate_code + code_loc = 0 + @statistics.each { |k, v| code_loc += v['codelines'] unless TEST_TYPES.include? k } + code_loc + end + + def calculate_tests + test_loc = 0 + @statistics.each { |k, v| test_loc += v['codelines'] if TEST_TYPES.include? k } + test_loc + end + + def print_header + print_splitter + puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" + print_splitter + end + + def print_splitter + puts "+----------------------+-------+-------+---------+---------+-----+-------+" + end + + def print_line(name, statistics) + m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0 + loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0 + + start = if TEST_TYPES.include? name + "| #{name.ljust(18)} " + else + "| #{name.ljust(20)} " + end + + puts start + + "| #{statistics["lines"].to_s.rjust(5)} " + + "| #{statistics["codelines"].to_s.rjust(5)} " + + "| #{statistics["classes"].to_s.rjust(7)} " + + "| #{statistics["methods"].to_s.rjust(7)} " + + "| #{m_over_c.to_s.rjust(3)} " + + "| #{loc_over_m.to_s.rjust(5)} |" + end + + def print_code_test_stats + code = calculate_code + tests = calculate_tests + + puts " Code LOC: #{code} Test LOC: #{tests} Code to Test Ratio: 1:#{sprintf("%.1f", tests.to_f/code)}" + puts "" + end + end diff --git a/vendor/rails/railties/lib/commands.rb b/vendor/rails/railties/lib/commands.rb new file mode 100644 index 0000000..841e98a --- /dev/null +++ b/vendor/rails/railties/lib/commands.rb @@ -0,0 +1,17 @@ +commands = Dir["#{File.dirname(__FILE__)}/commands/*.rb"].collect { |file_path| File.basename(file_path).split(".").first } + +if commands.include?(ARGV.first) + require "#{File.dirname(__FILE__)}/commands/#{ARGV.shift}" +else + puts <<-USAGE +The 'run' provides a unified access point for all the default Rails' commands. + +Usage: ./script/run [OPTIONS] + +Examples: + ./script/run generate controller Admin + ./script/run process reaper + +USAGE + puts "Choose: #{commands.join(", ")}" +end \ No newline at end of file diff --git a/vendor/rails/railties/lib/commands/about.rb b/vendor/rails/railties/lib/commands/about.rb new file mode 100644 index 0000000..313bc18 --- /dev/null +++ b/vendor/rails/railties/lib/commands/about.rb @@ -0,0 +1,2 @@ +require 'environment' +puts Rails::Info diff --git a/vendor/rails/railties/lib/commands/breakpointer.rb b/vendor/rails/railties/lib/commands/breakpointer.rb new file mode 100644 index 0000000..cc52010 --- /dev/null +++ b/vendor/rails/railties/lib/commands/breakpointer.rb @@ -0,0 +1 @@ +require 'breakpoint_client' diff --git a/vendor/rails/railties/lib/commands/console.rb b/vendor/rails/railties/lib/commands/console.rb new file mode 100644 index 0000000..8b35a8d --- /dev/null +++ b/vendor/rails/railties/lib/commands/console.rb @@ -0,0 +1,25 @@ +irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb' + +require 'optparse' +options = { :sandbox => false, :irb => irb } +OptionParser.new do |opt| + opt.banner = "Usage: console [environment] [options]" + opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v } + opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |v| options[:irb] = v } + opt.parse!(ARGV) +end + +libs = " -r irb/completion" +libs << " -r #{RAILS_ROOT}/config/environment" +libs << " -r console_app" +libs << " -r console_sandbox" if options[:sandbox] +libs << " -r console_with_helpers" + +ENV['RAILS_ENV'] = ARGV.first || ENV['RAILS_ENV'] || 'development' +if options[:sandbox] + puts "Loading #{ENV['RAILS_ENV']} environment in sandbox." + puts "Any modifications you make will be rolled back on exit." +else + puts "Loading #{ENV['RAILS_ENV']} environment." +end +exec "#{options[:irb]} #{libs} --simple-prompt" diff --git a/vendor/rails/railties/lib/commands/destroy.rb b/vendor/rails/railties/lib/commands/destroy.rb new file mode 100644 index 0000000..f4b81d6 --- /dev/null +++ b/vendor/rails/railties/lib/commands/destroy.rb @@ -0,0 +1,6 @@ +require "#{RAILS_ROOT}/config/environment" +require 'rails_generator' +require 'rails_generator/scripts/destroy' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +Rails::Generator::Scripts::Destroy.new.run(ARGV) diff --git a/vendor/rails/railties/lib/commands/generate.rb b/vendor/rails/railties/lib/commands/generate.rb new file mode 100755 index 0000000..3d3db3d --- /dev/null +++ b/vendor/rails/railties/lib/commands/generate.rb @@ -0,0 +1,6 @@ +require "#{RAILS_ROOT}/config/environment" +require 'rails_generator' +require 'rails_generator/scripts/generate' + +ARGV.shift if ['--help', '-h'].include?(ARGV[0]) +Rails::Generator::Scripts::Generate.new.run(ARGV) diff --git a/vendor/rails/railties/lib/commands/ncgi/listener b/vendor/rails/railties/lib/commands/ncgi/listener new file mode 100644 index 0000000..421c453 --- /dev/null +++ b/vendor/rails/railties/lib/commands/ncgi/listener @@ -0,0 +1,86 @@ +#!/usr/local/bin/ruby + +require 'stringio' +require 'fileutils' +require 'fcgi_handler' + +def message(s) + $stderr.puts "listener: #{s}" if ENV && ENV["DEBUG_GATEWAY"] +end + +class RemoteCGI < CGI + attr_accessor :stdinput, :stdoutput, :env_table + def initialize(env_table, input = nil, output = nil) + self.env_table = env_table + self.stdinput = input || StringIO.new + self.stdoutput = output || StringIO.new + super() + end + + def out(stream) # Ignore the requested output stream + super(stdoutput) + end +end + +class Listener + include DRbUndumped + + def initialize(timeout, socket_path) + @socket = File.expand_path(socket_path) + @mutex = Mutex.new + @active = false + @timeout = timeout + + @handler = RailsFCGIHandler.new + @handler.extend DRbUndumped + + message 'opening socket' + DRb.start_service("drbunix:#{@socket}", self) + + message 'entering process loop' + @handler.process! self + end + + def each_cgi(&cgi_block) + @cgi_block = cgi_block + message 'entering idle loop' + loop do + sleep @timeout rescue nil + die! unless @active + @active = false + end + end + + def process(env, input) + message 'received request' + @mutex.synchronize do + @active = true + + message 'creating input stream' + input_stream = StringIO.new(input) + message 'building CGI instance' + cgi = RemoteCGI.new(eval(env), input_stream) + + message 'yielding to fcgi handler' + @cgi_block.call cgi + message 'yield finished -- sending output' + + cgi.stdoutput.seek(0) + output = cgi.stdoutput.read + + return output + end + end + + def die! + message 'shutting down' + DRb.stop_service + FileUtils.rm_f @socket + Kernel.exit 0 + end +end + +socket_path = ARGV.shift +timeout = (ARGV.shift || 90).to_i + +Listener.new(timeout, socket_path) \ No newline at end of file diff --git a/vendor/rails/railties/lib/commands/ncgi/tracker b/vendor/rails/railties/lib/commands/ncgi/tracker new file mode 100644 index 0000000..859c9fa --- /dev/null +++ b/vendor/rails/railties/lib/commands/ncgi/tracker @@ -0,0 +1,69 @@ +#!/usr/local/bin/ruby + +require 'drb' +require 'thread' + +def message(s) + $stderr.puts "tracker: #{s}" if ENV && ENV["DEBUG_GATEWAY"] +end + +class Tracker + include DRbUndumped + + def initialize(instances, socket_path) + @instances = instances + @socket = File.expand_path(socket_path) + @active = false + + @listeners = [] + @instances.times { @listeners << Mutex.new } + + message "using #{@listeners.length} listeners" + message "opening socket at #{@socket}" + + @service = DRb.start_service("drbunix://#{@socket}", self) + end + + def with_listener + message "listener requested" + + mutex = has_lock = index = nil + 3.times do + @listeners.each_with_index do |mutex, index| + has_lock = mutex.try_lock + break if has_lock + end + break if has_lock + sleep 0.05 + end + + if has_lock + message "obtained listener #{index}" + @active = true + begin yield index + ensure + mutex.unlock + message "released listener #{index}" + end + else + message "dropping request because no listeners are available!" + end + end + + def background(check_interval = nil) + if check_interval + loop do + sleep check_interval + message "Idle for #{check_interval}, shutting down" unless @active + @active = false + Kernel.exit 0 + end + else DRb.thread.join + end + end +end + +socket_path = ARGV.shift +instances = ARGV.shift.to_i +t = Tracker.new(instances, socket_path) +t.background(ARGV.first ? ARGV.shift.to_i : 90) \ No newline at end of file diff --git a/vendor/rails/railties/lib/commands/performance/benchmarker.rb b/vendor/rails/railties/lib/commands/performance/benchmarker.rb new file mode 100644 index 0000000..e8804fe --- /dev/null +++ b/vendor/rails/railties/lib/commands/performance/benchmarker.rb @@ -0,0 +1,24 @@ +if ARGV.empty? + puts "Usage: ./script/performance/benchmarker [times] 'Person.expensive_way' 'Person.another_expensive_way' ..." + exit 1 +end + +begin + N = Integer(ARGV.first) + ARGV.shift +rescue ArgumentError + N = 1 +end + +require RAILS_ROOT + '/config/environment' +require 'benchmark' +include Benchmark + +# Don't include compilation in the benchmark +ARGV.each { |expression| eval(expression) } + +bm(6) do |x| + ARGV.each_with_index do |expression, idx| + x.report("##{idx + 1}") { N.times { eval(expression) } } + end +end diff --git a/vendor/rails/railties/lib/commands/performance/profiler.rb b/vendor/rails/railties/lib/commands/performance/profiler.rb new file mode 100644 index 0000000..464cea3 --- /dev/null +++ b/vendor/rails/railties/lib/commands/performance/profiler.rb @@ -0,0 +1,50 @@ +if ARGV.empty? + $stderr.puts "Usage: ./script/performance/profiler 'Person.expensive_method(10)' [times] [flat|graph|graph_html]" + exit(1) +end + +# Keep the expensive require out of the profile. +$stderr.puts 'Loading Rails...' +require RAILS_ROOT + '/config/environment' + +# Define a method to profile. +if ARGV[1] and ARGV[1].to_i > 1 + eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end" +else + eval "def profile_me() #{ARGV[0]} end" +end + +# Use the ruby-prof extension if available. Fall back to stdlib profiler. +begin + begin + require "ruby-prof" + $stderr.puts 'Using the ruby-prof extension.' + RubyProf.clock_mode = RubyProf::WALL_TIME + RubyProf.start + profile_me + results = RubyProf.stop + if ARGV[2] + printer_class = RubyProf.const_get((ARGV[2] + "_printer").classify) + else + printer_class = RubyProf::FlatPrinter + end + printer = printer_class.new(results) + printer.print($stderr, 0) + rescue LoadError + require "prof" + $stderr.puts 'Using the old ruby-prof extension.' + Prof.clock_mode = Prof::GETTIMEOFDAY + Prof.start + profile_me + results = Prof.stop + require 'rubyprof_ext' + Prof.print_profile(results, $stderr) + end +rescue LoadError + require 'profiler' + $stderr.puts 'Using the standard Ruby profiler.' + Profiler__.start_profile + profile_me + Profiler__.stop_profile + Profiler__.print_profile($stderr) +end diff --git a/vendor/rails/railties/lib/commands/plugin.rb b/vendor/rails/railties/lib/commands/plugin.rb new file mode 100644 index 0000000..48f2266 --- /dev/null +++ b/vendor/rails/railties/lib/commands/plugin.rb @@ -0,0 +1,917 @@ +# Rails Plugin Manager. +# +# Listing available plugins: +# +# $ ./script/plugin list +# continuous_builder http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder +# asset_timestamping http://svn.aviditybytes.com/rails/plugins/asset_timestamping +# enumerations_mixin http://svn.protocool.com/rails/plugins/enumerations_mixin/trunk +# calculations http://techno-weenie.net/svn/projects/calculations/ +# ... +# +# Installing plugins: +# +# $ ./script/plugin install continuous_builder asset_timestamping +# +# Finding Repositories: +# +# $ ./script/plugin discover +# +# Adding Repositories: +# +# $ ./script/plugin source http://svn.protocool.com/rails/plugins/ +# +# How it works: +# +# * Maintains a list of subversion repositories that are assumed to have +# a plugin directory structure. Manage them with the (source, unsource, +# and sources commands) +# +# * The discover command scrapes the following page for things that +# look like subversion repositories with plugins: +# http://wiki.rubyonrails.org/rails/pages/Plugins +# +# * Unless you specify that you want to use svn, script/plugin uses plain old +# HTTP for downloads. The following bullets are true if you specify +# that you want to use svn. +# +# * If `vendor/plugins` is under subversion control, the script will +# modify the svn:externals property and perform an update. You can +# use normal subversion commands to keep the plugins up to date. +# +# * Or, if `vendor/plugins` is not under subversion control, the +# plugin is pulled via `svn checkout` or `svn export` but looks +# exactly the same. +# +# This is Free Software, copyright 2005 by Ryan Tomayko (rtomayko@gmail.com) +# and is licensed MIT: (http://www.opensource.org/licenses/mit-license.php) + +$verbose = false + + +require 'open-uri' +require 'fileutils' +require 'tempfile' + +include FileUtils + +class RailsEnvironment + attr_reader :root + + def initialize(dir) + @root = dir + end + + def self.find(dir=nil) + dir ||= pwd + while dir.length > 1 + return new(dir) if File.exist?(File.join(dir, 'config', 'environment.rb')) + dir = File.dirname(dir) + end + end + + def self.default + @default ||= find + end + + def self.default=(rails_env) + @default = rails_env + end + + def install(name_uri_or_plugin) + if name_uri_or_plugin.is_a? String + if name_uri_or_plugin =~ /:\/\// + plugin = Plugin.new(name_uri_or_plugin) + else + plugin = Plugins[name_uri_or_plugin] + end + else + plugin = name_uri_or_plugin + end + unless plugin.nil? + plugin.install + else + puts "plugin not found: #{name_uri_or_plugin}" + end + end + + def use_svn? + require 'active_support/core_ext/kernel' + silence_stderr {`svn --version` rescue nil} + !$?.nil? && $?.success? + end + + def use_externals? + use_svn? && File.directory?("#{root}/vendor/plugins/.svn") + end + + def use_checkout? + # this is a bit of a guess. we assume that if the rails environment + # is under subversion then they probably want the plugin checked out + # instead of exported. This can be overridden on the command line + File.directory?("#{root}/.svn") + end + + def best_install_method + return :http unless use_svn? + case + when use_externals? then :externals + when use_checkout? then :checkout + else :export + end + end + + def externals + return [] unless use_externals? + ext = `svn propget svn:externals "#{root}/vendor/plugins"` + ext.reject{ |line| line.strip == '' }.map do |line| + line.strip.split(/\s+/, 2) + end + end + + def externals=(items) + unless items.is_a? String + items = items.map{|name,uri| "#{name.ljust(29)} #{uri.chomp('/')}"}.join("\n") + end + Tempfile.open("svn-set-prop") do |file| + file.write(items) + file.flush + system("svn propset -q svn:externals -F \"#{file.path}\" \"#{root}/vendor/plugins\"") + end + end + +end + +class Plugin + attr_reader :name, :uri + + def initialize(uri, name=nil) + @uri = uri + guess_name(uri) + end + + def self.find(name) + name =~ /\// ? new(name) : Repositories.instance.find_plugin(name) + end + + def to_s + "#{@name.ljust(30)}#{@uri}" + end + + def svn_url? + @uri =~ /svn(?:\+ssh)?:\/\/*/ + end + + def installed? + File.directory?("#{rails_env.root}/vendor/plugins/#{name}") \ + or rails_env.externals.detect{ |name, repo| self.uri == repo } + end + + def install(method=nil, options = {}) + method ||= rails_env.best_install_method? + method = :export if method == :http and svn_url? + + uninstall if installed? and options[:force] + + unless installed? + send("install_using_#{method}", options) + run_install_hook + else + puts "already installed: #{name} (#{uri}). pass --force to reinstall" + end + end + + def uninstall + path = "#{rails_env.root}/vendor/plugins/#{name}" + if File.directory?(path) + puts "Removing 'vendor/plugins/#{name}'" if $verbose + run_uninstall_hook + rm_r path + else + puts "Plugin doesn't exist: #{path}" + end + # clean up svn:externals + externals = rails_env.externals + externals.reject!{|n,u| name == n or name == u} + rails_env.externals = externals + end + + def info + tmp = "#{rails_env.root}/_tmp_about.yml" + if svn_url? + cmd = "svn export #{@uri} \"#{rails_env.root}/#{tmp}\"" + puts cmd if $verbose + system(cmd) + end + open(svn_url? ? tmp : File.join(@uri, 'about.yml')) do |stream| + stream.read + end rescue "No about.yml found in #{uri}" + ensure + FileUtils.rm_rf tmp if svn_url? + end + + private + + def run_install_hook + install_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/install.rb" + load install_hook_file if File.exists? install_hook_file + end + + def run_uninstall_hook + uninstall_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/uninstall.rb" + load uninstall_hook_file if File.exists? uninstall_hook_file + end + + def install_using_export(options = {}) + svn_command :export, options + end + + def install_using_checkout(options = {}) + svn_command :checkout, options + end + + def install_using_externals(options = {}) + externals = rails_env.externals + externals.push([@name, uri]) + rails_env.externals = externals + install_using_checkout(options) + end + + def install_using_http(options = {}) + root = rails_env.root + mkdir_p "#{root}/vendor/plugins" + Dir.chdir "#{root}/vendor/plugins" + puts "fetching from '#{uri}'" if $verbose + fetcher = RecursiveHTTPFetcher.new(uri) + fetcher.quiet = true if options[:quiet] + fetcher.fetch + end + + def svn_command(cmd, options = {}) + root = rails_env.root + mkdir_p "#{root}/vendor/plugins" + base_cmd = "svn #{cmd} #{uri} \"#{root}/vendor/plugins/#{name}\"" + base_cmd += ' -q' if options[:quiet] and not $verbose + base_cmd += " -r #{options[:revision]}" if options[:revision] + puts base_cmd if $verbose + system(base_cmd) + end + + def guess_name(url) + @name = File.basename(url) + if @name == 'trunk' || @name.empty? + @name = File.basename(File.dirname(url)) + end + end + + def rails_env + @rails_env || RailsEnvironment.default + end +end + +class Repositories + include Enumerable + + def initialize(cache_file = File.join(find_home, ".rails-plugin-sources")) + @cache_file = File.expand_path(cache_file) + load! + end + + def each(&block) + @repositories.each(&block) + end + + def add(uri) + unless find{|repo| repo.uri == uri } + @repositories.push(Repository.new(uri)).last + end + end + + def remove(uri) + @repositories.reject!{|repo| repo.uri == uri} + end + + def exist?(uri) + @repositories.detect{|repo| repo.uri == uri } + end + + def all + @repositories + end + + def find_plugin(name) + @repositories.each do |repo| + repo.each do |plugin| + return plugin if plugin.name == name + end + end + return nil + end + + def load! + contents = File.exist?(@cache_file) ? File.read(@cache_file) : defaults + contents = defaults if contents.empty? + @repositories = contents.split(/\n/).reject do |line| + line =~ /^\s*#/ or line =~ /^\s*$/ + end.map { |source| Repository.new(source.strip) } + end + + def save + File.open(@cache_file, 'w') do |f| + each do |repo| + f.write(repo.uri) + f.write("\n") + end + end + end + + def defaults + <<-DEFAULTS + http://dev.rubyonrails.com/svn/rails/plugins/ + DEFAULTS + end + + def find_home + ['HOME', 'USERPROFILE'].each do |homekey| + return ENV[homekey] if ENV[homekey] + end + if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] + return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" + end + begin + File.expand_path("~") + rescue StandardError => ex + if File::ALT_SEPARATOR + "C:/" + else + "/" + end + end + end + + def self.instance + @instance ||= Repositories.new + end + + def self.each(&block) + self.instance.each(&block) + end +end + +class Repository + include Enumerable + attr_reader :uri, :plugins + + def initialize(uri) + @uri = uri.chomp('/') << "/" + @plugins = nil + end + + def plugins + unless @plugins + if $verbose + puts "Discovering plugins in #{@uri}" + puts index + end + + @plugins = index.reject{ |line| line !~ /\/$/ } + @plugins.map! { |name| Plugin.new(File.join(@uri, name), name) } + end + + @plugins + end + + def each(&block) + plugins.each(&block) + end + + private + def index + @index ||= RecursiveHTTPFetcher.new(@uri).ls + end +end + + +# load default environment and parse arguments +require 'optparse' +module Commands + + class Plugin + attr_reader :environment, :script_name, :sources + def initialize + @environment = RailsEnvironment.default + @rails_root = RailsEnvironment.default.root + @script_name = File.basename($0) + @sources = [] + end + + def environment=(value) + @environment = value + RailsEnvironment.default = value + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@script_name} [OPTIONS] command" + o.define_head "Rails plugin manager." + + o.separator "" + o.separator "GENERAL OPTIONS" + + o.on("-r", "--root=DIR", String, + "Set an explicit rails app directory.", + "Default: #{@rails_root}") { |@rails_root| self.environment = RailsEnvironment.new(@rails_root) } + o.on("-s", "--source=URL1,URL2", Array, + "Use the specified plugin repositories instead of the defaults.") { |@sources|} + + o.on("-v", "--verbose", "Turn on verbose output.") { |$verbose| } + o.on("-h", "--help", "Show this help message.") { puts o; exit } + + o.separator "" + o.separator "COMMANDS" + + o.separator " discover Discover plugin repositories." + o.separator " list List available plugins." + o.separator " install Install plugin(s) from known repositories or URLs." + o.separator " update Update installed plugins." + o.separator " remove Uninstall plugins." + o.separator " source Add a plugin source repository." + o.separator " unsource Remove a plugin repository." + o.separator " sources List currently configured plugin repositories." + + o.separator "" + o.separator "EXAMPLES" + o.separator " Install a plugin:" + o.separator " #{@script_name} install continuous_builder\n" + o.separator " Install a plugin from a subversion URL:" + o.separator " #{@script_name} install http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder\n" + o.separator " Install a plugin and add a svn:externals entry to vendor/plugins" + o.separator " #{@script_name} install -x continuous_builder\n" + o.separator " List all available plugins:" + o.separator " #{@script_name} list\n" + o.separator " List plugins in the specified repository:" + o.separator " #{@script_name} list --source=http://dev.rubyonrails.com/svn/rails/plugins/\n" + o.separator " Discover and prompt to add new repositories:" + o.separator " #{@script_name} discover\n" + o.separator " Discover new repositories but just list them, don't add anything:" + o.separator " #{@script_name} discover -l\n" + o.separator " Add a new repository to the source list:" + o.separator " #{@script_name} source http://dev.rubyonrails.com/svn/rails/plugins/\n" + o.separator " Remove a repository from the source list:" + o.separator " #{@script_name} unsource http://dev.rubyonrails.com/svn/rails/plugins/\n" + o.separator " Show currently configured repositories:" + o.separator " #{@script_name} sources\n" + end + end + + def parse!(args=ARGV) + general, sub = split_args(args) + options.parse!(general) + + command = general.shift + if command =~ /^(list|discover|install|source|unsource|sources|remove|update|info)$/ + command = Commands.const_get(command.capitalize).new(self) + command.parse!(sub) + else + puts "Unknown command: #{command}" + puts options + exit 1 + end + end + + def split_args(args) + left = [] + left << args.shift while args[0] and args[0] =~ /^-/ + left << args.shift if args[0] + return [left, args] + end + + def self.parse!(args=ARGV) + Plugin.new.parse!(args) + end + end + + + class List + def initialize(base_command) + @base_command = base_command + @sources = [] + @local = false + @remote = true + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} list [OPTIONS] [PATTERN]" + o.define_head "List available plugins." + o.separator "" + o.separator "Options:" + o.separator "" + o.on( "-s", "--source=URL1,URL2", Array, + "Use the specified plugin repositories.") {|@sources|} + o.on( "--local", + "List locally installed plugins.") {|@local| @remote = false} + o.on( "--remote", + "List remotely availabled plugins. This is the default behavior", + "unless --local is provided.") {|@remote|} + end + end + + def parse!(args) + options.order!(args) + unless @sources.empty? + @sources.map!{ |uri| Repository.new(uri) } + else + @sources = Repositories.instance.all + end + if @remote + @sources.map{|r| r.plugins}.flatten.each do |plugin| + if @local or !plugin.installed? + puts plugin.to_s + end + end + else + cd "#{@base_command.environment.root}/vendor/plugins" + Dir["*"].select{|p| File.directory?(p)}.each do |name| + puts name + end + end + end + end + + + class Sources + def initialize(base_command) + @base_command = base_command + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} sources [OPTIONS] [PATTERN]" + o.define_head "List configured plugin repositories." + o.separator "" + o.separator "Options:" + o.separator "" + o.on( "-c", "--check", + "Report status of repository.") { |@sources|} + end + end + + def parse!(args) + options.parse!(args) + Repositories.each do |repo| + puts repo.uri + end + end + end + + + class Source + def initialize(base_command) + @base_command = base_command + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} source REPOSITORY [REPOSITORY [REPOSITORY]...]" + o.define_head "Add new repositories to the default search list." + end + end + + def parse!(args) + options.parse!(args) + count = 0 + args.each do |uri| + if Repositories.instance.add(uri) + puts "added: #{uri.ljust(50)}" if $verbose + count += 1 + else + puts "failed: #{uri.ljust(50)}" + end + end + Repositories.instance.save + puts "Added #{count} repositories." + end + end + + + class Unsource + def initialize(base_command) + @base_command = base_command + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} source URI [URI [URI]...]" + o.define_head "Remove repositories from the default search list." + o.separator "" + o.on_tail("-h", "--help", "Show this help message.") { puts o; exit } + end + end + + def parse!(args) + options.parse!(args) + count = 0 + args.each do |uri| + if Repositories.instance.remove(uri) + count += 1 + puts "removed: #{uri.ljust(50)}" + else + puts "failed: #{uri.ljust(50)}" + end + end + Repositories.instance.save + puts "Removed #{count} repositories." + end + end + + + class Discover + def initialize(base_command) + @base_command = base_command + @list = false + @prompt = true + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} discover URI [URI [URI]...]" + o.define_head "Discover repositories referenced on a page." + o.separator "" + o.separator "Options:" + o.separator "" + o.on( "-l", "--list", + "List but don't prompt or add discovered repositories.") { |@list| @prompt = !@list } + o.on( "-n", "--no-prompt", + "Add all new repositories without prompting.") { |v| @prompt = !v } + end + end + + def parse!(args) + options.parse!(args) + args = ['http://wiki.rubyonrails.org/rails/pages/Plugins'] if args.empty? + args.each do |uri| + scrape(uri) do |repo_uri| + catch(:next_uri) do + if @prompt + begin + $stdout.print "Add #{repo_uri}? [Y/n] " + throw :next_uri if $stdin.gets !~ /^y?$/i + rescue Interrupt + $stdout.puts + exit 1 + end + elsif @list + puts repo_uri + throw :next_uri + end + Repositories.instance.add(repo_uri) + puts "discovered: #{repo_uri}" if $verbose or !@prompt + end + end + end + Repositories.instance.save + end + + def scrape(uri) + require 'open-uri' + puts "Scraping #{uri}" if $verbose + dupes = [] + content = open(uri).each do |line| + begin + if line =~ /]*href=['"]([^'"]*)['"]/ || line =~ /(svn:\/\/[^<|\n]*)/ + uri = $1 + if uri =~ /^\w+:\/\// && uri =~ /\/plugins\// && uri !~ /\/browser\// && uri !~ /^http:\/\/wiki\.rubyonrails/ && uri !~ /http:\/\/instiki/ + uri = extract_repository_uri(uri) + yield uri unless dupes.include?(uri) || Repositories.instance.exist?(uri) + dupes << uri + end + end + rescue + puts "Problems scraping '#{uri}': #{$!.to_s}" + end + end + end + + def extract_repository_uri(uri) + uri.match(/(svn|https?):.*\/plugins\//i)[0] + end + end + + class Install + def initialize(base_command) + @base_command = base_command + @method = :http + @options = { :quiet => false, :revision => nil, :force => false } + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} install PLUGIN [PLUGIN [PLUGIN] ...]" + o.define_head "Install one or more plugins." + o.separator "" + o.separator "Options:" + o.on( "-x", "--externals", + "Use svn:externals to grab the plugin.", + "Enables plugin updates and plugin versioning.") { |v| @method = :externals } + o.on( "-o", "--checkout", + "Use svn checkout to grab the plugin.", + "Enables updating but does not add a svn:externals entry.") { |v| @method = :checkout } + o.on( "-q", "--quiet", + "Suppresses the output from installation.", + "Ignored if -v is passed (./script/plugin -v install ...)") { |v| @options[:quiet] = true } + o.on( "-r REVISION", "--revision REVISION", + "Checks out the given revision from subversion.", + "Ignored if subversion is not used.") { |v| @options[:revision] = v } + o.on( "-f", "--force", + "Reinstalls a plugin if it's already installed.") { |v| @options[:force] = true } + o.separator "" + o.separator "You can specify plugin names as given in 'plugin list' output or absolute URLs to " + o.separator "a plugin repository." + end + end + + def determine_install_method + best = @base_command.environment.best_install_method + @method = :http if best == :http and @method == :export + case + when (best == :http and @method != :http) + msg = "Cannot install using subversion because `svn' cannot be found in your PATH" + when (best == :export and (@method != :export and @method != :http)) + msg = "Cannot install using #{@method} because this project is not under subversion." + when (best != :externals and @method == :externals) + msg = "Cannot install using externals because vendor/plugins is not under subversion." + end + if msg + puts msg + exit 1 + end + @method + end + + def parse!(args) + options.parse!(args) + environment = @base_command.environment + install_method = determine_install_method + puts "Plugins will be installed using #{install_method}" if $verbose + args.each do |name| + ::Plugin.find(name).install(install_method, @options) + end + rescue + puts "Plugin not found: #{args.inspect}" + exit 1 + end + end + + class Update + def initialize(base_command) + @base_command = base_command + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} update [name [name]...]" + o.on( "-r REVISION", "--revision REVISION", + "Checks out the given revision from subversion.", + "Ignored if subversion is not used.") { |v| @revision = v } + o.define_head "Update plugins." + end + end + + def parse!(args) + options.parse!(args) + root = @base_command.environment.root + cd root + args = Dir["vendor/plugins/*"].map do |f| + File.directory?("#{f}/.svn") ? File.basename(f) : nil + end.compact if args.empty? + cd "vendor/plugins" + args.each do |name| + if File.directory?(name) + puts "Updating plugin: #{name}" + system("svn #{$verbose ? '' : '-q'} up \"#{name}\" #{@revision ? "-r #{@revision}" : ''}") + else + puts "Plugin doesn't exist: #{name}" + end + end + end + end + + class Remove + def initialize(base_command) + @base_command = base_command + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} remove name [name]..." + o.define_head "Remove plugins." + end + end + + def parse!(args) + options.parse!(args) + root = @base_command.environment.root + args.each do |name| + ::Plugin.new(name).uninstall + end + end + end + + class Info + def initialize(base_command) + @base_command = base_command + end + + def options + OptionParser.new do |o| + o.set_summary_indent(' ') + o.banner = "Usage: #{@base_command.script_name} info name [name]..." + o.define_head "Shows plugin info at {url}/about.yml." + end + end + + def parse!(args) + options.parse!(args) + args.each do |name| + puts ::Plugin.find(name).info + puts + end + end + end +end + +class RecursiveHTTPFetcher + attr_accessor :quiet + def initialize(urls_to_fetch, cwd = ".") + @cwd = cwd + @urls_to_fetch = urls_to_fetch.to_a + @quiet = false + end + + def ls + @urls_to_fetch.collect do |url| + if url =~ /^svn:\/\/.*/ + `svn ls #{url}`.split("\n").map {|entry| "/#{entry}"} rescue nil + else + open(url) do |stream| + links("", stream.read) + end rescue nil + end + end.flatten + end + + def push_d(dir) + @cwd = File.join(@cwd, dir) + FileUtils.mkdir_p(@cwd) + end + + def pop_d + @cwd = File.dirname(@cwd) + end + + def links(base_url, contents) + links = [] + contents.scan(/href\s*=\s*\"*[^\">]*/i) do |link| + link = link.sub(/href="/i, "") + next if link =~ /^http/i || link =~ /^\./ + links << File.join(base_url, link) + end + links + end + + def download(link) + puts "+ #{File.join(@cwd, File.basename(link))}" unless @quiet + open(link) do |stream| + File.open(File.join(@cwd, File.basename(link)), "wb") do |file| + file.write(stream.read) + end + end + end + + def fetch(links = @urls_to_fetch) + links.each do |l| + (l =~ /\/$/ || links == @urls_to_fetch) ? fetch_dir(l) : download(l) + end + end + + def fetch_dir(url) + push_d(File.basename(url)) + open(url) do |stream| + contents = stream.read + fetch(links(url, contents)) + end + pop_d + end +end + +Commands::Plugin.parse! diff --git a/vendor/rails/railties/lib/commands/process/inspector.rb b/vendor/rails/railties/lib/commands/process/inspector.rb new file mode 100644 index 0000000..79e1cf9 --- /dev/null +++ b/vendor/rails/railties/lib/commands/process/inspector.rb @@ -0,0 +1,68 @@ +require 'optparse' + +if RUBY_PLATFORM =~ /mswin32/ then abort("Inspector is only for Unix") end + +OPTIONS = { + :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'), + :pattern => "dispatch.*.pid", + :ps => "ps -o pid,state,user,start,time,pcpu,vsz,majflt,command -p %s" +} + +class Inspector + def self.inspect(pid_path, pattern) + new(pid_path, pattern).inspect + end + + def initialize(pid_path, pattern) + @pid_path, @pattern = pid_path, pattern + end + + def inspect + header = `#{OPTIONS[:ps] % 1}`.split("\n")[0] + "\n" + lines = pids.collect { |pid| `#{OPTIONS[:ps] % pid}`.split("\n")[1] } + + puts(header + lines.join("\n")) + end + + private + def pids + pid_files.collect do |pid_file| + File.read(pid_file).to_i + end + end + + def pid_files + Dir.glob(@pid_path + "/" + @pattern) + end +end + + +ARGV.options do |opts| + opts.banner = "Usage: inspector [options]" + + opts.separator "" + + opts.on <<-EOF + Description: + Displays system information about Rails dispatchers (or other processes that use pid files) through + the ps command. + + Examples: + inspector # default ps on all tmp/pids/dispatch.*.pid files + inspector -s 'ps -o user,start,majflt,pcpu,vsz -p %s' # custom ps, %s is where the pid is interleaved + EOF + + opts.on(" Options:") + + opts.on("-s", "--ps=command", "default: #{OPTIONS[:ps]}", String) { |v| OPTIONS[:ps] = v } + opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v } + opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v } + + opts.separator "" + + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + + opts.parse! +end + +Inspector.inspect(OPTIONS[:pid_path], OPTIONS[:pattern]) \ No newline at end of file diff --git a/vendor/rails/railties/lib/commands/process/reaper.rb b/vendor/rails/railties/lib/commands/process/reaper.rb new file mode 100644 index 0000000..0790cfc --- /dev/null +++ b/vendor/rails/railties/lib/commands/process/reaper.rb @@ -0,0 +1,135 @@ +require 'optparse' +require 'net/http' +require 'uri' + +if RUBY_PLATFORM =~ /mswin32/ then abort("Reaper is only for Unix") end + +class Killer + class << self + # Searches for all processes matching the given keywords, and then invokes + # a specific action on each of them. This is useful for (e.g.) reloading a + # set of processes: + # + # Killer.process(:reload, "/tmp/pids", "dispatcher.*.pid") + def process(action, pid_path, pattern) + new(pid_path, pattern).process(action) + end + + # Forces the (rails) application to reload by sending a +HUP+ signal to the + # process. + def reload(pid) + `kill -s HUP #{pid}` + end + + # Force the (rails) application to restart by sending a +USR2+ signal to the + # process. + def restart(pid) + `kill -s USR2 #{pid}` + end + + # Forces the (rails) application to gracefully terminate by sending a + # +TERM+ signal to the process. + def graceful(pid) + `kill -s TERM #{pid}` + end + + # Forces the (rails) application to terminate immediately by sending a -9 + # signal to the process. + def kill(pid) + `kill -9 #{pid}` + end + + # Send a +USR1+ signal to the process. + def usr1(pid) + `kill -s USR1 #{pid}` + end + end + + def initialize(pid_path, pattern) + @pid_path, @pattern = pid_path, pattern + end + + def process(action) + pids = find_processes + + if pids.empty? + puts "Couldn't find any pid file in '#{@pid_path}' matching '#{@pattern}'" + else + pids.each do |pid| + puts "#{action.capitalize}ing #{pid}" + self.class.send(action, pid) + end + + delete_pid_files if terminating?(action) + end + end + + private + def terminating?(action) + [ "kill", "graceful" ].include?(action) + end + + def find_processes + pid_files.collect { |pid_file| File.read(pid_file).to_i } + end + + def delete_pid_files + pid_files.each { |pid_file| File.delete(pid_file) } + end + + def pid_files + Dir.glob(@pid_path + "/" + @pattern) + end +end + + +OPTIONS = { + :action => "restart", + :pid_path => File.expand_path(RAILS_ROOT + '/tmp/pids'), + :pattern => "dispatch.[0-9]*.pid" +} + +ARGV.options do |opts| + opts.banner = "Usage: reaper [options]" + + opts.separator "" + + opts.on <<-EOF + Description: + The reaper is used to restart, reload, gracefully exit, and forcefully exit processes + running a Rails Dispatcher (or any other process responding to the same signals). This + is commonly done when a new version of the application is available, so the existing + processes can be updated to use the latest code. + + It uses pid files to work on the processes and by default assume them to be located + in RAILS_ROOT/tmp/pids. + + The reaper actions are: + + * restart : Restarts the application by reloading both application and framework code + * reload : Only reloads the application, but not the framework (like the development environment) + * graceful: Marks all of the processes for exit after the next request + * kill : Forcefully exists all processes regardless of whether they're currently serving a request + + Restart is the most common and default action. + + Examples: + reaper # restarts the default dispatchers + reaper -a reload # reload the default dispatchers + reaper -a kill -r *.pid # kill all processes that keep pids in tmp/pids + EOF + + opts.on(" Options:") + + opts.on("-a", "--action=name", "reload|graceful|kill (default: #{OPTIONS[:action]})", String) { |v| OPTIONS[:action] = v } + opts.on("-p", "--pidpath=path", "default: #{OPTIONS[:pid_path]}", String) { |v| OPTIONS[:pid_path] = v } + opts.on("-r", "--pattern=pattern", "default: #{OPTIONS[:pattern]}", String) { |v| OPTIONS[:pattern] = v } + + opts.separator "" + + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + + opts.parse! +end + +Killer.process(OPTIONS[:action], OPTIONS[:pid_path], OPTIONS[:pattern]) diff --git a/vendor/rails/railties/lib/commands/process/spawner.rb b/vendor/rails/railties/lib/commands/process/spawner.rb new file mode 100644 index 0000000..b3b4013 --- /dev/null +++ b/vendor/rails/railties/lib/commands/process/spawner.rb @@ -0,0 +1,166 @@ +require 'active_support' +require 'optparse' +require 'socket' +require 'fileutils' + +def daemonize #:nodoc: + 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. +end + +class Spawner + def self.record_pid(name = "#{OPTIONS[:process]}.spawner", id = Process.pid) + FileUtils.mkdir_p(OPTIONS[:pids]) + File.open(File.expand_path(OPTIONS[:pids] + "/#{name}.pid"), "w+") { |f| f.write(id) } + end + + def self.spawn_all + OPTIONS[:instances].times do |i| + port = OPTIONS[:port] + i + print "Checking if something is already running on port #{port}..." + + begin + srv = TCPServer.new('0.0.0.0', port) + srv.close + srv = nil + + puts "NO" + puts "Starting dispatcher on port: #{port}" + + FileUtils.mkdir_p(OPTIONS[:pids]) + spawn(port) + rescue + puts "YES" + end + end + end +end + +class FcgiSpawner < Spawner + def self.spawn(port) + system("#{OPTIONS[:spawner]} -f #{OPTIONS[:dispatcher]} -p #{port} -P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid") + end +end + +class MongrelSpawner < Spawner + def self.spawn(port) + system("mongrel_rails start -d -p #{port} -P #{OPTIONS[:pids]}/#{OPTIONS[:process]}.#{port}.pid -e #{OPTIONS[:environment]}") + end +end + + +begin + require_library_or_gem 'fcgi' +rescue Exception + # FCGI not available +end + +begin + require_library_or_gem 'mongrel' +rescue Exception + # Mongrel not available +end + +server = case ARGV.first + when "fcgi", "mongrel" + ARGV.shift + else + if defined?(Mongrel) + "mongrel" + elsif RUBY_PLATFORM !~ /mswin/ && !silence_stderr { `spawn-fcgi -version` }.blank? && defined?(FCGI) + "fcgi" + end +end + +case server + when "fcgi" + puts "=> Starting FCGI dispatchers" + spawner_class = FcgiSpawner + when "mongrel" + puts "=> Starting mongrel dispatchers" + spawner_class = MongrelSpawner + else + puts "Neither FCGI (spawn-fcgi) nor Mongrel was installed and available!" + exit(0) +end + + + +OPTIONS = { + :environment => "production", + :spawner => '/usr/bin/env spawn-fcgi', + :dispatcher => File.expand_path(RAILS_ROOT + '/public/dispatch.fcgi'), + :pids => File.expand_path(RAILS_ROOT + "/tmp/pids"), + :process => "dispatch", + :port => 8000, + :instances => 3, + :repeat => nil +} + +ARGV.options do |opts| + opts.banner = "Usage: spawner [platform] [options]" + + opts.separator "" + + opts.on <<-EOF + Description: + The spawner is a wrapper for spawn-fcgi and mongrel that makes it easier to start multiple + processes running the Rails dispatcher. The spawn-fcgi command is included with the lighttpd + web server, but can be used with both Apache and lighttpd (and any other web server supporting + externally managed FCGI processes). Mongrel automatically ships with with mongrel_rails for starting + dispatchers. + + The first choice you need to make is whether to spawn the Rails dispatchers as FCGI or Mongrel. By default, + this spawner will prefer Mongrel, so if that's installed, and no platform choice is made, Mongrel is used. + + Then decide a starting port (default is 8000) and the number of FCGI process instances you'd + like to run. So if you pick 9100 and 3 instances, you'll start processes on 9100, 9101, and 9102. + + By setting the repeat option, you get a protection loop, which will attempt to restart any FCGI processes + that might have been exited or outright crashed. + + Examples: + spawner # starts instances on 8000, 8001, and 8002 using Mongrel if available + spawner fcgi # starts instances on 8000, 8001, and 8002 using FCGI + spawner mongrel -i 5 # starts instances on 8000, 8001, 8002, 8003, and 8004 using Mongrel + spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109 using Mongrel if available + spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to 9102 and attempts start them every 5 seconds + EOF + + opts.on(" Options:") + + opts.on("-p", "--port=number", Integer, "Starting port number (default: #{OPTIONS[:port]})") { |v| OPTIONS[:port] = v } + opts.on("-i", "--instances=number", Integer, "Number of instances (default: #{OPTIONS[:instances]})") { |v| OPTIONS[:instances] = v } + opts.on("-r", "--repeat=seconds", Integer, "Repeat spawn attempts every n seconds (default: off)") { |v| OPTIONS[:repeat] = v } + opts.on("-e", "--environment=name", String, "test|development|production (default: #{OPTIONS[:environment]})") { |v| OPTIONS[:environment] = v } + opts.on("-n", "--process=name", String, "default: #{OPTIONS[:process]}") { |v| OPTIONS[:process] = v } + opts.on("-s", "--spawner=path", String, "default: #{OPTIONS[:spawner]}") { |v| OPTIONS[:spawner] = v } + opts.on("-d", "--dispatcher=path", String, "default: #{OPTIONS[:dispatcher]}") { |dispatcher| OPTIONS[:dispatcher] = File.expand_path(dispatcher) } + + opts.separator "" + + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + + opts.parse! +end + +ENV["RAILS_ENV"] = OPTIONS[:environment] + +if OPTIONS[:repeat] + daemonize + trap("TERM") { exit } + spawner_class.record_pid + + loop do + spawner_class.spawn_all + sleep(OPTIONS[:repeat]) + end +else + spawner_class.spawn_all +end diff --git a/vendor/rails/railties/lib/commands/process/spinner.rb b/vendor/rails/railties/lib/commands/process/spinner.rb new file mode 100644 index 0000000..c0b2f09 --- /dev/null +++ b/vendor/rails/railties/lib/commands/process/spinner.rb @@ -0,0 +1,57 @@ +require 'optparse' + +def daemonize #:nodoc: + 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. +end + +OPTIONS = { + :interval => 5.0, + :command => File.expand_path(RAILS_ROOT + '/script/process/spawner'), + :daemon => false +} + +ARGV.options do |opts| + opts.banner = "Usage: spinner [options]" + + opts.separator "" + + opts.on <<-EOF + Description: + The spinner is a protection loop for the spawner, which will attempt to restart any FCGI processes + that might have been exited or outright crashed. It's a brute-force attempt that'll just try + to run the spawner every X number of seconds, so it does pose a light load on the server. + + Examples: + spinner # attempts to run the spawner with default settings every second with output on the terminal + spinner -i 3 -d # only run the spawner every 3 seconds and detach from the terminal to become a daemon + spinner -c '/path/to/app/script/process/spawner -p 9000 -i 10' -d # using custom spawner + EOF + + opts.on(" Options:") + + opts.on("-c", "--command=path", String) { |v| OPTIONS[:command] = v } + opts.on("-i", "--interval=seconds", Float) { |v| OPTIONS[:interval] = v } + opts.on("-d", "--daemon") { |v| OPTIONS[:daemon] = v } + + opts.separator "" + + opts.on("-h", "--help", "Show this help message.") { puts opts; exit } + + opts.parse! +end + +daemonize if OPTIONS[:daemon] + +trap(OPTIONS[:daemon] ? "TERM" : "INT") { exit } + +loop do + system(OPTIONS[:command]) + sleep(OPTIONS[:interval]) +end \ No newline at end of file diff --git a/vendor/rails/railties/lib/commands/runner.rb b/vendor/rails/railties/lib/commands/runner.rb new file mode 100644 index 0000000..47186d5 --- /dev/null +++ b/vendor/rails/railties/lib/commands/runner.rb @@ -0,0 +1,27 @@ +require 'optparse' + +options = { :environment => (ENV['RAILS_ENV'] || "development").dup } + +ARGV.options do |opts| + script_name = File.basename($0) + opts.banner = "Usage: runner 'puts Person.find(1).name' [options]" + + opts.separator "" + + opts.on("-e", "--environment=name", String, + "Specifies the environment for the runner to operate under (test/development/production).", + "Default: development") { |v| options[:environment] = v } + + opts.separator "" + + opts.on("-h", "--help", + "Show this help message.") { puts opts; exit } + + opts.parse! +end + +ENV["RAILS_ENV"] = options[:environment] +RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV) + +require RAILS_ROOT + '/config/environment' +ARGV.empty? ? puts("Usage: runner 'code' [options]") : eval(ARGV.first) diff --git a/vendor/rails/railties/lib/commands/server.rb b/vendor/rails/railties/lib/commands/server.rb new file mode 100644 index 0000000..94208f7 --- /dev/null +++ b/vendor/rails/railties/lib/commands/server.rb @@ -0,0 +1,39 @@ +require 'active_support' +require 'fileutils' + +begin + require_library_or_gem 'fcgi' +rescue Exception + # FCGI not available +end + +begin + require_library_or_gem 'mongrel' +rescue Exception + # Mongrel not available +end + +server = case ARGV.first + when "lighttpd", "mongrel", "webrick" + ARGV.shift + else + if defined?(Mongrel) + "mongrel" + elsif RUBY_PLATFORM !~ /mswin/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI) + "lighttpd" + else + "webrick" + end +end + +case server + when "webrick" + puts "=> Booting WEBrick..." + when "lighttpd" + puts "=> Booting lighttpd (use 'script/server webrick' to force WEBrick)" + when "mongrel" + puts "=> Booting Mongrel (use 'script/server webrick' to force WEBrick)" +end + +['sessions', 'cache', 'sockets'].each { |dir_to_make| FileUtils.mkdir_p(File.join(RAILS_ROOT, 'tmp', dir_to_make)) } +require "commands/servers/#{server}" diff --git a/vendor/rails/railties/lib/commands/servers/base.rb b/vendor/rails/railties/lib/commands/servers/base.rb new file mode 100644 index 0000000..25b2935 --- /dev/null +++ b/vendor/rails/railties/lib/commands/servers/base.rb @@ -0,0 +1,19 @@ +def tail(log_file) + cursor = File.size(log_file) + last_checked = Time.now + tail_thread = Thread.new do + File.open(log_file, 'r') do |f| + loop do + f.seek cursor + if f.mtime > last_checked + last_checked = f.mtime + contents = f.read + cursor += contents.length + print contents + end + sleep 1 + end + end + end + tail_thread +end diff --git a/vendor/rails/railties/lib/commands/servers/lighttpd.rb b/vendor/rails/railties/lib/commands/servers/lighttpd.rb new file mode 100644 index 0000000..204dca7 --- /dev/null +++ b/vendor/rails/railties/lib/commands/servers/lighttpd.rb @@ -0,0 +1,94 @@ +require 'rbconfig' +require 'commands/servers/base' + +unless RUBY_PLATFORM !~ /mswin/ && !silence_stderr { `lighttpd -version` }.blank? + puts "PROBLEM: Lighttpd is not available on your system (or not in your path)" + exit 1 +end + +unless defined?(FCGI) + puts "PROBLEM: Lighttpd requires that the FCGI Ruby bindings are installed on the system" + exit 1 +end + +require 'initializer' +configuration = Rails::Initializer.run(:initialize_logger).configuration +default_config_file = config_file = Pathname.new("#{RAILS_ROOT}/config/lighttpd.conf").cleanpath + +require 'optparse' + +detach = false +command_line_port = nil + +ARGV.options do |opt| + opt.on("-p", "--port=port", "Changes the server.port number in the config/lighttpd.conf") { |port| command_line_port = port } + opt.on('-c', "--config=#{config_file}", 'Specify a different lighttpd config file.') { |path| config_file = path } + opt.on('-h', '--help', 'Show this message.') { puts opt; exit 0 } + opt.on('-d', '-d', 'Call with -d to detach') { detach = true; puts "=> Configuration in config/lighttpd.conf" } + opt.parse! +end + +unless File.exist?(config_file) + if config_file != default_config_file + puts "=> #{config_file} not found." + exit 1 + end + + require 'fileutils' + + source = File.expand_path(File.join(File.dirname(__FILE__), + "..", "..", "..", "configs", "lighttpd.conf")) + puts "=> #{config_file} not found, copying from #{source}" + + FileUtils.cp(source, config_file) +end + +# open the config/lighttpd.conf file and add the current user defined port setting to it +if command_line_port + File.open(config_file, 'r+') do |config| + lines = config.readlines + + lines.each do |line| + line.gsub!(/^\s*server.port\s*=\s*(\d+)/, "server.port = #{command_line_port}") + end + + config.rewind + config.print(lines) + config.truncate(config.pos) + end +end + +config = IO.read(config_file) +default_port, default_ip = 3000, '0.0.0.0' +port = config.scan(/^\s*server.port\s*=\s*(\d+)/).first rescue default_port +ip = config.scan(/^\s*server.bind\s*=\s*"([^"]+)"/).first rescue default_ip +puts "=> Rails application started on http://#{ip || default_ip}:#{port || default_port}" + +tail_thread = nil + +if !detach + puts "=> Call with -d to detach" + puts "=> Ctrl-C to shutdown server (see config/lighttpd.conf for options)" + detach = false + tail_thread = tail(configuration.log_path) +end + +trap(:INT) { exit } + +begin + `rake tmp:sockets:clear` # Needed if lighttpd crashes or otherwise leaves FCGI sockets around + `lighttpd #{!detach ? "-D " : ""}-f #{config_file}` +ensure + unless detach + tail_thread.kill if tail_thread + puts 'Exiting' + + # Ensure FCGI processes are reaped + silence_stream(STDOUT) do + ARGV.replace ['-a', 'kill'] + require 'commands/process/reaper' + end + + `rake tmp:sockets:clear` # Remove sockets on clean shutdown + end +end diff --git a/vendor/rails/railties/lib/commands/servers/mongrel.rb b/vendor/rails/railties/lib/commands/servers/mongrel.rb new file mode 100644 index 0000000..98441ac --- /dev/null +++ b/vendor/rails/railties/lib/commands/servers/mongrel.rb @@ -0,0 +1,54 @@ +require 'rbconfig' +require 'commands/servers/base' + +unless defined?(Mongrel) + puts "PROBLEM: Mongrel is not available on your system (or not in your path)" + exit 1 +end + +require 'initializer' +Rails::Initializer.run(:initialize_logger) + +require 'optparse' + +detach = false +ip = nil +port = nil + +ARGV.clone.options do |opt| + opt.on("-p", "--port=port", Integer, + "Runs Rails on the specified port.", + "Default: 3000") { |p| port = p } + opt.on("-a", "--binding=ip", String, + "Binds Rails to the specified ip.", + "Default: 0.0.0.0") { |i| ip = i } + opt.on('-h', '--help', 'Show this message.') { puts opt; exit 0 } + opt.on('-d', '-d', 'Call with -d to detach') { detach = true } + opt.parse! +end + +default_port, default_ip = 3000, '0.0.0.0' +puts "=> Rails application started on http://#{ip || default_ip}:#{port || default_port}" + +log_file = Pathname.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log").cleanpath + +tail_thread = nil + +if !detach + puts "=> Call with -d to detach" + puts "=> Ctrl-C to shutdown server" + detach = false + tail_thread = tail(log_file) +end + +trap(:INT) { exit } + +begin + ARGV.unshift("start") + load 'mongrel_rails' +ensure + unless detach + tail_thread.kill if tail_thread + puts 'Exiting' + end +end diff --git a/vendor/rails/railties/lib/commands/servers/webrick.rb b/vendor/rails/railties/lib/commands/servers/webrick.rb new file mode 100644 index 0000000..3fddcc5 --- /dev/null +++ b/vendor/rails/railties/lib/commands/servers/webrick.rb @@ -0,0 +1,59 @@ +require 'webrick' +require 'optparse' + +OPTIONS = { + :port => 3000, + :ip => "0.0.0.0", + :environment => (ENV['RAILS_ENV'] || "development").dup, + :server_root => File.expand_path(RAILS_ROOT + "/public/"), + :server_type => WEBrick::SimpleServer, + :charset => "UTF-8", + :mime_types => WEBrick::HTTPUtils::DefaultMimeTypes +} + +ARGV.options do |opts| + script_name = File.basename($0) + opts.banner = "Usage: ruby #{script_name} [options]" + + opts.separator "" + + opts.on("-p", "--port=port", Integer, + "Runs Rails on the specified port.", + "Default: 3000") { |v| OPTIONS[:port] = v } + opts.on("-b", "--binding=ip", String, + "Binds Rails to the specified ip.", + "Default: 0.0.0.0") { |v| OPTIONS[:ip] = v } + opts.on("-e", "--environment=name", String, + "Specifies the environment to run this server under (test/development/production).", + "Default: development") { |v| OPTIONS[:environment] = v } + opts.on("-m", "--mime-types=filename", String, + "Specifies an Apache style mime.types configuration file to be used for mime types", + "Default: none") { |mime_types_file| OPTIONS[:mime_types] = WEBrick::HTTPUtils::load_mime_types(mime_types_file) } + + opts.on("-d", "--daemon", + "Make Rails run as a Daemon (only works if fork is available -- meaning on *nix)." + ) { OPTIONS[:server_type] = WEBrick::Daemon } + + opts.on("-c", "--charset=charset", String, + "Set default charset for output.", + "Default: UTF-8") { |v| OPTIONS[:charset] = v } + + opts.separator "" + + opts.on("-h", "--help", + "Show this help message.") { puts opts; exit } + + opts.parse! +end + +ENV["RAILS_ENV"] = OPTIONS[:environment] +RAILS_ENV.replace(OPTIONS[:environment]) if defined?(RAILS_ENV) + +require RAILS_ROOT + "/config/environment" +require 'webrick_server' + +OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT) + +puts "=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" +puts "=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer +DispatchServlet.dispatch(OPTIONS) diff --git a/vendor/rails/railties/lib/commands/update.rb b/vendor/rails/railties/lib/commands/update.rb new file mode 100644 index 0000000..83ef833 --- /dev/null +++ b/vendor/rails/railties/lib/commands/update.rb @@ -0,0 +1,4 @@ +require "#{RAILS_ROOT}/config/environment" +require 'rails_generator' +require 'rails_generator/scripts/update' +Rails::Generator::Scripts::Update.new.run(ARGV) diff --git a/vendor/rails/railties/lib/console_app.rb b/vendor/rails/railties/lib/console_app.rb new file mode 100644 index 0000000..4c21260 --- /dev/null +++ b/vendor/rails/railties/lib/console_app.rb @@ -0,0 +1,27 @@ +require 'action_controller/integration' + +# work around the at_exit hook in test/unit, which kills IRB +Test::Unit.run = true + +# reference the global "app" instance, created on demand. To recreate the +# instance, pass a non-false value as the parameter. +def app(create=false) + @app_integration_instance = nil if create + @app_integration_instance ||= new_session do |sess| + sess.host! "www.example.com" + end +end + +# create a new session. If a block is given, the new session will be yielded +# to the block before being returned. +def new_session + session = ActionController::Integration::Session.new + yield session if block_given? + session +end + +#reloads the environment +def reload! + puts "Reloading..." + Dispatcher.reset_application! +end diff --git a/vendor/rails/railties/lib/console_sandbox.rb b/vendor/rails/railties/lib/console_sandbox.rb new file mode 100644 index 0000000..80f3dbc --- /dev/null +++ b/vendor/rails/railties/lib/console_sandbox.rb @@ -0,0 +1,6 @@ +ActiveRecord::Base.lock_mutex +ActiveRecord::Base.connection.begin_db_transaction +at_exit do + ActiveRecord::Base.connection.rollback_db_transaction + ActiveRecord::Base.unlock_mutex +end diff --git a/vendor/rails/railties/lib/console_with_helpers.rb b/vendor/rails/railties/lib/console_with_helpers.rb new file mode 100644 index 0000000..66a4248 --- /dev/null +++ b/vendor/rails/railties/lib/console_with_helpers.rb @@ -0,0 +1,23 @@ +class Module + def include_all_modules_from(parent_module) + parent_module.constants.each do |const| + mod = parent_module.const_get(const) + if mod.class == Module + send(:include, mod) + include_all_modules_from(mod) + end + end + end +end + +def helper + @helper_proxy ||= Object.new +end + +require 'application' + +class << helper + include_all_modules_from ActionView +end + +@controller = ApplicationController.new diff --git a/vendor/rails/railties/lib/dispatcher.rb b/vendor/rails/railties/lib/dispatcher.rb new file mode 100644 index 0000000..b62a497 --- /dev/null +++ b/vendor/rails/railties/lib/dispatcher.rb @@ -0,0 +1,161 @@ +#-- +# 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. +#++ + +# This class provides an interface for dispatching a CGI (or CGI-like) request +# to the appropriate controller and action. It also takes care of resetting +# the environment (when Dependencies.load? is true) after each request. +class Dispatcher + + class << self + + # Dispatch the given CGI request, using the given session options, and + # emitting the output via the given output. If you dispatch with your + # own CGI object be sure to handle the exceptions it raises on multipart + # requests (EOFError and ArgumentError). + def dispatch(cgi = nil, session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout) + controller = nil + if cgi ||= new_cgi(output) + request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi) + prepare_application + controller = ActionController::Routing::Routes.recognize(request) + controller.process(request, response).out(output) + end + rescue Object => exception + failsafe_response(output, '500 Internal Server Error', exception) do + controller ||= const_defined?(:ApplicationController) ? ApplicationController : ActionController::Base + controller.process_with_exception(request, response, exception).out(output) + end + ensure + # Do not give a failsafe response here. + reset_after_dispatch + end + + # Reset the application by clearing out loaded controllers, views, actions, + # mailers, and so forth. This allows them to be loaded again without having + # to restart the server (WEBrick, FastCGI, etc.). + def reset_application! + Dependencies.clear + Class.remove_class(*Reloadable.reloadable_classes) + ActiveRecord::Base.reset if defined?(ActiveRecord) + end + + + # Add a preparation callback. Preparation callbacks are run before every + # request in development mode, and before the first request in production + # mode. + # + # An optional identifier may be supplied for the callback. If provided, + # to_prepare may be called again with the same identifier to replace the + # existing callback. Passing an identifier is a suggested practice if the + # code adding a preparation block may be reloaded. + def to_prepare(identifier = nil, &block) + unless identifier.nil? + callback = preparation_callbacks.detect { |ident, _| ident == identifier } + if callback # Already registered: update the existing callback + callback[-1] = block + return + end + end + preparation_callbacks << [identifier, block] + nil + end + + private + + attr_accessor :preparation_callbacks, :preparation_callbacks_run + alias_method :preparation_callbacks_run?, :preparation_callbacks_run + + # CGI.new plus exception handling. CGI#read_multipart raises EOFError + # if body.empty? or body.size != Content-Length and raises ArgumentError + # if Content-Length is non-integer. + def new_cgi(output) + failsafe_response(output, '400 Bad Request') { CGI.new } + end + + def prepare_application + if Dependencies.load? + ActionController::Routing::Routes.reload + self.preparation_callbacks_run = false + end + + prepare_breakpoint + require_dependency('application.rb') unless Object.const_defined?(:ApplicationController) + ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord) + run_preparation_callbacks + end + + def reset_after_dispatch + reset_application! if Dependencies.load? + Breakpoint.deactivate_drb if defined?(BREAKPOINT_SERVER_PORT) + end + + def prepare_breakpoint + return unless defined?(BREAKPOINT_SERVER_PORT) + require 'breakpoint' + Breakpoint.activate_drb("druby://localhost:#{BREAKPOINT_SERVER_PORT}", nil, !defined?(FastCGI)) + true + rescue + nil + end + + def run_preparation_callbacks + return if preparation_callbacks_run? + preparation_callbacks.each { |_, callback| callback.call } + self.preparation_callbacks_run = true + end + + # If the block raises, send status code as a last-ditch response. + def failsafe_response(output, status, exception = nil) + yield + rescue Object + begin + output.write "Status: #{status}\r\n" + + if exception + message = exception.to_s + "\r\n" + exception.backtrace.join("\r\n") + error_path = File.join(RAILS_ROOT, 'public', '500.html') + + if defined?(RAILS_DEFAULT_LOGGER) && !RAILS_DEFAULT_LOGGER.nil? + RAILS_DEFAULT_LOGGER.fatal(message) + + output.write "Content-Type: text/html\r\n\r\n" + + if File.exists?(error_path) + output.write(IO.read(error_path)) + else + output.write("

      Application error (Rails)

      ") + end + else + output.write "Content-Type: text/plain\r\n\r\n" + output.write(message) + end + end + rescue Object + end + end + end + + self.preparation_callbacks = [] + self.preparation_callbacks_run = false + +end diff --git a/vendor/rails/railties/lib/fcgi_handler.rb b/vendor/rails/railties/lib/fcgi_handler.rb new file mode 100644 index 0000000..10f846f --- /dev/null +++ b/vendor/rails/railties/lib/fcgi_handler.rb @@ -0,0 +1,207 @@ +require 'fcgi' +require 'logger' +require 'dispatcher' +require 'rbconfig' + +class RailsFCGIHandler + SIGNALS = { + 'HUP' => :reload, + 'TERM' => :exit_now, + 'USR1' => :exit, + 'USR2' => :restart, + 'SIGTRAP' => :breakpoint + } + + attr_reader :when_ready + + attr_accessor :log_file_path + attr_accessor :gc_request_period + + + # Initialize and run the FastCGI instance, passing arguments through to new. + def self.process!(*args, &block) + new(*args, &block).process! + end + + # Initialize the FastCGI instance with the path to a crash log + # detailing unhandled exceptions (default RAILS_ROOT/log/fastcgi.crash.log) + # and the number of requests to process between garbage collection runs + # (default nil for normal GC behavior.) Optionally, pass a block which + # takes this instance as an argument for further configuration. + def initialize(log_file_path = nil, gc_request_period = nil) + self.log_file_path = log_file_path || "#{RAILS_ROOT}/log/fastcgi.crash.log" + self.gc_request_period = gc_request_period + + # Yield for additional configuration. + yield self if block_given? + + # Safely install signal handlers. + install_signal_handlers + + # Start error timestamp at 11 seconds ago. + @last_error_on = Time.now - 11 + + dispatcher_log :info, "starting" + end + + def process!(provider = FCGI) + # Make a note of $" so we can safely reload this instance. + mark! + + run_gc! if gc_request_period + + provider.each_cgi do |cgi| + process_request(cgi) + + case when_ready + when :reload + reload! + when :restart + close_connection(cgi) + restart! + when :exit + close_connection(cgi) + break + when :breakpoint + close_connection(cgi) + breakpoint! + end + + gc_countdown + end + + GC.enable + dispatcher_log :info, "terminated gracefully" + + rescue SystemExit => exit_error + dispatcher_log :info, "terminated by explicit exit" + + rescue Object => fcgi_error + # retry on errors that would otherwise have terminated the FCGI process, + # but only if they occur more than 10 seconds apart. + if !(SignalException === fcgi_error) && Time.now - @last_error_on > 10 + @last_error_on = Time.now + dispatcher_error(fcgi_error, "almost killed by this error") + retry + else + dispatcher_error(fcgi_error, "killed by this error") + end + end + + + private + def logger + @logger ||= Logger.new(@log_file_path) + end + + def dispatcher_log(level, msg) + time_str = Time.now.strftime("%d/%b/%Y:%H:%M:%S") + logger.send(level, "[#{time_str} :: #{$$}] #{msg}") + rescue Object => log_error + STDERR << "Couldn't write to #{@log_file_path.inspect}: #{msg}\n" + STDERR << " #{log_error.class}: #{log_error.message}\n" + end + + def dispatcher_error(e, msg = "") + error_message = + "Dispatcher failed to catch: #{e} (#{e.class})\n" + + " #{e.backtrace.join("\n ")}\n#{msg}" + dispatcher_log(:error, error_message) + end + + def install_signal_handlers + SIGNALS.each do |signal, handler_name| + install_signal_handler(signal, method("#{handler_name}_handler").to_proc) + end + end + + def install_signal_handler(signal, handler) + trap(signal, handler) + rescue ArgumentError + dispatcher_log :warn, "Ignoring unsupported signal #{signal}." + end + + def exit_now_handler(signal) + dispatcher_log :info, "asked to terminate immediately" + exit + end + + def exit_handler(signal) + dispatcher_log :info, "asked to terminate ASAP" + @when_ready = :exit + end + + def reload_handler(signal) + dispatcher_log :info, "asked to reload ASAP" + @when_ready = :reload + end + + def restart_handler(signal) + dispatcher_log :info, "asked to restart ASAP" + @when_ready = :restart + end + + def breakpoint_handler(signal) + dispatcher_log :info, "asked to breakpoint ASAP" + @when_ready = :breakpoint + end + + def process_request(cgi) + Dispatcher.dispatch(cgi) + rescue Object => e + raise if SignalException === e + dispatcher_error(e) + end + + def restart! + config = ::Config::CONFIG + ruby = File::join(config['bindir'], config['ruby_install_name']) + config['EXEEXT'] + command_line = [ruby, $0, ARGV].flatten.join(' ') + + dispatcher_log :info, "restarted" + + exec(command_line) + end + + def reload! + run_gc! if gc_request_period + restore! + @when_ready = nil + dispatcher_log :info, "reloaded" + end + + def mark! + @features = $".clone + end + + def restore! + $".replace @features + Dispatcher.reset_application! + ActionController::Routing::Routes.reload + end + + def breakpoint! + require 'breakpoint' + port = defined?(BREAKPOINT_SERVER_PORT) ? BREAKPOINT_SERVER_PORT : 42531 + Breakpoint.activate_drb("druby://localhost:#{port}", nil, !defined?(FastCGI)) + dispatcher_log :info, "breakpointing" + breakpoint + @when_ready = nil + end + + def run_gc! + @gc_request_countdown = gc_request_period + GC.enable; GC.start; GC.disable + end + + def gc_countdown + if gc_request_period + @gc_request_countdown -= 1 + run_gc! if @gc_request_countdown <= 0 + end + end + + def close_connection(cgi) + cgi.instance_variable_get("@request").finish + end +end \ No newline at end of file diff --git a/vendor/rails/railties/lib/initializer.rb b/vendor/rails/railties/lib/initializer.rb new file mode 100644 index 0000000..8abc323 --- /dev/null +++ b/vendor/rails/railties/lib/initializer.rb @@ -0,0 +1,625 @@ +require 'logger' +require 'set' +require File.join(File.dirname(__FILE__), 'railties_path') + +RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV) + +module Rails + # The Initializer is responsible for processing the Rails configuration, such + # as setting the $LOAD_PATH, requiring the right frameworks, initializing + # logging, and more. It can be run either as a single command that'll just + # use the default configuration, like this: + # + # Rails::Initializer.run + # + # But normally it's more interesting to pass in a custom configuration + # through the block running: + # + # Rails::Initializer.run do |config| + # config.frameworks -= [ :action_web_service ] + # end + # + # This will use the default configuration options from Rails::Configuration, + # but allow for overwriting on select areas. + class Initializer + # The Configuration instance used by this Initializer instance. + attr_reader :configuration + + # The set of loaded plugins. + attr_reader :loaded_plugins + + # Runs the initializer. By default, this will invoke the #process method, + # which simply executes all of the initialization routines. Alternately, + # you can specify explicitly which initialization routine you want: + # + # Rails::Initializer.run(:set_load_path) + # + # This is useful if you only want the load path initialized, without + # incuring the overhead of completely loading the entire environment. + def self.run(command = :process, configuration = Configuration.new) + yield configuration if block_given? + initializer = new configuration + initializer.send(command) + initializer + end + + # Create a new Initializer instance that references the given Configuration + # instance. + def initialize(configuration) + @configuration = configuration + @loaded_plugins = Set.new + end + + # Sequentially step through all of the available initialization routines, + # in order: + # + # * #set_load_path + # * #set_connection_adapters + # * #require_frameworks + # * #load_environment + # * #initialize_database + # * #initialize_logger + # * #initialize_framework_logging + # * #initialize_framework_views + # * #initialize_dependency_mechanism + # * #initialize_breakpoints + # * #initialize_whiny_nils + # * #initialize_framework_settings + # * #load_environment + # * #load_plugins + # * #load_observers + # * #initialize_routing + # + # (Note that #load_environment is invoked twice, once at the start and + # once at the end, to support the legacy configuration style where the + # environment could overwrite the defaults directly, instead of via the + # Configuration instance. + def process + check_ruby_version + set_load_path + set_connection_adapters + + require_frameworks + load_environment + + initialize_database + initialize_logger + initialize_framework_logging + initialize_framework_views + initialize_dependency_mechanism + initialize_breakpoints + initialize_whiny_nils + initialize_temporary_directories + initialize_framework_settings + + # Support for legacy configuration style where the environment + # could overwrite anything set from the defaults/global through + # the individual base class configurations. + load_environment + + add_support_load_paths + + load_plugins + + # Observers are loaded after plugins in case Observers or observed models are modified by plugins. + load_observers + + # Routing must be initialized after plugins to allow the former to extend the routes + initialize_routing + + # the framework is now fully initialized + after_initialize + end + + # Check for valid Ruby version + # This is done in an external file, so we can use it + # from the `rails` program as well without duplication. + def check_ruby_version + require 'ruby_version_check' + end + + # Set the $LOAD_PATH based on the value of + # Configuration#load_paths. Duplicates are removed. + def set_load_path + configuration.load_paths.reverse_each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) } + $LOAD_PATH.uniq! + end + + # Sets the +RAILS_CONNECTION_ADAPTERS+ constant based on the value of + # Configuration#connection_adapters. This constant is used to determine + # which database adapters should be loaded (by default, all adapters are + # loaded). + def set_connection_adapters + Object.const_set("RAILS_CONNECTION_ADAPTERS", configuration.connection_adapters) if configuration.connection_adapters + end + + # Requires all frameworks specified by the Configuration#frameworks + # list. By default, all frameworks (ActiveRecord, ActiveSupport, + # ActionPack, ActionMailer, and ActionWebService) are loaded. + def require_frameworks + configuration.frameworks.each { |framework| require(framework.to_s) } + end + + # Add the load paths used by support functions such as the info controller + def add_support_load_paths + builtins = File.join(File.dirname(File.dirname(__FILE__)), 'builtin', '*') + $LOAD_PATH.concat(Dir[builtins]) + end + + # Loads all plugins in config.plugin_paths. plugin_paths + # defaults to vendor/plugins but may also be set to a list of + # paths, such as + # config.plugin_paths = ['lib/plugins', 'vendor/plugins'] + # + # Each plugin discovered in plugin_paths is initialized: + # * add its +lib+ directory, if present, to the beginning of the load path + # * evaluate init.rb if present + # + # After all plugins are loaded, duplicates are removed from the load path. + # Plugins are loaded in alphabetical order. + def load_plugins + find_plugins(configuration.plugin_paths).sort.each { |path| load_plugin path } + $LOAD_PATH.uniq! + end + + # Loads the environment specified by Configuration#environment_path, which + # is typically one of development, testing, or production. + def load_environment + silence_warnings do + config = configuration + constants = self.class.constants + eval(IO.read(configuration.environment_path), binding) + (self.class.constants - constants).each do |const| + Object.const_set(const, self.class.const_get(const)) + end + end + end + + def load_observers + ActiveRecord::Base.instantiate_observers + end + + # This initialization routine does nothing unless :active_record + # is one of the frameworks to load (Configuration#frameworks). If it is, + # this sets the database configuration from Configuration#database_configuration + # and then establishes the connection. + def initialize_database + return unless configuration.frameworks.include?(:active_record) + ActiveRecord::Base.configurations = configuration.database_configuration + ActiveRecord::Base.establish_connection + end + + # If the +RAILS_DEFAULT_LOGGER+ constant is already set, this initialization + # routine does nothing. If the constant is not set, and Configuration#logger + # is not +nil+, this also does nothing. Otherwise, a new logger instance + # is created at Configuration#log_path, with a default log level of + # Configuration#log_level. + # + # If the log could not be created, the log will be set to output to + # +STDERR+, with a log level of +WARN+. + def initialize_logger + # if the environment has explicitly defined a logger, use it + return if defined?(RAILS_DEFAULT_LOGGER) + + unless logger = configuration.logger + begin + logger = Logger.new(configuration.log_path) + logger.level = Logger.const_get(configuration.log_level.to_s.upcase) + rescue StandardError + logger = Logger.new(STDERR) + logger.level = Logger::WARN + logger.warn( + "Rails Error: Unable to access log file. Please ensure that #{configuration.log_path} exists and is chmod 0666. " + + "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." + ) + end + end + + silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } + end + + # Sets the logger for ActiveRecord, ActionController, and ActionMailer + # (but only for those frameworks that are to be loaded). If the framework's + # logger is already set, it is not changed, otherwise it is set to use + # +RAILS_DEFAULT_LOGGER+. + def initialize_framework_logging + for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks) + framework.to_s.camelize.constantize.const_get("Base").logger ||= RAILS_DEFAULT_LOGGER + end + end + + # Sets the +template_root+ for ActionController::Base and ActionMailer::Base + # (but only for those frameworks that are to be loaded). If the framework's + # +template_root+ has already been set, it is not changed, otherwise it is + # set to use Configuration#view_path. + def initialize_framework_views + for framework in ([ :action_controller, :action_mailer ] & configuration.frameworks) + framework.to_s.camelize.constantize.const_get("Base").template_root ||= configuration.view_path + end + end + + # If ActionController is not one of the loaded frameworks (Configuration#frameworks) + # this does nothing. Otherwise, it loads the routing definitions and sets up + # loading module used to lazily load controllers (Configuration#controller_paths). + def initialize_routing + return unless configuration.frameworks.include?(:action_controller) + ActionController::Routing::Routes.reload + end + + # Sets the dependency loading mechanism based on the value of + # Configuration#cache_classes. + def initialize_dependency_mechanism + Dependencies.mechanism = configuration.cache_classes ? :require : :load + end + + # Sets the +BREAKPOINT_SERVER_PORT+ if Configuration#breakpoint_server + # is true. + def initialize_breakpoints + silence_warnings { Object.const_set("BREAKPOINT_SERVER_PORT", 42531) if configuration.breakpoint_server } + end + + # Loads support for "whiny nil" (noisy warnings when methods are invoked + # on +nil+ values) if Configuration#whiny_nils is true. + def initialize_whiny_nils + require('active_support/whiny_nil') if configuration.whiny_nils + end + + def initialize_temporary_directories + if configuration.frameworks.include?(:action_controller) + session_path = "#{RAILS_ROOT}/tmp/sessions/" + ActionController::Base.session_options[:tmpdir] = File.exist?(session_path) ? session_path : Dir::tmpdir + + cache_path = "#{RAILS_ROOT}/tmp/cache/" + if File.exist?(cache_path) + ActionController::Base.fragment_cache_store = :file_store, cache_path + end + end + end + + # Initializes framework-specific settings for each of the loaded frameworks + # (Configuration#frameworks). The available settings map to the accessors + # on each of the corresponding Base classes. + def initialize_framework_settings + configuration.frameworks.each do |framework| + base_class = framework.to_s.camelize.constantize.const_get("Base") + + configuration.send(framework).each do |setting, value| + base_class.send("#{setting}=", value) + end + end + end + + # Fires the user-supplied after_initialize block (Configuration#after_initialize) + def after_initialize + configuration.after_initialize_block.call if configuration.after_initialize_block + end + + protected + # Return a list of plugin paths within base_path. A plugin path is + # a directory that contains either a lib directory or an init.rb file. + # This recurses into directories which are not plugin paths, so you + # may organize your plugins within the plugin path. + def find_plugins(*base_paths) + base_paths.flatten.inject([]) do |plugins, base_path| + Dir.glob(File.join(base_path, '*')).each do |path| + if plugin_path?(path) + plugins << path + elsif File.directory?(path) + plugins += find_plugins(path) + end + end + plugins + end + end + + def plugin_path?(path) + File.directory?(path) and (File.directory?(File.join(path, 'lib')) or File.file?(File.join(path, 'init.rb'))) + end + + # Load the plugin at path unless already loaded. + # + # Each plugin is initialized: + # * add its +lib+ directory, if present, to the beginning of the load path + # * evaluate init.rb if present + # + # Returns true if the plugin is successfully loaded or + # false if it is already loaded (similar to Kernel#require). + # Raises LoadError if the plugin is not found. + def load_plugin(directory) + name = File.basename(directory) + return false if loaded_plugins.include?(name) + + # Catch nonexistent and empty plugins. + raise LoadError, "No such plugin: #{directory}" unless plugin_path?(directory) + + lib_path = File.join(directory, 'lib') + init_path = File.join(directory, 'init.rb') + has_lib = File.directory?(lib_path) + has_init = File.file?(init_path) + + # Add lib to load path *after* the application lib, to allow + # application libraries to override plugin libraries. + if has_lib + application_lib_index = $LOAD_PATH.index(File.join(RAILS_ROOT, "lib")) || 0 + $LOAD_PATH.insert(application_lib_index + 1, lib_path) + end + + # Allow plugins to reference the current configuration object + config = configuration + + # Add to set of loaded plugins before 'name' collapsed in eval. + loaded_plugins << name + + # Evaluate init.rb. + silence_warnings { eval(IO.read(init_path), binding, init_path) } if has_init + + true + end + end + + # The Configuration class holds all the parameters for the Initializer and + # ships with defaults that suites most Rails applications. But it's possible + # to overwrite everything. Usually, you'll create an Configuration file + # implicitly through the block running on the Initializer, but it's also + # possible to create the Configuration instance in advance and pass it in + # like this: + # + # config = Rails::Configuration.new + # Rails::Initializer.run(:process, config) + class Configuration + # A stub for setting options on ActionController::Base + attr_accessor :action_controller + + # A stub for setting options on ActionMailer::Base + attr_accessor :action_mailer + + # A stub for setting options on ActionView::Base + attr_accessor :action_view + + # A stub for setting options on ActionWebService::Base + attr_accessor :action_web_service + + # A stub for setting options on ActiveRecord::Base + attr_accessor :active_record + + # Whether or not to use the breakpoint server (boolean) + attr_accessor :breakpoint_server + + # Whether or not classes should be cached (set to false if you want + # application classes to be reloaded on each request) + attr_accessor :cache_classes + + # The list of connection adapters to load. (By default, all connection + # adapters are loaded. You can set this to be just the adapter(s) you + # will use to reduce your application's load time.) + attr_accessor :connection_adapters + + # The list of paths that should be searched for controllers. (Defaults + # to app/controllers and components.) + attr_accessor :controller_paths + + # The path to the database configuration file to use. (Defaults to + # config/database.yml.) + attr_accessor :database_configuration_file + + # The list of rails framework components that should be loaded. (Defaults + # to :active_record, :action_controller, + # :action_view, :action_mailer, and + # :action_web_service). + attr_accessor :frameworks + + # An array of additional paths to prepend to the load path. By default, + # all +app+, +lib+, +vendor+ and mock paths are included in this list. + attr_accessor :load_paths + + # The log level to use for the default Rails logger. In production mode, + # this defaults to :info. In development mode, it defaults to + # :debug. + attr_accessor :log_level + + # The path to the log file to use. Defaults to log/#{environment}.log + # (e.g. log/development.log or log/production.log). + attr_accessor :log_path + + # The specific logger to use. By default, a logger will be created and + # initialized using #log_path and #log_level, but a programmer may + # specifically set the logger to use via this accessor and it will be + # used directly. + attr_accessor :logger + + # The root of the application's views. (Defaults to app/views.) + attr_accessor :view_path + + # Set to +true+ if you want to be warned (noisily) when you try to invoke + # any method of +nil+. Set to +false+ for the standard Ruby behavior. + attr_accessor :whiny_nils + + # The path to the root of the plugins directory. By default, it is in + # vendor/plugins. + attr_accessor :plugin_paths + + # Create a new Configuration instance, initialized with the default + # values. + def initialize + self.frameworks = default_frameworks + self.load_paths = default_load_paths + self.log_path = default_log_path + self.log_level = default_log_level + self.view_path = default_view_path + self.controller_paths = default_controller_paths + self.cache_classes = default_cache_classes + self.breakpoint_server = default_breakpoint_server + self.whiny_nils = default_whiny_nils + self.plugin_paths = default_plugin_paths + self.database_configuration_file = default_database_configuration_file + + for framework in default_frameworks + self.send("#{framework}=", Rails::OrderedOptions.new) + end + end + + # Loads and returns the contents of the #database_configuration_file. The + # contents of the file are processed via ERB before being sent through + # YAML::load. + def database_configuration + YAML::load(ERB.new(IO.read(database_configuration_file)).result) + end + + # The path to the current environment's file (development.rb, etc.). By + # default the file is at config/environments/#{environment}.rb. + def environment_path + "#{root_path}/config/environments/#{environment}.rb" + end + + # Return the currently selected environment. By default, it returns the + # value of the +RAILS_ENV+ constant. + def environment + ::RAILS_ENV + end + + # Sets a block which will be executed after rails has been fully initialized. + # Useful for per-environment configuration which depends on the framework being + # fully initialized. + def after_initialize(&after_initialize_block) + @after_initialize_block = after_initialize_block + end + + # Returns the block set in Configuration#after_initialize + def after_initialize_block + @after_initialize_block + end + + # Add a preparation callback that will run before every request in development + # mode, or before the first request in production. + # + # See Dispatcher#to_prepare. + def to_prepare(&callback) + Dispatcher.to_prepare(&callback) + end + + private + def root_path + ::RAILS_ROOT + end + + def framework_root_path + defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root_path}/vendor/rails" + end + + def default_frameworks + [ :active_record, :action_controller, :action_view, :action_mailer, :action_web_service ] + end + + def default_load_paths + paths = ["#{root_path}/test/mocks/#{environment}"] + + # Add the app's controller directory + paths.concat(Dir["#{root_path}/app/controllers/"]) + + # Then model subdirectories. + # TODO: Don't include .rb models as load paths + paths.concat(Dir["#{root_path}/app/models/[_a-z]*"]) + paths.concat(Dir["#{root_path}/components/[_a-z]*"]) + + # Followed by the standard includes. + paths.concat %w( + app + app/models + app/controllers + app/helpers + app/services + app/apis + components + config + lib + vendor + ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) } + + # TODO: Don't include dirs for frameworks that are not used + paths.concat %w( + railties + railties/lib + actionpack/lib + activesupport/lib + activerecord/lib + actionmailer/lib + actionwebservice/lib + ).map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) } + end + + def default_log_path + File.join(root_path, 'log', "#{environment}.log") + end + + def default_log_level + environment == 'production' ? :info : :debug + end + + def default_database_configuration_file + File.join(root_path, 'config', 'database.yml') + end + + def default_view_path + File.join(root_path, 'app', 'views') + end + + def default_controller_paths + [ File.join(root_path, 'app', 'controllers'), File.join(root_path, 'components'), File.join(RAILTIES_PATH, 'builtin', 'controllers') ] + end + + def default_dependency_mechanism + :load + end + + def default_cache_classes + false + end + + def default_breakpoint_server + false + end + + def default_whiny_nils + false + end + + def default_plugin_paths + ["#{root_path}/vendor/plugins"] + end + end +end + +# Needs to be duplicated from Active Support since its needed before Active +# Support is available. Here both Options and Hash are namespaced to prevent +# conflicts with other implementations AND with the classes residing in ActiveSupport. +class Rails::OrderedOptions < Array #:nodoc: + def []=(key, value) + key = key.to_sym + + if pair = find_pair(key) + pair.pop + pair << value + else + self << [key, value] + end + end + + def [](key) + pair = find_pair(key.to_sym) + pair ? pair.last : nil + end + + def method_missing(name, *args) + if name.to_s =~ /(.*)=$/ + self[$1.to_sym] = args.first + else + self[name] + end + end + + private + def find_pair(key) + self.each { |i| return i if i.first == key } + return false + end +end \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails/version.rb b/vendor/rails/railties/lib/rails/version.rb new file mode 100644 index 0000000..bc87eea --- /dev/null +++ b/vendor/rails/railties/lib/rails/version.rb @@ -0,0 +1,9 @@ +module Rails + module VERSION #:nodoc: + MAJOR = 1 + MINOR = 1 + TINY = 2 + + STRING = [MAJOR, MINOR, TINY].join('.') + end +end diff --git a/vendor/rails/railties/lib/rails_generator.rb b/vendor/rails/railties/lib/rails_generator.rb new file mode 100644 index 0000000..9c587c9 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator.rb @@ -0,0 +1,43 @@ +#-- +# Copyright (c) 2004 Jeremy Kemper +# +# 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__) + "/../../activesupport/lib") + +begin + require 'active_support' +rescue LoadError + require 'rubygems' + require_gem 'activesupport' +end + +require 'rails_generator/base' +require 'rails_generator/lookup' +require 'rails_generator/commands' + +Rails::Generator::Base.send(:include, Rails::Generator::Lookup) +Rails::Generator::Base.send(:include, Rails::Generator::Commands) + +# Set up a default logger for convenience. +require 'rails_generator/simple_logger' +Rails::Generator::Base.logger = Rails::Generator::SimpleLogger.new(STDOUT) diff --git a/vendor/rails/railties/lib/rails_generator/base.rb b/vendor/rails/railties/lib/rails_generator/base.rb new file mode 100644 index 0000000..6a56f96 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/base.rb @@ -0,0 +1,203 @@ +require File.dirname(__FILE__) + '/options' +require File.dirname(__FILE__) + '/manifest' +require File.dirname(__FILE__) + '/spec' + +# Rails::Generator is a code generation platform tailored for the Rails +# web application framework. Generators are easily invoked within Rails +# applications to add and remove components such as models and controllers. +# New generators are easy to create and may be distributed as RubyGems or +# tarballs for inclusion system-wide, per-user, or per-application. +# +# Generators may subclass other generators to provide variations that +# require little or no new logic but replace the template files. +# The postback generator is an example: it subclasses the scaffold +# generator and just replaces the code templates with its own. +# +# Now go forth and multiply^Wgenerate. +module Rails + module Generator + class GeneratorError < StandardError; end + class UsageError < GeneratorError; end + + + # The base code generator is bare-bones. It sets up the source and + # destination paths and tells the logger whether to keep its trap shut. + # You're probably looking for NamedBase, a subclass meant for generating + # "named" components such as models, controllers, and mailers. + # + # Generators create a manifest of the actions they perform then hand + # the manifest to a command which replay the actions to do the heavy + # lifting. Create, destroy, and list commands are included. Since a + # single manifest may be used by any command, creating new generators is + # as simple as writing some code templates and declaring what you'd like + # to do with them. + # + # The manifest method must be implemented by subclasses, returning a + # Rails::Generator::Manifest. The record method is provided as a + # convenience for manifest creation. Example: + # class EliteGenerator < Rails::Generator::Base + # def manifest + # record do |m| + # m.do(some) + # m.things(in) { here } + # end + # end + # end + class Base + include Options + + # Declare default options for the generator. These options + # are inherited to subclasses. + default_options :collision => :ask, :quiet => false + + # A logger instance available everywhere in the generator. + cattr_accessor :logger + + # Every generator that is dynamically looked up is tagged with a + # Spec describing where it was found. + class_inheritable_accessor :spec + + attr_reader :source_root, :destination_root, :args + + def initialize(runtime_args, runtime_options = {}) + @args = runtime_args + parse!(@args, runtime_options) + + # Derive source and destination paths. + @source_root = options[:source] || File.join(spec.path, 'templates') + if options[:destination] + @destination_root = options[:destination] + elsif defined? ::RAILS_ROOT + @destination_root = ::RAILS_ROOT + end + + # Silence the logger if requested. + logger.quiet = options[:quiet] + + # Raise usage error if help is requested. + usage if options[:help] + end + + # Generators must provide a manifest. Use the record method to create + # a new manifest and record your generator's actions. + def manifest + raise NotImplementedError, "No manifest for '#{spec.name}' generator." + end + + # Return the full path from the source root for the given path. + # Example for source_root = '/source': + # source_path('some/path.rb') == '/source/some/path.rb' + # + # The given path may include a colon ':' character to indicate that + # the file belongs to another generator. This notation allows any + # generator to borrow files from another. Example: + # source_path('model:fixture.yml') = '/model/source/path/fixture.yml' + def source_path(relative_source) + # Check whether we're referring to another generator's file. + name, path = relative_source.split(':', 2) + + # If not, return the full path to our source file. + if path.nil? + File.join(source_root, name) + + # Otherwise, ask our referral for the file. + else + # FIXME: this is broken, though almost always true. Others' + # source_root are not necessarily the templates dir. + File.join(self.class.lookup(name).path, 'templates', path) + end + end + + # Return the full path from the destination root for the given path. + # Example for destination_root = '/dest': + # destination_path('some/path.rb') == '/dest/some/path.rb' + def destination_path(relative_destination) + File.join(destination_root, relative_destination) + end + + protected + # Convenience method for generator subclasses to record a manifest. + def record + Rails::Generator::Manifest.new(self) { |m| yield m } + end + + # Override with your own usage banner. + def banner + "Usage: #{$0} #{spec.name} [options]" + end + + # Read USAGE from file in generator base path. + def usage_message + File.read(File.join(spec.path, 'USAGE')) rescue '' + end + end + + + # The base generator for named components: models, controllers, mailers, + # etc. The target name is taken as the first argument and inflected to + # singular, plural, class, file, and table forms for your convenience. + # The remaining arguments are aliased to actions for controller and + # mailer convenience. + # + # If no name is provided, the generator raises a usage error with content + # optionally read from the USAGE file in the generator's base path. + # + # See Rails::Generator::Base for a discussion of Manifests and Commands. + class NamedBase < Base + attr_reader :name, :class_name, :singular_name, :plural_name, :table_name + attr_reader :class_path, :file_path, :class_nesting, :class_nesting_depth + alias_method :file_name, :singular_name + alias_method :actions, :args + + def initialize(runtime_args, runtime_options = {}) + super + + # Name argument is required. + usage if runtime_args.empty? + + @args = runtime_args.dup + base_name = @args.shift + assign_names!(base_name) + end + + protected + # Override with your own usage banner. + def banner + "Usage: #{$0} #{spec.name} #{spec.name.camelize}Name [options]" + end + + private + def assign_names!(name) + @name = name + base_name, @class_path, @file_path, @class_nesting, @class_nesting_depth = extract_modules(@name) + @class_name_without_nesting, @singular_name, @plural_name = inflect_names(base_name) + @table_name = ActiveRecord::Base.pluralize_table_names ? plural_name : singular_name + if @class_nesting.empty? + @class_name = @class_name_without_nesting + else + @class_name = "#{@class_nesting}::#{@class_name_without_nesting}" + end + end + + # Extract modules from filesystem-style or ruby-style path: + # good/fun/stuff + # Good::Fun::Stuff + # produce the same results. + def extract_modules(name) + modules = name.include?('/') ? name.split('/') : name.split('::') + name = modules.pop + path = modules.map { |m| m.underscore } + file_path = (path + [name.underscore]).join('/') + nesting = modules.map { |m| m.camelize }.join('::') + [name, path, file_path, nesting, modules.size] + end + + def inflect_names(name) + camel = name.camelize + under = camel.underscore + plural = under.pluralize + [camel, under, plural] + end + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/commands.rb b/vendor/rails/railties/lib/rails_generator/commands.rb new file mode 100644 index 0000000..0d03983 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/commands.rb @@ -0,0 +1,519 @@ +require 'delegate' +require 'optparse' +require 'fileutils' +require 'erb' + +module Rails + module Generator + module Commands + # Here's a convenient way to get a handle on generator commands. + # Command.instance('destroy', my_generator) instantiates a Destroy + # delegate of my_generator ready to do your dirty work. + def self.instance(command, generator) + const_get(command.to_s.camelize).new(generator) + end + + # Even more convenient access to commands. Include Commands in + # the generator Base class to get a nice #command instance method + # which returns a delegate for the requested command. + def self.included(base) + base.send(:define_method, :command) do |command| + Commands.instance(command, self) + end + end + + + # Generator commands delegate Rails::Generator::Base and implement + # a standard set of actions. Their behavior is defined by the way + # they respond to these actions: Create brings life; Destroy brings + # death; List passively observes. + # + # Commands are invoked by replaying (or rewinding) the generator's + # manifest of actions. See Rails::Generator::Manifest and + # Rails::Generator::Base#manifest method that generator subclasses + # are required to override. + # + # Commands allows generators to "plug in" invocation behavior, which + # corresponds to the GoF Strategy pattern. + class Base < DelegateClass(Rails::Generator::Base) + # Replay action manifest. RewindBase subclass rewinds manifest. + def invoke! + manifest.replay(self) + end + + def dependency(generator_name, args, runtime_options = {}) + logger.dependency(generator_name) do + self.class.new(instance(generator_name, args, full_options(runtime_options))).invoke! + end + end + + # Does nothing for all commands except Create. + def class_collisions(*class_names) + end + + # Does nothing for all commands except Create. + def readme(*args) + end + + protected + def migration_directory(relative_path) + directory(@migration_directory = relative_path) + end + + def existing_migrations(file_name) + Dir.glob("#{@migration_directory}/[0-9]*_*.rb").grep(/[0-9]+_#{file_name}.rb$/) + end + + def migration_exists?(file_name) + not existing_migrations(file_name).empty? + end + + def current_migration_number + Dir.glob("#{@migration_directory}/[0-9]*.rb").inject(0) do |max, file_path| + n = File.basename(file_path).split('_', 2).first.to_i + if n > max then n else max end + end + end + + def next_migration_number + current_migration_number + 1 + end + + def next_migration_string(padding = 3) + "%.#{padding}d" % next_migration_number + end + + private + # Ask the user interactively whether to force collision. + def force_file_collision?(destination) + $stdout.print "overwrite #{destination}? [Ynaq] " + case $stdin.gets + when /a/i + $stdout.puts "forcing #{spec.name}" + options[:collision] = :force + when /q/i + $stdout.puts "aborting #{spec.name}" + raise SystemExit + when /n/i then :skip + else :force + end + rescue + retry + end + + def render_template_part(template_options) + # Getting Sandbox to evaluate part template in it + part_binding = template_options[:sandbox].call.sandbox_binding + part_rel_path = template_options[:insert] + part_path = source_path(part_rel_path) + + # Render inner template within Sandbox binding + rendered_part = ERB.new(File.readlines(part_path).join, nil, '-').result(part_binding) + begin_mark = template_part_mark(template_options[:begin_mark], template_options[:mark_id]) + end_mark = template_part_mark(template_options[:end_mark], template_options[:mark_id]) + begin_mark + rendered_part + end_mark + end + + def template_part_mark(name, id) + "\n" + end + end + + # Base class for commands which handle generator actions in reverse, such as Destroy. + class RewindBase < Base + # Rewind action manifest. + def invoke! + manifest.rewind(self) + end + end + + + # Create is the premier generator command. It copies files, creates + # directories, renders templates, and more. + class Create < Base + + # Check whether the given class names are already taken by + # Ruby or Rails. In the future, expand to check other namespaces + # such as the rest of the user's app. + def class_collisions(*class_names) + class_names.flatten.each do |class_name| + # Convert to string to allow symbol arguments. + class_name = class_name.to_s + + # Skip empty strings. + next if class_name.strip.empty? + + # Split the class from its module nesting. + nesting = class_name.split('::') + name = nesting.pop + + # Extract the last Module in the nesting. + last = nesting.inject(Object) { |last, nest| + break unless last.const_defined?(nest) + last.const_get(nest) + } + + # If the last Module exists, check whether the given + # class exists and raise a collision if so. + if last and last.const_defined?(name.camelize) + raise_class_collision(class_name) + end + end + end + + # Copy a file from source to destination with collision checking. + # + # The file_options hash accepts :chmod and :shebang and :collision options. + # :chmod sets the permissions of the destination file: + # file 'config/empty.log', 'log/test.log', :chmod => 0664 + # :shebang sets the #!/usr/bin/ruby line for scripts + # file 'bin/generate.rb', 'script/generate', :chmod => 0755, :shebang => '/usr/bin/env ruby' + # :collision sets the collision option only for the destination file: + # file 'settings/server.yml', 'config/server.yml', :collision => :skip + # + # Collisions are handled by checking whether the destination file + # exists and either skipping the file, forcing overwrite, or asking + # the user what to do. + def file(relative_source, relative_destination, file_options = {}, &block) + # Determine full paths for source and destination files. + source = source_path(relative_source) + destination = destination_path(relative_destination) + destination_exists = File.exists?(destination) + + # If source and destination are identical then we're done. + if destination_exists and identical?(source, destination, &block) + return logger.identical(relative_destination) + end + + # Check for and resolve file collisions. + if destination_exists + + # Make a choice whether to overwrite the file. :force and + # :skip already have their mind made up, but give :ask a shot. + choice = case (file_options[:collision] || options[:collision]).to_sym #|| :ask + when :ask then force_file_collision?(relative_destination) + when :force then :force + when :skip then :skip + else raise "Invalid collision option: #{options[:collision].inspect}" + end + + # Take action based on our choice. Bail out if we chose to + # skip the file; otherwise, log our transgression and continue. + case choice + when :force then logger.force(relative_destination) + when :skip then return(logger.skip(relative_destination)) + else raise "Invalid collision choice: #{choice}.inspect" + end + + # File doesn't exist so log its unbesmirched creation. + else + logger.create relative_destination + end + + # If we're pretending, back off now. + return if options[:pretend] + + # Write destination file with optional shebang. Yield for content + # if block given so templaters may render the source file. If a + # shebang is requested, replace the existing shebang or insert a + # new one. + File.open(destination, 'wb') do |df| + File.open(source, 'rb') do |sf| + if block_given? + df.write(yield(sf)) + else + if file_options[:shebang] + df.puts("#!#{file_options[:shebang]}") + if line = sf.gets + df.puts(line) if line !~ /^#!/ + end + end + df.write(sf.read) + end + end + end + + # Optionally change permissions. + if file_options[:chmod] + FileUtils.chmod(file_options[:chmod], destination) + end + + # Optionally add file to subversion + system("svn add #{destination}") if options[:svn] + end + + # Checks if the source and the destination file are identical. If + # passed a block then the source file is a template that needs to first + # be evaluated before being compared to the destination. + def identical?(source, destination, &block) + return false if File.directory? destination + source = block_given? ? File.open(source) {|sf| yield(sf)} : IO.read(source) + destination = IO.read(destination) + source == destination + end + + # Generate a file for a Rails application using an ERuby template. + # Looks up and evalutes a template by name and writes the result. + # + # The ERB template uses explicit trim mode to best control the + # proliferation of whitespace in generated code. <%- trims leading + # whitespace; -%> trims trailing whitespace including one newline. + # + # A hash of template options may be passed as the last argument. + # The options accepted by the file are accepted as well as :assigns, + # a hash of variable bindings. Example: + # template 'foo', 'bar', :assigns => { :action => 'view' } + # + # Template is implemented in terms of file. It calls file with a + # block which takes a file handle and returns its rendered contents. + def template(relative_source, relative_destination, template_options = {}) + file(relative_source, relative_destination, template_options) do |file| + # Evaluate any assignments in a temporary, throwaway binding. + vars = template_options[:assigns] || {} + b = binding + vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b } + + # Render the source file with the temporary binding. + ERB.new(file.read, nil, '-').result(b) + end + end + + def complex_template(relative_source, relative_destination, template_options = {}) + options = template_options.dup + options[:assigns] ||= {} + options[:assigns]['template_for_inclusion'] = render_template_part(template_options) + template(relative_source, relative_destination, options) + end + + # Create a directory including any missing parent directories. + # Always directories which exist. + def directory(relative_path) + path = destination_path(relative_path) + if File.exists?(path) + logger.exists relative_path + else + logger.create relative_path + FileUtils.mkdir_p(path) unless options[:pretend] + + # Optionally add file to subversion + system("svn add #{path}") if options[:svn] + end + end + + # Display a README. + def readme(*relative_sources) + relative_sources.flatten.each do |relative_source| + logger.readme relative_source + puts File.read(source_path(relative_source)) unless options[:pretend] + end + end + + # When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template. + def migration_template(relative_source, relative_destination, template_options = {}) + migration_directory relative_destination + migration_file_name = template_options[:migration_file_name] || file_name + raise "Another migration is already named #{migration_file_name}: #{existing_migrations(migration_file_name).first}" if migration_exists?(migration_file_name) + template(relative_source, "#{relative_destination}/#{next_migration_string}_#{migration_file_name}.rb", template_options) + end + + private + # Raise a usage error with an informative WordNet suggestion. + # Thanks to Florian Gross (flgr). + def raise_class_collision(class_name) + message = <", "") + data.scan(/^Sense \d+\n.+?\n\n/m) + end + end + rescue Exception + return nil + end + end + + + # Undo the actions performed by a generator. Rewind the action + # manifest and attempt to completely erase the results of each action. + class Destroy < RewindBase + # Remove a file if it exists and is a file. + def file(relative_source, relative_destination, file_options = {}) + destination = destination_path(relative_destination) + if File.exists?(destination) + logger.rm relative_destination + unless options[:pretend] + if options[:svn] + # If the file has been marked to be added + # but has not yet been checked in, revert and delete + if options[:svn][relative_destination] + system("svn revert #{destination}") + FileUtils.rm(destination) + else + # If the directory is not in the status list, it + # has no modifications so we can simply remove it + system("svn rm #{destination}") + end + else + FileUtils.rm(destination) + end + end + else + logger.missing relative_destination + return + end + end + + # Templates are deleted just like files and the actions take the + # same parameters, so simply alias the file method. + alias_method :template, :file + + # Remove each directory in the given path from right to left. + # Remove each subdirectory if it exists and is a directory. + def directory(relative_path) + parts = relative_path.split('/') + until parts.empty? + partial = File.join(parts) + path = destination_path(partial) + if File.exists?(path) + if Dir[File.join(path, '*')].empty? + logger.rmdir partial + unless options[:pretend] + if options[:svn] + # If the directory has been marked to be added + # but has not yet been checked in, revert and delete + if options[:svn][relative_path] + system("svn revert #{path}") + FileUtils.rmdir(path) + else + # If the directory is not in the status list, it + # has no modifications so we can simply remove it + system("svn rm #{path}") + end + else + FileUtils.rmdir(path) + end + end + else + logger.notempty partial + end + else + logger.missing partial + end + parts.pop + end + end + + def complex_template(*args) + # nothing should be done here + end + + # When deleting a migration, it knows to delete every file named "[0-9]*_#{file_name}". + def migration_template(relative_source, relative_destination, template_options = {}) + migration_directory relative_destination + + migration_file_name = template_options[:migration_file_name] || file_name + unless migration_exists?(migration_file_name) + puts "There is no migration named #{migration_file_name}" + return + end + + + existing_migrations(migration_file_name).each do |file_path| + file(relative_source, file_path, template_options) + end + end + end + + + # List a generator's action manifest. + class List < Base + def dependency(generator_name, args, options = {}) + logger.dependency "#{generator_name}(#{args.join(', ')}, #{options.inspect})" + end + + def class_collisions(*class_names) + logger.class_collisions class_names.join(', ') + end + + def file(relative_source, relative_destination, options = {}) + logger.file relative_destination + end + + def template(relative_source, relative_destination, options = {}) + logger.template relative_destination + end + + def complex_template(relative_source, relative_destination, options = {}) + logger.template "#{options[:insert]} inside #{relative_destination}" + end + + def directory(relative_path) + logger.directory "#{destination_path(relative_path)}/" + end + + def readme(*args) + logger.readme args.join(', ') + end + + def migration_template(relative_source, relative_destination, options = {}) + migration_directory relative_destination + logger.migration_template file_name + end + end + + # Update generator's action manifest. + class Update < Create + def file(relative_source, relative_destination, options = {}) + # logger.file relative_destination + end + + def template(relative_source, relative_destination, options = {}) + # logger.template relative_destination + end + + def complex_template(relative_source, relative_destination, template_options = {}) + + begin + dest_file = destination_path(relative_destination) + source_to_update = File.readlines(dest_file).join + rescue Errno::ENOENT + logger.missing relative_destination + return + end + + logger.refreshing "#{template_options[:insert].gsub(/\.rhtml/,'')} inside #{relative_destination}" + + begin_mark = Regexp.quote(template_part_mark(template_options[:begin_mark], template_options[:mark_id])) + end_mark = Regexp.quote(template_part_mark(template_options[:end_mark], template_options[:mark_id])) + + # Refreshing inner part of the template with freshly rendered part. + rendered_part = render_template_part(template_options) + source_to_update.gsub!(/#{begin_mark}.*?#{end_mark}/m, rendered_part) + + File.open(dest_file, 'w') { |file| file.write(source_to_update) } + end + + def directory(relative_path) + # logger.directory "#{destination_path(relative_path)}/" + end + end + + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/applications/app/USAGE b/vendor/rails/railties/lib/rails_generator/generators/applications/app/USAGE new file mode 100644 index 0000000..3bb5511 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/applications/app/USAGE @@ -0,0 +1,16 @@ +Description: + The 'rails' command creates a new Rails application with a default + directory structure and configuration at the path you specify. + +Example: + rails ~/Code/Ruby/weblog + + This generates a skeletal Rails installation in ~/Code/Ruby/weblog. + See the README in the newly created application to get going. + +WARNING: + Only specify --without-gems if you did not use gems to install Rails. + Your application will expect to find activerecord, actionpack, and + actionmailer directories in the vendor directory. A popular way to track + the bleeding edge of Rails development is to checkout from source control + directly to the vendor directory. See http://dev.rubyonrails.com diff --git a/vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb new file mode 100644 index 0000000..584aa0c --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/applications/app/app_generator.rb @@ -0,0 +1,160 @@ +require 'rbconfig' + +class AppGenerator < Rails::Generator::Base + DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'], + Config::CONFIG['ruby_install_name']) + + DATABASES = %w(mysql oracle postgresql sqlite2 sqlite3 frontbase) + + default_options :db => "mysql", :shebang => DEFAULT_SHEBANG, :freeze => false + mandatory_options :source => "#{File.dirname(__FILE__)}/../../../../.." + + def initialize(runtime_args, runtime_options = {}) + super + usage if args.empty? + usage("Databases supported for preconfiguration are: #{DATABASES.join(", ")}") if (options[:db] && !DATABASES.include?(options[:db])) + @destination_root = args.shift + @app_name = File.basename(File.expand_path(@destination_root)) + end + + def manifest + # Use /usr/bin/env if no special shebang was specified + script_options = { :chmod => 0755, :shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang] } + dispatcher_options = { :chmod => 0755, :shebang => options[:shebang] } + + record do |m| + # Root directory and all subdirectories. + m.directory '' + BASEDIRS.each { |path| m.directory path } + + # Root + m.file "fresh_rakefile", "Rakefile" + m.file "README", "README" + + # Application + m.template "helpers/application.rb", "app/controllers/application.rb", :assigns => { :app_name => @app_name } + m.template "helpers/application_helper.rb", "app/helpers/application_helper.rb" + m.template "helpers/test_helper.rb", "test/test_helper.rb" + + # database.yml and .htaccess + m.template "configs/databases/#{options[:db]}.yml", "config/database.yml", :assigns => { + :app_name => @app_name, + :socket => options[:db] == "mysql" ? mysql_socket_location : nil + } + m.template "configs/routes.rb", "config/routes.rb" + m.template "configs/apache.conf", "public/.htaccess" + + # Environments + m.file "environments/boot.rb", "config/boot.rb" + m.template "environments/environment.rb", "config/environment.rb", :assigns => { :freeze => options[:freeze] } + m.file "environments/production.rb", "config/environments/production.rb" + m.file "environments/development.rb", "config/environments/development.rb" + m.file "environments/test.rb", "config/environments/test.rb" + + # Scripts + %w( about breakpointer console destroy generate performance/benchmarker performance/profiler process/reaper process/spawner process/inspector runner server plugin ).each do |file| + m.file "bin/#{file}", "script/#{file}", script_options + end + + # Dispatches + m.file "dispatches/dispatch.rb", "public/dispatch.rb", dispatcher_options + m.file "dispatches/dispatch.rb", "public/dispatch.cgi", dispatcher_options + m.file "dispatches/dispatch.fcgi", "public/dispatch.fcgi", dispatcher_options + + # HTML files + %w(404 500 index).each do |file| + m.template "html/#{file}.html", "public/#{file}.html" + end + + m.template "html/favicon.ico", "public/favicon.ico" + m.template "html/robots.txt", "public/robots.txt" + m.file "html/images/rails.png", "public/images/rails.png" + + # Javascripts + m.file "html/javascripts/prototype.js", "public/javascripts/prototype.js" + m.file "html/javascripts/effects.js", "public/javascripts/effects.js" + m.file "html/javascripts/dragdrop.js", "public/javascripts/dragdrop.js" + m.file "html/javascripts/controls.js", "public/javascripts/controls.js" + m.file "html/javascripts/application.js", "public/javascripts/application.js" + + # Docs + m.file "doc/README_FOR_APP", "doc/README_FOR_APP" + + # Logs + %w(server production development test).each { |file| + m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666 + } + end + end + + protected + def banner + "Usage: #{$0} /path/to/your/app [options]" + end + + def add_options!(opt) + opt.separator '' + opt.separator 'Options:' + opt.on("-r", "--ruby=path", String, + "Path to the Ruby binary of your choice (otherwise scripts use env, dispatchers current path).", + "Default: #{DEFAULT_SHEBANG}") { |v| options[:shebang] = v } + + opt.on("-d", "--database=name", String, + "Preconfigure for selected database (options: mysql/oracle/postgresql/sqlite2/sqlite3).", + "Default: mysql") { |v| options[:db] = v } + + opt.on("-f", "--freeze", + "Freeze Rails in vendor/rails from the gems generating the skeleton", + "Default: false") { |v| options[:freeze] = v } + end + + def mysql_socket_location + RUBY_PLATFORM =~ /mswin32/ ? MYSQL_SOCKET_LOCATIONS.find { |f| File.exists?(f) } : nil + end + + + # Installation skeleton. Intermediate directories are automatically + # created so don't sweat their absence here. + BASEDIRS = %w( + app/controllers + app/helpers + app/models + app/views/layouts + config/environments + components + db + doc + lib + lib/tasks + log + public/images + public/javascripts + public/stylesheets + script/performance + script/process + test/fixtures + test/functional + test/integration + test/mocks/development + test/mocks/test + test/unit + vendor + vendor/plugins + tmp/sessions + tmp/sockets + tmp/cache + tmp/pids + ) + + MYSQL_SOCKET_LOCATIONS = [ + "/tmp/mysql.sock", # default + "/var/run/mysqld/mysqld.sock", # debian/gentoo + "/var/tmp/mysql.sock", # freebsd + "/var/lib/mysql/mysql.sock", # fedora + "/opt/local/lib/mysql/mysql.sock", # fedora + "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql + "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 + "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 + "/opt/lampp/var/mysql/mysql.sock" # xampp for linux + ] +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/controller/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/controller/USAGE new file mode 100644 index 0000000..ec64209 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/controller/USAGE @@ -0,0 +1,30 @@ +Description: + The controller generator creates stubs for a new controller and its views. + + The generator takes a controller name and a list of views as arguments. + The controller name may be given in CamelCase or under_score and should + not be suffixed with 'Controller'. To create a controller within a + module, specify the controller name as 'module/controller'. + + The generator creates a controller class in app/controllers with view + templates in app/views/controller_name, a helper class in app/helpers, + and a functional test suite in test/functional. + +Example: + ./script/generate controller CreditCard open debit credit close + + Credit card controller with URLs like /credit_card/debit. + Controller: app/controllers/credit_card_controller.rb + Views: app/views/credit_card/debit.rhtml [...] + Helper: app/helpers/credit_card_helper.rb + Test: test/functional/credit_card_controller_test.rb + +Modules Example: + ./script/generate controller 'admin/credit_card' suspend late_fee + + Credit card admin controller with URLs /admin/credit_card/suspend. + Controller: app/controllers/admin/credit_card_controller.rb + Views: app/views/admin/credit_card/debit.rhtml [...] + Helper: app/helpers/admin/credit_card_helper.rb + Test: test/functional/admin/credit_card_controller_test.rb + diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/controller/controller_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/controller/controller_generator.rb new file mode 100644 index 0000000..358d357 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/controller/controller_generator.rb @@ -0,0 +1,38 @@ +class ControllerGenerator < Rails::Generator::NamedBase + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions class_path, "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper" + + # Controller, helper, views, and test directories. + m.directory File.join('app/controllers', class_path) + m.directory File.join('app/helpers', class_path) + m.directory File.join('app/views', class_path, file_name) + m.directory File.join('test/functional', class_path) + + # Controller class, functional test, and helper class. + m.template 'controller.rb', + File.join('app/controllers', + class_path, + "#{file_name}_controller.rb") + + m.template 'functional_test.rb', + File.join('test/functional', + class_path, + "#{file_name}_controller_test.rb") + + m.template 'helper.rb', + File.join('app/helpers', + class_path, + "#{file_name}_helper.rb") + + # View template for each action. + actions.each do |action| + path = File.join('app/views', class_path, file_name, "#{action}.rhtml") + m.template 'view.rhtml', + path, + :assigns => { :action => action, :path => path } + end + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/controller.rb b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/controller.rb new file mode 100644 index 0000000..da71b5f --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/controller.rb @@ -0,0 +1,10 @@ +class <%= class_name %>Controller < ApplicationController +<% if options[:scaffold] -%> + scaffold :<%= singular_name %> +<% end -%> +<% for action in actions -%> + + def <%= action %> + end +<% end -%> +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb new file mode 100644 index 0000000..abe9c4c --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper' +require '<%= file_path %>_controller' + +# Re-raise errors caught by the controller. +class <%= class_name %>Controller; def rescue_action(e) raise e end; end + +class <%= class_name %>ControllerTest < Test::Unit::TestCase + def setup + @controller = <%= class_name %>Controller.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/helper.rb b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/helper.rb new file mode 100644 index 0000000..3fe2ecd --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/helper.rb @@ -0,0 +1,2 @@ +module <%= class_name %>Helper +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/view.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/view.rhtml new file mode 100644 index 0000000..ad85431 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/controller/templates/view.rhtml @@ -0,0 +1,2 @@ +

      <%= class_name %>#<%= action %>

      +

      Find me in <%= path %>

      diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/USAGE new file mode 100644 index 0000000..d1ed71a --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/USAGE @@ -0,0 +1,14 @@ +Description: + The model generator creates a stub for a new integration test. + + The generator takes an integration test name as its argument. The test + name may be given in CamelCase or under_score and should not be suffixed + with 'Test'. + + The generator creates an integration test class in test/integration. + +Example: + ./script/generate integration_test GeneralStories + + This will create a GeneralStores integration test: + test/integration/general_stories_test.rb diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb new file mode 100644 index 0000000..90fa969 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/integration_test_generator.rb @@ -0,0 +1,16 @@ +class IntegrationTestGenerator < Rails::Generator::NamedBase + default_options :skip_migration => false + + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions class_path, class_name, "#{class_name}Test" + + # integration test directory + m.directory File.join('test/integration', class_path) + + # integration test stub + m.template 'integration_test.rb', File.join('test/integration', class_path, "#{file_name}_test.rb") + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb new file mode 100644 index 0000000..61688ae --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/integration_test/templates/integration_test.rb @@ -0,0 +1,10 @@ +require "#{File.dirname(__FILE__)}<%= '/..' * class_nesting_depth %>/../test_helper" + +class <%= class_name %>Test < ActionController::IntegrationTest + # fixtures :your, :models + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/mailer/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/USAGE new file mode 100644 index 0000000..f3c295e --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/USAGE @@ -0,0 +1,18 @@ +Description: + The mailer generator creates stubs for a new mailer and its views. + + The generator takes a mailer name and a list of views as arguments. + The mailer name may be given in CamelCase or under_score. + + The generator creates a mailer class in app/models with view templates + in app/views/mailer_name, and a test suite with fixtures in test/unit. + +Example: + ./script/generate mailer Notifications signup forgot_password invoice + + This will create a Notifications mailer class: + Mailer: app/models/notifications.rb + Views: app/views/notifications/signup.rhtml [...] + Test: test/unit/test/unit/notifications_test.rb + Fixtures: test/fixtures/notifications/signup [...] + diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb new file mode 100644 index 0000000..bac0cb0 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb @@ -0,0 +1,32 @@ +class MailerGenerator < Rails::Generator::NamedBase + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions class_path, class_name, "#{class_name}Test" + + # Mailer, view, test, and fixture directories. + m.directory File.join('app/models', class_path) + m.directory File.join('app/views', class_path, file_name) + m.directory File.join('test/unit', class_path) + m.directory File.join('test/fixtures', class_path, file_name) + + # Mailer class and unit test. + m.template "mailer.rb", File.join('app/models', + class_path, + "#{file_name}.rb") + m.template "unit_test.rb", File.join('test/unit', + class_path, + "#{file_name}_test.rb") + + # View template and fixture for each action. + actions.each do |action| + m.template "view.rhtml", + File.join('app/views', class_path, file_name, "#{action}.rhtml"), + :assigns => { :action => action } + m.template "fixture.rhtml", + File.join('test/fixtures', class_path, file_name, action), + :assigns => { :action => action } + end + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml new file mode 100644 index 0000000..b481906 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml @@ -0,0 +1,3 @@ +<%= class_name %>#<%= action %> + +Find me in app/views/<%= file_name %>/<%= action %>.rhtml diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb new file mode 100644 index 0000000..127495f --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb @@ -0,0 +1,13 @@ +class <%= class_name %> < ActionMailer::Base +<% for action in actions -%> + + def <%= action %>(sent_at = Time.now) + @subject = '<%= class_name %>#<%= action %>' + @body = {} + @recipients = '' + @from = '' + @sent_on = sent_at + @headers = {} + end +<% end -%> +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb new file mode 100644 index 0000000..0512cad --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb @@ -0,0 +1,37 @@ +require File.dirname(__FILE__) + '/../test_helper' +require '<%= file_name %>' + +class <%= class_name %>Test < Test::Unit::TestCase + FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures' + CHARSET = "utf-8" + + include ActionMailer::Quoting + + def setup + ActionMailer::Base.delivery_method = :test + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries = [] + + @expected = TMail::Mail.new + @expected.set_content_type "text", "plain", { "charset" => CHARSET } + end + +<% for action in actions -%> + def test_<%= action %> + @expected.subject = '<%= class_name %>#<%= action %>' + @expected.body = read_fixture('<%= action %>') + @expected.date = Time.now + + assert_equal @expected.encoded, <%= class_name %>.create_<%= action %>(@expected.date).encoded + end + +<% end -%> + private + def read_fixture(action) + IO.readlines("#{FIXTURES_PATH}/<%= file_name %>/#{action}") + end + + def encode(subject) + quoted_printable(subject, CHARSET) + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml new file mode 100644 index 0000000..b481906 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml @@ -0,0 +1,3 @@ +<%= class_name %>#<%= action %> + +Find me in app/views/<%= file_name %>/<%= action %>.rhtml diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/migration/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/migration/USAGE new file mode 100644 index 0000000..749076e --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/migration/USAGE @@ -0,0 +1,14 @@ +Description: + The migration generator creates a stub for a new database migration. + + The generator takes a migration name as its argument. The migration name may be + given in CamelCase or under_score. + + The generator creates a migration class in db/migrate prefixed by its number + in the queue. + +Example: + ./script/generate migration AddSslFlag + + With 4 existing migrations, this will create an AddSslFlag migration in the + file db/migrate/005_add_ssl_flag.rb \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/migration/migration_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/migration/migration_generator.rb new file mode 100644 index 0000000..a0d0d47 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/migration/migration_generator.rb @@ -0,0 +1,7 @@ +class MigrationGenerator < Rails::Generator::NamedBase + def manifest + record do |m| + m.migration_template 'migration.rb', 'db/migrate' + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/migration/templates/migration.rb b/vendor/rails/railties/lib/rails_generator/generators/components/migration/templates/migration.rb new file mode 100644 index 0000000..2600610 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/migration/templates/migration.rb @@ -0,0 +1,7 @@ +class <%= class_name.underscore.camelize %> < ActiveRecord::Migration + def self.up + end + + def self.down + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/model/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/model/USAGE new file mode 100644 index 0000000..57156ea --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/model/USAGE @@ -0,0 +1,19 @@ +Description: + The model generator creates stubs for a new model. + + The generator takes a model name as its argument. The model name may be + given in CamelCase or under_score and should not be suffixed with 'Model'. + + The generator creates a model class in app/models, a test suite in + test/unit, test fixtures in test/fixtures/singular_name.yml, and a migration + in db/migrate. + +Example: + ./script/generate model Account + + This will create an Account model: + Model: app/models/account.rb + Test: test/unit/account_test.rb + Fixtures: test/fixtures/accounts.yml + Migration: db/migrate/XXX_add_accounts.rb + diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb new file mode 100644 index 0000000..e242482 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/model/model_generator.rb @@ -0,0 +1,34 @@ +class ModelGenerator < Rails::Generator::NamedBase + default_options :skip_migration => false + + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions class_path, class_name, "#{class_name}Test" + + # Model, test, and fixture directories. + m.directory File.join('app/models', class_path) + m.directory File.join('test/unit', class_path) + m.directory File.join('test/fixtures', class_path) + + # Model class, unit test, and fixtures. + m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb") + m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb") + m.template 'fixtures.yml', File.join('test/fixtures', class_path, "#{table_name}.yml") + + unless options[:skip_migration] + m.migration_template 'migration.rb', 'db/migrate', :assigns => { + :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}" + }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}" + end + end + end + + protected + def add_options!(opt) + opt.separator '' + opt.separator 'Options:' + opt.on("--skip-migration", + "Don't generate a migration file for this model") { |v| options[:skip_migration] = v } + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/fixtures.yml b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/fixtures.yml new file mode 100644 index 0000000..8794d28 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/fixtures.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 +another: + id: 2 diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/migration.rb b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/migration.rb new file mode 100644 index 0000000..a6954eb --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/migration.rb @@ -0,0 +1,11 @@ +class <%= migration_name %> < ActiveRecord::Migration + def self.up + create_table :<%= table_name %> do |t| + # t.column :name, :string + end + end + + def self.down + drop_table :<%= table_name %> + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/model.rb b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/model.rb new file mode 100644 index 0000000..8d4c89e --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/model.rb @@ -0,0 +1,2 @@ +class <%= class_name %> < ActiveRecord::Base +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb new file mode 100644 index 0000000..b464de4 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper' + +class <%= class_name %>Test < Test::Unit::TestCase + fixtures :<%= table_name %> + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/observer/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/observer/USAGE new file mode 100644 index 0000000..8abaf36 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/observer/USAGE @@ -0,0 +1,15 @@ +Description: + The observer generator creates stubs for a new observer. + + The generator takes a observer name as its argument. The observer name may be + given in CamelCase or under_score and should not be suffixed with 'Observer'. + + The generator creates a observer class in app/models and a test suite in + test/unit. + +Example: + ./script/generate observer Account + + This will create an Account observer: + Observer: app/models/account_observer.rb + Test: test/unit/account_observer_test.rb diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/observer/observer_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/observer/observer_generator.rb new file mode 100644 index 0000000..18fbd32 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/observer/observer_generator.rb @@ -0,0 +1,16 @@ +class ObserverGenerator < Rails::Generator::NamedBase + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions class_path, "#{class_name}Observer", "#{class_name}ObserverTest" + + # Observer, and test directories. + m.directory File.join('app/models', class_path) + m.directory File.join('test/unit', class_path) + + # Observer class and unit test fixtures. + m.template 'observer.rb', File.join('app/models', class_path, "#{file_name}_observer.rb") + m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_observer_test.rb") + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/observer/templates/observer.rb b/vendor/rails/railties/lib/rails_generator/generators/components/observer/templates/observer.rb new file mode 100644 index 0000000..b9a3004 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/observer/templates/observer.rb @@ -0,0 +1,2 @@ +class <%= class_name %>Observer < ActiveRecord::Observer +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb new file mode 100644 index 0000000..68a8cf0 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper' + +class <%= class_name %>ObserverTest < Test::Unit::TestCase + fixtures :<%= table_name %> + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/USAGE new file mode 100644 index 0000000..f033c81 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/USAGE @@ -0,0 +1,35 @@ +Description: + The plugin generator creates stubs for a new plugin. + + The generator takes a plugin name as its argument. The plugin name may be + given in CamelCase or under_score and should not be suffixed with 'Plugin'. + + The generator creates a plugin directory in vendor/plugins that includes + both init.rb and README files as well as lib, task, and test directories. + + It's also possible to generate stub files for a generator to go with the + plugin by using --with-generator + +Example: + ./script/generate plugin BrowserFilters + + This will create: + vendor/plugins/browser_filters/README + vendor/plugins/browser_filters/init.rb + vendor/plugins/browser_filters/install.rb + vendor/plugins/browser_filters/lib/browser_filters.rb + vendor/plugins/browser_filters/test/browser_filters_test.rb + vendor/plugins/browser_filters/tasks/browser_filters_tasks.rake + + ./script/generate plugin BrowserFilters --with-generator + + This will create: + vendor/plugins/browser_filters/README + vendor/plugins/browser_filters/init.rb + vendor/plugins/browser_filters/install.rb + vendor/plugins/browser_filters/lib/browser_filters.rb + vendor/plugins/browser_filters/test/browser_filters_test.rb + vendor/plugins/browser_filters/tasks/browser_filters_tasks.rake + vendor/plugins/browser_filters/generators/browser_filters/browser_filters_generator.rb + vendor/plugins/browser_filters/generators/browser_filters/USAGE + vendor/plugins/browser_filters/generators/browser_filters/templates/ diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb new file mode 100644 index 0000000..8a55952 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/plugin_generator.rb @@ -0,0 +1,35 @@ +class PluginGenerator < Rails::Generator::NamedBase + attr_reader :plugin_path + + def initialize(runtime_args, runtime_options = {}) + @with_generator = runtime_args.delete("--with-generator") + super + @plugin_path = "vendor/plugins/#{file_name}" + end + + def manifest + record do |m| + m.directory "#{plugin_path}/lib" + m.directory "#{plugin_path}/tasks" + m.directory "#{plugin_path}/test" + + m.template 'README', "#{plugin_path}/README" + m.template 'Rakefile', "#{plugin_path}/Rakefile" + m.template 'init.rb', "#{plugin_path}/init.rb" + m.template 'install.rb', "#{plugin_path}/install.rb" + m.template 'uninstall.rb', "#{plugin_path}/uninstall.rb" + m.template 'plugin.rb', "#{plugin_path}/lib/#{file_name}.rb" + m.template 'tasks.rake', "#{plugin_path}/tasks/#{file_name}_tasks.rake" + m.template 'unit_test.rb', "#{plugin_path}/test/#{file_name}_test.rb" + + if @with_generator + m.directory "#{plugin_path}/generators" + m.directory "#{plugin_path}/generators/#{file_name}" + m.directory "#{plugin_path}/generators/#{file_name}/templates" + + m.template 'generator.rb', "#{plugin_path}/generators/#{file_name}/#{file_name}_generator.rb" + m.template 'USAGE', "#{plugin_path}/generators/#{file_name}/USAGE" + end + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/README b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/README new file mode 100644 index 0000000..d727641 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/README @@ -0,0 +1,4 @@ +<%= class_name %> +<%= "=" * class_name.size %> + +Description goes here \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile new file mode 100755 index 0000000..1824fb1 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the <%= file_name %> plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the <%= file_name %> plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = '<%= class_name %>' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/USAGE new file mode 100644 index 0000000..f927799 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/USAGE @@ -0,0 +1,8 @@ +Description: + Explain the generator + +Example: + ./script/generate <%= file_name %> Thing + + This will create: + what/will/it/create \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/generator.rb new file mode 100644 index 0000000..3e800df --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/generator.rb @@ -0,0 +1,8 @@ +class <%= class_name %>Generator < Rails::Generator::NamedBase + def manifest + record do |m| + # m.directory "lib" + # m.template 'README', "README" + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/init.rb b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/init.rb new file mode 100644 index 0000000..ada2eec --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/init.rb @@ -0,0 +1 @@ +# Include hook code here \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/install.rb b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/install.rb new file mode 100644 index 0000000..f7732d3 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/install.rb @@ -0,0 +1 @@ +# Install hook code here diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/plugin.rb b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/plugin.rb new file mode 100644 index 0000000..1fa5b90 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/plugin.rb @@ -0,0 +1 @@ +# <%= class_name %> \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/tasks.rake b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/tasks.rake new file mode 100644 index 0000000..5222b22 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :<%= file_name %> do +# # Task goes here +# end \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/uninstall.rb b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/uninstall.rb new file mode 100644 index 0000000..9738333 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/uninstall.rb @@ -0,0 +1 @@ +# Uninstall hook code here diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb new file mode 100644 index 0000000..9028b84 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/plugin/templates/unit_test.rb @@ -0,0 +1,8 @@ +require 'test/unit' + +class <%= class_name %>Test < Test::Unit::TestCase + # Replace this with your real tests. + def test_this_plugin + flunk + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/USAGE new file mode 100644 index 0000000..1b6eaa2 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/USAGE @@ -0,0 +1,32 @@ +Description: + The scaffold generator creates a controller to interact with a model. + If the model does not exist, it creates the model as well. The generated + code is equivalent to the "scaffold :model" declaration, making it easy + to migrate when you wish to customize your controller and views. + + The generator takes a model name, an optional controller name, and a + list of views as arguments. Scaffolded actions and views are created + automatically. Any views left over generate empty stubs. + + The scaffolded actions and views are: + index, list, show, new, create, edit, update, destroy + + If a controller name is not given, the plural form of the model name + will be used. The model and controller names may be given in CamelCase + or under_score and should not be suffixed with 'Model' or 'Controller'. + Both model and controller names may be prefixed with a module like a + file path; see the Modules Example for usage. + +Example: + ./script/generate scaffold Account Bank debit credit + + This will generate an Account model and BankController with a full test + suite and a basic user interface. Now create the accounts table in your + database and browse to http://localhost/bank/ -- voila, you're on Rails! + +Modules Example: + ./script/generate scaffold CreditCard 'admin/credit_card' suspend late_fee + + This will generate a CreditCard model and CreditCardController controller + in the admin module. + diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb new file mode 100644 index 0000000..2a978bc --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb @@ -0,0 +1,188 @@ +class ScaffoldingSandbox + include ActionView::Helpers::ActiveRecordHelper + + attr_accessor :form_action, :singular_name, :suffix, :model_instance + + def sandbox_binding + binding + end + + def default_input_block + Proc.new { |record, column| "


      \n#{input(record, column.name)}

      \n" } + end + +end + +class ActionView::Helpers::InstanceTag + def to_input_field_tag(field_type, options={}) + field_meth = "#{field_type}_field" + "<%= #{field_meth} '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+options.inspect} %>" + end + + def to_text_area_tag(options = {}) + "<%= text_area '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>" + end + + def to_date_select_tag(options = {}) + "<%= date_select '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>" + end + + def to_datetime_select_tag(options = {}) + "<%= datetime_select '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>" + end +end + +class ScaffoldGenerator < Rails::Generator::NamedBase + attr_reader :controller_name, + :controller_class_path, + :controller_file_path, + :controller_class_nesting, + :controller_class_nesting_depth, + :controller_class_name, + :controller_singular_name, + :controller_plural_name + alias_method :controller_file_name, :controller_singular_name + alias_method :controller_table_name, :controller_plural_name + + def initialize(runtime_args, runtime_options = {}) + super + + # Take controller name from the next argument. Default to the pluralized model name. + @controller_name = args.shift + @controller_name ||= ActiveRecord::Base.pluralize_table_names ? @name.pluralize : @name + + base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name) + @controller_class_name_without_nesting, @controller_singular_name, @controller_plural_name = inflect_names(base_name) + + if @controller_class_nesting.empty? + @controller_class_name = @controller_class_name_without_nesting + else + @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}" + end + end + + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}ControllerTest", "#{controller_class_name}Helper" + + # Controller, helper, views, and test directories. + m.directory File.join('app/controllers', controller_class_path) + m.directory File.join('app/helpers', controller_class_path) + m.directory File.join('app/views', controller_class_path, controller_file_name) + m.directory File.join('test/functional', controller_class_path) + + # Depend on model generator but skip if the model exists. + m.dependency 'model', [singular_name], :collision => :skip, :skip_migration => true + + # Scaffolded forms. + m.complex_template "form.rhtml", + File.join('app/views', + controller_class_path, + controller_file_name, + "_form.rhtml"), + :insert => 'form_scaffolding.rhtml', + :sandbox => lambda { create_sandbox }, + :begin_mark => 'form', + :end_mark => 'eoform', + :mark_id => singular_name + + + # Scaffolded views. + scaffold_views.each do |action| + m.template "view_#{action}.rhtml", + File.join('app/views', + controller_class_path, + controller_file_name, + "#{action}.rhtml"), + :assigns => { :action => action } + end + + # Controller class, functional test, helper, and views. + m.template 'controller.rb', + File.join('app/controllers', + controller_class_path, + "#{controller_file_name}_controller.rb") + + m.template 'functional_test.rb', + File.join('test/functional', + controller_class_path, + "#{controller_file_name}_controller_test.rb") + + m.template 'helper.rb', + File.join('app/helpers', + controller_class_path, + "#{controller_file_name}_helper.rb") + + # Layout and stylesheet. + m.template 'layout.rhtml', + File.join('app/views/layouts', + controller_class_path, + "#{controller_file_name}.rhtml") + + m.template 'style.css', 'public/stylesheets/scaffold.css' + + + # Unscaffolded views. + unscaffolded_actions.each do |action| + path = File.join('app/views', + controller_class_path, + controller_file_name, + "#{action}.rhtml") + m.template "controller:view.rhtml", path, + :assigns => { :action => action, :path => path} + end + end + end + + protected + # Override with your own usage banner. + def banner + "Usage: #{$0} scaffold ModelName [ControllerName] [action, ...]" + end + + def scaffold_views + %w(list show new edit) + end + + def scaffold_actions + scaffold_views + %w(index create update destroy) + end + + def model_name + class_name.demodulize + end + + def unscaffolded_actions + args - scaffold_actions + end + + def suffix + "_#{singular_name}" if options[:suffix] + end + + def create_sandbox + sandbox = ScaffoldingSandbox.new + sandbox.singular_name = singular_name + begin + sandbox.model_instance = model_instance + sandbox.instance_variable_set("@#{singular_name}", sandbox.model_instance) + rescue ActiveRecord::StatementInvalid => e + logger.error "Before updating scaffolding from new DB schema, try creating a table for your model (#{class_name})" + raise SystemExit + end + sandbox.suffix = suffix + sandbox + end + + def model_instance + base = class_nesting.split('::').inject(Object) do |base, nested| + break base.const_get(nested) if base.const_defined?(nested) + base.const_set(nested, Module.new) + end + unless base.const_defined?(@class_name_without_nesting) + base.const_set(@class_name_without_nesting, Class.new(ActiveRecord::Base)) + end + class_name.constantize.new + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb new file mode 100644 index 0000000..c059e74 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb @@ -0,0 +1,58 @@ +class <%= controller_class_name %>Controller < ApplicationController +<% unless suffix -%> + def index + list + render :action => 'list' + end +<% end -%> + +<% for action in unscaffolded_actions -%> + def <%= action %><%= suffix %> + end + +<% end -%> + # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) + 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 + end + + def show<%= suffix %> + @<%= singular_name %> = <%= model_name %>.find(params[:id]) + end + + def new<%= suffix %> + @<%= singular_name %> = <%= model_name %>.new + end + + def create<%= suffix %> + @<%= singular_name %> = <%= model_name %>.new(params[:<%= singular_name %>]) + if @<%= singular_name %>.save + flash[:notice] = '<%= model_name %> was successfully created.' + redirect_to :action => 'list<%= suffix %>' + else + render :action => 'new<%= suffix %>' + end + end + + def edit<%= suffix %> + @<%= singular_name %> = <%= model_name %>.find(params[:id]) + end + + def update + @<%= singular_name %> = <%= model_name %>.find(params[:id]) + if @<%= singular_name %>.update_attributes(params[:<%= singular_name %>]) + flash[:notice] = '<%= model_name %> was successfully updated.' + redirect_to :action => 'show<%= suffix %>', :id => @<%= singular_name %> + else + render :action => 'edit<%= suffix %>' + end + end + + def destroy<%= suffix %> + <%= model_name %>.find(params[:id]).destroy + redirect_to :action => 'list<%= suffix %>' + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml new file mode 100644 index 0000000..d15f0d4 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml @@ -0,0 +1,3 @@ +<%%= error_messages_for '<%= singular_name %>' %> + +<%= template_for_inclusion %> diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form_scaffolding.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form_scaffolding.rhtml new file mode 100644 index 0000000..c7a8755 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/form_scaffolding.rhtml @@ -0,0 +1 @@ +<%= all_input_tags(@model_instance, @singular_name, {}) %> \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb new file mode 100644 index 0000000..ed7136b --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb @@ -0,0 +1,102 @@ +require File.dirname(__FILE__) + '<%= "/.." * controller_class_nesting_depth %>/../test_helper' +require '<%= controller_file_path %>_controller' + +# Re-raise errors caught by the controller. +class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end + +class <%= controller_class_name %>ControllerTest < Test::Unit::TestCase + fixtures :<%= table_name %> + + def setup + @controller = <%= controller_class_name %>Controller.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @first_id = <%= plural_name %>(:first).id + end + +<% for action in unscaffolded_actions -%> + def test_<%= action %> + get :<%= action %> + assert_response :success + assert_template '<%= action %>' + end + +<% end -%> +<% unless suffix -%> + def test_index + get :index + assert_response :success + assert_template 'list' + end + +<% end -%> + def test_list<%= suffix %> + get :list<%= suffix %> + + assert_response :success + assert_template 'list<%= suffix %>' + + assert_not_nil assigns(:<%= plural_name %>) + end + + def test_show<%= suffix %> + get :show<%= suffix %>, :id => @first_id + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:<%= singular_name %>) + assert assigns(:<%= singular_name %>).valid? + end + + def test_new<%= suffix %> + get :new<%= suffix %> + + assert_response :success + assert_template 'new<%= suffix %>' + + assert_not_nil assigns(:<%= singular_name %>) + end + + def test_create + num_<%= plural_name %> = <%= model_name %>.count + + post :create<%= suffix %>, :<%= singular_name %> => {} + + assert_response :redirect + assert_redirected_to :action => 'list<%= suffix %>' + + assert_equal num_<%= plural_name %> + 1, <%= model_name %>.count + end + + def test_edit<%= suffix %> + get :edit<%= suffix %>, :id => @first_id + + assert_response :success + assert_template 'edit<%= suffix %>' + + assert_not_nil assigns(:<%= singular_name %>) + assert assigns(:<%= singular_name %>).valid? + end + + def test_update<%= suffix %> + post :update<%= suffix %>, :id => @first_id + assert_response :redirect + assert_redirected_to :action => 'show<%= suffix %>', :id => @first_id + end + + def test_destroy<%= suffix %> + assert_nothing_raised { + <%= model_name %>.find(@first_id) + } + + post :destroy, :id => @first_id + assert_response :redirect + assert_redirected_to :action => 'list<%= suffix %>' + + assert_raise(ActiveRecord::RecordNotFound) { + <%= model_name %>.find(@first_id) + } + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/helper.rb b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/helper.rb new file mode 100644 index 0000000..9bd821b --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/helper.rb @@ -0,0 +1,2 @@ +module <%= controller_class_name %>Helper +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml new file mode 100644 index 0000000..b5ba9a4 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml @@ -0,0 +1,13 @@ + + + <%= controller_class_name %>: <%%= controller.action_name %> + <%%= stylesheet_link_tag 'scaffold' %> + + + +

      <%%= flash[:notice] %>

      + +<%%= yield %> + + + diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/style.css b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/style.css new file mode 100644 index 0000000..8f239a3 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/style.css @@ -0,0 +1,74 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} + diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml new file mode 100644 index 0000000..2db0909 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml @@ -0,0 +1,9 @@ +

      Editing <%= singular_name %>

      + +<%%= start_form_tag :action => 'update<%= @suffix %>', :id => @<%= singular_name %> %> + <%%= render :partial => 'form' %> + <%%= submit_tag 'Edit' %> +<%%= end_form_tag %> + +<%%= link_to 'Show', :action => 'show<%= suffix %>', :id => @<%= singular_name %> %> | +<%%= link_to 'Back', :action => 'list<%= suffix %>' %> diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml new file mode 100644 index 0000000..ea1ca7e --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml @@ -0,0 +1,27 @@ +

      Listing <%= plural_name %>

      + + + + <%% for column in <%= model_name %>.content_columns %> + + <%% end %> + + +<%% for <%= singular_name %> in @<%= plural_name %> %> + + <%% for column in <%= model_name %>.content_columns %> + + <%% end %> + + + + +<%% end %> +
      <%%= column.human_name %>
      <%%=h <%= singular_name %>.send(column.name) %><%%= link_to 'Show', :action => 'show<%= suffix %>', :id => <%= singular_name %> %><%%= link_to 'Edit', :action => 'edit<%= suffix %>', :id => <%= singular_name %> %><%%= link_to 'Destroy', { :action => 'destroy<%= suffix %>', :id => <%= singular_name %> }, :confirm => 'Are you sure?', :post => true %>
      + +<%%= link_to 'Previous page', { :page => @<%= singular_name %>_pages.current.previous } if @<%= singular_name %>_pages.current.previous %> +<%%= link_to 'Next page', { :page => @<%= singular_name %>_pages.current.next } if @<%= singular_name %>_pages.current.next %> + +
      + +<%%= link_to 'New <%= singular_name %>', :action => 'new<%= suffix %>' %> diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml new file mode 100644 index 0000000..286f850 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml @@ -0,0 +1,8 @@ +

      New <%= singular_name %>

      + +<%%= start_form_tag :action => 'create<%= @suffix %>' %> + <%%= render :partial => 'form' %> + <%%= submit_tag "Create" %> +<%%= end_form_tag %> + +<%%= link_to 'Back', :action => 'list<%= suffix %>' %> diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml new file mode 100644 index 0000000..c9245cd --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml @@ -0,0 +1,8 @@ +<%% for column in <%= model_name %>.content_columns %> +

      + <%%= column.human_name %>: <%%=h @<%= singular_name %>.send(column.name) %> +

      +<%% end %> + +<%%= link_to 'Edit', :action => 'edit<%= suffix %>', :id => @<%= singular_name %> %> | +<%%= link_to 'Back', :action => 'list<%= suffix %>' %> diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/USAGE new file mode 100644 index 0000000..3234a4e --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/USAGE @@ -0,0 +1,15 @@ +Description: + The session table migration generator creates a migration for adding a session table + used by CGI::Session::ActiveRecordStore. + + The generator takes a migration name as its argument. The migration name may be + given in CamelCase or under_score. + + The generator creates a migration class in db/migrate prefixed by its number + in the queue. + +Example: + ./script/generate session_migration AddSessionTable + + With 4 existing migrations, this will create an AddSessionTable migration in the + file db/migrate/005_add_session_table.rb \ No newline at end of file diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/session_migration_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/session_migration_generator.rb new file mode 100644 index 0000000..2e17703 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/session_migration_generator.rb @@ -0,0 +1,18 @@ +class SessionMigrationGenerator < Rails::Generator::NamedBase + def initialize(runtime_args, runtime_options = {}) + runtime_args << 'add_session_table' if runtime_args.empty? + super + end + + def manifest + record do |m| + m.migration_template 'migration.rb', 'db/migrate', + :assigns => { :session_table_name => default_session_table_name } + end + end + + protected + def default_session_table_name + ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session' + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/templates/migration.rb b/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/templates/migration.rb new file mode 100644 index 0000000..c76bd8d --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/session_migration/templates/migration.rb @@ -0,0 +1,15 @@ +class <%= class_name %> < ActiveRecord::Migration + def self.up + create_table :<%= session_table_name %> do |t| + t.column :session_id, :string + t.column :data, :text + t.column :updated_at, :datetime + end + + add_index :<%= session_table_name %>, :session_id + end + + def self.down + drop_table :<%= session_table_name %> + end +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/web_service/USAGE b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/USAGE new file mode 100644 index 0000000..d3e45b7 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/USAGE @@ -0,0 +1,28 @@ +Description: + The web service generator creates the controller and API definition for + a web service. + + The generator takes a web service name and a list of API methods as arguments. + The web service name may be given in CamelCase or under_score and should + contain no extra suffixes. To create a web service within a + module, specify the web service name as 'module/webservice'. + + The generator creates a controller class in app/controllers, an API definition + in app/apis, and a functional test suite in test/functional. + +Example: + ./script/generate web_service User add edit list remove + + User web service. + Controller: app/controllers/user_controller.rb + API: app/apis/user_api.rb + Test: test/functional/user_api_test.rb + +Modules Example: + ./script/generate web_service 'api/registration' register renew + + Registration web service. + Controller: app/controllers/api/registration_controller.rb + API: app/apis/api/registration_api.rb + Test: test/functional/api/registration_api_test.rb + diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/api_definition.rb b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/api_definition.rb new file mode 100644 index 0000000..97d0b60 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/api_definition.rb @@ -0,0 +1,5 @@ +class <%= class_name %>Api < ActionWebService::API::Base +<% for method_name in args -%> + api_method :<%= method_name %> +<% end -%> +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/controller.rb b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/controller.rb new file mode 100644 index 0000000..7b0a865 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/controller.rb @@ -0,0 +1,8 @@ +class <%= class_name %>Controller < ApplicationController + wsdl_service_name '<%= class_name %>' +<% for method_name in args -%> + + def <%= method_name %> + end +<% end -%> +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/functional_test.rb b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/functional_test.rb new file mode 100644 index 0000000..c4d136f --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/templates/functional_test.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper' +require '<%= file_path %>_controller' + +class <%= class_name %>Controller; def rescue_action(e) raise e end; end + +class <%= class_name %>ControllerApiTest < Test::Unit::TestCase + def setup + @controller = <%= class_name %>Controller.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end +<% for method_name in args -%> + + def test_<%= method_name %> + result = invoke :<%= method_name %> + assert_equal nil, result + end +<% end -%> +end diff --git a/vendor/rails/railties/lib/rails_generator/generators/components/web_service/web_service_generator.rb b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/web_service_generator.rb new file mode 100644 index 0000000..ee18bf8 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/generators/components/web_service/web_service_generator.rb @@ -0,0 +1,29 @@ +class WebServiceGenerator < Rails::Generator::NamedBase + def manifest + record do |m| + # Check for class naming collisions. + m.class_collisions class_path, "#{class_name}Api", "#{class_name}Controller", "#{class_name}ApiTest" + + # API and test directories. + m.directory File.join('app/apis', class_path) + m.directory File.join('app/controllers', class_path) + m.directory File.join('test/functional', class_path) + + # API definition, controller, and functional test. + m.template 'api_definition.rb', + File.join('app/apis', + class_path, + "#{file_name}_api.rb") + + m.template 'controller.rb', + File.join('app/controllers', + class_path, + "#{file_name}_controller.rb") + + m.template 'functional_test.rb', + File.join('test/functional', + class_path, + "#{file_name}_api_test.rb") + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/lookup.rb b/vendor/rails/railties/lib/rails_generator/lookup.rb new file mode 100644 index 0000000..06f1a9e --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/lookup.rb @@ -0,0 +1,209 @@ +require File.dirname(__FILE__) + '/spec' + +class Object + class << self + # Lookup missing generators using const_missing. This allows any + # generator to reference another without having to know its location: + # RubyGems, ~/.rails/generators, and RAILS_ROOT/generators. + def lookup_missing_generator(class_id) + if md = /(.+)Generator$/.match(class_id.to_s) + name = md.captures.first.demodulize.underscore + Rails::Generator::Base.lookup(name).klass + else + const_missing_before_generators(class_id) + end + end + + unless respond_to?(:const_missing_before_generators) + alias_method :const_missing_before_generators, :const_missing + alias_method :const_missing, :lookup_missing_generator + end + end +end + +# User home directory lookup adapted from RubyGems. +def Dir.user_home + if ENV['HOME'] + ENV['HOME'] + elsif ENV['USERPROFILE'] + ENV['USERPROFILE'] + elsif ENV['HOMEDRIVE'] and ENV['HOMEPATH'] + "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}" + else + File.expand_path '~' + end +end + + +module Rails + module Generator + + # Generator lookup is managed by a list of sources which return specs + # describing where to find and how to create generators. This module + # provides class methods for manipulating the source list and looking up + # generator specs, and an #instance wrapper for quickly instantiating + # generators by name. + # + # A spec is not a generator: it's a description of where to find + # the generator and how to create it. A source is anything that + # yields generators from #each. PathSource and GemSource are provided. + module Lookup + def self.included(base) + base.extend(ClassMethods) + base.use_component_sources! + end + + # Convenience method to instantiate another generator. + def instance(generator_name, args, runtime_options = {}) + self.class.instance(generator_name, args, runtime_options) + end + + module ClassMethods + # The list of sources where we look, in order, for generators. + def sources + read_inheritable_attribute(:sources) or use_component_sources! + end + + # Add a source to the end of the list. + def append_sources(*args) + sources.concat(args.flatten) + invalidate_cache! + end + + # Add a source to the beginning of the list. + def prepend_sources(*args) + write_inheritable_array(:sources, args.flatten + sources) + invalidate_cache! + end + + # Reset the source list. + def reset_sources + write_inheritable_attribute(:sources, []) + invalidate_cache! + end + + # Use application generators (app, ?). + def use_application_sources! + reset_sources + sources << PathSource.new(:builtin, "#{File.dirname(__FILE__)}/generators/applications") + end + + # Use component generators (model, controller, etc). + # 1. Rails application. If RAILS_ROOT is defined we know we're + # generating in the context of a Rails application, so search + # RAILS_ROOT/generators. + # 2. User home directory. Search ~/.rails/generators. + # 3. RubyGems. Search for gems named *_generator. + # 4. Builtins. Model, controller, mailer, scaffold. + def use_component_sources! + reset_sources + if defined? ::RAILS_ROOT + sources << PathSource.new(:lib, "#{::RAILS_ROOT}/lib/generators") + sources << PathSource.new(:vendor, "#{::RAILS_ROOT}/vendor/generators") + sources << PathSource.new(:plugins, "#{::RAILS_ROOT}/vendor/plugins/**/generators") + end + sources << PathSource.new(:user, "#{Dir.user_home}/.rails/generators") + sources << GemSource.new if Object.const_defined?(:Gem) + sources << PathSource.new(:builtin, "#{File.dirname(__FILE__)}/generators/components") + end + + # Lookup knows how to find generators' Specs from a list of Sources. + # Searches the sources, in order, for the first matching name. + def lookup(generator_name) + @found ||= {} + generator_name = generator_name.to_s.downcase + @found[generator_name] ||= cache.find { |spec| spec.name == generator_name } + unless @found[generator_name] + chars = generator_name.scan(/./).map{|c|"#{c}.*?"} + rx = /^#{chars}$/ + gns = cache.select{|spec| spec.name =~ rx } + @found[generator_name] ||= gns.first if gns.length == 1 + raise GeneratorError, "Pattern '#{generator_name}' matches more than one generator: #{gns.map{|sp|sp.name}.join(', ')}" if gns.length > 1 + end + @found[generator_name] or raise GeneratorError, "Couldn't find '#{generator_name}' generator" + end + + # Convenience method to lookup and instantiate a generator. + def instance(generator_name, args = [], runtime_options = {}) + lookup(generator_name).klass.new(args, full_options(runtime_options)) + end + + private + # Lookup and cache every generator from the source list. + def cache + @cache ||= sources.inject([]) { |cache, source| cache + source.map } + end + + # Clear the cache whenever the source list changes. + def invalidate_cache! + @cache = nil + end + end + end + + # Sources enumerate (yield from #each) generator specs which describe + # where to find and how to create generators. Enumerable is mixed in so, + # for example, source.collect will retrieve every generator. + # Sources may be assigned a label to distinguish them. + class Source + include Enumerable + + attr_reader :label + def initialize(label) + @label = label + end + + # The each method must be implemented in subclasses. + # The base implementation raises an error. + def each + raise NotImplementedError + end + + # Return a convenient sorted list of all generator names. + def names + map { |spec| spec.name }.sort + end + end + + + # PathSource looks for generators in a filesystem directory. + class PathSource < Source + attr_reader :path + + def initialize(label, path) + super label + @path = path + end + + # Yield each eligible subdirectory. + def each + Dir["#{path}/[a-z]*"].each do |dir| + if File.directory?(dir) + yield Spec.new(File.basename(dir), dir, label) + end + end + end + end + + + # GemSource hits the mines to quarry for generators. The latest versions + # of gems named *_generator are selected. + class GemSource < Source + def initialize + super :RubyGems + end + + # Yield latest versions of generator gems. + def each + Gem::cache.search(/_generator$/).inject({}) { |latest, gem| + hem = latest[gem.name] + latest[gem.name] = gem if hem.nil? or gem.version > hem.version + latest + }.values.each { |gem| + yield Spec.new(gem.name.sub(/_generator$/, ''), gem.full_gem_path, label) + } + end + end + + end +end diff --git a/vendor/rails/railties/lib/rails_generator/manifest.rb b/vendor/rails/railties/lib/rails_generator/manifest.rb new file mode 100644 index 0000000..702effa --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/manifest.rb @@ -0,0 +1,53 @@ +module Rails + module Generator + + # Manifest captures the actions a generator performs. Instantiate + # a manifest with an optional target object, hammer it with actions, + # then replay or rewind on the object of your choice. + # + # Example: + # manifest = Manifest.new { |m| + # m.make_directory '/foo' + # m.create_file '/foo/bar.txt' + # } + # manifest.replay(creator) + # manifest.rewind(destroyer) + class Manifest + attr_reader :target + + # Take a default action target. Yield self if block given. + def initialize(target = nil) + @target, @actions = target, [] + yield self if block_given? + end + + # Record an action. + def method_missing(action, *args, &block) + @actions << [action, args, block] + end + + # Replay recorded actions. + def replay(target = nil) + send_actions(target || @target, @actions) + end + + # Rewind recorded actions. + def rewind(target = nil) + send_actions(target || @target, @actions.reverse) + end + + # Erase recorded actions. + def erase + @actions = [] + end + + private + def send_actions(target, actions) + actions.each do |method, args, block| + target.send(method, *args, &block) + end + end + end + + end +end diff --git a/vendor/rails/railties/lib/rails_generator/options.rb b/vendor/rails/railties/lib/rails_generator/options.rb new file mode 100644 index 0000000..042e051 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/options.rb @@ -0,0 +1,143 @@ +require 'optparse' + +module Rails + module Generator + module Options + def self.included(base) + base.extend(ClassMethods) + class << base + if respond_to?(:inherited) + alias_method :inherited_without_options, :inherited + end + alias_method :inherited, :inherited_with_options + end + end + + module ClassMethods + def inherited_with_options(sub) + inherited_without_options(sub) if respond_to?(:inherited_without_options) + sub.extend(Rails::Generator::Options::ClassMethods) + end + + def mandatory_options(options = nil) + if options + write_inheritable_attribute(:mandatory_options, options) + else + read_inheritable_attribute(:mandatory_options) or write_inheritable_attribute(:mandatory_options, {}) + end + end + + def default_options(options = nil) + if options + write_inheritable_attribute(:default_options, options) + else + read_inheritable_attribute(:default_options) or write_inheritable_attribute(:default_options, {}) + end + end + + # Merge together our class options. In increasing precedence: + # default_options (class default options) + # runtime_options (provided as argument) + # mandatory_options (class mandatory options) + def full_options(runtime_options = {}) + default_options.merge(runtime_options).merge(mandatory_options) + end + + end + + # Each instance has an options hash that's populated by #parse. + def options + @options ||= {} + end + attr_writer :options + + protected + # Convenient access to class mandatory options. + def mandatory_options + self.class.mandatory_options + end + + # Convenient access to class default options. + def default_options + self.class.default_options + end + + # Merge together our instance options. In increasing precedence: + # default_options (class default options) + # options (instance options) + # runtime_options (provided as argument) + # mandatory_options (class mandatory options) + def full_options(runtime_options = {}) + self.class.full_options(options.merge(runtime_options)) + end + + # Parse arguments into the options hash. Classes may customize + # parsing behavior by overriding these methods: + # #banner Usage: ./script/generate [options] + # #add_options! Options: + # some options.. + # #add_general_options! General Options: + # general options.. + def parse!(args, runtime_options = {}) + self.options = {} + + @option_parser = OptionParser.new do |opt| + opt.banner = banner + add_options!(opt) + add_general_options!(opt) + opt.parse!(args) + end + + return args + ensure + self.options = full_options(runtime_options) + end + + # Raise a usage error. Override usage_message to provide a blurb + # after the option parser summary. + def usage(message = usage_message) + raise UsageError, "#{@option_parser}\n#{message}" + end + + def usage_message + '' + end + + # Override with your own usage banner. + def banner + "Usage: #{$0} [options]" + end + + # Override to add your options to the parser: + # def add_options!(opt) + # opt.on('-v', '--verbose') { |value| options[:verbose] = value } + # end + def add_options!(opt) + end + + # Adds general options like -h and --quiet. Usually don't override. + def add_general_options!(opt) + opt.separator '' + opt.separator 'Rails Info:' + opt.on('-v', '--version', 'Show the Rails version number and quit.') + opt.on('-h', '--help', 'Show this help message and quit.') { |v| options[:help] = v } + + opt.separator '' + opt.separator 'General Options:' + + opt.on('-p', '--pretend', 'Run but do not make any changes.') { |v| options[:pretend] = v } + opt.on('-f', '--force', 'Overwrite files that already exist.') { options[:collision] = :force } + opt.on('-s', '--skip', 'Skip files that already exist.') { options[:collision] = :skip } + opt.on('-q', '--quiet', 'Suppress normal output.') { |v| options[:quiet] = v } + opt.on('-t', '--backtrace', 'Debugging: show backtrace on errors.') { |v| options[:backtrace] = v } + opt.on('-c', '--svn', 'Modify files with subversion. (Note: svn must be in path)') do + options[:svn] = `svn status`.inject({}) do |opt, e| + opt[e.chomp[7..-1]] = true + opt + end + end + end + + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/scripts.rb b/vendor/rails/railties/lib/rails_generator/scripts.rb new file mode 100644 index 0000000..14156e9 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/scripts.rb @@ -0,0 +1,83 @@ +require File.dirname(__FILE__) + '/options' + +module Rails + module Generator + module Scripts + + # Generator scripts handle command-line invocation. Each script + # responds to an invoke! class method which handles option parsing + # and generator invocation. + class Base + include Options + default_options :collision => :ask, :quiet => false + + # Run the generator script. Takes an array of unparsed arguments + # and a hash of parsed arguments, takes the generator as an option + # or first remaining argument, and invokes the requested command. + def run(args = [], runtime_options = {}) + begin + parse!(args.dup, runtime_options) + rescue OptionParser::InvalidOption => e + # Don't cry, script. Generators want what you think is invalid. + end + + # Generator name is the only required option. + unless options[:generator] + usage if args.empty? + options[:generator] ||= args.shift + end + + # Look up generator instance and invoke command on it. + Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke! + rescue => e + puts e + puts " #{e.backtrace.join("\n ")}\n" if options[:backtrace] + raise SystemExit + end + + protected + # Override with your own script usage banner. + def banner + "Usage: #{$0} generator [options] [args]" + end + + def usage_message + usage = "\nInstalled Generators\n" + Rails::Generator::Base.sources.each do |source| + label = source.label.to_s.capitalize + names = source.names + usage << " #{label}: #{names.join(', ')}\n" unless names.empty? + end + + usage << < :destroy + end +end diff --git a/vendor/rails/railties/lib/rails_generator/scripts/generate.rb b/vendor/rails/railties/lib/rails_generator/scripts/generate.rb new file mode 100644 index 0000000..1fe2f54 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/scripts/generate.rb @@ -0,0 +1,7 @@ +require File.dirname(__FILE__) + '/../scripts' + +module Rails::Generator::Scripts + class Generate < Base + mandatory_options :command => :create + end +end diff --git a/vendor/rails/railties/lib/rails_generator/scripts/update.rb b/vendor/rails/railties/lib/rails_generator/scripts/update.rb new file mode 100644 index 0000000..53a9faa --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/scripts/update.rb @@ -0,0 +1,12 @@ +require File.dirname(__FILE__) + '/../scripts' + +module Rails::Generator::Scripts + class Update < Base + mandatory_options :command => :update + + protected + def banner + "Usage: #{$0} [options] scaffold" + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/simple_logger.rb b/vendor/rails/railties/lib/rails_generator/simple_logger.rb new file mode 100644 index 0000000..d750f07 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/simple_logger.rb @@ -0,0 +1,46 @@ +module Rails + module Generator + class SimpleLogger # :nodoc: + attr_reader :out + attr_accessor :quiet + + def initialize(out = $stdout) + @out = out + @quiet = false + @level = 0 + end + + def log(status, message, &block) + @out.print("%12s %s%s\n" % [status, ' ' * @level, message]) unless quiet + indent(&block) if block_given? + end + + def indent(&block) + @level += 1 + if block_given? + begin + block.call + ensure + outdent + end + end + end + + def outdent + @level -= 1 + if block_given? + begin + block.call + ensure + indent + end + end + end + + private + def method_missing(method, *args, &block) + log(method.to_s, args.first, &block) + end + end + end +end diff --git a/vendor/rails/railties/lib/rails_generator/spec.rb b/vendor/rails/railties/lib/rails_generator/spec.rb new file mode 100644 index 0000000..ad609b8 --- /dev/null +++ b/vendor/rails/railties/lib/rails_generator/spec.rb @@ -0,0 +1,44 @@ +module Rails + module Generator + # A spec knows where a generator was found and how to instantiate it. + # Metadata include the generator's name, its base path, and the source + # which yielded it (PathSource, GemSource, etc.) + class Spec + attr_reader :name, :path, :source + + def initialize(name, path, source) + @name, @path, @source = name, path, source + end + + # Look up the generator class. Require its class file, find the class + # in ObjectSpace, tag it with this spec, and return. + def klass + unless @klass + require class_file + @klass = lookup_class + @klass.spec = self + end + @klass + end + + def class_file + "#{path}/#{name}_generator.rb" + end + + def class_name + "#{name.camelize}Generator" + end + + private + # Search for the first Class descending from Rails::Generator::Base + # whose name matches the requested class name. + def lookup_class + ObjectSpace.each_object(Class) do |obj| + return obj if obj.ancestors.include?(Rails::Generator::Base) and + obj.name.split('::').last == class_name + end + raise NameError, "Missing #{class_name} class in #{class_file}" + end + end + end +end diff --git a/vendor/rails/railties/lib/railties_path.rb b/vendor/rails/railties/lib/railties_path.rb new file mode 100644 index 0000000..8179405 --- /dev/null +++ b/vendor/rails/railties/lib/railties_path.rb @@ -0,0 +1 @@ +RAILTIES_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..')) \ No newline at end of file diff --git a/vendor/rails/railties/lib/ruby_version_check.rb b/vendor/rails/railties/lib/ruby_version_check.rb new file mode 100644 index 0000000..68d3acc --- /dev/null +++ b/vendor/rails/railties/lib/ruby_version_check.rb @@ -0,0 +1,17 @@ +min_release = "1.8.2 (2004-12-25)" +ruby_release = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" +if ruby_release =~ /1\.8\.3/ + abort <<-end_message + + Rails does not work with Ruby version 1.8.3. + Please upgrade to version 1.8.4 or downgrade to 1.8.2. + + end_message +elsif ruby_release < min_release + abort <<-end_message + + Rails requires Ruby version #{min_release} or later. + You're running #{ruby_release}; please upgrade to continue. + + end_message +end diff --git a/vendor/rails/railties/lib/rubyprof_ext.rb b/vendor/rails/railties/lib/rubyprof_ext.rb new file mode 100644 index 0000000..f6e9035 --- /dev/null +++ b/vendor/rails/railties/lib/rubyprof_ext.rb @@ -0,0 +1,35 @@ +require 'prof' + +module Prof #:nodoc: + # Adapted from Shugo Maeda's unprof.rb + def self.print_profile(results, io = $stderr) + total = results.detect { |i| + i.method_class.nil? && i.method_id == :"#toplevel" + }.total_time + total = 0.001 if total < 0.001 + + io.puts " %% cumulative self self total" + io.puts " time seconds seconds calls ms/call ms/call name" + + sum = 0.0 + for r in results + sum += r.self_time + + name = if r.method_class.nil? + r.method_id.to_s + elsif r.method_class.is_a?(Class) + "#{r.method_class}##{r.method_id}" + else + "#{r.method_class}.#{r.method_id}" + end + io.printf "%6.2f %8.3f %8.3f %8d %8.2f %8.2f %s\n", + r.self_time / total * 100, + sum, + r.self_time, + r.count, + r.self_time * 1000 / r.count, + r.total_time * 1000 / r.count, + name + end + end +end diff --git a/vendor/rails/railties/lib/tasks/databases.rake b/vendor/rails/railties/lib/tasks/databases.rake new file mode 100644 index 0000000..cd91a94 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/databases.rake @@ -0,0 +1,185 @@ +namespace :db do + desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x" + task :migrate => :environment do + ActiveRecord::Migrator.migrate("db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + namespace :fixtures do + desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y" + task :load => :environment do + require 'active_record/fixtures' + ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym) + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.{yml,csv}'))).each do |fixture_file| + Fixtures.create_fixtures('test/fixtures', File.basename(fixture_file, '.*')) + end + end + end + + namespace :schema do + desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" + task :dump => :environment do + require 'active_record/schema_dumper' + File.open(ENV['SCHEMA'] || "db/schema.rb", "w") do |file| + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) + end + end + + desc "Load a schema.rb file into the database" + task :load => :environment do + file = ENV['SCHEMA'] || "db/schema.rb" + load(file) + end + end + + namespace :structure do + desc "Dump the database structure to a SQL file" + task :dump => :environment do + abcs = ActiveRecord::Base.configurations + case abcs[RAILS_ENV]["adapter"] + when "mysql", "oci", "oracle" + ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) + File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } + when "postgresql" + ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] + ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] + ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] + search_path = abcs[RAILS_ENV]["schema_search_path"] + search_path = "--schema=#{search_path}" if search_path + `pg_dump -i -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{search_path} #{abcs[RAILS_ENV]["database"]}` + raise "Error dumping database" if $?.exitstatus == 1 + when "sqlite", "sqlite3" + dbfile = abcs[RAILS_ENV]["database"] || abcs[RAILS_ENV]["dbfile"] + `#{abcs[RAILS_ENV]["adapter"]} #{dbfile} .schema > db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` + `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` + when "firebird" + set_firebird_env(abcs[RAILS_ENV]) + db_string = firebird_db_string(abcs[RAILS_ENV]) + sh "isql -a #{db_string} > db/#{RAILS_ENV}_structure.sql" + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + + if ActiveRecord::Base.connection.supports_migrations? + File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } + end + end + end + + namespace :test do + desc "Recreate the test database from the current environment's database schema" + task :clone => %w(db:schema:dump db:test:purge) do + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) + ActiveRecord::Schema.verbose = false + Rake::Task["db:schema:load"].invoke + end + + + desc "Recreate the test databases from the development structure" + task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + when "mysql" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') + IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| + ActiveRecord::Base.connection.execute(table) + end + when "postgresql" + ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] + ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] + ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] + `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` + when "sqlite", "sqlite3" + dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + `#{abcs["test"]["adapter"]} #{dbfile} < db/#{RAILS_ENV}_structure.sql` + when "sqlserver" + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + when "oci", "oracle" + ActiveRecord::Base.establish_connection(:test) + IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| + ActiveRecord::Base.connection.execute(ddl) + end + when "firebird" + set_firebird_env(abcs["test"]) + db_string = firebird_db_string(abcs["test"]) + sh "isql -i db/#{RAILS_ENV}_structure.sql #{db_string}" + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + end + + desc "Empty the test database" + task :purge => :environment do + abcs = ActiveRecord::Base.configurations + case abcs["test"]["adapter"] + when "mysql" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"]) + when "postgresql" + ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] + ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] + ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] + enc_option = "-E #{abcs["test"]["encoding"]}" if abcs["test"]["encoding"] + `dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` + `createdb #{enc_option} -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` + when "sqlite","sqlite3" + dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + File.delete(dbfile) if File.exist?(dbfile) + when "sqlserver" + dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` + `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` + when "oci", "oracle" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| + ActiveRecord::Base.connection.execute(ddl) + end + when "firebird" + ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.connection.recreate_database! + else + raise "Task not supported by '#{abcs["test"]["adapter"]}'" + end + end + + desc 'Prepare the test database and load the schema' + task :prepare => :environment do + if defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank? + Rake::Task[{ :sql => "db:test:clone_structure", :ruby => "db:test:clone" }[ActiveRecord::Base.schema_format]].invoke + end + end + end + + namespace :sessions do + desc "Creates a sessions table for use with CGI::Session::ActiveRecordStore" + task :create => :environment do + raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? + require 'rails_generator' + require 'rails_generator/scripts/generate' + Rails::Generator::Scripts::Generate.new.run(["session_migration", ENV["MIGRATION"] || "AddSessions"]) + end + + desc "Clear the sessions table" + task :clear => :environment do + session_table = 'session' + session_table = Inflector.pluralize(session_table) if ActiveRecord::Base.pluralize_table_names + ActiveRecord::Base.connection.execute "DELETE FROM #{session_table}" + end + end +end + +def session_table_name + ActiveRecord::Base.pluralize_table_names ? :sessions : :session +end + +def set_firebird_env(config) + ENV["ISC_USER"] = config["username"].to_s if config["username"] + ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"] +end + +def firebird_db_string(config) + FireRuby::Database.db_string_for(config.symbolize_keys) +end diff --git a/vendor/rails/railties/lib/tasks/documentation.rake b/vendor/rails/railties/lib/tasks/documentation.rake new file mode 100644 index 0000000..35db023 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/documentation.rake @@ -0,0 +1,82 @@ +namespace :doc do + desc "Generate documentation for the application" + Rake::RDocTask.new("app") { |rdoc| + rdoc.rdoc_dir = 'doc/app' + rdoc.title = "Rails Application Documentation" + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('doc/README_FOR_APP') + rdoc.rdoc_files.include('app/**/*.rb') + rdoc.rdoc_files.include('lib/**/*.rb') + } + + desc "Generate documentation for the Rails framework" + Rake::RDocTask.new("rails") { |rdoc| + rdoc.rdoc_dir = 'doc/api' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.title = "Rails Framework Documentation" + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE') + rdoc.rdoc_files.include('vendor/rails/activerecord/README') + rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb') + rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*') + rdoc.rdoc_files.include('vendor/rails/actionpack/README') + rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionmailer/README') + rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/README') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/api/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/client/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/container/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/dispatcher/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/protocol/*.rb') + rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/support/*.rb') + rdoc.rdoc_files.include('vendor/rails/activesupport/README') + rdoc.rdoc_files.include('vendor/rails/activesupport/CHANGELOG') + rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb') + } + + plugins = FileList['vendor/plugins/**'].collect { |plugin| File.basename(plugin) } + + desc "Generate documation for all installed plugins" + task :plugins => plugins.collect { |plugin| "doc:plugins:#{plugin}" } + + desc "Remove plugin documentation" + task :clobber_plugins do + rm_rf 'doc/plugins' rescue nil + end + + namespace :plugins do + # Define doc tasks for each plugin + plugins.each do |plugin| + task(plugin => :environment) do + plugin_base = "vendor/plugins/#{plugin}" + options = [] + files = Rake::FileList.new + options << "-o doc/plugins/#{plugin}" + options << "--title '#{plugin.titlecase} Plugin Documentation'" + options << '--line-numbers' << '--inline-source' + options << '-T html' + + files.include("#{plugin_base}/lib/**/*.rb") + if File.exists?("#{plugin_base}/README") + files.include("#{plugin_base}/README") + options << "--main '#{plugin_base}/README'" + end + files.include("#{plugin_base}/CHANGELOG") if File.exists?("#{plugin_base}/CHANGELOG") + + options << files.to_s + + sh %(rdoc #{options * ' '}) + end + end + end +end \ No newline at end of file diff --git a/vendor/rails/railties/lib/tasks/framework.rake b/vendor/rails/railties/lib/tasks/framework.rake new file mode 100644 index 0000000..e9316b6 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/framework.rake @@ -0,0 +1,112 @@ +namespace :rails do + namespace :freeze do + desc "Lock this application to the current gems (by unpacking them into vendor/rails)" + task :gems do + deps = %w(actionpack activerecord actionmailer activesupport actionwebservice) + require 'rubygems' + Gem.manage_gems + + rails = (version = ENV['VERSION']) ? + Gem.cache.search('rails', "= #{version}").first : + Gem.cache.search('rails').sort_by { |g| g.version }.last + + version ||= rails.version + + unless rails + puts "No rails gem #{version} is installed. Do 'gem list rails' to see what you have available." + exit + end + + puts "Freezing to the gems for Rails #{rails.version}" + rm_rf "vendor/rails" + mkdir_p "vendor/rails" + + chdir("vendor/rails") do + rails.dependencies.select { |g| deps.include? g.name }.each do |g| + Gem::GemRunner.new.run(["unpack", "-v", "#{g.version_requirements}", "#{g.name}"]) + mv(Dir.glob("#{g.name}*").first, g.name) + end + + Gem::GemRunner.new.run(["unpack", "-v", "=#{version}", "rails"]) + FileUtils.mv(Dir.glob("rails*").first, "railties") + end + end + + desc "Lock to latest Edge Rails or a specific revision with REVISION=X (ex: REVISION=4021) or a tag with TAG=Y (ex: TAG=rel_1-1-0)" + task :edge do + $verbose = false + `svn --version` rescue nil + unless !$?.nil? && $?.success? + $stderr.puts "ERROR: Must have subversion (svn) available in the PATH to lock this application to Edge Rails" + exit 1 + end + + rm_rf "vendor/rails" + mkdir_p "vendor/rails" + + svn_root = "http://dev.rubyonrails.org/svn/rails/" + + if ENV['TAG'] + rails_svn = "#{svn_root}/tags/#{ENV['TAG']}" + touch "vendor/rails/TAG_#{ENV['TAG']}" + else + rails_svn = "#{svn_root}/trunk" + + if ENV['REVISION'].nil? + ENV['REVISION'] = /^r(\d+)/.match(%x{svn -qr HEAD log #{svn_root}})[1] + puts "REVISION not set. Using HEAD, which is revision #{ENV['REVISION']}." + end + + touch "vendor/rails/REVISION_#{ENV['REVISION']}" + end + + for framework in %w( railties actionpack activerecord actionmailer activesupport actionwebservice ) + system "svn export #{rails_svn}/#{framework} vendor/rails/#{framework}" + (ENV['REVISION'] ? " -r #{ENV['REVISION']}" : "") + end + end + end + + desc "Unlock this application from freeze of gems or edge and return to a fluid use of system gems" + task :unfreeze do + rm_rf "vendor/rails" + end + + desc "Update both configs, scripts and public/javascripts from Rails" + task :update => [ "update:scripts", "update:javascripts", "update:configs" ] + + namespace :update do + desc "Add new scripts to the application script/ directory" + task :scripts do + local_base = "script" + edge_base = "#{File.dirname(__FILE__)}/../../bin" + + local = Dir["#{local_base}/**/*"].reject { |path| File.directory?(path) } + edge = Dir["#{edge_base}/**/*"].reject { |path| File.directory?(path) } + + edge.each do |script| + base_name = script[(edge_base.length+1)..-1] + next if base_name == "rails" + next if local.detect { |path| base_name == path[(local_base.length+1)..-1] } + if !File.directory?("#{local_base}/#{File.dirname(base_name)}") + mkdir_p "#{local_base}/#{File.dirname(base_name)}" + end + install script, "#{local_base}/#{base_name}", :mode => 0755 + end + end + + desc "Update your javascripts from your current rails install" + task :javascripts do + require 'railties_path' + project_dir = RAILS_ROOT + '/public/javascripts/' + scripts = Dir[RAILTIES_PATH + '/html/javascripts/*.js'] + scripts.reject!{|s| File.basename(s) == 'application.js'} if File.exists?(project_dir + 'application.js') + FileUtils.cp(scripts, project_dir) + end + + desc "Update config/boot.rb from your current rails install" + task :configs do + require 'railties_path' + FileUtils.cp(RAILTIES_PATH + '/environments/boot.rb', RAILS_ROOT + '/config/boot.rb') + end + end +end diff --git a/vendor/rails/railties/lib/tasks/log.rake b/vendor/rails/railties/lib/tasks/log.rake new file mode 100644 index 0000000..6e13346 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/log.rake @@ -0,0 +1,9 @@ +namespace :log do + desc "Truncates all *.log files in log/ to zero bytes" + task :clear do + FileList["log/*.log"].each do |log_file| + f = File.open(log_file, "w") + f.close + end + end +end diff --git a/vendor/rails/railties/lib/tasks/misc.rake b/vendor/rails/railties/lib/tasks/misc.rake new file mode 100644 index 0000000..02ba886 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/misc.rake @@ -0,0 +1,4 @@ +task :default => :test +task :environment do + require(File.join(RAILS_ROOT, 'config', 'environment')) +end \ No newline at end of file diff --git a/vendor/rails/railties/lib/tasks/pre_namespace_aliases.rake b/vendor/rails/railties/lib/tasks/pre_namespace_aliases.rake new file mode 100644 index 0000000..46215e7 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/pre_namespace_aliases.rake @@ -0,0 +1,46 @@ +# clear +task :clear_logs => "log:clear" + +# test +task :recent => "test:recent" +task :test_units => "test:units" +task :test_functional => "test:functionals" +task :test_plugins => "test:plugins" + + +# doc +task :appdoc => "doc:app" +task :apidoc => "doc:rails" +task :plugindoc => "doc:plugins" +task :clobber_plugindoc => "doc:clobber_plugins" + +FileList['vendor/plugins/**'].collect { |plugin| File.basename(plugin) }.each do |plugin| + task :"#{plugin}_plugindoc" => "doc:plugins:#{plugin}" +end + + +# rails +task :freeze_gems => "rails:freeze:gems" +task :freeze_edge => "rails:freeze:edge" +task :unfreeze_rails => "rails:unfreeze" +task :add_new_scripts => "rails:update:scripts" +task :update_javascripts => "rails:update:javascripts" + + +# db +task :migrate => "db:migrate" +task :load_fixtures => "db:fixtures:load" + +task :db_schema_dump => "db:schema:dump" +task :db_schema_import => "db:schema:load" + +task :db_structure_dump => "db:structure:dump" + +task :purge_test_database => "db:test:purge" +task :clone_schema_to_test => "db:test:clone" +task :clone_structure_to_test => "db:test:clone_structure" +task :prepare_test_database => "db:test:prepare" + +task :create_sessions_table => "db:sessions:create" +task :drop_sessions_table => "db:sessions:drop" +task :purge_sessions_table => "db:sessions:recreate" diff --git a/vendor/rails/railties/lib/tasks/rails.rb b/vendor/rails/railties/lib/tasks/rails.rb new file mode 100644 index 0000000..48a9d67 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/rails.rb @@ -0,0 +1,8 @@ +$VERBOSE = nil + +# Load Rails rakefile extensions +Dir["#{File.dirname(__FILE__)}/*.rake"].each { |ext| load ext } + +# Load any custom rakefile extensions +Dir["./lib/tasks/**/*.rake"].sort.each { |ext| load ext } +Dir["./vendor/plugins/*/tasks/**/*.rake"].sort.each { |ext| load ext } \ No newline at end of file diff --git a/vendor/rails/railties/lib/tasks/statistics.rake b/vendor/rails/railties/lib/tasks/statistics.rake new file mode 100644 index 0000000..87b89e5 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/statistics.rake @@ -0,0 +1,17 @@ +STATS_DIRECTORIES = [ + %w(Helpers app/helpers), + %w(Controllers app/controllers), + %w(APIs app/apis), + %w(Components components), + %w(Functional\ tests test/functional), + %w(Models app/models), + %w(Unit\ tests test/unit), + %w(Libraries lib/), + %w(Integration\ tests test/integration) +].collect { |name, dir| [ name, "#{RAILS_ROOT}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } + +desc "Report code statistics (KLOCs, etc) from the application" +task :stats do + require 'code_statistics' + CodeStatistics.new(*STATS_DIRECTORIES).to_s +end diff --git a/vendor/rails/railties/lib/tasks/testing.rake b/vendor/rails/railties/lib/tasks/testing.rake new file mode 100644 index 0000000..acf70d0 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/testing.rake @@ -0,0 +1,117 @@ +TEST_CHANGES_SINCE = Time.now - 600 + +# Look up tests for recently modified sources. +def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) + FileList[source_pattern].map do |path| + if File.mtime(path) > touched_since + tests = [] + source_dir = File.dirname(path).split("/") + source_file = File.basename(path, '.rb') + + # Support subdirs in app/models and app/controllers + modified_test_path = source_dir.length > 2 ? "#{test_path}/" << source_dir[1..source_dir.length].join('/') : test_path + + # For modified files in app/ run the tests for it. ex. /test/functional/account_controller.rb + test = "#{modified_test_path}/#{source_file}_test.rb" + tests.push test if File.exists?(test) + + # For modified files in app, run tests in subdirs too. ex. /test/functional/account/*_test.rb + test = "#{modified_test_path}/#{File.basename(path, '.rb').sub("_controller","")}" + FileList["#{test}/*_test.rb"].each { |f| tests.push f } if File.exists?(test) + + return tests + + end + end.flatten.compact +end + + +# Recreated here from ActiveSupport because :uncommitted needs it before Rails is available +module Kernel + def silence_stderr + old_stderr = STDERR.dup + STDERR.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null') + STDERR.sync = true + yield + ensure + STDERR.reopen(old_stderr) + end +end + +desc 'Test all units and functionals' +task :test do + Rake::Task["test:units"].invoke rescue got_error = true + Rake::Task["test:functionals"].invoke rescue got_error = true + + if File.exist?("test/integration") + Rake::Task["test:integration"].invoke rescue got_error = true + end + + raise "Test failures" if got_error +end + +namespace :test do + desc 'Test recent changes' + Rake::TestTask.new(:recent => "db:test:prepare") do |t| + since = TEST_CHANGES_SINCE + touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + + recent_tests('app/models/**/*.rb', 'test/unit', since) + + recent_tests('app/controllers/**/*.rb', 'test/functional', since) + + t.libs << 'test' + t.verbose = true + t.test_files = touched.uniq + end + + desc 'Test changes since last checkin (only Subversion)' + Rake::TestTask.new(:uncommitted => "db:test:prepare") do |t| + def t.file_list + changed_since_checkin = silence_stderr { `svn status` }.map { |path| path.chomp[7 .. -1] } + + models = changed_since_checkin.select { |path| path =~ /app\/models\/.*\.rb/ } + controllers = changed_since_checkin.select { |path| path =~ /app\/controllers\/.*\.rb/ } + + unit_tests = models.map { |model| "test/unit/#{File.basename(model, '.rb')}_test.rb" } + functional_tests = controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" } + + unit_tests.uniq + functional_tests.uniq + end + + t.libs << 'test' + t.verbose = true + end + + desc "Run the unit tests in test/unit" + Rake::TestTask.new(:units => "db:test:prepare") do |t| + t.libs << "test" + t.pattern = 'test/unit/**/*_test.rb' + t.verbose = true + end + + desc "Run the functional tests in test/functional" + Rake::TestTask.new(:functionals => "db:test:prepare") do |t| + t.libs << "test" + t.pattern = 'test/functional/**/*_test.rb' + t.verbose = true + end + + desc "Run the integration tests in test/integration" + Rake::TestTask.new(:integration => "db:test:prepare") do |t| + t.libs << "test" + t.pattern = 'test/integration/**/*_test.rb' + t.verbose = true + end + + desc "Run the plugin tests in vendor/plugins/**/test (or specify with PLUGIN=name)" + Rake::TestTask.new(:plugins => :environment) do |t| + t.libs << "test" + + if ENV['PLUGIN'] + t.pattern = "vendor/plugins/#{ENV['PLUGIN']}/test/**/*_test.rb" + else + t.pattern = 'vendor/plugins/**/test/**/*_test.rb' + end + + t.verbose = true + end +end diff --git a/vendor/rails/railties/lib/tasks/tmp.rake b/vendor/rails/railties/lib/tasks/tmp.rake new file mode 100644 index 0000000..b191039 --- /dev/null +++ b/vendor/rails/railties/lib/tasks/tmp.rake @@ -0,0 +1,37 @@ +namespace :tmp do + desc "Clear session, cache, and socket files from tmp/" + task :clear => [ "tmp:sessions:clear", "tmp:cache:clear", "tmp:sockets:clear"] + + desc "Creates tmp directories for sessions, cache, and sockets" + task :create do + FileUtils.mkdir_p(%w( tmp/sessions tmp/cache tmp/sockets tmp/pids )) + end + + namespace :sessions do + desc "Clears all files in tmp/sessions" + task :clear do + FileUtils.rm(Dir['tmp/sessions/[^.]*']) + end + end + + namespace :cache do + desc "Clears all files and directories in tmp/cache" + task :clear do + FileUtils.rm_rf(Dir['tmp/cache/[^.]*']) + end + end + + namespace :sockets do + desc "Clears all files in tmp/sockets" + task :clear do + FileUtils.rm(Dir['tmp/sockets/[^.]*']) + end + end + + namespace :pids do + desc "Clears all files in tmp/pids" + task :clear do + FileUtils.rm(Dir['tmp/pids/[^.]*']) + end + end +end \ No newline at end of file diff --git a/vendor/rails/railties/lib/test_help.rb b/vendor/rails/railties/lib/test_help.rb new file mode 100644 index 0000000..dcf4736 --- /dev/null +++ b/vendor/rails/railties/lib/test_help.rb @@ -0,0 +1,18 @@ +require 'application' + +# Make double-sure the RAILS_ENV is set to test, +# so fixtures are loaded to the right database +silence_warnings { RAILS_ENV = "test" } + +require 'test/unit' +require 'active_record/fixtures' +require 'action_controller/test_process' +require 'action_controller/integration' +require 'action_web_service/test_invoke' +require 'breakpoint' + +Test::Unit::TestCase.fixture_path = RAILS_ROOT + "/test/fixtures/" + +def create_fixtures(*table_names) + Fixtures.create_fixtures(RAILS_ROOT + "/test/fixtures", table_names) +end diff --git a/vendor/rails/railties/lib/webrick_server.rb b/vendor/rails/railties/lib/webrick_server.rb new file mode 100644 index 0000000..8f4ddb1 --- /dev/null +++ b/vendor/rails/railties/lib/webrick_server.rb @@ -0,0 +1,165 @@ +# Donated by Florian Gross + +require 'webrick' +require 'cgi' +require 'stringio' + +include WEBrick + +ABSOLUTE_RAILS_ROOT = File.expand_path(RAILS_ROOT) + +class CGI #:nodoc: + def stdinput + @stdin || $stdin + end + + def env_table + @env_table || ENV + end + + def initialize(type = "query", table = nil, stdin = nil) + @env_table, @stdin = table, stdin + + if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE") + Apache.request.setup_cgi_env + end + + extend QueryExtension + @multipart = false + if defined?(CGI_PARAMS) + warn "do not use CGI_PARAMS and CGI_COOKIES" + @params = CGI_PARAMS.dup + @cookies = CGI_COOKIES.dup + else + initialize_query() # set @params, @cookies + end + @output_cookies = nil + @output_hidden = nil + end +end + +# A custom dispatch servlet for use with WEBrick. It dispatches requests +# (using the Rails Dispatcher) to the appropriate controller/action. By default, +# it restricts WEBrick to a managing a single Rails request at a time, but you +# can change this behavior by setting ActionController::Base.allow_concurrency +# to true. +class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet + REQUEST_MUTEX = Mutex.new + + # Start the WEBrick server with the given options, mounting the + # DispatchServlet at /. + def self.dispatch(options = {}) + Socket.do_not_reverse_lookup = true # patch for OS X + + params = { :Port => options[:port].to_i, + :ServerType => options[:server_type], + :BindAddress => options[:ip] } + params[:MimeTypes] = options[:mime_types] if options[:mime_types] + + server = WEBrick::HTTPServer.new(params) + server.mount('/', DispatchServlet, options) + + trap("INT") { server.shutdown } + + server.start + end + + def initialize(server, options) #:nodoc: + @server_options = options + @file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root]) + Dir.chdir(ABSOLUTE_RAILS_ROOT) + super + end + + def service(req, res) #:nodoc: + unless handle_file(req, res) + begin + REQUEST_MUTEX.lock unless ActionController::Base.allow_concurrency + unless handle_dispatch(req, res) + raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found." + end + ensure + unless ActionController::Base.allow_concurrency + REQUEST_MUTEX.unlock if REQUEST_MUTEX.locked? + end + end + end + end + + def handle_file(req, res) #:nodoc: + begin + req = req.dup + path = req.path.dup + + # Add .html if the last path piece has no . in it + path << '.html' if path != '/' && (%r{(^|/)[^./]+$} =~ path) + path.gsub!('+', ' ') # Unescape + since FileHandler doesn't do so. + + req.instance_variable_set(:@path_info, path) # Set the modified path... + + @file_handler.send(:service, req, res) + return true + rescue HTTPStatus::PartialContent, HTTPStatus::NotModified => err + res.set_error(err) + return true + rescue => err + return false + end + end + + def handle_dispatch(req, res, origin = nil) #:nodoc: + data = StringIO.new + Dispatcher.dispatch( + CGI.new("query", create_env_table(req, origin), StringIO.new(req.body || "")), + ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, + data + ) + + header, body = extract_header_and_body(data) + + set_charset(header) + assign_status(res, header) + res.cookies.concat(header.delete('set-cookie') || []) + header.each { |key, val| res[key] = val.join(", ") } + + res.body = body + return true + rescue => err + p err, err.backtrace + return false + end + + private + def create_env_table(req, origin) + env = req.meta_vars.clone + env.delete "SCRIPT_NAME" + env["QUERY_STRING"] = req.request_uri.query + env["REQUEST_URI"] = origin if origin + return env + end + + def extract_header_and_body(data) + data.rewind + data = data.read + + raw_header, body = *data.split(/^[\xd\xa]+/on, 2) + header = WEBrick::HTTPUtils::parse_header(raw_header) + + return header, body + end + + def set_charset(header) + ct = header["content-type"] + if ct.any? { |x| x =~ /^text\// } && ! ct.any? { |x| x =~ /charset=/ } + ch = @server_options[:charset] || "UTF-8" + ct.find { |x| x =~ /^text\// } << ("; charset=" + ch) + end + end + + def assign_status(res, header) + if /^(\d+)/ =~ header['status'][0] + res.status = $1.to_i + header.delete('status') + end + end +end diff --git a/vendor/rails/railties/test/dispatcher_test.rb b/vendor/rails/railties/test/dispatcher_test.rb new file mode 100644 index 0000000..8fb19b1 --- /dev/null +++ b/vendor/rails/railties/test/dispatcher_test.rb @@ -0,0 +1,141 @@ +$:.unshift File.dirname(__FILE__) + "/../lib" +$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" +$:.unshift File.dirname(__FILE__) + "/../../actionmailer/lib" + +require 'test/unit' +require 'stringio' +require 'cgi' + +require 'dispatcher' +require 'action_controller' +require 'action_mailer' + +ACTION_MAILER_DEF = < 'table' + assert_response :success + end + + def test_rails_info_properties_error_rendered_for_non_local_request + Rails::InfoController.local_request = false + get :properties + assert_tag :tag => 'p' + assert_response 500 + end +end diff --git a/vendor/rails/railties/test/rails_info_test.rb b/vendor/rails/railties/test/rails_info_test.rb new file mode 100644 index 0000000..926f254 --- /dev/null +++ b/vendor/rails/railties/test/rails_info_test.rb @@ -0,0 +1,92 @@ +$:.unshift File.dirname(__FILE__) + "/../lib" +$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" + +require 'test/unit' +require 'active_support' +require 'rails_info' + +class InfoTest < Test::Unit::TestCase + def setup + Rails.send :remove_const, :Info + silence_warnings { load 'rails_info.rb' } + end + + def test_edge_rails_revision_not_set_when_svn_info_is_empty + Rails::Info.property 'Test that this will not be defined' do + Rails::Info.edge_rails_revision '' + end + assert !property_defined?('Test that this will not be defined') + end + + def test_edge_rails_revision_extracted_from_svn_info + Rails::Info.property 'Test Edge Rails revision' do + Rails::Info.edge_rails_revision <<-EOS +Path: . +URL: http://www.rubyonrails.com/svn/rails/trunk +Repository UUID: 5ecf4fe2-1ee6-0310-87b1-e25e094e27de +Revision: 2881 +Node Kind: directory +Schedule: normal +Last Changed Author: sam +Last Changed Rev: 2881 +Last Changed Date: 2005-11-04 21:04:41 -0600 (Fri, 04 Nov 2005) +Properties Last Updated: 2005-10-28 19:30:00 -0500 (Fri, 28 Oct 2005) + +EOS + end + + assert_property 'Test Edge Rails revision', '2881' + end + + def test_property_with_block_swallows_exceptions_and_ignores_property + assert_nothing_raised do + Rails::Info.module_eval do + property('Bogus') {raise} + end + end + assert !property_defined?('Bogus') + end + + def test_property_with_string + Rails::Info.module_eval do + property 'Hello', 'World' + end + assert_property 'Hello', 'World' + end + + def test_property_with_block + Rails::Info.module_eval do + property('Goodbye') {'World'} + end + assert_property 'Goodbye', 'World' + end + + def test_component_version + assert_property 'Active Support version', ActiveSupport::VERSION::STRING + end + +protected + def svn_info=(info) + Rails::Info.module_eval do + class << self + def svn_info + info + end + end + end + end + + def properties + Rails::Info.properties + end + + def property_defined?(property_name) + properties.names.include? property_name + end + + def assert_property(property_name, value) + raise "Property #{property_name.inspect} not defined" unless + property_defined? property_name + assert_equal value, properties.value_for(property_name) + end +end diff --git a/vendor/rails/railties/test/webrick_dispatcher_test.rb b/vendor/rails/railties/test/webrick_dispatcher_test.rb new file mode 100644 index 0000000..7f5be65 --- /dev/null +++ b/vendor/rails/railties/test/webrick_dispatcher_test.rb @@ -0,0 +1,27 @@ +#!/bin/env ruby + +$:.unshift(File.dirname(__FILE__) + "/../lib") + +require 'test/unit' +require 'webrick_server' + +class ParseUriTest < Test::Unit::TestCase + + def test_parse_uri_proper_behavior + assert_equal({:id=>"1", :controller=>"forum", :action=>"index"}, DispatchServlet.parse_uri('/forum/index/1')) + assert_equal({:controller=>"forum", :action=>"index"}, DispatchServlet.parse_uri('/forum')) + assert_equal({:controller=>"forum", :action=>"index"}, DispatchServlet.parse_uri('/forum/index')) + assert_equal({:controller=>"forum", :action=>"index"}, DispatchServlet.parse_uri('/forum/')) + assert_equal({:action=>"index", :module=>"admin", :controller=>"forum"}, DispatchServlet.parse_uri('/admin/forum/')) + end + + def test_parse_uri_failures + assert_equal false, DispatchServlet.parse_uri('/forum/index/1/') + assert_equal false, DispatchServlet.parse_uri('/') + assert_equal false, DispatchServlet.parse_uri('a') + assert_equal false, DispatchServlet.parse_uri('/forum//') + assert_equal false, DispatchServlet.parse_uri('/+forum/') + assert_equal false, DispatchServlet.parse_uri('forum/') + end + +end