i can haz railz 3
This commit is contained in:
parent
d762fdb864
commit
6593b5709b
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
.bundle
|
||||
db/*.sqlite3
|
||||
log/*.log
|
||||
tmp/**/*
|
||||
.DS_Store
|
||||
|
|
10
Gemfile
Normal file
10
Gemfile
Normal file
|
@ -0,0 +1,10 @@
|
|||
source 'http://rubygems.org'
|
||||
|
||||
gem 'rails', '3.0.0'
|
||||
|
||||
gem "authlogic"
|
||||
gem 'mime-types', :require => 'mime/types'
|
||||
gem "carrierwave", :git => 'git://github.com/jnicklas/carrierwave.git'
|
||||
#gem "mysql2"
|
||||
gem 'pg'
|
||||
gem "rmagick", :require => 'RMagick'
|
87
Gemfile.lock
Normal file
87
Gemfile.lock
Normal file
|
@ -0,0 +1,87 @@
|
|||
GIT
|
||||
remote: git://github.com/jnicklas/carrierwave.git
|
||||
revision: 3372a078a0ea4d6e4e2ee1990610db9186f43b41
|
||||
specs:
|
||||
carrierwave (0.5.0.beta2)
|
||||
activesupport (~> 3.0.0)
|
||||
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
abstract (1.0.0)
|
||||
actionmailer (3.0.0)
|
||||
actionpack (= 3.0.0)
|
||||
mail (~> 2.2.5)
|
||||
actionpack (3.0.0)
|
||||
activemodel (= 3.0.0)
|
||||
activesupport (= 3.0.0)
|
||||
builder (~> 2.1.2)
|
||||
erubis (~> 2.6.6)
|
||||
i18n (~> 0.4.1)
|
||||
rack (~> 1.2.1)
|
||||
rack-mount (~> 0.6.12)
|
||||
rack-test (~> 0.5.4)
|
||||
tzinfo (~> 0.3.23)
|
||||
activemodel (3.0.0)
|
||||
activesupport (= 3.0.0)
|
||||
builder (~> 2.1.2)
|
||||
i18n (~> 0.4.1)
|
||||
activerecord (3.0.0)
|
||||
activemodel (= 3.0.0)
|
||||
activesupport (= 3.0.0)
|
||||
arel (~> 1.0.0)
|
||||
tzinfo (~> 0.3.23)
|
||||
activeresource (3.0.0)
|
||||
activemodel (= 3.0.0)
|
||||
activesupport (= 3.0.0)
|
||||
activesupport (3.0.0)
|
||||
arel (1.0.1)
|
||||
activesupport (~> 3.0.0)
|
||||
authlogic (2.1.6)
|
||||
activesupport
|
||||
builder (2.1.2)
|
||||
erubis (2.6.6)
|
||||
abstract (>= 1.0.0)
|
||||
i18n (0.4.1)
|
||||
mail (2.2.6.1)
|
||||
activesupport (>= 2.3.6)
|
||||
mime-types
|
||||
treetop (>= 1.4.5)
|
||||
mime-types (1.16)
|
||||
pg (0.9.0)
|
||||
polyglot (0.3.1)
|
||||
rack (1.2.1)
|
||||
rack-mount (0.6.13)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (0.5.4)
|
||||
rack (>= 1.0)
|
||||
rails (3.0.0)
|
||||
actionmailer (= 3.0.0)
|
||||
actionpack (= 3.0.0)
|
||||
activerecord (= 3.0.0)
|
||||
activeresource (= 3.0.0)
|
||||
activesupport (= 3.0.0)
|
||||
bundler (~> 1.0.0)
|
||||
railties (= 3.0.0)
|
||||
railties (3.0.0)
|
||||
actionpack (= 3.0.0)
|
||||
activesupport (= 3.0.0)
|
||||
rake (>= 0.8.4)
|
||||
thor (~> 0.14.0)
|
||||
rake (0.8.7)
|
||||
rmagick (2.13.1)
|
||||
thor (0.14.1)
|
||||
treetop (1.4.8)
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.23)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
authlogic
|
||||
carrierwave!
|
||||
mime-types
|
||||
pg
|
||||
rails (= 3.0.0)
|
||||
rmagick
|
7
Rakefile
7
Rakefile
|
@ -1,10 +1,7 @@
|
|||
# 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 File.expand_path('../config/application', __FILE__)
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
require 'tasks/rails'
|
||||
Balder::Application.load_tasks
|
||||
|
|
|
@ -5,7 +5,6 @@ class ApplicationController < ActionController::Base
|
|||
helper :all # include all helpers, all the time
|
||||
protect_from_forgery # See ActionController::RequestForgeryProtection for details
|
||||
|
||||
filter_parameter_logging :password, :password_confirmation
|
||||
helper_method :current_user, :current_user_session
|
||||
|
||||
before_filter :setup
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
module AlbumsHelper
|
||||
def new_upload_path_with_session_information
|
||||
session_key = ActionController::Base.session_options[:key]
|
||||
session_key = self.get_session_key
|
||||
photos_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token)
|
||||
end
|
||||
|
||||
def get_session_key
|
||||
ActionController::Base.session_options[:key]
|
||||
Rails.application.config.session_options[:key]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,11 +8,12 @@ class FlashSessionCookieMiddleware
|
|||
|
||||
def call(env)
|
||||
if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
|
||||
params = ::Rack::Request.new(env).params
|
||||
|
||||
unless params[@session_key].nil?
|
||||
env['HTTP_COOKIE'] = "#{@session_key}=#{params[@session_key]}".freeze
|
||||
end
|
||||
req = Rack::Request.new(env)
|
||||
env['HTTP_COOKIE'] = [ @session_key,
|
||||
req.params[@session_key] ]
|
||||
.join('=').freeze unless req.params[@session_key].nil?
|
||||
env['HTTP_ACCEPT'] = "#{req.params['_http_accept']}"
|
||||
.freeze unless req.params['_http_accept'].nil?
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
|
|
|
@ -13,9 +13,9 @@ class Album < ActiveRecord::Base
|
|||
attr_accessor :tags
|
||||
#attr_protected :path
|
||||
|
||||
named_scope :untouched, :conditions => "albums.id IN ( SELECT DISTINCT photos.album_id FROM photos WHERE photos.description IS NULL AND photos.id NOT IN ( SELECT photo_id FROM photo_tags) )", :order => 'title'
|
||||
named_scope :unused, :conditions => "albums.id NOT IN (SELECT album_id FROM collection_albums)"
|
||||
named_scope :used, :conditions => "albums.id IN (SELECT album_id FROM collection_albums)"
|
||||
scope :untouched, :conditions => "albums.id IN ( SELECT DISTINCT photos.album_id FROM photos WHERE photos.description IS NULL AND photos.id NOT IN ( SELECT photo_id FROM photo_tags) )", :order => 'title'
|
||||
scope :unused, :conditions => "albums.id NOT IN (SELECT album_id FROM collection_albums)"
|
||||
scope :used, :conditions => "albums.id IN (SELECT album_id FROM collection_albums)"
|
||||
|
||||
def to_param
|
||||
"#{id}-#{title.parameterize}"
|
||||
|
|
|
@ -15,9 +15,9 @@ class Photo < ActiveRecord::Base
|
|||
attr_accessor :tag_list
|
||||
#attr_protected :path
|
||||
|
||||
named_scope :untouched, :conditions => "photos.description IS NULL AND photos.id NOT IN ( SELECT photo_id FROM photo_tags)", :include => :album
|
||||
named_scope :previous, lambda { |p,a| { :conditions => ["id < :id AND album_Id = :album ", { :id => p, :album => a } ], :limit => 1, :order => "id DESC"} }
|
||||
named_scope :next, lambda { |p,a| { :conditions => ["id > :id AND album_Id = :album ", { :id => p, :album => a } ], :limit => 1, :order => "id ASC"} }
|
||||
scope :untouched, :conditions => "photos.description IS NULL AND photos.id NOT IN ( SELECT photo_id FROM photo_tags)", :include => :album
|
||||
scope :previous, lambda { |p,a| { :conditions => ["id < :id AND album_Id = :album ", { :id => p, :album => a } ], :limit => 1, :order => "id DESC"} }
|
||||
scope :next, lambda { |p,a| { :conditions => ["id > :id AND album_Id = :album ", { :id => p, :album => a } ], :limit => 1, :order => "id ASC"} }
|
||||
|
||||
def to_param
|
||||
"#{id}-#{title.parameterize}"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h1>Edit Account</h1>
|
||||
|
||||
<% form_for @user do |f| %>
|
||||
<%= form_for @user do |f| %>
|
||||
<%= f.error_messages %>
|
||||
<%= render :partial => "form", :object => f %>
|
||||
<%= f.submit "Update" %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<%= image_tag @photo.file.preview.url %>
|
||||
|
||||
<% form_for @photo do |f| %>
|
||||
<%= form_for @photo do |f| %>
|
||||
<%= f.error_messages %>
|
||||
<%= render :partial => "form", :object => f %>
|
||||
<%= f.submit "Update" %>
|
||||
|
|
|
@ -31,10 +31,10 @@ $(document).ready(function() {
|
|||
</script>
|
||||
<% end %>
|
||||
<form>
|
||||
<input type="file" id="photo_file" />
|
||||
<br />
|
||||
<input type="file" id="photo_file">
|
||||
<br>
|
||||
<div id="thumbs"></div>
|
||||
</form>
|
||||
|
||||
<br /><%= link_to "Edit uploaded photos", untouched_album_photos_path( @album ) %>
|
||||
<br /><%= link_to "Back to #{@album.title}", @album %>
|
||||
<br><%= link_to "Edit uploaded photos", untouched_album_photos_path( @album ) %>
|
||||
<br><%= link_to "Back to #{@album.title}", @album %>
|
4
config.ru
Normal file
4
config.ru
Normal file
|
@ -0,0 +1,4 @@
|
|||
# This file is used by Rack-based servers to start the application.
|
||||
|
||||
require ::File.expand_path('../config/environment', __FILE__)
|
||||
run Balder::Application
|
21
config/application.rb
Normal file
21
config/application.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
require File.expand_path('../boot', __FILE__)
|
||||
|
||||
require 'rails/all'
|
||||
|
||||
# If you have a Gemfile, require the gems listed there, including any gems
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(:default, Rails.env) if defined?(Bundler)
|
||||
|
||||
module Balder
|
||||
class Application < Rails::Application
|
||||
|
||||
# Custom directories with classes and modules you want to be autoloadable.
|
||||
# config.autoload_paths += %W(#{config.root}/
|
||||
config.autoload_paths += %W(#{Rails.root}/lib #{config.root}/app/middleware/)
|
||||
|
||||
config.encoding = "utf-8"
|
||||
|
||||
# Configure sensitive parameters which will be filtered from the log file.
|
||||
config.filter_parameters += [:password, :password_confirmation]
|
||||
end
|
||||
end
|
119
config/boot.rb
119
config/boot.rb
|
@ -1,110 +1,13 @@
|
|||
# Don't change this file!
|
||||
# Configure your app in config/environment.rb and config/environments/*.rb
|
||||
|
||||
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
|
||||
|
||||
module Rails
|
||||
class << self
|
||||
def boot!
|
||||
unless booted?
|
||||
preinitialize
|
||||
pick_boot.run
|
||||
end
|
||||
end
|
||||
|
||||
def booted?
|
||||
defined? Rails::Initializer
|
||||
end
|
||||
|
||||
def pick_boot
|
||||
(vendor_rails? ? VendorBoot : GemBoot).new
|
||||
end
|
||||
|
||||
def vendor_rails?
|
||||
File.exist?("#{RAILS_ROOT}/vendor/rails")
|
||||
end
|
||||
|
||||
def preinitialize
|
||||
load(preinitializer_path) if File.exist?(preinitializer_path)
|
||||
end
|
||||
|
||||
def preinitializer_path
|
||||
"#{RAILS_ROOT}/config/preinitializer.rb"
|
||||
end
|
||||
end
|
||||
|
||||
class Boot
|
||||
def run
|
||||
load_initializer
|
||||
Rails::Initializer.run(:set_load_path)
|
||||
end
|
||||
end
|
||||
|
||||
class VendorBoot < Boot
|
||||
def load_initializer
|
||||
require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
|
||||
Rails::Initializer.run(:install_gem_spec_stubs)
|
||||
Rails::GemDependency.add_frozen_gem_path
|
||||
end
|
||||
end
|
||||
|
||||
class GemBoot < Boot
|
||||
def load_initializer
|
||||
self.class.load_rubygems
|
||||
load_rails_gem
|
||||
require 'initializer'
|
||||
end
|
||||
|
||||
def load_rails_gem
|
||||
if version = self.class.gem_version
|
||||
gem 'rails', version
|
||||
else
|
||||
gem 'rails'
|
||||
end
|
||||
rescue Gem::LoadError => load_error
|
||||
$stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
|
||||
exit 1
|
||||
end
|
||||
|
||||
class << self
|
||||
def rubygems_version
|
||||
Gem::RubyGemsVersion rescue nil
|
||||
end
|
||||
|
||||
def gem_version
|
||||
if defined? RAILS_GEM_VERSION
|
||||
RAILS_GEM_VERSION
|
||||
elsif ENV.include?('RAILS_GEM_VERSION')
|
||||
ENV['RAILS_GEM_VERSION']
|
||||
else
|
||||
parse_gem_version(read_environment_rb)
|
||||
end
|
||||
end
|
||||
|
||||
def load_rubygems
|
||||
require 'rubygems'
|
||||
min_version = '1.3.1'
|
||||
unless rubygems_version >= min_version
|
||||
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
||||
exit 1
|
||||
end
|
||||
|
||||
rescue LoadError
|
||||
$stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
||||
exit 1
|
||||
end
|
||||
|
||||
def parse_gem_version(text)
|
||||
$1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
|
||||
end
|
||||
|
||||
private
|
||||
def read_environment_rb
|
||||
File.read("#{RAILS_ROOT}/config/environment.rb")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# All that for this:
|
||||
Rails.boot!
|
||||
# Set up gems listed in the Gemfile.
|
||||
gemfile = File.expand_path('../../Gemfile', __FILE__)
|
||||
begin
|
||||
ENV['BUNDLE_GEMFILE'] = gemfile
|
||||
require 'bundler'
|
||||
Bundler.setup
|
||||
rescue Bundler::GemNotFound => e
|
||||
STDERR.puts e.message
|
||||
STDERR.puts "Try running `bundle install`."
|
||||
exit!
|
||||
end if File.exist?(gemfile)
|
||||
|
|
|
@ -1,25 +1,9 @@
|
|||
# Be sure to restart your server when you modify this file
|
||||
# Load the rails application
|
||||
require File.expand_path('../application', __FILE__)
|
||||
|
||||
# Specifies gem version of Rails to use when vendor/rails is not present
|
||||
#RAILS_GEM_VERSION = '2.3.4' unless defined? RAILS_GEM_VERSION
|
||||
|
||||
# Bootstrap the Rails environment, frameworks, and default configuration
|
||||
require File.join(File.dirname(__FILE__), 'boot')
|
||||
|
||||
# Load heroku vars from local file
|
||||
balder_env = File.join(RAILS_ROOT, 'config', 'balder.rb')
|
||||
# Load app vars from local file
|
||||
balder_env = File.join(Rails.root, 'config', 'balder.rb')
|
||||
load(balder_env) if File.exists?(balder_env)
|
||||
|
||||
|
||||
Rails::Initializer.run do |config|
|
||||
|
||||
config.gem "authlogic"
|
||||
config.gem 'mime-types', :lib => 'mime/types'
|
||||
config.gem "carrierwave"
|
||||
config.gem "mysql2"
|
||||
#config.gem "mini_exiftool"
|
||||
#config.gem "aws-s3", :lib => "aws/s3"
|
||||
|
||||
config.load_paths += %W( #{RAILS_ROOT}/app/middleware )
|
||||
|
||||
end
|
||||
# Initialize the rails application
|
||||
Balder::Application.initialize!
|
|
@ -1,3 +1,4 @@
|
|||
Balder::Application.configure do
|
||||
# Settings specified here will take precedence over those in config/environment.rb
|
||||
|
||||
# In the development environment your application's code is reloaded on
|
||||
|
@ -9,11 +10,19 @@ config.cache_classes = false
|
|||
config.whiny_nils = true
|
||||
|
||||
# Show full error reports and disable caching
|
||||
config.action_controller.consider_all_requests_local = true
|
||||
config.consider_all_requests_local = true
|
||||
config.action_view.debug_rjs = true
|
||||
config.action_controller.perform_caching = false
|
||||
|
||||
# Don't care if the mailer can't send
|
||||
config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
# Print deprecation notices to the Rails logger
|
||||
config.active_support.deprecation = :log
|
||||
|
||||
# Only use best-standards-support built into browsers
|
||||
config.action_dispatch.best_standards_support = :builtin
|
||||
|
||||
ENV["RAILS_ASSET_ID"] = ""
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
Balder::Application.configure do
|
||||
# Settings specified here will take precedence over those in config/environment.rb
|
||||
|
||||
# The production environment is meant for finished, "live" apps.
|
||||
|
@ -5,9 +6,17 @@
|
|||
config.cache_classes = true
|
||||
|
||||
# Full error reports are disabled and caching is turned on
|
||||
config.action_controller.consider_all_requests_local = false
|
||||
config.consider_all_requests_local = false
|
||||
config.action_controller.perform_caching = true
|
||||
config.action_view.cache_template_loading = true
|
||||
|
||||
# Specifies the header that your server uses for sending files
|
||||
config.action_dispatch.x_sendfile_header = "X-Sendfile"
|
||||
|
||||
# For nginx:
|
||||
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
|
||||
|
||||
# If you have no front-end server that supports something like X-Sendfile,
|
||||
# just comment this out and Rails will serve the files
|
||||
|
||||
# See everything in the log (default is :info)
|
||||
# config.log_level = :debug
|
||||
|
@ -18,6 +27,10 @@ config.action_view.cache_template_loading = true
|
|||
# Use a different cache store in production
|
||||
# config.cache_store = :mem_cache_store
|
||||
|
||||
# Disable Rails's static asset server
|
||||
# In production, Apache or nginx will already do this
|
||||
config.serve_static_assets = false
|
||||
|
||||
# Enable serving of images, stylesheets, and javascripts from an asset server
|
||||
# config.action_controller.asset_host = "http://assets.example.com"
|
||||
|
||||
|
@ -26,3 +39,11 @@ config.action_view.cache_template_loading = true
|
|||
|
||||
# Enable threaded mode
|
||||
# config.threadsafe!
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
# the I18n.default_locale when a translation can not be found)
|
||||
config.i18n.fallbacks = true
|
||||
|
||||
# Send deprecation notices to registered listeners
|
||||
config.active_support.deprecation = :notify
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
Balder::Application.configure do
|
||||
# Settings specified here will take precedence over those in config/environment.rb
|
||||
|
||||
# The test environment is used exclusively to run your application's
|
||||
|
@ -10,9 +11,11 @@ config.cache_classes = true
|
|||
config.whiny_nils = true
|
||||
|
||||
# Show full error reports and disable caching
|
||||
config.action_controller.consider_all_requests_local = true
|
||||
config.consider_all_requests_local = true
|
||||
config.action_controller.perform_caching = false
|
||||
config.action_view.cache_template_loading = true
|
||||
|
||||
# Raise exceptions instead of rendering exception templates
|
||||
config.action_dispatch.show_exceptions = false
|
||||
|
||||
# Disable request forgery protection in test environment
|
||||
config.action_controller.allow_forgery_protection = false
|
||||
|
@ -26,3 +29,7 @@ config.action_mailer.delivery_method = :test
|
|||
# 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
|
||||
|
||||
# Print deprecation notices to the stderr
|
||||
config.active_support.deprecation = :stderr
|
||||
end
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
|
||||
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
|
||||
|
||||
# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
|
||||
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
|
||||
# Rails.backtrace_cleaner.remove_silencers!
|
|
@ -3,6 +3,3 @@
|
|||
# Add new mime types for use in respond_to blocks:
|
||||
# Mime::Type.register "text/richtext", :rtf
|
||||
# Mime::Type.register_alias "text/html", :iphone
|
||||
|
||||
Mime::Type.register_alias "text/html", :iphone
|
||||
Mime::Type.register_alias "text/html", :ipad
|
|
@ -1,19 +0,0 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# These settings change the behavior of Rails 2 apps and will be defaults
|
||||
# for Rails 3. You can remove this initializer when Rails 3 is released.
|
||||
|
||||
if defined?(ActiveRecord)
|
||||
# Include Active Record class name as root for JSON serialized output.
|
||||
ActiveRecord::Base.include_root_in_json = true
|
||||
|
||||
# Store the full class name (including module namespace) in STI type column.
|
||||
ActiveRecord::Base.store_full_sti_class = true
|
||||
end
|
||||
|
||||
# Use ISO 8601 format for JSON serialized times and dates.
|
||||
ActiveSupport.use_standard_json_time_format = true
|
||||
|
||||
# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
|
||||
# if you're including raw json in an HTML page.
|
||||
ActiveSupport.escape_html_entities_in_json = false
|
7
config/initializers/secret_token.rb
Normal file
7
config/initializers/secret_token.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Your secret key for verifying the integrity of signed cookies.
|
||||
# If you change this key, all old signed cookies will become invalid!
|
||||
# Make sure the secret is at least 30 characters and all random,
|
||||
# no regular words or you'll be exposed to dictionary attacks.
|
||||
Balder::Application.config.secret_token = '23aabccbfd8e48ab8b96104ba699a1603e6bad13a125317be3ed9963c18cd24b9afa725d218688202f3752fd693cc8358f522c0f18c42a5a0b8ce3c84a8b89a2'
|
|
@ -1,19 +1,14 @@
|
|||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
# Your secret key for verifying cookie session data integrity.
|
||||
# If you change this key, all old sessions will become invalid!
|
||||
# Make sure the secret is at least 30 characters and all random,
|
||||
# no regular words or you'll be exposed to dictionary attacks.
|
||||
ActionController::Base.session = {
|
||||
:key => '_gallery_session',
|
||||
:secret => '06xfeafeop90cuepaiam324eoimxeaioa2b4220c445486dace48f53fc1a0d4ec4e8de033e1db323628d66b6cx990loibjustintime99'
|
||||
}
|
||||
Balder::Application.config.session_store :cookie_store, :key => '_balder_session'
|
||||
|
||||
Rails.application.config.middleware.insert_before(
|
||||
ActionDispatch::Session::CookieStore,
|
||||
FlashSessionCookieMiddleware,
|
||||
Rails.application.config.session_options[:key]
|
||||
)
|
||||
|
||||
# Use the database for sessions instead of the cookie-based default,
|
||||
# which shouldn't be used to store highly confidential information
|
||||
# (create the session table with "rake db:sessions:create")
|
||||
# ActionController::Base.session_store = :active_record_store
|
||||
|
||||
|
||||
ActionController::Dispatcher.middleware.insert_before(ActionController::Session::CookieStore, FlashSessionCookieMiddleware, ActionController::Base.session_options[:key])
|
||||
#ActionController::Dispatcher.middleware.use FlashSessionCookieMiddleware, ActionController::Base.session_options[:key]
|
||||
# Balder::Application.config.session_store :active_record_store
|
||||
|
|
7
db/seeds.rb
Normal file
7
db/seeds.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# This file should contain all the record creation needed to seed the database with its default values.
|
||||
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
|
||||
# Mayor.create(:name => 'Daley', :city => cities.first)
|
0
lib/tasks/.gitkeep
Normal file
0
lib/tasks/.gitkeep
Normal file
|
@ -1,10 +1,6 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<title>The page you were looking for doesn't exist (404)</title>
|
||||
<style type="text/css">
|
||||
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<title>The change you wanted was rejected (422)</title>
|
||||
<style type="text/css">
|
||||
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
<title>We're sorry, but something went wrong (500)</title>
|
||||
<style type="text/css">
|
||||
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
||||
|
|
965
public/javascripts/controls.js
vendored
Normal file
965
public/javascripts/controls.js
vendored
Normal file
|
@ -0,0 +1,965 @@
|
|||
// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
|
||||
|
||||
// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// 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.
|
||||
|
||||
if(typeof Effect == 'undefined')
|
||||
throw("controls.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Autocompleter = { };
|
||||
Autocompleter.Base = Class.create({
|
||||
baseInitialize: function(element, update, options) {
|
||||
element = $(element);
|
||||
this.element = element;
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
this.oldElementValue = this.element.value;
|
||||
|
||||
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);
|
||||
// Force carriage returns as token delimiters anyway
|
||||
if (!this.options.tokens.include('\n'))
|
||||
this.options.tokens.push('\n');
|
||||
|
||||
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, 'keydown', this.onKeyPress.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix &&
|
||||
(Prototype.Browser.IE) &&
|
||||
(Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
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();
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_DOWN:
|
||||
this.markNext();
|
||||
this.render();
|
||||
Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||
(Prototype.Browser.WebKit > 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 = $(selectedElement).select('.' + this.options.select) || [];
|
||||
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
|
||||
} else
|
||||
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
|
||||
var bounds = this.getTokenBounds();
|
||||
if (bounds[0] != -1) {
|
||||
var newValue = this.element.value.substr(0, bounds[0]);
|
||||
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
|
||||
if (whitespace)
|
||||
newValue += whitespace[0];
|
||||
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
|
||||
} else {
|
||||
this.element.value = value;
|
||||
}
|
||||
this.oldElementValue = this.element.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.down());
|
||||
|
||||
if(this.update.firstChild && this.update.down().childNodes) {
|
||||
this.entryCount =
|
||||
this.update.down().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;
|
||||
this.tokenBounds = null;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.getUpdatedChoices();
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
var bounds = this.getTokenBounds();
|
||||
return this.element.value.substring(bounds[0], bounds[1]).strip();
|
||||
},
|
||||
|
||||
getTokenBounds: function() {
|
||||
if (null != this.tokenBounds) return this.tokenBounds;
|
||||
var value = this.element.value;
|
||||
if (value.strip().empty()) return [-1, 0];
|
||||
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
|
||||
var offset = (diff == this.oldElementValue.length ? 1 : 0);
|
||||
var prevTokenPos = -1, nextTokenPos = value.length;
|
||||
var tp;
|
||||
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
|
||||
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
|
||||
if (tp > prevTokenPos) prevTokenPos = tp;
|
||||
tp = value.indexOf(this.options.tokens[index], diff + offset);
|
||||
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
|
||||
}
|
||||
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
|
||||
}
|
||||
});
|
||||
|
||||
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
|
||||
var boundary = Math.min(newS.length, oldS.length);
|
||||
for (var index = 0; index < boundary; ++index)
|
||||
if (newS[index] != oldS[index])
|
||||
return index;
|
||||
return boundary;
|
||||
};
|
||||
|
||||
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
|
||||
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() {
|
||||
this.startIndicator();
|
||||
|
||||
var 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.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("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
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("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
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 "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || { });
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX in-place editor and collection editor
|
||||
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
|
||||
|
||||
// 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({
|
||||
initialize: function(element, url, options) {
|
||||
this.url = url;
|
||||
this.element = element = $(element);
|
||||
this.prepareOptions();
|
||||
this._controls = { };
|
||||
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
|
||||
Object.extend(this.options, options || { });
|
||||
if (!this.options.formId && this.element.id) {
|
||||
this.options.formId = this.element.id + '-inplaceeditor';
|
||||
if ($(this.options.formId))
|
||||
this.options.formId = '';
|
||||
}
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl = $(this.options.externalControl);
|
||||
if (!this.options.externalControl)
|
||||
this.options.externalControlOnly = false;
|
||||
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
|
||||
this.element.title = this.options.clickToEditText;
|
||||
this._boundCancelHandler = this.handleFormCancellation.bind(this);
|
||||
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
|
||||
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
|
||||
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
|
||||
this._boundWrapperHandler = this.wrapUp.bind(this);
|
||||
this.registerListeners();
|
||||
},
|
||||
checkForEscapeOrReturn: function(e) {
|
||||
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
|
||||
if (Event.KEY_ESC == e.keyCode)
|
||||
this.handleFormCancellation(e);
|
||||
else if (Event.KEY_RETURN == e.keyCode)
|
||||
this.handleFormSubmission(e);
|
||||
},
|
||||
createControl: function(mode, handler, extraClasses) {
|
||||
var control = this.options[mode + 'Control'];
|
||||
var text = this.options[mode + 'Text'];
|
||||
if ('button' == control) {
|
||||
var btn = document.createElement('input');
|
||||
btn.type = 'submit';
|
||||
btn.value = text;
|
||||
btn.className = 'editor_' + mode + '_button';
|
||||
if ('cancel' == mode)
|
||||
btn.onclick = this._boundCancelHandler;
|
||||
this._form.appendChild(btn);
|
||||
this._controls[mode] = btn;
|
||||
} else if ('link' == control) {
|
||||
var link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.appendChild(document.createTextNode(text));
|
||||
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
|
||||
link.className = 'editor_' + mode + '_link';
|
||||
if (extraClasses)
|
||||
link.className += ' ' + extraClasses;
|
||||
this._form.appendChild(link);
|
||||
this._controls[mode] = link;
|
||||
}
|
||||
},
|
||||
createEditField: function() {
|
||||
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
|
||||
var fld;
|
||||
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
|
||||
fld = document.createElement('input');
|
||||
fld.type = 'text';
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (0 < size) fld.size = size;
|
||||
} else {
|
||||
fld = document.createElement('textarea');
|
||||
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
|
||||
fld.cols = this.options.cols || 40;
|
||||
}
|
||||
fld.name = this.options.paramName;
|
||||
fld.value = text; // No HTML breaks conversion anymore
|
||||
fld.className = 'editor_field';
|
||||
if (this.options.submitOnBlur)
|
||||
fld.onblur = this._boundSubmitHandler;
|
||||
this._controls.editor = fld;
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
createForm: function() {
|
||||
var ipe = this;
|
||||
function addText(mode, condition) {
|
||||
var text = ipe.options['text' + mode + 'Controls'];
|
||||
if (!text || condition === false) return;
|
||||
ipe._form.appendChild(document.createTextNode(text));
|
||||
};
|
||||
this._form = $(document.createElement('form'));
|
||||
this._form.id = this.options.formId;
|
||||
this._form.addClassName(this.options.formClassName);
|
||||
this._form.onsubmit = this._boundSubmitHandler;
|
||||
this.createEditField();
|
||||
if ('textarea' == this._controls.editor.tagName.toLowerCase())
|
||||
this._form.appendChild(document.createElement('br'));
|
||||
if (this.options.onFormCustomization)
|
||||
this.options.onFormCustomization(this, this._form);
|
||||
addText('Before', this.options.okControl || this.options.cancelControl);
|
||||
this.createControl('ok', this._boundSubmitHandler);
|
||||
addText('Between', this.options.okControl && this.options.cancelControl);
|
||||
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
|
||||
addText('After', this.options.okControl || this.options.cancelControl);
|
||||
},
|
||||
destroy: function() {
|
||||
if (this._oldInnerHTML)
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this.leaveEditMode();
|
||||
this.unregisterListeners();
|
||||
},
|
||||
enterEditMode: function(e) {
|
||||
if (this._saving || this._editing) return;
|
||||
this._editing = true;
|
||||
this.triggerCallback('onEnterEditMode');
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.hide();
|
||||
this.element.hide();
|
||||
this.createForm();
|
||||
this.element.parentNode.insertBefore(this._form, this.element);
|
||||
if (!this.options.loadTextURL)
|
||||
this.postProcessEditField();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
enterHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.addClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onEnterHover');
|
||||
},
|
||||
getText: function() {
|
||||
return this.element.innerHTML.unescapeHTML();
|
||||
},
|
||||
handleAJAXFailure: function(transport) {
|
||||
this.triggerCallback('onFailure', transport);
|
||||
if (this._oldInnerHTML) {
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this._oldInnerHTML = null;
|
||||
}
|
||||
},
|
||||
handleFormCancellation: function(e) {
|
||||
this.wrapUp();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
handleFormSubmission: function(e) {
|
||||
var form = this._form;
|
||||
var value = $F(this._controls.editor);
|
||||
this.prepareSubmission();
|
||||
var params = this.options.callback(form, value) || '';
|
||||
if (Object.isString(params))
|
||||
params = params.toQueryParams();
|
||||
params.editorId = this.element.id;
|
||||
if (this.options.htmlResponse) {
|
||||
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Updater({ success: this.element }, this.url, options);
|
||||
} else {
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.url, options);
|
||||
}
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
leaveEditMode: function() {
|
||||
this.element.removeClassName(this.options.savingClassName);
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.show();
|
||||
this._saving = false;
|
||||
this._editing = false;
|
||||
this._oldInnerHTML = null;
|
||||
this.triggerCallback('onLeaveEditMode');
|
||||
},
|
||||
leaveHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.removeClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onLeaveHover');
|
||||
},
|
||||
loadExternalText: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this._controls.editor.disabled = true;
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
var text = transport.responseText;
|
||||
if (this.options.stripLoadedTextTags)
|
||||
text = text.stripTags();
|
||||
this._controls.editor.value = text;
|
||||
this._controls.editor.disabled = false;
|
||||
this.postProcessEditField();
|
||||
}.bind(this),
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
postProcessEditField: function() {
|
||||
var fpc = this.options.fieldPostCreation;
|
||||
if (fpc)
|
||||
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
|
||||
},
|
||||
prepareOptions: function() {
|
||||
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
|
||||
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
|
||||
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
|
||||
Object.extend(this.options, defs);
|
||||
}.bind(this));
|
||||
},
|
||||
prepareSubmission: function() {
|
||||
this._saving = true;
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.showSaving();
|
||||
},
|
||||
registerListeners: function() {
|
||||
this._listeners = { };
|
||||
var listener;
|
||||
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
|
||||
listener = this[pair.value].bind(this);
|
||||
this._listeners[pair.key] = listener;
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.observe(pair.key, listener);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.observe(pair.key, listener);
|
||||
}.bind(this));
|
||||
},
|
||||
removeForm: function() {
|
||||
if (!this._form) return;
|
||||
this._form.remove();
|
||||
this._form = null;
|
||||
this._controls = { };
|
||||
},
|
||||
showSaving: function() {
|
||||
this._oldInnerHTML = this.element.innerHTML;
|
||||
this.element.innerHTML = this.options.savingText;
|
||||
this.element.addClassName(this.options.savingClassName);
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
},
|
||||
triggerCallback: function(cbName, arg) {
|
||||
if ('function' == typeof this.options[cbName]) {
|
||||
this.options[cbName](this, arg);
|
||||
}
|
||||
},
|
||||
unregisterListeners: function() {
|
||||
$H(this._listeners).each(function(pair) {
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.stopObserving(pair.key, pair.value);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.stopObserving(pair.key, pair.value);
|
||||
}.bind(this));
|
||||
},
|
||||
wrapUp: function(transport) {
|
||||
this.leaveEditMode();
|
||||
// Can't use triggerCallback due to backward compatibility: requires
|
||||
// binding + direct element
|
||||
this._boundComplete(transport, this.element);
|
||||
}
|
||||
});
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor.prototype, {
|
||||
dispose: Ajax.InPlaceEditor.prototype.destroy
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
|
||||
initialize: function($super, element, url, options) {
|
||||
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
|
||||
$super(element, url, options);
|
||||
},
|
||||
|
||||
createEditField: function() {
|
||||
var list = document.createElement('select');
|
||||
list.name = this.options.paramName;
|
||||
list.size = 1;
|
||||
this._controls.editor = list;
|
||||
this._collection = this.options.collection || [];
|
||||
if (this.options.loadCollectionURL)
|
||||
this.loadCollection();
|
||||
else
|
||||
this.checkForExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
|
||||
loadCollection: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this.showLoadingText(this.options.loadingCollectionText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
var js = transport.responseText.strip();
|
||||
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
|
||||
throw('Server returned an invalid collection representation.');
|
||||
this._collection = eval(js);
|
||||
this.checkForExternalText();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadCollectionURL, options);
|
||||
},
|
||||
|
||||
showLoadingText: function(text) {
|
||||
this._controls.editor.disabled = true;
|
||||
var tempOption = this._controls.editor.firstChild;
|
||||
if (!tempOption) {
|
||||
tempOption = document.createElement('option');
|
||||
tempOption.value = '';
|
||||
this._controls.editor.appendChild(tempOption);
|
||||
tempOption.selected = true;
|
||||
}
|
||||
tempOption.update((text || '').stripScripts().stripTags());
|
||||
},
|
||||
|
||||
checkForExternalText: function() {
|
||||
this._text = this.getText();
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
else
|
||||
this.buildOptionList();
|
||||
},
|
||||
|
||||
loadExternalText: function() {
|
||||
this.showLoadingText(this.options.loadingText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._text = transport.responseText.strip();
|
||||
this.buildOptionList();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
|
||||
buildOptionList: function() {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
this._collection = this._collection.map(function(entry) {
|
||||
return 2 === entry.length ? entry : [entry, entry].flatten();
|
||||
});
|
||||
var marker = ('value' in this.options) ? this.options.value : this._text;
|
||||
var textFound = this._collection.any(function(entry) {
|
||||
return entry[0] == marker;
|
||||
}.bind(this));
|
||||
this._controls.editor.update('');
|
||||
var option;
|
||||
this._collection.each(function(entry, index) {
|
||||
option = document.createElement('option');
|
||||
option.value = entry[0];
|
||||
option.selected = textFound ? entry[0] == marker : 0 == index;
|
||||
option.appendChild(document.createTextNode(entry[1]));
|
||||
this._controls.editor.appendChild(option);
|
||||
}.bind(this));
|
||||
this._controls.editor.disabled = false;
|
||||
Field.scrollFreeActivate(this._controls.editor);
|
||||
}
|
||||
});
|
||||
|
||||
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
|
||||
//**** This only exists for a while, in order to let ****
|
||||
//**** users adapt to the new API. Read up on the new ****
|
||||
//**** API and convert your code to it ASAP! ****
|
||||
|
||||
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
|
||||
if (!options) return;
|
||||
function fallback(name, expr) {
|
||||
if (name in options || expr === undefined) return;
|
||||
options[name] = expr;
|
||||
};
|
||||
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
|
||||
options.cancelLink == options.cancelButton == false ? false : undefined)));
|
||||
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
|
||||
options.okLink == options.okButton == false ? false : undefined)));
|
||||
fallback('highlightColor', options.highlightcolor);
|
||||
fallback('highlightEndColor', options.highlightendcolor);
|
||||
};
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor, {
|
||||
DefaultOptions: {
|
||||
ajaxOptions: { },
|
||||
autoRows: 3, // Use when multi-line w/ rows == 1
|
||||
cancelControl: 'link', // 'link'|'button'|false
|
||||
cancelText: 'cancel',
|
||||
clickToEditText: 'Click to edit',
|
||||
externalControl: null, // id|elt
|
||||
externalControlOnly: false,
|
||||
fieldPostCreation: 'activate', // 'activate'|'focus'|false
|
||||
formClassName: 'inplaceeditor-form',
|
||||
formId: null, // id|elt
|
||||
highlightColor: '#ffff99',
|
||||
highlightEndColor: '#ffffff',
|
||||
hoverClassName: '',
|
||||
htmlResponse: true,
|
||||
loadingClassName: 'inplaceeditor-loading',
|
||||
loadingText: 'Loading...',
|
||||
okControl: 'button', // 'link'|'button'|false
|
||||
okText: 'ok',
|
||||
paramName: 'value',
|
||||
rows: 1, // If 1 and multi-line, uses autoRows
|
||||
savingClassName: 'inplaceeditor-saving',
|
||||
savingText: 'Saving...',
|
||||
size: 0,
|
||||
stripLoadedTextTags: false,
|
||||
submitOnBlur: false,
|
||||
textAfterControls: '',
|
||||
textBeforeControls: '',
|
||||
textBetweenControls: ''
|
||||
},
|
||||
DefaultCallbacks: {
|
||||
callback: function(form) {
|
||||
return Form.serialize(form);
|
||||
},
|
||||
onComplete: function(transport, element) {
|
||||
// For backward compatibility, this one is bound to the IPE, and passes
|
||||
// the element directly. It was too often customized, so we don't break it.
|
||||
new Effect.Highlight(element, {
|
||||
startcolor: this.options.highlightColor, keepBackgroundImage: true });
|
||||
},
|
||||
onEnterEditMode: null,
|
||||
onEnterHover: function(ipe) {
|
||||
ipe.element.style.backgroundColor = ipe.options.highlightColor;
|
||||
if (ipe._effect)
|
||||
ipe._effect.cancel();
|
||||
},
|
||||
onFailure: function(transport, ipe) {
|
||||
alert('Error communication with the server: ' + transport.responseText.stripTags());
|
||||
},
|
||||
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
|
||||
onLeaveEditMode: null,
|
||||
onLeaveHover: function(ipe) {
|
||||
ipe._effect = new Effect.Highlight(ipe.element, {
|
||||
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
|
||||
restorecolor: ipe._originalBackground, keepBackgroundImage: true
|
||||
});
|
||||
}
|
||||
},
|
||||
Listeners: {
|
||||
click: 'enterEditMode',
|
||||
keydown: 'checkForEscapeOrReturn',
|
||||
mouseover: 'enterHover',
|
||||
mouseout: 'leaveHover'
|
||||
}
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor.DefaultOptions = {
|
||||
loadingCollectionText: 'Loading options...'
|
||||
};
|
||||
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// but waits for delay after last key input
|
||||
// Ideal for live-search fields
|
||||
|
||||
Form.Element.DelayedObserver = Class.create({
|
||||
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));
|
||||
}
|
||||
});
|
974
public/javascripts/dragdrop.js
vendored
Normal file
974
public/javascripts/dragdrop.js
vendored
Normal file
|
@ -0,0 +1,974 @@
|
|||
// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
|
||||
|
||||
// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
if(Object.isUndefined(Effect))
|
||||
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(Object.isArray(containment)) {
|
||||
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 drop, affected = [];
|
||||
|
||||
this.drops.each( function(drop) {
|
||||
if(Droppables.isAffected(point, element, drop))
|
||||
affected.push(drop);
|
||||
});
|
||||
|
||||
if(affected.length>0)
|
||||
drop = Droppables.findDeepestChild(affected);
|
||||
|
||||
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
|
||||
if (drop) {
|
||||
Position.within(drop.element, point[0], point[1]);
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
|
||||
if (drop != this.last_active) 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);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
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({
|
||||
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 = Object.isNumber(element._opacity) ? 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,
|
||||
quiet: 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] || Object.isUndefined(arguments[1].endeffect))
|
||||
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 && Object.isString(options.handle))
|
||||
this.handle = this.element.down('.'+options.handle, 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.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(!Object.isUndefined(Draggable._dragging[this.element]) &&
|
||||
Draggable._dragging[this.element]) return;
|
||||
if(Event.isLeftClick(event)) {
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
if((tag_name = src.tagName.toUpperCase()) && (
|
||||
tag_name=='INPUT' ||
|
||||
tag_name=='SELECT' ||
|
||||
tag_name=='OPTION' ||
|
||||
tag_name=='BUTTON' ||
|
||||
tag_name=='TEXTAREA')) return;
|
||||
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var pos = this.element.cumulativeOffset();
|
||||
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.delta)
|
||||
this.delta = this.currentDelta();
|
||||
|
||||
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);
|
||||
this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
|
||||
if (!this._originallyAbsolute)
|
||||
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);
|
||||
|
||||
if(!this.options.quiet){
|
||||
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 + Position.deltaX;
|
||||
p[1] += this.options.scroll.scrollTop + Position.deltaY;
|
||||
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(Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
finishDrag: function(event, success) {
|
||||
this.dragging = false;
|
||||
|
||||
if(this.options.quiet){
|
||||
Position.prepare();
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
if (!this._originallyAbsolute)
|
||||
Position.relativize(this.element);
|
||||
delete this._originallyAbsolute;
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
var dropped = false;
|
||||
if(success) {
|
||||
dropped = Droppables.fire(event, this.element);
|
||||
if (!dropped) dropped = false;
|
||||
}
|
||||
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
|
||||
Draggables.notify('onEnd', this, event);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && Object.isFunction(revert)) revert = revert(this.element);
|
||||
|
||||
var d = this.currentDelta();
|
||||
if(revert && this.options.reverteffect) {
|
||||
if (dropped == 0 || revert != 'failure')
|
||||
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 = this.element.cumulativeOffset();
|
||||
if(this.options.ghosting) {
|
||||
var r = Position.realOffset(this.element);
|
||||
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(Object.isFunction(this.options.snap)) {
|
||||
p = this.options.snap(p[0],p[1],this);
|
||||
} else {
|
||||
if(Object.isArray(this.options.snap)) {
|
||||
p = p.map( function(v, i) {
|
||||
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
|
||||
} else {
|
||||
p = p.map( function(v) {
|
||||
return (v/this.options.snap).round()*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 };
|
||||
}
|
||||
});
|
||||
|
||||
Draggable._dragging = { };
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var SortableObserver = Class.create({
|
||||
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.toUpperCase() != "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){
|
||||
element = $(element);
|
||||
var s = Sortable.sortables[element.id];
|
||||
|
||||
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,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: this.SERIALIZE_RULE,
|
||||
|
||||
// these take arrays of elements or ids and can be
|
||||
// used for better initialization performance
|
||||
elements: false,
|
||||
handles: false,
|
||||
|
||||
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,
|
||||
quiet: options.quiet,
|
||||
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
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
|
||||
var handle = options.handles ? $(options.handles[i]) :
|
||||
(options.handle ? $(e).select('.' + 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.identify()] = 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) Sortable._marker.hide();
|
||||
},
|
||||
|
||||
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') || Element.extend(document.createElement('DIV'))).
|
||||
hide().addClassName('dropmarker').setStyle({position:'absolute'});
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = dropon.cumulativeOffset();
|
||||
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
|
||||
else
|
||||
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
|
||||
|
||||
Sortable._marker.show();
|
||||
},
|
||||
|
||||
_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: [],
|
||||
position: parent.children.length,
|
||||
container: $(children[i]).down(options.treeTag)
|
||||
};
|
||||
|
||||
/* 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;
|
||||
},
|
||||
|
||||
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: [],
|
||||
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) {
|
||||
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
|
||||
};
|
1123
public/javascripts/effects.js
vendored
Normal file
1123
public/javascripts/effects.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
6001
public/javascripts/prototype.js
vendored
Normal file
6001
public/javascripts/prototype.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
175
public/javascripts/rails.js
Normal file
175
public/javascripts/rails.js
Normal file
|
@ -0,0 +1,175 @@
|
|||
(function() {
|
||||
// Technique from Juriy Zaytsev
|
||||
// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
|
||||
function isEventSupported(eventName) {
|
||||
var el = document.createElement('div');
|
||||
eventName = 'on' + eventName;
|
||||
var isSupported = (eventName in el);
|
||||
if (!isSupported) {
|
||||
el.setAttribute(eventName, 'return;');
|
||||
isSupported = typeof el[eventName] == 'function';
|
||||
}
|
||||
el = null;
|
||||
return isSupported;
|
||||
}
|
||||
|
||||
function isForm(element) {
|
||||
return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
|
||||
}
|
||||
|
||||
function isInput(element) {
|
||||
if (Object.isElement(element)) {
|
||||
var name = element.nodeName.toUpperCase()
|
||||
return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
|
||||
}
|
||||
else return false
|
||||
}
|
||||
|
||||
var submitBubbles = isEventSupported('submit'),
|
||||
changeBubbles = isEventSupported('change')
|
||||
|
||||
if (!submitBubbles || !changeBubbles) {
|
||||
// augment the Event.Handler class to observe custom events when needed
|
||||
Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
|
||||
function(init, element, eventName, selector, callback) {
|
||||
init(element, eventName, selector, callback)
|
||||
// is the handler being attached to an element that doesn't support this event?
|
||||
if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
|
||||
(!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
|
||||
// "submit" => "emulated:submit"
|
||||
this.eventName = 'emulated:' + this.eventName
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (!submitBubbles) {
|
||||
// discover forms on the page by observing focus events which always bubble
|
||||
document.on('focusin', 'form', function(focusEvent, form) {
|
||||
// special handler for the real "submit" event (one-time operation)
|
||||
if (!form.retrieve('emulated:submit')) {
|
||||
form.on('submit', function(submitEvent) {
|
||||
var emulated = form.fire('emulated:submit', submitEvent, true)
|
||||
// if custom event received preventDefault, cancel the real one too
|
||||
if (emulated.returnValue === false) submitEvent.preventDefault()
|
||||
})
|
||||
form.store('emulated:submit', true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!changeBubbles) {
|
||||
// discover form inputs on the page
|
||||
document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
|
||||
// special handler for real "change" events
|
||||
if (!input.retrieve('emulated:change')) {
|
||||
input.on('change', function(changeEvent) {
|
||||
input.fire('emulated:change', changeEvent, true)
|
||||
})
|
||||
input.store('emulated:change', true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleRemote(element) {
|
||||
var method, url, params;
|
||||
|
||||
var event = element.fire("ajax:before");
|
||||
if (event.stopped) return false;
|
||||
|
||||
if (element.tagName.toLowerCase() === 'form') {
|
||||
method = element.readAttribute('method') || 'post';
|
||||
url = element.readAttribute('action');
|
||||
params = element.serialize();
|
||||
} else {
|
||||
method = element.readAttribute('data-method') || 'get';
|
||||
url = element.readAttribute('href');
|
||||
params = {};
|
||||
}
|
||||
|
||||
new Ajax.Request(url, {
|
||||
method: method,
|
||||
parameters: params,
|
||||
evalScripts: true,
|
||||
|
||||
onComplete: function(request) { element.fire("ajax:complete", request); },
|
||||
onSuccess: function(request) { element.fire("ajax:success", request); },
|
||||
onFailure: function(request) { element.fire("ajax:failure", request); }
|
||||
});
|
||||
|
||||
element.fire("ajax:after");
|
||||
}
|
||||
|
||||
function handleMethod(element) {
|
||||
var method = element.readAttribute('data-method'),
|
||||
url = element.readAttribute('href'),
|
||||
csrf_param = $$('meta[name=csrf-param]')[0],
|
||||
csrf_token = $$('meta[name=csrf-token]')[0];
|
||||
|
||||
var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
|
||||
element.parentNode.insert(form);
|
||||
|
||||
if (method !== 'post') {
|
||||
var field = new Element('input', { type: 'hidden', name: '_method', value: method });
|
||||
form.insert(field);
|
||||
}
|
||||
|
||||
if (csrf_param) {
|
||||
var param = csrf_param.readAttribute('content'),
|
||||
token = csrf_token.readAttribute('content'),
|
||||
field = new Element('input', { type: 'hidden', name: param, value: token });
|
||||
form.insert(field);
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
|
||||
|
||||
document.on("click", "*[data-confirm]", function(event, element) {
|
||||
var message = element.readAttribute('data-confirm');
|
||||
if (!confirm(message)) event.stop();
|
||||
});
|
||||
|
||||
document.on("click", "a[data-remote]", function(event, element) {
|
||||
if (event.stopped) return;
|
||||
handleRemote(element);
|
||||
event.stop();
|
||||
});
|
||||
|
||||
document.on("click", "a[data-method]", function(event, element) {
|
||||
if (event.stopped) return;
|
||||
handleMethod(element);
|
||||
event.stop();
|
||||
});
|
||||
|
||||
document.on("submit", function(event) {
|
||||
var element = event.findElement(),
|
||||
message = element.readAttribute('data-confirm');
|
||||
if (message && !confirm(message)) {
|
||||
event.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputs = element.select("input[type=submit][data-disable-with]");
|
||||
inputs.each(function(input) {
|
||||
input.disabled = true;
|
||||
input.writeAttribute('data-original-value', input.value);
|
||||
input.value = input.readAttribute('data-disable-with');
|
||||
});
|
||||
|
||||
var element = event.findElement("form[data-remote]");
|
||||
if (element) {
|
||||
handleRemote(element);
|
||||
event.stop();
|
||||
}
|
||||
});
|
||||
|
||||
document.on("ajax:after", "form", function(event, element) {
|
||||
var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
|
||||
inputs.each(function(input) {
|
||||
input.value = input.readAttribute('data-original-value');
|
||||
input.removeAttribute('data-original-value');
|
||||
input.disabled = false;
|
||||
});
|
||||
});
|
||||
})();
|
0
public/stylesheets/.gitkeep
Normal file
0
public/stylesheets/.gitkeep
Normal file
|
@ -1,4 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
|
||||
require 'commands/about'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
require 'commands/console'
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
begin
|
||||
load File.expand_path(File.dirname(__FILE__) + "/../vendor/plugins/cucumber/bin/cucumber")
|
||||
rescue LoadError => e
|
||||
raise unless e.to_s =~ /cucumber/
|
||||
require "rubygems"
|
||||
load File.join(Gem.bindir, "cucumber")
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
require 'commands/dbconsole'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
require 'commands/destroy'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
require 'commands/generate'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../../config/boot'
|
||||
require 'commands/performance/benchmarker'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../../config/boot'
|
||||
require 'commands/performance/profiler'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
require 'commands/plugin'
|
6
script/rails
Executable file
6
script/rails
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env ruby
|
||||
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
||||
|
||||
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
||||
require File.expand_path('../../config/boot', __FILE__)
|
||||
require 'rails/commands'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
require 'commands/runner'
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
require File.dirname(__FILE__) + '/../config/boot'
|
||||
require 'commands/server'
|
|
@ -1,8 +1,8 @@
|
|||
require 'test_helper'
|
||||
require 'performance_test_help'
|
||||
require 'rails/performance_test_help'
|
||||
|
||||
# Profiling results for each test method are written to tmp/performance.
|
||||
class BrowsingTest < ActionController::PerformanceTest
|
||||
class BrowsingTest < ActionDispatch::PerformanceTest
|
||||
def test_homepage
|
||||
get '/'
|
||||
end
|
||||
|
|
|
@ -1,33 +1,8 @@
|
|||
ENV["RAILS_ENV"] = "test"
|
||||
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
||||
require 'test_help'
|
||||
require File.expand_path('../../config/environment', __FILE__)
|
||||
require 'rails/test_help'
|
||||
|
||||
class ActiveSupport::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.
|
||||
#
|
||||
# The only drawback to using transactional fixtures is when you actually
|
||||
# need to test transactions. Since your test is bracketed by a transaction,
|
||||
# any transactions started in your code will be automatically rolled back.
|
||||
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
|
||||
|
||||
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
|
||||
#
|
||||
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
||||
|
|
0
vendor/plugins/.gitkeep
vendored
Normal file
0
vendor/plugins/.gitkeep
vendored
Normal file
20
vendor/plugins/dynamic_form/MIT-LICENSE
vendored
Normal file
20
vendor/plugins/dynamic_form/MIT-LICENSE
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2010 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.
|
13
vendor/plugins/dynamic_form/README
vendored
Normal file
13
vendor/plugins/dynamic_form/README
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
DynamicForm
|
||||
===========
|
||||
|
||||
DynamicForm holds a few helpers method to help you deal with your models, they are:
|
||||
|
||||
* input(record, method, options = {})
|
||||
* form(record, options = {})
|
||||
* error_message_on(object, method, options={})
|
||||
* error_messages_for(record, options={})
|
||||
|
||||
It also adds f.error_messages and f.error_messages_on to your form builders.
|
||||
|
||||
Copyright (c) 2010 David Heinemeier Hansson, released under the MIT license
|
10
vendor/plugins/dynamic_form/Rakefile
vendored
Normal file
10
vendor/plugins/dynamic_form/Rakefile
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
require 'rake/testtask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the active_model_helper plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'test'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
end
|
5
vendor/plugins/dynamic_form/init.rb
vendored
Normal file
5
vendor/plugins/dynamic_form/init.rb
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
require 'action_view/helpers/dynamic_form'
|
||||
|
||||
class ActionView::Base
|
||||
include DynamicForm
|
||||
end
|
300
vendor/plugins/dynamic_form/lib/action_view/helpers/dynamic_form.rb
vendored
Normal file
300
vendor/plugins/dynamic_form/lib/action_view/helpers/dynamic_form.rb
vendored
Normal file
|
@ -0,0 +1,300 @@
|
|||
require 'action_view/helpers'
|
||||
require 'active_support/i18n'
|
||||
require 'active_support/core_ext/enumerable'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
# The Active Record Helper makes it easier to create forms for records kept in instance variables. The most far-reaching is the +form+
|
||||
# method that creates a complete form for all the basic content types of the record (not associations or aggregations, though). This
|
||||
# is a great way of making the record quickly available for editing, but likely to prove lackluster for a complicated real-world form.
|
||||
# In that case, it's better to use the +input+ method and the specialized +form+ methods in link:classes/ActionView/Helpers/FormHelper.html
|
||||
module DynamicForm
|
||||
# Returns a default input tag for the type of object returned by the method. For example, if <tt>@post</tt>
|
||||
# has an attribute +title+ mapped to a +VARCHAR+ column that holds "Hello World":
|
||||
#
|
||||
# input("post", "title")
|
||||
# # => <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
|
||||
def input(record_name, method, options = {})
|
||||
InstanceTag.new(record_name, method, self).to_tag(options)
|
||||
end
|
||||
|
||||
# Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt>
|
||||
# has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
|
||||
#
|
||||
# form("post")
|
||||
#
|
||||
# would yield a form like the following (modulus formatting):
|
||||
#
|
||||
# <form action='/posts/create' method='post'>
|
||||
# <p>
|
||||
# <label for="post_title">Title</label><br />
|
||||
# <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
|
||||
# </p>
|
||||
# <p>
|
||||
# <label for="post_body">Body</label><br />
|
||||
# <textarea cols="40" id="post_body" name="post[body]" rows="20"></textarea>
|
||||
# </p>
|
||||
# <input name="commit" type="submit" value="Create" />
|
||||
# </form>
|
||||
#
|
||||
# It's possible to specialize the form builder by using a different action name and by supplying another
|
||||
# block renderer. For example, if <tt>@entry</tt> has an attribute +message+ of type +VARCHAR+ then
|
||||
#
|
||||
# form("entry",
|
||||
# :action => "sign",
|
||||
# :input_block => Proc.new { |record, column|
|
||||
# "#{column.human_name}: #{input(record, column.name)}<br />"
|
||||
# })
|
||||
#
|
||||
# would yield a form like the following (modulus formatting):
|
||||
#
|
||||
# <form action="/entries/sign" method="post">
|
||||
# Message:
|
||||
# <input id="entry_message" name="entry[message]" size="30" type="text" /><br />
|
||||
# <input name="commit" type="submit" value="Sign" />
|
||||
# </form>
|
||||
#
|
||||
# It's also possible to add additional content to the form by giving it a block, such as:
|
||||
#
|
||||
# form("entry", :action => "sign") do |form|
|
||||
# form << content_tag("b", "Department")
|
||||
# form << collection_select("department", "id", @departments, "id", "name")
|
||||
# end
|
||||
#
|
||||
# The following options are available:
|
||||
#
|
||||
# * <tt>:action</tt> - The action used when submitting the form (default: +create+ if a new record, otherwise +update+).
|
||||
# * <tt>:input_block</tt> - Specialize the output using a different block, see above.
|
||||
# * <tt>:method</tt> - The method used when submitting the form (default: +post+).
|
||||
# * <tt>:multipart</tt> - Whether to change the enctype of the form to "multipart/form-data", used when uploading a file (default: +false+).
|
||||
# * <tt>:submit_value</tt> - The text of the submit button (default: "Create" if a new record, otherwise "Update").
|
||||
def form(record_name, options = {})
|
||||
record = instance_variable_get("@#{record_name}")
|
||||
record = convert_to_model(record)
|
||||
|
||||
options = options.symbolize_keys
|
||||
options[:action] ||= record.persisted? ? "update" : "create"
|
||||
action = url_for(:action => options[:action], :id => record)
|
||||
|
||||
submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize
|
||||
|
||||
contents = form_tag({:action => action}, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil)
|
||||
contents.safe_concat hidden_field(record_name, :id) if record.persisted?
|
||||
contents.safe_concat all_input_tags(record, record_name, options)
|
||||
yield contents if block_given?
|
||||
contents.safe_concat submit_tag(submit_value)
|
||||
contents.safe_concat('</form>')
|
||||
end
|
||||
|
||||
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
|
||||
# This error message is wrapped in a <tt>DIV</tt> tag by default or with <tt>:html_tag</tt> if specified,
|
||||
# which can be extended to include a <tt>:prepend_text</tt> and/or <tt>:append_text</tt> (to properly explain
|
||||
# the error), and a <tt>:css_class</tt> to style it accordingly. +object+ should either be the name of an
|
||||
# instance variable or the actual object. The method can be passed in either as a string or a symbol.
|
||||
# As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
|
||||
#
|
||||
# <%= error_message_on "post", "title" %>
|
||||
# # => <div class="formError">can't be empty</div>
|
||||
#
|
||||
# <%= error_message_on @post, :title %>
|
||||
# # => <div class="formError">can't be empty</div>
|
||||
#
|
||||
# <%= error_message_on "post", "title",
|
||||
# :prepend_text => "Title simply ",
|
||||
# :append_text => " (or it won't work).",
|
||||
# :html_tag => "span",
|
||||
# :css_class => "inputError" %>
|
||||
# # => <span class="inputError">Title simply can't be empty (or it won't work).</span>
|
||||
def error_message_on(object, method, *args)
|
||||
options = args.extract_options!
|
||||
unless args.empty?
|
||||
ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
|
||||
'prepend_text, append_text, html_tag, and css_class arguments', caller)
|
||||
|
||||
options[:prepend_text] = args[0] || ''
|
||||
options[:append_text] = args[1] || ''
|
||||
options[:html_tag] = args[2] || 'div'
|
||||
options[:css_class] = args[3] || 'formError'
|
||||
end
|
||||
options.reverse_merge!(:prepend_text => '', :append_text => '', :html_tag => 'div', :css_class => 'formError')
|
||||
|
||||
object = convert_to_model(object)
|
||||
|
||||
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
|
||||
(errors = obj.errors[method]).presence
|
||||
content_tag(options[:html_tag],
|
||||
(options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]),
|
||||
:class => options[:css_class]
|
||||
)
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a string with a <tt>DIV</tt> containing all of the error messages for the objects located as instance variables by the names
|
||||
# given. If more than one object is specified, the errors for the objects are displayed in the order that the object names are
|
||||
# provided.
|
||||
#
|
||||
# This <tt>DIV</tt> can be tailored by the following options:
|
||||
#
|
||||
# * <tt>:header_tag</tt> - Used for the header of the error div (default: "h2").
|
||||
# * <tt>:id</tt> - The id of the error div (default: "errorExplanation").
|
||||
# * <tt>:class</tt> - The class of the error div (default: "errorExplanation").
|
||||
# * <tt>:object</tt> - The object (or array of objects) for which to display errors,
|
||||
# if you need to escape the instance variable convention.
|
||||
# * <tt>:object_name</tt> - The object name to use in the header, or any text that you prefer.
|
||||
# If <tt>:object_name</tt> is not set, the name of the first object will be used.
|
||||
# * <tt>:header_message</tt> - The message in the header of the error div. Pass +nil+
|
||||
# or an empty string to avoid the header message altogether. (Default: "X errors
|
||||
# prohibited this object from being saved").
|
||||
# * <tt>:message</tt> - The explanation message after the header message and before
|
||||
# the error list. Pass +nil+ or an empty string to avoid the explanation message
|
||||
# altogether. (Default: "There were problems with the following fields:").
|
||||
#
|
||||
# To specify the display for one object, you simply provide its name as a parameter.
|
||||
# For example, for the <tt>@user</tt> model:
|
||||
#
|
||||
# error_messages_for 'user'
|
||||
#
|
||||
# You can also supply an object:
|
||||
#
|
||||
# error_messages_for @user
|
||||
#
|
||||
# This will use the last part of the model name in the presentation. For instance, if
|
||||
# this is a MyKlass::User object, this will use "user" as the name in the String. This
|
||||
# is taken from MyKlass::User.model_name.human, which can be overridden.
|
||||
#
|
||||
# To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which
|
||||
# will be the name used in the header message:
|
||||
#
|
||||
# error_messages_for 'user_common', 'user', :object_name => 'user'
|
||||
#
|
||||
# You can also use a number of objects, which will have the same naming semantics
|
||||
# as a single object.
|
||||
#
|
||||
# error_messages_for @user, @post
|
||||
#
|
||||
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual
|
||||
# object (or array of objects to use):
|
||||
#
|
||||
# error_messages_for 'user', :object => @question.user
|
||||
#
|
||||
# NOTE: This is a pre-packaged presentation of the errors with embedded strings and a certain HTML structure. If what
|
||||
# you need is significantly different from the default presentation, it makes plenty of sense to access the <tt>object.errors</tt>
|
||||
# instance yourself and set it up. View the source of this method to see how easy it is.
|
||||
def error_messages_for(*params)
|
||||
options = params.extract_options!.symbolize_keys
|
||||
|
||||
objects = Array.wrap(options.delete(:object) || params).map do |object|
|
||||
object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model)
|
||||
object = convert_to_model(object)
|
||||
|
||||
if object.class.respond_to?(:model_name)
|
||||
options[:object_name] ||= object.class.model_name.human.downcase
|
||||
end
|
||||
|
||||
object
|
||||
end
|
||||
|
||||
objects.compact!
|
||||
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
||||
|
||||
unless count.zero?
|
||||
html = {}
|
||||
[:id, :class].each do |key|
|
||||
if options.include?(key)
|
||||
value = options[key]
|
||||
html[key] = value unless value.blank?
|
||||
else
|
||||
html[key] = 'errorExplanation'
|
||||
end
|
||||
end
|
||||
options[:object_name] ||= params.first
|
||||
|
||||
I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale|
|
||||
header_message = if options.include?(:header_message)
|
||||
options[:header_message]
|
||||
else
|
||||
locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ')
|
||||
end
|
||||
|
||||
message = options.include?(:message) ? options[:message] : locale.t(:body)
|
||||
|
||||
error_messages = objects.sum do |object|
|
||||
object.errors.full_messages.map do |msg|
|
||||
content_tag(:li, msg)
|
||||
end
|
||||
end.join.html_safe
|
||||
|
||||
contents = ''
|
||||
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
|
||||
contents << content_tag(:p, message) unless message.blank?
|
||||
contents << content_tag(:ul, error_messages)
|
||||
|
||||
content_tag(:div, contents.html_safe, html)
|
||||
end
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def all_input_tags(record, record_name, options)
|
||||
input_block = options[:input_block] || default_input_block
|
||||
record.class.content_columns.collect{ |column| input_block.call(record_name, column) }.join("\n")
|
||||
end
|
||||
|
||||
def default_input_block
|
||||
Proc.new { |record, column| %(<p><label for="#{record}_#{column.name}">#{column.human_name}</label><br />#{input(record, column.name)}</p>) }
|
||||
end
|
||||
|
||||
module InstanceTagMethods
|
||||
def to_tag(options = {})
|
||||
case column_type
|
||||
when :string
|
||||
field_type = @method_name.include?("password") ? "password" : "text"
|
||||
to_input_field_tag(field_type, options)
|
||||
when :text
|
||||
to_text_area_tag(options)
|
||||
when :integer, :float, :decimal
|
||||
to_input_field_tag("text", options)
|
||||
when :date
|
||||
to_date_select_tag(options)
|
||||
when :datetime, :timestamp
|
||||
to_datetime_select_tag(options)
|
||||
when :time
|
||||
to_time_select_tag(options)
|
||||
when :boolean
|
||||
to_boolean_select_tag(options)
|
||||
end
|
||||
end
|
||||
|
||||
def column_type
|
||||
object.send(:column_for_attribute, @method_name).type
|
||||
end
|
||||
end
|
||||
|
||||
module FormBuilderMethods
|
||||
def error_message_on(method, *args)
|
||||
@template.error_message_on(@object || @object_name, method, *args)
|
||||
end
|
||||
|
||||
def error_messages(options = {})
|
||||
@template.error_messages_for(@object_name, objectify_options(options))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class InstanceTag
|
||||
include DynamicForm::InstanceTagMethods
|
||||
end
|
||||
|
||||
class FormBuilder
|
||||
include DynamicForm::FormBuilderMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
I18n.load_path << File.expand_path("../../locale/en.yml", __FILE__)
|
8
vendor/plugins/dynamic_form/lib/action_view/locale/en.yml
vendored
Normal file
8
vendor/plugins/dynamic_form/lib/action_view/locale/en.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
en:
|
||||
errors:
|
||||
template:
|
||||
header:
|
||||
one: "1 error prohibited this %{model} from being saved"
|
||||
other: "%{count} errors prohibited this %{model} from being saved"
|
||||
# The variable :count is also available
|
||||
body: "There were problems with the following fields:"
|
42
vendor/plugins/dynamic_form/test/dynamic_form_i18n_test.rb
vendored
Normal file
42
vendor/plugins/dynamic_form/test/dynamic_form_i18n_test.rb
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
require 'test_helper'
|
||||
|
||||
class DynamicFormI18nTest < Test::Unit::TestCase
|
||||
include ActionView::Context
|
||||
include ActionView::Helpers::DynamicForm
|
||||
|
||||
attr_reader :request
|
||||
|
||||
def setup
|
||||
@object = stub :errors => stub(:count => 1, :full_messages => ['full_messages'])
|
||||
@object.stubs :to_model => @object
|
||||
@object.stubs :class => stub(:model_name => stub(:human => ""))
|
||||
|
||||
@object_name = 'book_seller'
|
||||
@object_name_without_underscore = 'book seller'
|
||||
|
||||
stubs(:content_tag).returns 'content_tag'
|
||||
|
||||
I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved"
|
||||
I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message
|
||||
I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').never
|
||||
error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en')
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_no_header_option_it_translates_header_message
|
||||
I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns 'header message'
|
||||
error_messages_for(:object => @object, :locale => 'en')
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_a_message_option_it_does_not_translate_message
|
||||
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).never
|
||||
error_messages_for(:object => @object, :message => 'message', :locale => 'en')
|
||||
end
|
||||
|
||||
def test_error_messages_for_given_no_message_option_it_translates_message
|
||||
I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:'
|
||||
error_messages_for(:object => @object, :locale => 'en')
|
||||
end
|
||||
end
|
370
vendor/plugins/dynamic_form/test/dynamic_form_test.rb
vendored
Normal file
370
vendor/plugins/dynamic_form/test/dynamic_form_test.rb
vendored
Normal file
File diff suppressed because one or more lines are too long
9
vendor/plugins/dynamic_form/test/test_helper.rb
vendored
Normal file
9
vendor/plugins/dynamic_form/test/test_helper.rb
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'active_support'
|
||||
require 'active_support/core_ext'
|
||||
require 'action_view'
|
||||
require 'action_controller'
|
||||
require 'action_controller/test_case'
|
||||
require 'active_model'
|
||||
require 'action_view/helpers/dynamic_form'
|
20
vendor/plugins/rails_upgrade/MIT-LICENSE
vendored
Normal file
20
vendor/plugins/rails_upgrade/MIT-LICENSE
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2010 Jeremy McAnally
|
||||
|
||||
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.
|
20
vendor/plugins/rails_upgrade/README
vendored
Normal file
20
vendor/plugins/rails_upgrade/README
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
= rails-upgrade
|
||||
|
||||
A simple battery of scripts for upgrading Rails app/checking them for required updates. This application should work on Rails 2.x and 3.0, with a focus on upgrading to 3.0.
|
||||
|
||||
== Usage
|
||||
|
||||
# Check your app for required upgrades
|
||||
rake rails:upgrade:check
|
||||
|
||||
# Backup your likely modified files that might be overwritten by the generator
|
||||
rake rails:upgrade:backup
|
||||
|
||||
# Generate a new route file
|
||||
rake rails:upgrade:routes
|
||||
|
||||
# Generate a Gemfile from your config.gem directives
|
||||
rake rails:upgrade:gems
|
||||
|
||||
# Generate code for a new config/application.rb from your environment.rb
|
||||
rake rails:upgrade:configuration
|
22
vendor/plugins/rails_upgrade/Rakefile
vendored
Normal file
22
vendor/plugins/rails_upgrade/Rakefile
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << 'lib'
|
||||
t.libs << 'test'
|
||||
t.test_files = FileList['test/*_test.rb']
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the rails_upgrade plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'Rails-upgrade'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
2
vendor/plugins/rails_upgrade/init.rb
vendored
Normal file
2
vendor/plugins/rails_upgrade/init.rb
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Get long stack traces for easier debugging; you'll thank me later.
|
||||
Rails.backtrace_cleaner.remove_silencers!
|
38
vendor/plugins/rails_upgrade/install.rb
vendored
Normal file
38
vendor/plugins/rails_upgrade/install.rb
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
puts "Thanks for installing the Rails upgrade plugin. This is a set of generators and analysis tools to help you upgrade your application to Rails 3. It consists of three tasks...
|
||||
|
||||
To get a feel for what you'll need to change to get your app running, run the application analysis:
|
||||
|
||||
rake rails:upgrade:check
|
||||
|
||||
This should give you an idea of the manual changes that need to be done, but you'll probably want to upgrade some of those automatically. The fastest way to do this is to run 'rails .', which will simply generate a new app on top of your existing code. But this generation also has the effect of replacing some existing files, some of which you might not want to replace. To back those up, first run:
|
||||
|
||||
rake rails:upgrade:backup
|
||||
|
||||
That will backup files you've probably edited that will be replaced in the upgrade; if you finish the upgrade and find that you don't need the old copies, just delete them. Otherwise, copy their contents back into the new files or run one of the following upgraders...
|
||||
|
||||
Routes upgrader
|
||||
===============
|
||||
|
||||
To generate a new routes file from your existing routes file, simply run the following Rake task:
|
||||
|
||||
rake rails:upgrade:routes
|
||||
|
||||
This will output a new routes file that you can copy and paste or pipe into a new, Rails 3 compatible config/routes.rb.
|
||||
|
||||
Gemfile generator
|
||||
=================
|
||||
|
||||
Creating a new Gemfile is as simple as running:
|
||||
|
||||
rake rails:upgrade:gems
|
||||
|
||||
This task will extract your config.gem calls and generate code you can put into a bundler compatible Gemfile.
|
||||
|
||||
Configuration generator
|
||||
=======================
|
||||
|
||||
Much of the configuration information that lived in environment.rb now belongs in a new file named config/application.rb; use the following task to generate code you can put into config/application.rb from your existing config/environment.rb:
|
||||
|
||||
rake rails:upgrade:configuration
|
||||
|
||||
"
|
472
vendor/plugins/rails_upgrade/lib/application_checker.rb
vendored
Normal file
472
vendor/plugins/rails_upgrade/lib/application_checker.rb
vendored
Normal file
|
@ -0,0 +1,472 @@
|
|||
require 'open3'
|
||||
|
||||
module Rails
|
||||
module Upgrading
|
||||
class ApplicationChecker
|
||||
def initialize
|
||||
@issues = []
|
||||
|
||||
raise NotInRailsAppError unless in_rails_app?
|
||||
end
|
||||
|
||||
def in_rails_app?
|
||||
File.exist?("config/environment.rb")
|
||||
end
|
||||
|
||||
# Run all the check methods
|
||||
def run
|
||||
# Ruby 1.8 returns method names as strings whereas 1.9 uses symbols
|
||||
the_methods = (self.public_methods - Object.methods) - [:run, :initialize, "run", "initialize"]
|
||||
|
||||
the_methods.each {|m| send m }
|
||||
end
|
||||
|
||||
# Check for deprecated ActiveRecord calls
|
||||
def check_ar_methods
|
||||
files = []
|
||||
["find(:all", "find(:first", "find.*:conditions =>", ":joins =>"].each do |v|
|
||||
lines = grep_for(v, "app/")
|
||||
files += extract_filenames(lines) || []
|
||||
end
|
||||
|
||||
unless files.empty?
|
||||
alert(
|
||||
"Soon-to-be-deprecated ActiveRecord calls",
|
||||
"Methods such as find(:all), find(:first), finds with conditions, and the :joins option will soon be deprecated.",
|
||||
"http://m.onkey.org/2010/1/22/active-record-query-interface",
|
||||
files
|
||||
)
|
||||
end
|
||||
|
||||
lines = grep_for("named_scope", "app/models/")
|
||||
files = extract_filenames(lines)
|
||||
|
||||
if files
|
||||
alert(
|
||||
"named_scope is now just scope",
|
||||
"The named_scope method has been renamed to just scope.",
|
||||
"http://github.com/rails/rails/commit/d60bb0a9e4be2ac0a9de9a69041a4ddc2e0cc914",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def check_validation_on_methods
|
||||
files = []
|
||||
|
||||
["validate_on_create", "validate_on_update"].each do |v|
|
||||
lines = grep_for(v, "app/models/")
|
||||
files += extract_filenames(lines) || []
|
||||
end
|
||||
|
||||
unless files.empty?
|
||||
alert(
|
||||
"Updated syntax for validate_on_* methods",
|
||||
"Validate-on-callback methods (validate_on_create/validate_on_destroy) have been changed to validate :x, :on => :create",
|
||||
"https://rails.lighthouseapp.com/projects/8994/tickets/3880-validate_on_create-and-validate_on_update-no-longer-seem-to-exist",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def check_before_validation_on_methods
|
||||
files = []
|
||||
|
||||
%w(before_validation_on_create before_validation_on_update).each do |v|
|
||||
lines = grep_for(v, "app/models/")
|
||||
files += extract_filenames(lines) || []
|
||||
end
|
||||
|
||||
unless files.empty?
|
||||
alert(
|
||||
"Updated syntax for before_validation_on_* methods",
|
||||
"before_validation_on_* methods have been changed to before_validation(:on => :create/:update) { ... }",
|
||||
"https://rails.lighthouseapp.com/projects/8994/tickets/4699-before_validation_on_create-and-before_validation_on_update-doesnt-exist",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Check for deprecated router syntax
|
||||
def check_routes
|
||||
lines = ["map\\.", "ActionController::Routing::Routes", "\\.resources"].map do |v|
|
||||
grep_for(v, "config/routes.rb").empty? ? nil : true
|
||||
end.compact
|
||||
|
||||
unless lines.empty?
|
||||
alert(
|
||||
"Old router API",
|
||||
"The router API has totally changed.",
|
||||
"http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up/",
|
||||
"config/routes.rb"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Check for deprecated test_help require
|
||||
def check_test_help
|
||||
files = []
|
||||
|
||||
# Hate to duplicate code, but we have to double quote this one...
|
||||
lines = grep_for("\'test_help\'", "test/", true)
|
||||
files += extract_filenames(lines) || []
|
||||
|
||||
lines = grep_for("\"test_help\"", "test/")
|
||||
files += extract_filenames(lines) || []
|
||||
|
||||
files.uniq!
|
||||
|
||||
unless files.empty?
|
||||
alert(
|
||||
"Deprecated test_help path",
|
||||
"You now must require 'rails/test_help' not just 'test_help'.",
|
||||
"http://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Check for old (pre-application.rb) environment.rb file
|
||||
def check_environment
|
||||
unless File.exist?("config/application.rb")
|
||||
alert(
|
||||
"New file needed: config/application.rb",
|
||||
"You need to add a config/application.rb.",
|
||||
"http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade",
|
||||
"config/application.rb"
|
||||
)
|
||||
end
|
||||
|
||||
lines = grep_for("config.", "config/environment.rb")
|
||||
|
||||
unless lines.empty?
|
||||
alert(
|
||||
"Old environment.rb",
|
||||
"environment.rb doesn't do what it used to; you'll need to move some of that into application.rb.",
|
||||
"http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade",
|
||||
"config/environment.rb"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Check for deprecated constants
|
||||
def check_deprecated_constants
|
||||
files = []
|
||||
["RAILS_ENV", "RAILS_ROOT", "RAILS_DEFAULT_LOGGER"].each do |v|
|
||||
lines = grep_for(v, "app/")
|
||||
files += extract_filenames(lines) || []
|
||||
|
||||
lines = grep_for(v, "lib/")
|
||||
files += extract_filenames(lines) || []
|
||||
end
|
||||
|
||||
unless files.empty?
|
||||
alert(
|
||||
"Deprecated constant(s)",
|
||||
"Constants like RAILS_ENV, RAILS_ROOT, and RAILS_DEFAULT_LOGGER are now deprecated.",
|
||||
"http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/",
|
||||
files.uniq
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Check for old-style config.gem calls
|
||||
def check_gems
|
||||
lines = grep_for("config.gem ", "config/*.rb")
|
||||
files = extract_filenames(lines)
|
||||
|
||||
if files
|
||||
alert(
|
||||
"Old gem bundling (config.gems)",
|
||||
"The old way of bundling is gone now. You need a Gemfile for bundler.",
|
||||
"http://omgbloglol.com/post/353978923/the-path-to-rails-3-approaching-the-upgrade",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks for old mailer syntax in both mailer classes and those
|
||||
# classes utilizing the mailers
|
||||
def check_mailers
|
||||
lines = grep_for("deliver_", "app/models/ #{base_path}app/controllers/ #{base_path}app/observers/")
|
||||
files = extract_filenames(lines)
|
||||
|
||||
if files
|
||||
alert(
|
||||
"Deprecated ActionMailer API",
|
||||
"You're using the old ActionMailer API to send e-mails in a controller, model, or observer.",
|
||||
"http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3",
|
||||
files
|
||||
)
|
||||
end
|
||||
|
||||
files = []
|
||||
["recipients ", "attachment(?!s) ", "(?<!:)subject ", "(?<!:)from "].each do |v|
|
||||
lines = grep_for_with_perl_regex(v, "app/models/")
|
||||
files += extract_filenames(lines) || []
|
||||
end
|
||||
|
||||
unless files.empty?
|
||||
alert(
|
||||
"Old ActionMailer class API",
|
||||
"You're using the old API in a mailer class.",
|
||||
"http://lindsaar.net/2010/1/26/new-actionmailer-api-in-rails-3",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks for old-style generators
|
||||
def check_generators
|
||||
generators = Dir.glob(base_path + "vendor/plugins/**/generators/**/")
|
||||
|
||||
unless generators.empty?
|
||||
files = generators.reject do |g|
|
||||
grep_for("def manifest", g).empty?
|
||||
end.compact
|
||||
|
||||
if !files.empty?
|
||||
alert(
|
||||
"Old Rails generator API",
|
||||
"A plugin in the app is using the old generator API (a new one may be available at http://github.com/trydionel/rails3-generators).",
|
||||
"http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators/",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Checks a list of known broken plugins and gems
|
||||
def check_plugins
|
||||
# This list is off the wiki; will need to be updated often, esp. since RSpec is working on it
|
||||
bad_plugins = ["rspec", "rspec-rails", "hoptoad", "authlogic", "nifty-generators",
|
||||
"restful_authentication", "searchlogic", "cucumber", "cucumber-rails", "devise",
|
||||
"inherited_resources"]
|
||||
|
||||
bad_plugins = bad_plugins.map do |p|
|
||||
p if File.exist?("#{base_path}vendor/plugins/#{p}") || !Dir.glob("#{base_path}vendor/gems/#{p}-*").empty?
|
||||
end.compact
|
||||
|
||||
unless bad_plugins.empty?
|
||||
alert(
|
||||
"Known broken plugins",
|
||||
"At least one plugin in your app is broken (according to the wiki). Most of project maintainers are rapidly working towards compatability, but do be aware you may encounter issues.",
|
||||
"http://wiki.rubyonrails.org/rails/version3/plugins_and_gems",
|
||||
bad_plugins
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks for old-style ERb helpers
|
||||
def check_old_helpers
|
||||
|
||||
lines = grep_for("<% .*content_tag.* do.*%>", "app/views/**/*")
|
||||
lines += grep_for("<% .*javascript_tag.* do.*%>", "app/views/**/*")
|
||||
lines += grep_for("<% .*form_for.* do.*%>", "app/views/**/*")
|
||||
lines += grep_for("<% .*form_tag.* do.*%>", "app/views/**/*")
|
||||
lines += grep_for("<% .*fields_for.* do.*%>", "app/views/**/*")
|
||||
lines += grep_for("<% .*field_set_tag.* do.*%>", "app/views/**/*")
|
||||
|
||||
files = extract_filenames(lines)
|
||||
|
||||
if files
|
||||
alert(
|
||||
"Deprecated ERb helper calls",
|
||||
"Block helpers that use concat (e.g., form_for) should use <%= instead of <%. The current form will continue to work for now, but you will get deprecation warnings since this form will go away in the future.",
|
||||
"http://weblog.rubyonrails.org/",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks for old-style AJAX helpers
|
||||
def check_old_ajax_helpers
|
||||
files = []
|
||||
['link_to_remote','form_remote_tag','remote_form_for'].each do |type|
|
||||
lines = grep_for(type, "app/views/**/*")
|
||||
inner_files = extract_filenames(lines)
|
||||
files += inner_files unless inner_files.nil?
|
||||
end
|
||||
|
||||
unless files.empty?
|
||||
alert(
|
||||
"Deprecated AJAX helper calls",
|
||||
"AJAX javascript helpers have been switched to be unobtrusive and use :remote => true instead of having a seperate function to handle remote requests.",
|
||||
"http://www.themodestrubyist.com/2010/02/24/rails-3-ujs-and-csrf-meta-tags/",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks for old cookie secret settings
|
||||
def check_old_cookie_secret
|
||||
lines = grep_for("ActionController::Base.cookie_verifier_secret = ", "config/**/*")
|
||||
files = extract_filenames(lines)
|
||||
|
||||
if files
|
||||
alert(
|
||||
"Deprecated cookie secret setting",
|
||||
"Previously, cookie secret was set directly on ActionController::Base; it's now config.secret_token.",
|
||||
"http://lindsaar.net/2010/4/7/rails_3_session_secret_and_session_store",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def check_old_session_secret
|
||||
lines = grep_for("ActionController::Base.session = {", "config/**/*")
|
||||
files = extract_filenames(lines)
|
||||
|
||||
if files
|
||||
alert(
|
||||
"Deprecated session secret setting",
|
||||
"Previously, session secret was set directly on ActionController::Base; it's now config.secret_token.",
|
||||
"http://lindsaar.net/2010/4/7/rails_3_session_secret_and_session_store",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks for old session settings
|
||||
def check_old_session_setting
|
||||
lines = grep_for("ActionController::Base.session_store", "config/**/*")
|
||||
files = extract_filenames(lines)
|
||||
|
||||
if files
|
||||
alert(
|
||||
"Old session store setting",
|
||||
"Previously, session store was set directly on ActionController::Base; it's now config.session_store :whatever.",
|
||||
"http://lindsaar.net/2010/4/7/rails_3_session_secret_and_session_store",
|
||||
files
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def grep_for_with_perl_regex(text, where = "./", double_quote = false)
|
||||
grep_for(text, where, double_quote, true)
|
||||
end
|
||||
|
||||
# Find a string in a set of files; calls +find_with_grep+ and +find_with_rak+
|
||||
# depending on platform.
|
||||
#
|
||||
# TODO: Figure out if this works on Windows.
|
||||
def grep_for(text, where = "./", double_quote = false, perl_regex = false)
|
||||
# If they're on Windows, they probably don't have grep.
|
||||
@probably_has_grep ||= (Config::CONFIG['host_os'].downcase =~ /mswin|windows|mingw/).nil?
|
||||
|
||||
# protect against double root paths in Rails 3
|
||||
where.gsub!(Regexp.new(base_path),'')
|
||||
|
||||
lines = if @probably_has_grep
|
||||
find_with_grep(text, base_path + where, double_quote, perl_regex)
|
||||
else
|
||||
find_with_rak(text, base_path + where, double_quote)
|
||||
end
|
||||
|
||||
# ignore comments
|
||||
lines.gsub /^(\/[^:]+:)?\s*#.+$/m, ""
|
||||
end
|
||||
|
||||
# Sets a base path for finding files; mostly for testing
|
||||
def base_path
|
||||
Dir.pwd + "/"
|
||||
end
|
||||
|
||||
# Use the grep utility to find a string in a set of files
|
||||
def find_with_grep(text, where, double_quote, perl_regex = false)
|
||||
value = ""
|
||||
# Specifically double quote for finding 'test_help'
|
||||
command = if double_quote
|
||||
"grep -r #{"-P" if perl_regex} --exclude=\*.svn\* \"#{text}\" #{where}"
|
||||
else
|
||||
"grep -r #{"-P" if perl_regex} --exclude=\*.svn\* '#{text}' #{where}"
|
||||
end
|
||||
|
||||
Open3.popen3(command) do |stdin, stdout, stderr|
|
||||
value = stdout.read
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
# Use the rak gem to grep the files (not yet implemented)
|
||||
def find_with_rak(text, where, double_quote)
|
||||
value = ""
|
||||
Open3.popen3("rak --nogroup -l '#{Regexp.escape(text)}' #{where}") do |stdin, stdout, stderr|
|
||||
value = stdout.read
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
# Extract the filenames from the grep output
|
||||
def extract_filenames(output)
|
||||
if @probably_has_grep
|
||||
extract_filenames_from_grep(output)
|
||||
else
|
||||
extract_filenames_from_rak(output)
|
||||
end
|
||||
end
|
||||
|
||||
def extract_filenames_from_grep(output)
|
||||
return nil if output.empty?
|
||||
|
||||
output.split("\n").map do |fn|
|
||||
if m = fn.match(/^(.+?):/)
|
||||
m[1]
|
||||
end
|
||||
end.compact.uniq
|
||||
end
|
||||
|
||||
def extract_filenames_from_rak(output)
|
||||
return nil if output.empty?
|
||||
|
||||
output.split("\n").uniq
|
||||
end
|
||||
|
||||
# Terminal colors, borrowed from Thor
|
||||
CLEAR = "\e[0m"
|
||||
BOLD = "\e[1m"
|
||||
RED = "\e[31m"
|
||||
YELLOW = "\e[33m"
|
||||
CYAN = "\e[36m"
|
||||
WHITE = "\e[37m"
|
||||
|
||||
# Show an upgrade alert to the user
|
||||
def alert(title, text, more_info_url, culprits)
|
||||
if Config::CONFIG['host_os'].downcase =~ /mswin|windows|mingw/
|
||||
basic_alert(title, text, more_info_url, culprits)
|
||||
else
|
||||
color_alert(title, text, more_info_url, culprits)
|
||||
end
|
||||
end
|
||||
|
||||
# Show an upgrade alert to the user. If we're on Windows, we can't
|
||||
# use terminal colors, hence this method.
|
||||
def basic_alert(title, text, more_info_url, culprits)
|
||||
puts "** " + title
|
||||
puts text
|
||||
puts "More information: #{more_info_url}"
|
||||
puts
|
||||
puts "The culprits: "
|
||||
Array(culprits).each do |c|
|
||||
puts "\t- #{c}"
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
# Show a colorful alert to the user
|
||||
def color_alert(title, text, more_info_url, culprits)
|
||||
puts "#{RED}#{BOLD}#{title}#{CLEAR}"
|
||||
puts "#{WHITE}#{text}"
|
||||
puts "#{BOLD}More information:#{CLEAR} #{CYAN}#{more_info_url}"
|
||||
puts
|
||||
puts "#{WHITE}The culprits: "
|
||||
Array(culprits).each do |c|
|
||||
puts "#{YELLOW}\t- #{c}"
|
||||
end
|
||||
ensure
|
||||
puts "#{CLEAR}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
95
vendor/plugins/rails_upgrade/lib/gemfile_generator.rb
vendored
Normal file
95
vendor/plugins/rails_upgrade/lib/gemfile_generator.rb
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
module Rails
|
||||
module Upgrading
|
||||
class GemfileGenerator
|
||||
def generate_new_gemfile
|
||||
if has_environment?
|
||||
generate_gemfile
|
||||
else
|
||||
raise FileNotFoundError, "Can't find environment.rb [config/environment.rb]!"
|
||||
end
|
||||
end
|
||||
|
||||
def has_environment?
|
||||
File.exists?("config/environment.rb")
|
||||
end
|
||||
|
||||
def environment_code
|
||||
File.open("config/environment.rb").read
|
||||
end
|
||||
|
||||
def generate_gemfile
|
||||
environment_file = environment_code
|
||||
|
||||
# Get each line that starts with config.gem
|
||||
gem_lines = environment_file.split("\n").select {|l| l =~ /^\s*config\.gem/}
|
||||
|
||||
# Toss those lines to a generator class; the lines are evaluated in the
|
||||
# context of that instance.
|
||||
config = GemfileGenerator.new
|
||||
config.instance_eval(gem_lines.join("\n"))
|
||||
|
||||
config.output
|
||||
end
|
||||
end
|
||||
|
||||
class GemfileGenerator
|
||||
# Creates a target for the config.gem calls
|
||||
def config
|
||||
self
|
||||
end
|
||||
|
||||
def initialize
|
||||
@gems = []
|
||||
end
|
||||
|
||||
# Receive a call to add a gem to the list
|
||||
def gem(name, options={})
|
||||
data = {}
|
||||
|
||||
# Add new keys from old keys
|
||||
data[:require] = options[:lib] if options[:lib]
|
||||
data[:source] = options[:source] if options[:source]
|
||||
|
||||
version = options[:version]
|
||||
@gems << [name, version, data]
|
||||
end
|
||||
|
||||
# Generate the Gemfile output
|
||||
def output
|
||||
# Generic preamble, taken from Yehuda Katz's blog
|
||||
preamble = <<STR
|
||||
# Edit this Gemfile to bundle your application's dependencies.
|
||||
# This preamble is the current preamble for Rails 3 apps; edit as needed.
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'rails', '3.0.0.beta3'
|
||||
|
||||
STR
|
||||
preamble + generate_upgraded_code
|
||||
end
|
||||
|
||||
# Get Gemfile call for all the gems
|
||||
def generate_upgraded_code
|
||||
code = @gems.map do |name, version, data|
|
||||
version_string = (version ? "'#{version}'" : nil)
|
||||
source = data.delete(:source)
|
||||
|
||||
data_string = nil
|
||||
unless data.empty?
|
||||
data_string = data.to_a.map {|k, v| ":#{k} => '#{v}'"}.join(", ")
|
||||
end
|
||||
|
||||
# If we have a source, generate a call to +source+ then output the
|
||||
# gem call. Otherwise, just generate the gem requirement.
|
||||
if source
|
||||
str = ["'#{name}'", version_string, data_string].compact.join(", ")
|
||||
"source '#{source}'\ngem #{str}"
|
||||
else
|
||||
str = ["'#{name}'", version_string, data_string].compact.join(", ")
|
||||
"gem #{str}"
|
||||
end
|
||||
end.join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
51
vendor/plugins/rails_upgrade/lib/new_configuration_generator.rb
vendored
Normal file
51
vendor/plugins/rails_upgrade/lib/new_configuration_generator.rb
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
module Rails
|
||||
module Upgrading
|
||||
class NewConfigurationGenerator
|
||||
def generate_new_configurations
|
||||
if has_environment?
|
||||
generate_new_application_rb
|
||||
else
|
||||
raise FileNotFoundError, "Can't find environment.rb [config/environment.rb]!"
|
||||
end
|
||||
end
|
||||
|
||||
def has_environment?
|
||||
File.exists?("config/environment.rb")
|
||||
end
|
||||
|
||||
def environment_code
|
||||
File.open("config/environment.rb").read
|
||||
end
|
||||
|
||||
def generate_new_application_rb
|
||||
environment_file = environment_code
|
||||
|
||||
initializer_code = ""
|
||||
if matches = environment_file.match(/Rails\:\:Initializer\.run do \|config\|\n(.*)\nend/m)
|
||||
initializer_code = matches[1]
|
||||
else
|
||||
raise "There doesn't seem to be a real environment.rb in your app. Are you sure config/environment.rb has the right contents?"
|
||||
end
|
||||
|
||||
frame = "# Put this in config/application.rb
|
||||
require File.expand_path('../boot', __FILE__)
|
||||
|
||||
module #{app_name.classify}
|
||||
class Application < Rails::Application
|
||||
%s
|
||||
end
|
||||
end"
|
||||
|
||||
frame % [indent(initializer_code)]
|
||||
end
|
||||
|
||||
def indent(text)
|
||||
text.split("\n").map {|l| " #{l}"}.join("\n")
|
||||
end
|
||||
|
||||
def app_name
|
||||
File.basename(Dir.pwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
0
vendor/plugins/rails_upgrade/lib/rails_upgrade.rb
vendored
Normal file
0
vendor/plugins/rails_upgrade/lib/rails_upgrade.rb
vendored
Normal file
349
vendor/plugins/rails_upgrade/lib/routes_upgrader.rb
vendored
Normal file
349
vendor/plugins/rails_upgrade/lib/routes_upgrader.rb
vendored
Normal file
|
@ -0,0 +1,349 @@
|
|||
# TODO: Fix formatting on member/collection methods
|
||||
|
||||
module Rails
|
||||
module Upgrading
|
||||
module FakeRouter
|
||||
module ActionController
|
||||
module Routing
|
||||
class Routes
|
||||
def self.setup
|
||||
@redrawer = Rails::Upgrading::RouteRedrawer.new
|
||||
end
|
||||
|
||||
def self.redrawer
|
||||
@redrawer
|
||||
end
|
||||
|
||||
def self.draw
|
||||
yield @redrawer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RoutesUpgrader
|
||||
def generate_new_routes
|
||||
if has_routes_file?
|
||||
upgrade_routes
|
||||
else
|
||||
raise FileNotFoundError, "Can't find your routes file [config/routes.rb]!"
|
||||
end
|
||||
end
|
||||
|
||||
def has_routes_file?
|
||||
File.exists?("config/routes.rb")
|
||||
end
|
||||
|
||||
def routes_code
|
||||
File.read("config/routes.rb")
|
||||
end
|
||||
|
||||
def upgrade_routes
|
||||
FakeRouter::ActionController::Routing::Routes.setup
|
||||
|
||||
# Read and eval the file; our fake route mapper will capture
|
||||
# the calls to draw routes and generate new route code
|
||||
FakeRouter.module_eval(routes_code)
|
||||
|
||||
# Give the route set to the code generator and get its output
|
||||
generator = RouteGenerator.new(FakeRouter::ActionController::Routing::Routes.redrawer.routes)
|
||||
generator.generate
|
||||
end
|
||||
end
|
||||
|
||||
class RouteRedrawer
|
||||
attr_accessor :routes
|
||||
|
||||
def self.stack
|
||||
@stack
|
||||
end
|
||||
|
||||
def self.stack=(val)
|
||||
@stack = val
|
||||
end
|
||||
|
||||
def initialize
|
||||
@routes = []
|
||||
|
||||
# The old default route was actually two routes; we generate the new style
|
||||
# one only if we haven't generated it for the first old default route.
|
||||
@default_route_generated = false
|
||||
|
||||
# Setup the stack for parents; used use proper indentation
|
||||
self.class.stack = [@routes]
|
||||
end
|
||||
|
||||
def root(options)
|
||||
debug "mapping root"
|
||||
@routes << FakeRoute.new("/", options)
|
||||
end
|
||||
|
||||
def connect(path, options={})
|
||||
debug "connecting #{path}"
|
||||
|
||||
if (path == ":controller/:action/:id.:format" || path == ":controller/:action/:id")
|
||||
if !@default_route_generated
|
||||
current_parent << FakeRoute.new("/:controller(/:action(/:id))", {:default_route => true})
|
||||
|
||||
@default_route_generated = true
|
||||
end
|
||||
else
|
||||
current_parent << FakeRoute.new(path, options)
|
||||
end
|
||||
end
|
||||
|
||||
def resources(*args)
|
||||
if block_given?
|
||||
parent = FakeResourceRoute.new(args.shift)
|
||||
debug "mapping resources #{parent.name} with block"
|
||||
|
||||
parent = stack(parent) do
|
||||
yield(self)
|
||||
end
|
||||
|
||||
current_parent << parent
|
||||
else
|
||||
if args.last.is_a?(Hash)
|
||||
current_parent << FakeResourceRoute.new(args.shift, args.pop)
|
||||
debug "mapping resources #{current_parent.last.name} w/o block with args"
|
||||
else
|
||||
args.each do |a|
|
||||
current_parent << FakeResourceRoute.new(a)
|
||||
debug "mapping resources #{current_parent.last.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def resource(*args)
|
||||
if block_given?
|
||||
parent = FakeSingletonResourceRoute.new(args.shift)
|
||||
debug "mapping resource #{parent.name} with block"
|
||||
|
||||
parent = stack(parent) do
|
||||
yield(self)
|
||||
end
|
||||
|
||||
current_parent << parent
|
||||
else
|
||||
if args.last.is_a?(Hash)
|
||||
current_parent << FakeSingletonResourceRoute.new(args.shift, args.pop)
|
||||
debug "mapping resources #{current_parent.last.name} w/o block with args"
|
||||
else
|
||||
args.each do |a|
|
||||
current_parent << FakeSingletonResourceRoute.new(a)
|
||||
debug "mapping resources #{current_parent.last.name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def namespace(name)
|
||||
debug "mapping namespace #{name}"
|
||||
namespace = FakeNamespace.new(name)
|
||||
|
||||
namespace = stack(namespace) do
|
||||
yield(self)
|
||||
end
|
||||
|
||||
current_parent << namespace
|
||||
end
|
||||
|
||||
def method_missing(m, *args)
|
||||
debug "named route: #{m}"
|
||||
current_parent << FakeRoute.new(args.shift, args.pop, m.to_s)
|
||||
end
|
||||
|
||||
def self.indent
|
||||
' ' * ((stack.length) * 2)
|
||||
end
|
||||
|
||||
private
|
||||
def debug(txt)
|
||||
puts txt if ENV['DEBUG']
|
||||
end
|
||||
|
||||
def stack(obj)
|
||||
self.class.stack << obj
|
||||
yield
|
||||
self.class.stack.pop
|
||||
end
|
||||
|
||||
def current_parent
|
||||
self.class.stack.last
|
||||
end
|
||||
end
|
||||
|
||||
class RouteObject
|
||||
def indent_lines(code_lines)
|
||||
if code_lines.length > 1
|
||||
code_lines.flatten.map {|l| "#{@indent}#{l.chomp}"}.join("\n") + "\n"
|
||||
else
|
||||
"#{@indent}#{code_lines.shift}"
|
||||
end
|
||||
end
|
||||
|
||||
def opts_to_string(opts)
|
||||
opts.is_a?(Hash) ? opts.map {|k, v|
|
||||
":#{k} => " + (v.is_a?(Hash) ? ('{ ' + opts_to_string(v) + ' }') : "#{value_to_string(v)}")
|
||||
}.join(", ") : opts.to_s
|
||||
end
|
||||
|
||||
def value_to_string(value)
|
||||
case value
|
||||
when Regexp then value.inspect
|
||||
when String then "'" + value.to_s + "'"
|
||||
else value.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FakeNamespace < RouteObject
|
||||
attr_accessor :routes, :name
|
||||
|
||||
def initialize(name)
|
||||
@routes = []
|
||||
@name = name
|
||||
@indent = RouteRedrawer.indent
|
||||
end
|
||||
|
||||
def to_route_code
|
||||
lines = ["namespace :#{@name} do", @routes.map {|r| r.to_route_code}, "end"]
|
||||
|
||||
indent_lines(lines)
|
||||
end
|
||||
|
||||
def <<(val)
|
||||
@routes << val
|
||||
end
|
||||
|
||||
def last
|
||||
@routes.last
|
||||
end
|
||||
end
|
||||
|
||||
class FakeRoute < RouteObject
|
||||
attr_accessor :name, :path, :options
|
||||
|
||||
def initialize(path, options, name = "")
|
||||
@path = path
|
||||
@options = options || {}
|
||||
@name = name
|
||||
@indent = RouteRedrawer.indent
|
||||
end
|
||||
|
||||
def to_route_code
|
||||
if @options[:default_route]
|
||||
indent_lines ["match '#{@path}'"]
|
||||
else
|
||||
base = "match '%s' => '%s#%s'"
|
||||
extra_options = []
|
||||
|
||||
if not name.empty?
|
||||
extra_options << ":as => :#{name}"
|
||||
end
|
||||
|
||||
if @options[:requirements]
|
||||
@options[:constraints] = @options.delete(:requirements)
|
||||
end
|
||||
|
||||
if @options[:conditions]
|
||||
@options[:via] = @options.delete(:conditions).delete(:method)
|
||||
end
|
||||
|
||||
@options ||= {}
|
||||
base = (base % [@path, @options.delete(:controller), (@options.delete(:action) || "index")])
|
||||
opts = opts_to_string(@options)
|
||||
|
||||
route_pieces = ([base] + extra_options + [opts])
|
||||
route_pieces.delete("")
|
||||
|
||||
indent_lines [route_pieces.join(", ")]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class FakeResourceRoute < RouteObject
|
||||
attr_accessor :name, :children
|
||||
|
||||
def initialize(name, options = {})
|
||||
@name = name
|
||||
@children = []
|
||||
@options = options
|
||||
@indent = RouteRedrawer.indent
|
||||
end
|
||||
|
||||
def to_route_code
|
||||
|
||||
if !@children.empty? || @options.has_key?(:collection) || @options.has_key?(:member)
|
||||
prefix = ["#{route_method} :#{@name} do"]
|
||||
lines = prefix + custom_methods + [@children.map {|r| r.to_route_code}.join("\n"), "end"]
|
||||
|
||||
indent_lines(lines)
|
||||
else
|
||||
base = "#{route_method} :%s"
|
||||
indent_lines [base % [@name]]
|
||||
end
|
||||
end
|
||||
|
||||
def custom_methods
|
||||
collection_code = generate_custom_methods_for(:collection)
|
||||
member_code = generate_custom_methods_for(:member)
|
||||
[collection_code, member_code]
|
||||
end
|
||||
|
||||
def generate_custom_methods_for(group)
|
||||
return "" unless @options[group]
|
||||
|
||||
method_code = []
|
||||
|
||||
RouteRedrawer.stack << self
|
||||
@options[group].each do |k, v|
|
||||
method_code << "#{v} :#{k}"
|
||||
end
|
||||
RouteRedrawer.stack.pop
|
||||
|
||||
indent_lines ["#{group} do", method_code, "end"].flatten
|
||||
end
|
||||
|
||||
def route_method
|
||||
"resources"
|
||||
end
|
||||
|
||||
def <<(val)
|
||||
@children << val
|
||||
end
|
||||
|
||||
def last
|
||||
@children.last
|
||||
end
|
||||
end
|
||||
|
||||
class FakeSingletonResourceRoute < FakeResourceRoute
|
||||
def route_method
|
||||
"resource"
|
||||
end
|
||||
end
|
||||
|
||||
class RouteGenerator
|
||||
def initialize(routes)
|
||||
@routes = routes
|
||||
@new_code = ""
|
||||
end
|
||||
|
||||
def generate
|
||||
@new_code = @routes.map do |r|
|
||||
r.to_route_code
|
||||
end.join("\n")
|
||||
|
||||
"#{app_name.underscore.classify}::Application.routes.draw do\n#{@new_code}\nend\n"
|
||||
end
|
||||
|
||||
private
|
||||
def app_name
|
||||
File.basename(Dir.pwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
78
vendor/plugins/rails_upgrade/lib/tasks/rails_upgrade_tasks.rake
vendored
Normal file
78
vendor/plugins/rails_upgrade/lib/tasks/rails_upgrade_tasks.rake
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
$:.unshift(File.dirname(__FILE__) + "/../../lib")
|
||||
require 'routes_upgrader'
|
||||
require 'gemfile_generator'
|
||||
require 'application_checker'
|
||||
require 'new_configuration_generator'
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
namespace :rails do
|
||||
namespace :upgrade do
|
||||
desc "Runs a battery of checks on your Rails 2.x app and generates a report on required upgrades for Rails 3"
|
||||
task :check do
|
||||
checker = Rails::Upgrading::ApplicationChecker.new
|
||||
checker.run
|
||||
end
|
||||
|
||||
desc "Generates a Gemfile for your Rails 3 app out of your config.gem directives"
|
||||
task :gems do
|
||||
generator = Rails::Upgrading::GemfileGenerator.new
|
||||
new_gemfile = generator.generate_new_gemfile
|
||||
|
||||
puts new_gemfile
|
||||
end
|
||||
|
||||
desc "Create a new, upgraded route file from your current routes.rb"
|
||||
task :routes do
|
||||
upgrader = Rails::Upgrading::RoutesUpgrader.new
|
||||
new_routes = upgrader.generate_new_routes
|
||||
|
||||
puts new_routes
|
||||
end
|
||||
|
||||
desc "Extracts your configuration code so you can create a new config/application.rb"
|
||||
task :configuration do
|
||||
upgrader = Rails::Upgrading::NewConfigurationGenerator.new
|
||||
new_config = upgrader.generate_new_application_rb
|
||||
|
||||
puts new_config
|
||||
end
|
||||
|
||||
CLEAR = "\e[0m"
|
||||
CYAN = "\e[36m"
|
||||
WHITE = "\e[37m"
|
||||
|
||||
desc "Backs up your likely modified files so you can run the Rails 3 generator on your app with little risk"
|
||||
task :backup do
|
||||
files = [".gitignore",
|
||||
"app/controllers/application_controller.rb",
|
||||
"app/helpers/application_helper.rb",
|
||||
"config/routes.rb",
|
||||
"config/environment.rb",
|
||||
"config/environments/development.rb",
|
||||
"config/environments/production.rb",
|
||||
"config/environments/staging.rb",
|
||||
"config/database.yml",
|
||||
"config.ru",
|
||||
"doc/README_FOR_APP",
|
||||
"test/test_helper.rb"]
|
||||
|
||||
puts
|
||||
files.each do |f|
|
||||
if File.exist?(f)
|
||||
puts "#{CYAN}* #{CLEAR}backing up #{WHITE}#{f}#{CLEAR} to #{WHITE}#{f}.rails2#{CLEAR}"
|
||||
FileUtils.cp(f, "#{f}.rails2")
|
||||
end
|
||||
end
|
||||
|
||||
puts
|
||||
puts "This is a list of the files analyzed and backed up (if they existed);\nyou will probably not want the generator to replace them since\nyou probably modified them (but now they're safe if you accidentally do!)."
|
||||
puts
|
||||
|
||||
files.each do |f|
|
||||
puts "#{CYAN}- #{CLEAR}#{f}"
|
||||
end
|
||||
puts
|
||||
end
|
||||
end
|
||||
end
|
293
vendor/plugins/rails_upgrade/test/application_checker_test.rb
vendored
Normal file
293
vendor/plugins/rails_upgrade/test/application_checker_test.rb
vendored
Normal file
|
@ -0,0 +1,293 @@
|
|||
require 'test_helper'
|
||||
require 'application_checker'
|
||||
require 'fileutils'
|
||||
|
||||
tmp_dir = "#{File.dirname(__FILE__)}/fixtures/tmp"
|
||||
|
||||
if defined? BASE_ROOT
|
||||
BASE_ROOT.replace tmp_dir
|
||||
else
|
||||
BASE_ROOT = tmp_dir
|
||||
end
|
||||
FileUtils.mkdir_p BASE_ROOT
|
||||
|
||||
# Stub out methods on upgrader class
|
||||
module Rails
|
||||
module Upgrading
|
||||
class ApplicationChecker
|
||||
attr_reader :alerts
|
||||
|
||||
def base_path
|
||||
BASE_ROOT + "/"
|
||||
end
|
||||
|
||||
def in_rails_app?
|
||||
true
|
||||
end
|
||||
|
||||
def initialize
|
||||
@alerts = {}
|
||||
end
|
||||
|
||||
def alert(title, text, more_info_url, culprits)
|
||||
@alerts[title] = [text, more_info_url, culprits]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ApplicationCheckerTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@checker = Rails::Upgrading::ApplicationChecker.new
|
||||
@old_dir = Dir.pwd
|
||||
|
||||
Dir.chdir(BASE_ROOT)
|
||||
end
|
||||
|
||||
def test_check_ar_methods_in_controller
|
||||
make_file("app/controllers", "post_controller.rb", "Post.find(:all)")
|
||||
@checker.check_ar_methods
|
||||
|
||||
assert @checker.alerts.has_key?("Soon-to-be-deprecated ActiveRecord calls")
|
||||
end
|
||||
|
||||
def test_check_ar_methods_in_models
|
||||
make_file("app/models", "post.rb", "Post.find(:all)")
|
||||
@checker.check_ar_methods
|
||||
|
||||
assert @checker.alerts.has_key?("Soon-to-be-deprecated ActiveRecord calls")
|
||||
end
|
||||
|
||||
def test_check_validation_on_methods
|
||||
make_file("app/models", "post.rb", "validate_on_create :comments_valid?")
|
||||
@checker.check_validation_on_methods
|
||||
|
||||
assert @checker.alerts.has_key?("Updated syntax for validate_on_* methods")
|
||||
end
|
||||
|
||||
def test_check_before_validation_on_methods
|
||||
make_file("app/models", "post.rb", "before_validation_on_create :comments_valid?")
|
||||
@checker.check_before_validation_on_methods
|
||||
|
||||
assert @checker.alerts.has_key?("Updated syntax for before_validation_on_* methods")
|
||||
end
|
||||
|
||||
def test_named_scope_left_over
|
||||
make_file("app/models", "post.rb", "named_scope :failure")
|
||||
@checker.check_ar_methods
|
||||
|
||||
assert @checker.alerts.has_key?("named_scope is now just scope")
|
||||
end
|
||||
|
||||
def test_check_routes
|
||||
make_file("config/", "routes.rb", " map.connect 'fail'")
|
||||
@checker.check_routes
|
||||
|
||||
assert @checker.alerts.has_key?("Old router API")
|
||||
end
|
||||
|
||||
def test_check_for_old_test_help
|
||||
make_file("test/", "test_helper.rb", " require 'test_help'")
|
||||
@checker.check_test_help
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated test_help path")
|
||||
end
|
||||
|
||||
def test_check_for_old_test_help_with_double_quotes
|
||||
make_file("test/", "test_helper.rb", " require \"test_help\"")
|
||||
@checker.check_test_help
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated test_help path")
|
||||
end
|
||||
|
||||
def test_check_for_old_test_help_doesnt_see_test_helper
|
||||
make_file("test/", "test_helper.rb", " require 'test_helper'")
|
||||
@checker.check_test_help
|
||||
|
||||
assert !@checker.alerts.has_key?("Deprecated test_help path")
|
||||
end
|
||||
|
||||
def test_check_lack_of_app_dot_rb
|
||||
@checker.check_environment
|
||||
|
||||
assert @checker.alerts.has_key?("New file needed: config/application.rb")
|
||||
end
|
||||
|
||||
def test_check_environment_syntax
|
||||
make_file("config/", "environment.rb", "config.frameworks = []")
|
||||
@checker.check_environment
|
||||
|
||||
assert @checker.alerts.has_key?("Old environment.rb")
|
||||
end
|
||||
|
||||
def test_check_gems
|
||||
make_file("config/", "environment.rb", "config.gem 'rails'")
|
||||
@checker.check_gems
|
||||
|
||||
assert @checker.alerts.has_key?("Old gem bundling (config.gems)")
|
||||
end
|
||||
|
||||
def test_check_mailer_syntax
|
||||
make_file("app/models/", "notifications.rb", "def signup\nrecipients @users\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert @checker.alerts.has_key?("Old ActionMailer class API")
|
||||
end
|
||||
|
||||
def test_check_mailer_syntax_from
|
||||
make_file("app/models/", "notifications.rb", "def signup\nfrom @user\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert @checker.alerts.has_key?("Old ActionMailer class API")
|
||||
end
|
||||
|
||||
def test_check_mailer_syntax_subject
|
||||
make_file("app/models/", "notifications.rb", "def signup\nsubject @subject\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert @checker.alerts.has_key?("Old ActionMailer class API")
|
||||
end
|
||||
|
||||
def test_check_mailer_syntax_attachment
|
||||
make_file("app/models/", "notifications.rb", "def signup\nattachment 'application/pdf' do |a|\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert @checker.alerts.has_key?("Old ActionMailer class API")
|
||||
end
|
||||
|
||||
def test_new_check_mailer_syntax_from
|
||||
make_file("app/models/", "notifications.rb", "def signup\n:from => @users\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert ! @checker.alerts.has_key?("Old ActionMailer class API")
|
||||
end
|
||||
|
||||
def test_new_check_mailer_syntax_subject
|
||||
make_file("app/models/", "notifications.rb", "def signup\n:subject => @users\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert ! @checker.alerts.has_key?("Old ActionMailer class API")
|
||||
end
|
||||
|
||||
def test_new_check_mailer_syntax_attachments
|
||||
make_file("app/models/", "notifications.rb", "def signup\nattachments['an-image.jp'] = File.read('an-image.jpg')\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert ! @checker.alerts.has_key?("Old ActionMailer class API")
|
||||
end
|
||||
|
||||
def test_check_mailer_api
|
||||
make_file("app/controllers/", "thing_controller.rb", "def signup\n Notifications.deliver_signup\n end")
|
||||
@checker.check_mailers
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated ActionMailer API")
|
||||
end
|
||||
|
||||
def test_check_generators
|
||||
make_file("vendor/plugins/thing/generators/thing/", "thing_generator.rb", "def manifest\n m.whatever\n end")
|
||||
@checker.check_generators
|
||||
|
||||
assert @checker.alerts.has_key?("Old Rails generator API")
|
||||
end
|
||||
|
||||
def test_check_plugins
|
||||
make_file("vendor/plugins/rspec-rails/", "whatever.rb", "def rspec; end")
|
||||
@checker.check_plugins
|
||||
|
||||
assert @checker.alerts.has_key?("Known broken plugins")
|
||||
end
|
||||
|
||||
def test_ignoring_comments
|
||||
make_file("config/", "routes.rb", "# map.connect 'fail'")
|
||||
@checker.check_routes
|
||||
|
||||
assert !@checker.alerts.has_key?("Old router API")
|
||||
end
|
||||
|
||||
def test_check_deprecated_constants_in_app_code
|
||||
make_file("app/controllers/", "thing_controller.rb", "class ThingController; THING = RAILS_ENV; end;")
|
||||
@checker.check_deprecated_constants
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated constant(s)")
|
||||
end
|
||||
|
||||
def test_check_deprecated_constants_in_lib
|
||||
make_file("lib/", "extra_thing.rb", "class ExtraThing; THING = RAILS_ENV; end;")
|
||||
@checker.check_deprecated_constants
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated constant(s)")
|
||||
end
|
||||
|
||||
def test_check_deprecated_cookie_settings
|
||||
make_file("config/initializers/", "more_settings.rb", "ActionController::Base.cookie_verifier_secret = 'OMG'")
|
||||
@checker.check_old_cookie_secret
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated cookie secret setting")
|
||||
end
|
||||
|
||||
def test_check_deprecated_session_secret
|
||||
make_file("config/initializers/", "more_settings.rb", "ActionController::Base.session = {\n:whatever => 'woot'\n}")
|
||||
@checker.check_old_session_secret
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated session secret setting")
|
||||
end
|
||||
|
||||
def test_check_deprecated_session_settings
|
||||
make_file("config/initializers/", "more_settings.rb", "ActionController::Base.session_store = :cookie\nthings.awesome(:whatever)")
|
||||
@checker.check_old_session_setting
|
||||
|
||||
assert @checker.alerts.has_key?("Old session store setting")
|
||||
end
|
||||
|
||||
def test_check_helpers
|
||||
make_file("app/views/users/", "test.html.erb", "<b>blah blah blah</b><% form_for(:thing) do |f| %> <label>doo dah</label> <%= f.whatever %> <% end %>")
|
||||
@checker.check_old_helpers
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated ERb helper calls")
|
||||
|
||||
end
|
||||
|
||||
def test_check_old_helpers_lets_regular_blocks_pass
|
||||
make_file("app/views/users/", "another_test.html.erb", "<b>blah blah blah</b><% @some_items.each do |item| %> <label>doo dah</label> <%= item %> <% end %>")
|
||||
@checker.check_old_helpers
|
||||
|
||||
assert_equal @checker.alerts.has_key?("Deprecated ERb helper calls"), false
|
||||
end
|
||||
|
||||
def test_check_old_helpers_lets_regular_blocks_pass
|
||||
make_file("app/views/users/", "another_test.html.erb", "<b>blah blah blah</b><% @some_items.each do |item| %> <label>doo dah</label> <%= item %> <% end %>")
|
||||
@checker.check_old_helpers
|
||||
|
||||
assert_equal @checker.alerts.has_key?("Deprecated ERb helper calls"), false
|
||||
end
|
||||
|
||||
def test_check_old_ajax_helpers
|
||||
make_file("app/views/sections", "section.js", "<%= link_to_remote 'section-', :update => 'sections', :url => {:action => :destroy, :controller => 'sections', :id => @section.id } %>")
|
||||
@checker.check_old_ajax_helpers
|
||||
|
||||
assert @checker.alerts.has_key?("Deprecated AJAX helper calls")
|
||||
end
|
||||
|
||||
def test_check_old_ajax_helpers_empty
|
||||
@checker.check_old_ajax_helpers
|
||||
|
||||
assert ! @checker.alerts.has_key?("Deprecated AJAX helper calls")
|
||||
end
|
||||
|
||||
def teardown
|
||||
clear_files
|
||||
|
||||
Dir.chdir(@old_dir)
|
||||
end
|
||||
|
||||
def make_file(where, name=nil, contents=nil)
|
||||
FileUtils.mkdir_p "#{BASE_ROOT}/#{where}"
|
||||
File.open("#{BASE_ROOT}/#{where}/#{name}", "w+") do |f|
|
||||
f.write(contents)
|
||||
end if name
|
||||
end
|
||||
|
||||
def clear_files
|
||||
FileUtils.rm_rf(Dir.glob("#{BASE_ROOT}/*"))
|
||||
end
|
||||
end
|
72
vendor/plugins/rails_upgrade/test/gemfile_generator_test.rb
vendored
Normal file
72
vendor/plugins/rails_upgrade/test/gemfile_generator_test.rb
vendored
Normal file
|
@ -0,0 +1,72 @@
|
|||
require 'test_helper'
|
||||
require 'gemfile_generator'
|
||||
|
||||
# Stub out methods on upgrader class
|
||||
module Rails
|
||||
module Upgrading
|
||||
class GemfileGenerator
|
||||
attr_writer :environment_code
|
||||
|
||||
def has_environment?
|
||||
true
|
||||
end
|
||||
|
||||
def environment_code
|
||||
@environment_code
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class GemfileGeneratorTest < ActiveSupport::TestCase
|
||||
PREAMBLE = <<STR
|
||||
# Edit this Gemfile to bundle your application's dependencies.
|
||||
# This preamble is the current preamble for Rails 3 apps; edit as needed.
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'rails', '3.0.0.beta3'
|
||||
|
||||
STR
|
||||
|
||||
def test_generates_with_no_gems
|
||||
generator = Rails::Upgrading::GemfileGenerator.new
|
||||
generator.environment_code = ""
|
||||
|
||||
assert_equal PREAMBLE, generator.generate_gemfile
|
||||
end
|
||||
|
||||
def test_generates_with_gem
|
||||
generator = Rails::Upgrading::GemfileGenerator.new
|
||||
generator.environment_code = "config.gem 'camping'"
|
||||
|
||||
assert_equal PREAMBLE + "gem 'camping'", generator.generate_gemfile
|
||||
end
|
||||
|
||||
def test_generates_with_version
|
||||
generator = Rails::Upgrading::GemfileGenerator.new
|
||||
generator.environment_code = "config.gem 'camping', :version => '2.1.1'"
|
||||
|
||||
assert_equal PREAMBLE + "gem 'camping', '2.1.1'", generator.generate_gemfile
|
||||
end
|
||||
|
||||
def test_can_add_sources
|
||||
generator = Rails::Upgrading::GemfileGenerator.new
|
||||
generator.environment_code = "config.gem 'camping', :source => 'http://code.whytheluckystiff.net'"
|
||||
|
||||
assert_equal PREAMBLE + "source 'http://code.whytheluckystiff.net'\ngem 'camping'", generator.generate_gemfile
|
||||
end
|
||||
|
||||
def test_changes_lib_to_new_key
|
||||
generator = Rails::Upgrading::GemfileGenerator.new
|
||||
generator.environment_code = "config.gem 'camping', :lib => 'kamping'"
|
||||
|
||||
assert_equal PREAMBLE + "gem 'camping', :require => 'kamping'", generator.generate_gemfile
|
||||
end
|
||||
|
||||
def test_generates_with_all_options
|
||||
generator = Rails::Upgrading::GemfileGenerator.new
|
||||
generator.environment_code = "config.gem 'camping', :lib => 'kamping', :source => 'http://code.whytheluckystiff.net', :version => '2.1.1'"
|
||||
|
||||
assert_equal PREAMBLE + "source 'http://code.whytheluckystiff.net'\ngem 'camping', '2.1.1', :require => 'kamping'", generator.generate_gemfile
|
||||
end
|
||||
end
|
63
vendor/plugins/rails_upgrade/test/new_configuration_generator_test.rb
vendored
Normal file
63
vendor/plugins/rails_upgrade/test/new_configuration_generator_test.rb
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
require 'test_helper'
|
||||
require 'new_configuration_generator'
|
||||
|
||||
# Stub out methods on upgrader class
|
||||
module Rails
|
||||
module Upgrading
|
||||
class NewConfigurationGenerator
|
||||
attr_writer :environment_code
|
||||
|
||||
def has_environment?
|
||||
true
|
||||
end
|
||||
|
||||
def environment_code
|
||||
@environment_code
|
||||
end
|
||||
|
||||
def app_name
|
||||
"my_application"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NewConfigurationGeneratorTest < ActiveSupport::TestCase
|
||||
FRAME = "# Put this in config/application.rb
|
||||
require File.expand_path('../boot', __FILE__)
|
||||
|
||||
module MyApplication
|
||||
class Application < Rails::Application
|
||||
%s
|
||||
end
|
||||
end"
|
||||
|
||||
CONFIG = " config.what_have_you = 'thing'
|
||||
config.action_controller = 'what'"
|
||||
|
||||
CODE = "require 'w/e'
|
||||
|
||||
this_happens_before_the(code)
|
||||
more_before_the_code!
|
||||
|
||||
Rails::Initializer.run do |config|
|
||||
%s
|
||||
end
|
||||
|
||||
this_is_after_the_code
|
||||
"
|
||||
|
||||
def test_raises_error_with_no_code
|
||||
generator = Rails::Upgrading::NewConfigurationGenerator.new
|
||||
generator.environment_code = ""
|
||||
|
||||
assert_raises(RuntimeError) { generator.generate_new_application_rb }
|
||||
end
|
||||
|
||||
def test_generates_with_code
|
||||
generator = Rails::Upgrading::NewConfigurationGenerator.new
|
||||
generator.environment_code = CODE % [CONFIG]
|
||||
|
||||
assert_equal FRAME % [generator.indent(CONFIG)], generator.generate_new_application_rb
|
||||
end
|
||||
end
|
142
vendor/plugins/rails_upgrade/test/routes_upgrader_test.rb
vendored
Normal file
142
vendor/plugins/rails_upgrade/test/routes_upgrader_test.rb
vendored
Normal file
|
@ -0,0 +1,142 @@
|
|||
require 'test_helper'
|
||||
require 'routes_upgrader'
|
||||
|
||||
# Stub out methods on upgrader class
|
||||
module Rails
|
||||
module Upgrading
|
||||
class RoutesUpgrader
|
||||
attr_writer :routes_code
|
||||
|
||||
def has_routes_file?
|
||||
true
|
||||
end
|
||||
|
||||
def routes_code
|
||||
@routes_code
|
||||
end
|
||||
end
|
||||
|
||||
class RouteGenerator
|
||||
def app_name
|
||||
"MyApplication"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RoutesUpgraderTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
Rails::Upgrading::RouteRedrawer.stack = []
|
||||
end
|
||||
|
||||
def test_generates_routes_file
|
||||
routes_code = "
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.connect '/home', :controller => 'home', :action => 'index'
|
||||
map.login '/login', :controller => 'sessions', :action => 'new'
|
||||
|
||||
map.resources :hats
|
||||
map.resource :store
|
||||
end
|
||||
"
|
||||
|
||||
new_routes_code = "MyApplication::Application.routes.draw do
|
||||
match '/home' => 'home#index'
|
||||
match '/login' => 'sessions#new', :as => :login
|
||||
resources :hats
|
||||
resource :store
|
||||
end
|
||||
"
|
||||
|
||||
upgrader = Rails::Upgrading::RoutesUpgrader.new
|
||||
upgrader.routes_code = routes_code
|
||||
|
||||
result = upgrader.generate_new_routes
|
||||
|
||||
assert_equal new_routes_code, result
|
||||
end
|
||||
|
||||
def test_generates_code_for_regular_route
|
||||
route = Rails::Upgrading::FakeRoute.new("/about", {:controller => 'static', :action => 'about'})
|
||||
assert_equal "match '/about' => 'static#about'", route.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_named_route
|
||||
route = Rails::Upgrading::FakeRoute.new("/about", {:controller => 'static', :action => 'about'}, "about")
|
||||
assert_equal "match '/about' => 'static#about', :as => :about", route.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_namespace
|
||||
ns = Rails::Upgrading::FakeNamespace.new("static")
|
||||
# Add a route to the namespace
|
||||
ns << Rails::Upgrading::FakeRoute.new("/about", {:controller => 'static', :action => 'about'})
|
||||
|
||||
assert_equal "namespace :static do\nmatch '/about' => 'static#about'\nend\n", ns.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_resources
|
||||
route = Rails::Upgrading::FakeResourceRoute.new("hats")
|
||||
assert_equal "resources :hats", route.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_resources
|
||||
route = Rails::Upgrading::FakeSingletonResourceRoute.new("hat")
|
||||
assert_equal "resource :hat", route.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_resources_with_special_methods
|
||||
route = Rails::Upgrading::FakeResourceRoute.new("hats", {:member => {:wear => :get}, :collection => {:toss => :post}})
|
||||
assert_equal "resources :hats do\ncollection do\npost :toss\nend\nmember do\nget :wear\nend\n\nend\n", route.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_route_with_extra_params
|
||||
route = Rails::Upgrading::FakeRoute.new("/about", {:controller => 'static', :action => 'about', :something => 'extra'})
|
||||
assert_equal "match '/about' => 'static#about', :something => 'extra'", route.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_route_with_requirements
|
||||
route = Rails::Upgrading::FakeRoute.new("/foo", {:controller => 'foo', :action => 'bar', :requirements => {:digit => /%d/}})
|
||||
assert_equal "match '/foo' => 'foo#bar', :constraints => { :digit => /%d/ }", route.to_route_code
|
||||
end
|
||||
|
||||
def test_generates_code_for_root
|
||||
routes_code = "
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.root :controller => 'home', :action => 'index'
|
||||
end
|
||||
"
|
||||
|
||||
new_routes_code = "MyApplication::Application.routes.draw do
|
||||
match '/' => 'home#index'
|
||||
end
|
||||
"
|
||||
|
||||
upgrader = Rails::Upgrading::RoutesUpgrader.new
|
||||
upgrader.routes_code = routes_code
|
||||
|
||||
result = upgrader.generate_new_routes
|
||||
|
||||
assert_equal new_routes_code, result
|
||||
end
|
||||
|
||||
def test_generates_code_for_default_route
|
||||
routes_code = "
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.connect ':controller/:action/:id.:format'
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
"
|
||||
|
||||
new_routes_code = "MyApplication::Application.routes.draw do
|
||||
match '/:controller(/:action(/:id))'
|
||||
end
|
||||
"
|
||||
|
||||
upgrader = Rails::Upgrading::RoutesUpgrader.new
|
||||
upgrader.routes_code = routes_code
|
||||
|
||||
result = upgrader.generate_new_routes
|
||||
|
||||
assert_equal new_routes_code, result
|
||||
end
|
||||
end
|
5
vendor/plugins/rails_upgrade/test/test_helper.rb
vendored
Normal file
5
vendor/plugins/rails_upgrade/test/test_helper.rb
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
require 'test/unit'
|
||||
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'active_support/test_case'
|
1
vendor/plugins/rails_upgrade/uninstall.rb
vendored
Normal file
1
vendor/plugins/rails_upgrade/uninstall.rb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
# Uninstall hook code here
|
Loading…
Reference in a new issue